| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,11 +3,10 @@ | |||
| 3 | 3 | const { | |
| 4 | 4 | ArrayIsArray, | |
| 5 | 5 | ArrayPrototypeJoin, | |
| 6 | - ArrayPrototypeShift, | ||
| 6 | + ArrayPrototypeMap, | ||
| 7 | 7 | JSONStringify, | |
| 8 | 8 | ObjectGetOwnPropertyNames, | |
| 9 | 9 | ObjectPrototypeHasOwnProperty, | |
| 10 | - RegExp, | ||
| 11 | 10 | RegExpPrototypeExec, | |
| 12 | 11 | RegExpPrototypeSymbolReplace, | |
| 13 | 12 | SafeMap, | |
@@ -21,6 +20,7 @@ const { | |||
| 21 | 20 | StringPrototypeSlice, | |
| 22 | 21 | StringPrototypeSplit, | |
| 23 | 22 | StringPrototypeStartsWith, | |
| 23 | + encodeURIComponent, | ||
| 24 | 24 | } = primordials; | |
| 25 | 25 | const internalFS = require('internal/fs/utils'); | |
| 26 | 26 | const { BuiltinModule } = require('internal/bootstrap/realm'); | |
@@ -30,7 +30,7 @@ const { getOptionValue } = require('internal/options'); | |||
| 30 | 30 | const policy = getOptionValue('--experimental-policy') ? | |
| 31 | 31 | require('internal/process/policy') : | |
| 32 | 32 | null; | |
| 33 | - const { sep, relative, toNamespacedPath, resolve } = require('path'); | ||
| 33 | + const { sep, posix: { relative: relativePosixPath }, toNamespacedPath, resolve } = require('path'); | ||
| 34 | 34 | const preserveSymlinks = getOptionValue('--preserve-symlinks'); | |
| 35 | 35 | const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); | |
| 36 | 36 | const experimentalNetworkImports = | |
@@ -921,6 +921,7 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) { | |||
| 921 | 921 | * Try to resolve an import as a CommonJS module. | |
| 922 | 922 | * @param {string} specifier - The specifier to resolve. | |
| 923 | 923 | * @param {string} parentURL - The base URL. | |
| 924 | + * @returns {string | Buffer | false} | ||
| 924 | 925 | */ | |
| 925 | 926 | function resolveAsCommonJS(specifier, parentURL) { | |
| 926 | 927 | try { | |
@@ -933,29 +934,38 @@ function resolveAsCommonJS(specifier, parentURL) { | |||
| 933 | 934 | // If it is a relative specifier return the relative path | |
| 934 | 935 | // to the parent | |
| 935 | 936 | if (isRelativeSpecifier(specifier)) { | |
| 936 | - found = relative(parent, found); | ||
| 937 | - // Add '.separator if the path does not start with '..separator' | ||
| 937 | + const foundURL = pathToFileURL(found).pathname; | ||
| 938 | + found = relativePosixPath( | ||
| 939 | + StringPrototypeSlice(parentURL, 'file://'.length, StringPrototypeLastIndexOf(parentURL, '/')), | ||
| 940 | + foundURL); | ||
| 941 | + | ||
| 942 | + // Add './' if the path does not start with '../' | ||
| 938 | 943 | // This should be a safe assumption because when loading | |
| 939 | 944 | // esm modules there should be always a file specified so | |
| 940 | 945 | // there should not be a specifier like '..' or '.' | |
| 941 | - if (!StringPrototypeStartsWith(found, `..${sep}`)) { | ||
| 942 | - found = `.${sep}${found}`; | ||
| 946 | + if (!StringPrototypeStartsWith(found, '../')) { | ||
| 947 | + found = `./${found}`; | ||
| 943 | 948 | } | |
| 944 | 949 | } else if (isBareSpecifier(specifier)) { | |
| 945 | 950 | // If it is a bare specifier return the relative path within the | |
| 946 | 951 | // module | |
| 947 | - const pkg = StringPrototypeSplit(specifier, '/')[0]; | ||
| 948 | - const index = StringPrototypeIndexOf(found, pkg); | ||
| 952 | + const i = StringPrototypeIndexOf(specifier, '/'); | ||
| 953 | + const pkg = i === -1 ? specifier : StringPrototypeSlice(specifier, 0, i); | ||
| 954 | + const needle = `${sep}node_modules${sep}${pkg}${sep}`; | ||
| 955 | + const index = StringPrototypeLastIndexOf(found, needle); | ||
| 949 | 956 | if (index !== -1) { | |
| 950 | - found = StringPrototypeSlice(found, index); | ||
| 957 | + found = pkg + '/' + ArrayPrototypeJoin( | ||
| 958 | + ArrayPrototypeMap( | ||
| 959 | + StringPrototypeSplit(StringPrototypeSlice(found, index + needle.length), sep), | ||
| 960 | + // Escape URL-special characters to avoid generating a incorrect suggestion | ||
| 961 | + encodeURIComponent, | ||
| 962 | + ), | ||
| 963 | + '/', | ||
| 964 | + ); | ||
| 965 | + } else { | ||
| 966 | + found = `${pathToFileURL(found)}`; | ||
| 951 | 967 | } | |
| 952 | 968 | } | |
| 953 | - // Normalize the path separator to give a valid suggestion | ||
| 954 | - // on Windows | ||
| 955 | - if (process.platform === 'win32') { | ||
| 956 | - found = RegExpPrototypeSymbolReplace(new RegExp(`\\${sep}`, 'g'), | ||
| 957 | - found, '/'); | ||
| 958 | - } | ||
| 959 | 969 | return found; | |
| 960 | 970 | } catch { | |
| 961 | 971 | return false; | |
@@ -1163,14 +1173,14 @@ function defaultResolve(specifier, context = {}) { | |||
| 1163 | 1173 | */ | |
| 1164 | 1174 | function decorateErrorWithCommonJSHints(error, specifier, parentURL) { | |
| 1165 | 1175 | const found = resolveAsCommonJS(specifier, parentURL); | |
| 1166 | - if (found) { | ||
| 1176 | + if (found && found !== specifier) { // Don't suggest the same input the user provided. | ||
| 1167 | 1177 | // Modify the stack and message string to include the hint | |
| 1168 | - const lines = StringPrototypeSplit(error.stack, '\n'); | ||
| 1169 | - const hint = `Did you mean to import ${found}?`; | ||
| 1178 | + const endOfFirstLine = StringPrototypeIndexOf(error.stack, '\n'); | ||
| 1179 | + const hint = `Did you mean to import ${JSONStringify(found)}?`; | ||
| 1170 | 1180 | error.stack = | |
| 1171 | - ArrayPrototypeShift(lines) + '\n' + | ||
| 1172 | - hint + '\n' + | ||
| 1173 | - ArrayPrototypeJoin(lines, '\n'); | ||
| 1181 | + StringPrototypeSlice(error.stack, 0, endOfFirstLine) + '\n' + | ||
| 1182 | + hint + | ||
| 1183 | + StringPrototypeSlice(error.stack, endOfFirstLine); | ||
| 1174 | 1184 | error.message += `\n${hint}`; | |
| 1175 | 1185 | } | |
| 1176 | 1186 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,32 +1,53 @@ | |||
| 1 | 1 | import { spawnPromisified } from '../common/index.mjs'; | |
| 2 | - import { fixturesDir } from '../common/fixtures.mjs'; | ||
| 2 | + import { fixturesDir, fileURL as fixtureSubDir } from '../common/fixtures.mjs'; | ||
| 3 | 3 | import { match, notStrictEqual } from 'node:assert'; | |
| 4 | 4 | import { execPath } from 'node:process'; | |
| 5 | 5 | import { describe, it } from 'node:test'; | |
| 6 | 6 | ||
| 7 | 7 | ||
| 8 | 8 | describe('ESM: module not found hint', { concurrency: true }, () => { | |
| 9 | 9 | for ( | |
| 10 | - const { input, expected } | ||
| 10 | + const { input, expected, cwd = fixturesDir } | ||
| 11 | 11 | of [ | |
| 12 | 12 | { | |
| 13 | 13 | input: 'import "./print-error-message"', | |
| 14 | - // Did you mean to import ../print-error-message.js? | ||
| 15 | - expected: / \.\.\/print-error-message\.js\?/, | ||
| 14 | + // Did you mean to import "./print-error-message.js"? | ||
| 15 | + expected: / "\.\/print-error-message\.js"\?/, | ||
| 16 | + }, | ||
| 17 | + { | ||
| 18 | + input: 'import "./es-modules/folder%25with percentage#/index.js"', | ||
| 19 | + // Did you mean to import "./es-modules/folder%2525with%20percentage%23/index.js"? | ||
| 20 | + expected: / "\.\/es-modules\/folder%2525with%20percentage%23\/index\.js"\?/, | ||
| 21 | + }, | ||
| 22 | + { | ||
| 23 | + input: 'import "../folder%25with percentage#/index.js"', | ||
| 24 | + // Did you mean to import "../es-modules/folder%2525with%20percentage%23/index.js"? | ||
| 25 | + expected: / "\.\.\/folder%2525with%20percentage%23\/index\.js"\?/, | ||
| 26 | + cwd: fixtureSubDir('es-modules/tla/'), | ||
| 16 | 27 | }, | |
| 17 | 28 | { | |
| 18 | 29 | input: 'import obj from "some_module/obj"', | |
| 19 | - expected: / some_module\/obj\.js\?/, | ||
| 30 | + expected: / "some_module\/obj\.js"\?/, | ||
| 31 | + }, | ||
| 32 | + { | ||
| 33 | + input: 'import obj from "some_module/folder%25with percentage#/index.js"', | ||
| 34 | + expected: / "some_module\/folder%2525with%20percentage%23\/index\.js"\?/, | ||
| 35 | + }, | ||
| 36 | + { | ||
| 37 | + input: 'import "@nodejsscope/pkg/index"', | ||
| 38 | + expected: / "@nodejsscope\/pkg\/index\.js"\?/, | ||
| 39 | + }, | ||
| 40 | + { | ||
| 41 | + input: 'import obj from "lone_file.js"', | ||
| 42 | + expected: /node_modules\/lone_file\.js"\?/, | ||
| 20 | 43 | }, | |
| 21 | 44 | ] | |
| 22 | 45 | ) it('should cite a variant form', async () => { | |
| 23 | 46 | const { code, stderr } = await spawnPromisified(execPath, [ | |
| 24 | 47 | '--input-type=module', | |
| 25 | 48 | '--eval', | |
| 26 | 49 | input, | |
| 27 | - ], { | ||
| 28 | - cwd: fixturesDir, | ||
| 29 | - }); | ||
| 50 | + ], { cwd }); | ||
| 30 | 51 | ||
| 31 | 52 | match(stderr, expected); | |
| 32 | 53 | notStrictEqual(code, 0); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,7 +26,7 @@ describe('--experimental-default-type=module should not support extension search | |||
| 26 | 26 | cwd: fixtures.path('es-modules/package-without-type'), | |
| 27 | 27 | }); | |
| 28 | 28 | ||
| 29 | - match(stderr, /ENOENT.*Did you mean to import .*index\.js\?/s); | ||
| 29 | + match(stderr, /ENOENT.*Did you mean to import .*index\.js"\?/s); | ||
| 30 | 30 | strictEqual(stdout, ''); | |
| 31 | 31 | strictEqual(code, 1); | |
| 32 | 32 | strictEqual(signal, null); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + 'use strict'; | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments