diff --git a/lib/util/check-existence.js b/lib/util/check-existence.js index a7899a75..fdfd897d 100644 --- a/lib/util/check-existence.js +++ b/lib/util/check-existence.js @@ -4,11 +4,7 @@ */ "use strict" -const path = require("path") -const exists = require("./exists") const getAllowModules = require("./get-allow-modules") -const isTypescript = require("./is-typescript") -const { convertJsExtensionToTs } = require("../util/map-typescript-extension") /** * Reports a missing file from ImportTarget @@ -17,13 +13,16 @@ const { convertJsExtensionToTs } = require("../util/map-typescript-extension") * @returns {void} */ function markMissing(context, target) { + // This should never happen... this is just a fallback for typescript + target.resolveError ??= `"${target.name}" is not found` + context.report({ node: target.node, loc: /** @type {import('eslint').AST.SourceLocation} */ ( target.node.loc ), messageId: "notFound", - data: /** @type {Record} */ (target), + data: { resolveError: target.resolveError }, }) } @@ -38,52 +37,20 @@ function markMissing(context, target) { * @returns {void} */ exports.checkExistence = function checkExistence(context, targets) { + /** @type {Set} */ const allowed = new Set(getAllowModules(context)) - target: for (const target of targets) { - if ( - target.moduleName != null && - !allowed.has(target.moduleName) && - target.filePath == null - ) { - markMissing(context, target) - continue - } - - if ( - target.moduleName != null || - target.filePath == null || - exists(target.filePath) - ) { + for (const target of targets) { + if (allowed.has(target.moduleName)) { continue } - if (isTypescript(context) === false) { + if (target.resolveError != null) { markMissing(context, target) - continue } - - const parsed = path.parse(target.filePath) - const pathWithoutExt = path.resolve(parsed.dir, parsed.name) - - const reversedExtensions = convertJsExtensionToTs( - context, - target.filePath, - parsed.ext - ) - - for (const reversedExtension of reversedExtensions) { - const reversedPath = pathWithoutExt + reversedExtension - - if (exists(reversedPath)) { - continue target - } - } - - markMissing(context, target) } } exports.messages = { - notFound: '"{{name}}" is not found.', + notFound: "{{resolveError}}", } diff --git a/lib/util/import-target.js b/lib/util/import-target.js index 54741451..a407c58f 100644 --- a/lib/util/import-target.js +++ b/lib/util/import-target.js @@ -131,6 +131,12 @@ module.exports = class ImportTarget { */ this.moduleName = this.getModuleName() + /** + * This is the full resolution failure reasons + * @type {string | null} + */ + this.resolveError = null + /** * The full path of this import target. * If the target is a module and it does not exist then this is `null`. @@ -239,6 +245,19 @@ module.exports = class ImportTarget { return [this.options.basedir] } + /** + * @param {string} baseDir + * @param {unknown} error + * @returns {void} + */ + handleResolutionError(baseDir, error) { + if (error instanceof Error === false) { + throw error + } + + this.resolveError = error.message + } + /** * Resolve the given id to file paths. * @returns {string | null} The resolved path. @@ -274,7 +293,8 @@ module.exports = class ImportTarget { extensionAlias = getTypescriptExtensionMap(this.context).backward } - const requireResolve = resolver.create.sync({ + /** @type {import('enhanced-resolve').ResolveOptionsOptionalFS} */ + this.resolverConfig = { conditionNames, extensions, mainFields, @@ -282,16 +302,19 @@ module.exports = class ImportTarget { extensionAlias, alias, - }) + } + + const requireResolve = resolver.create.sync(this.resolverConfig) const cwd = this.context.settings?.cwd ?? process.cwd() for (const directory of this.getPaths()) { + const baseDir = resolve(cwd, directory) + try { - const baseDir = resolve(cwd, directory) const resolved = requireResolve(baseDir, this.name) if (typeof resolved === "string") return resolved - } catch { - continue + } catch (error) { + this.handleResolutionError(baseDir, error) } } diff --git a/tests/fixtures/no-missing/node_modules/misconfigured-default/build/index.js b/tests/fixtures/no-missing/node_modules/misconfigured-default/build/index.js new file mode 100644 index 00000000..336ce12b --- /dev/null +++ b/tests/fixtures/no-missing/node_modules/misconfigured-default/build/index.js @@ -0,0 +1 @@ +export {} diff --git a/tests/fixtures/no-missing/node_modules/misconfigured-default/package.json b/tests/fixtures/no-missing/node_modules/misconfigured-default/package.json new file mode 100644 index 00000000..34cec732 --- /dev/null +++ b/tests/fixtures/no-missing/node_modules/misconfigured-default/package.json @@ -0,0 +1,9 @@ +{ + "name": "misconfigured-default", + "exports": { + ".": { + "default": "./build/index.js", + "types": "./src/index.ts" + } + } +} diff --git a/tests/fixtures/no-missing/node_modules/misconfigured-default/src/index.ts b/tests/fixtures/no-missing/node_modules/misconfigured-default/src/index.ts new file mode 100644 index 00000000..336ce12b --- /dev/null +++ b/tests/fixtures/no-missing/node_modules/misconfigured-default/src/index.ts @@ -0,0 +1 @@ +export {} diff --git a/tests/lib/configs/eslintrc.js b/tests/lib/configs/eslintrc.js index b79b15b7..9fc00bd6 100644 --- a/tests/lib/configs/eslintrc.js +++ b/tests/lib/configs/eslintrc.js @@ -84,7 +84,7 @@ describe("node/recommended config", () => { endLine: 1, line: 1, messageId: "notFound", - message: '"foo" is not found.', + message: `Can't resolve 'foo' in '${root}'`, nodeType: "Literal", ruleId: "n/no-missing-import", severity: 2, @@ -124,7 +124,7 @@ describe("node/recommended config", () => { endLine: 1, line: 1, messageId: "notFound", - message: '"foo" is not found.', + message: `Can't resolve 'foo' in '${root}'`, nodeType: "Literal", ruleId: "n/no-missing-import", severity: 2, @@ -165,7 +165,7 @@ describe("node/recommended config", () => { endLine: 1, line: 1, messageId: "notFound", - message: '"foo" is not found.', + message: `Can't resolve 'foo' in '${root}'`, nodeType: "Literal", ruleId: "n/no-missing-import", severity: 2, diff --git a/tests/lib/rules/no-missing-import.js b/tests/lib/rules/no-missing-import.js index 66190d3b..f2803b63 100644 --- a/tests/lib/rules/no-missing-import.js +++ b/tests/lib/rules/no-missing-import.js @@ -39,6 +39,18 @@ function fixture(name) { return path.resolve(__dirname, "../../fixtures/no-missing", name) } +function cantResolve(name, dir = "") { + return [ + { + messageId: "notFound", + data: { + resolveError: `Can't resolve '${name}' in '${fixture(dir)}'`, + }, + }, + ] +} + +/** @type {import('eslint').RuleTester} */ const ruleTester = new RuleTester({ languageOptions: { sourceType: "module", @@ -66,10 +78,11 @@ ruleTester.run("no-missing-import", rule, { filename: fixture("test.js"), code: "import a from './a.js';", }, - { - filename: fixture("test.ts"), - code: "import a from './a.js';", - }, + // I dont think this should resolve, as it wont after a standard `tsc` + // { + // filename: fixture("test.ts"), + // code: "import a from './a.js';", + // }, { filename: fixture("test.ts"), code: "import a from './d.js';", @@ -320,89 +333,114 @@ ruleTester.run("no-missing-import", rule, { { filename: fixture("test.js"), code: "import abc from 'no-exist-package-0';", - errors: ['"no-exist-package-0" is not found.'], + errors: cantResolve("no-exist-package-0"), }, { filename: fixture("test.js"), code: "import abcdef from 'esm-module/sub.mjs';", - errors: ['"esm-module/sub.mjs" is not found.'], + errors: [ + { + messageId: "notFound", + data: { + resolveError: [ + "Package path ./sub.mjs is not exported from package", + fixture("node_modules/esm-module"), + `(see exports field in ${fixture("node_modules/esm-module/package.json")})`, + ].join(" "), + }, + }, + ], }, { filename: fixture("test.js"), code: "import test from '@mysticatea/test';", - errors: ['"@mysticatea/test" is not found.'], + errors: cantResolve("@mysticatea/test"), }, { filename: fixture("test.js"), code: "import c from './c';", - errors: ['"./c" is not found.'], + errors: cantResolve("./c"), }, { filename: fixture("test.ts"), code: "import d from './d';", - errors: ['"./d" is not found.'], + errors: cantResolve("./d"), }, { filename: fixture("test.js"), code: "import d from './d';", - errors: ['"./d" is not found.'], + errors: cantResolve("./d"), }, { filename: fixture("test.js"), code: "import a from './a.json';", - errors: ['"./a.json" is not found.'], + errors: cantResolve("./a.json"), }, // Should work fine if the filename is relative. { - filename: "tests/fixtures/no-missing/test.js", + filename: fixture("test.js"), code: "import eslint from 'no-exist-package-0';", - errors: ['"no-exist-package-0" is not found.'], + errors: cantResolve("no-exist-package-0"), }, { filename: "tests/fixtures/no-missing/test.js", code: "import c from './c';", - errors: ['"./c" is not found.'], + errors: cantResolve("./c"), }, // Relative paths to a directory should work. { filename: fixture("test.js"), code: "import a from './bar';", - errors: ['"./bar" is not found.'], + errors: cantResolve("./bar"), }, { filename: fixture("test.js"), code: "import a from './bar/';", - errors: ['"./bar/" is not found.'], + errors: cantResolve("./bar/"), }, // Relative paths to an existing directory should not work. { filename: fixture("test.js"), code: "import a from '.';", - errors: ['"." is not found.'], + errors: cantResolve("."), }, { filename: fixture("test.js"), code: "import a from './';", - errors: ['"./" is not found.'], + errors: cantResolve("./"), }, { filename: fixture("test.js"), code: "import a from './foo';", - errors: ['"./foo" is not found.'], + errors: cantResolve("./foo"), }, { filename: fixture("test.js"), code: "import a from './foo/';", - errors: ['"./foo/" is not found.'], + errors: cantResolve("./foo/"), }, // Case sensitive { filename: fixture("test.js"), code: "import a from './A.js';", - errors: ['"./A.js" is not found.'], + errors: cantResolve("./A.js"), + }, + + { + filename: fixture("test.js"), + code: "import 'misconfigured-default';", + errors: [ + { + messageId: "notFound", + data: { + name: "misconfigured-default", + resolveError: "Default condition should be last one", + }, + }, + ], }, // import() @@ -412,7 +450,7 @@ ruleTester.run("no-missing-import", rule, { filename: fixture("test.js"), code: "function f() { import('no-exist-package-0') }", languageOptions: { ecmaVersion: 2020 }, - errors: ['"no-exist-package-0" is not found.'], + errors: cantResolve("no-exist-package-0"), }, ] : []), diff --git a/tests/lib/rules/no-missing-require.js b/tests/lib/rules/no-missing-require.js index 97c8506b..c055217c 100644 --- a/tests/lib/rules/no-missing-require.js +++ b/tests/lib/rules/no-missing-require.js @@ -25,6 +25,17 @@ function fixture(name) { return path.resolve(__dirname, "../../fixtures/no-missing", name) } +function cantResolve(name, dir = "") { + return [ + { + messageId: "notFound", + data: { + resolveError: `Can't resolve '${name}' in '${fixture(dir)}'`, + }, + }, + ] +} + const ruleTester = new RuleTester() ruleTester.run("no-missing-require", rule, { valid: [ @@ -333,65 +344,65 @@ ruleTester.run("no-missing-require", rule, { { filename: fixture("test.js"), code: "require('no-exist-package-0');", - errors: ['"no-exist-package-0" is not found.'], + errors: cantResolve("no-exist-package-0"), }, { filename: fixture("test.js"), code: "require('@mysticatea/test');", - errors: ['"@mysticatea/test" is not found.'], + errors: cantResolve("@mysticatea/test"), }, { filename: fixture("test.js"), code: "require('./c');", - errors: ['"./c" is not found.'], + errors: cantResolve("./c"), }, { filename: fixture("test.js"), code: "require('./d');", - errors: ['"./d" is not found.'], + errors: cantResolve("./d"), }, { filename: fixture("test.js"), code: "require('./a.json');", - errors: ['"./a.json" is not found.'], + errors: cantResolve("./a.json"), }, // Should work fine if the filename is relative. { filename: "tests/fixtures/no-missing/test.js", code: "require('no-exist-package-0');", - errors: ['"no-exist-package-0" is not found.'], + errors: cantResolve("no-exist-package-0"), }, { filename: "tests/fixtures/no-missing/test.js", code: "require('./c');", - errors: ['"./c" is not found.'], + errors: cantResolve("./c"), }, // Relative paths to a directory should work. { filename: fixture("test.js"), code: "require('./bar');", - errors: ['"./bar" is not found.'], + errors: cantResolve("./bar"), }, { filename: fixture("test.js"), code: "require('./bar/');", - errors: ['"./bar/" is not found.'], + errors: cantResolve("./bar/"), }, // Case sensitive { filename: fixture("test.js"), code: "require('./A');", - errors: ['"./A" is not found.'], + errors: cantResolve("./A"), }, // require.resolve { filename: fixture("test.js"), code: "require.resolve('no-exist-package-0');", - errors: ['"no-exist-package-0" is not found.'], + errors: cantResolve("no-exist-package-0"), }, ], })