Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(import-target): Add resolution error reason #231

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 9 additions & 42 deletions lib/util/check-existence.js
Expand Up @@ -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
Expand All @@ -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<string, *>} */ (target),
data: { resolveError: target.resolveError },
})
}

Expand All @@ -38,52 +37,20 @@ function markMissing(context, target) {
* @returns {void}
*/
exports.checkExistence = function checkExistence(context, targets) {
/** @type {Set<string | undefined>} */
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}}",
}
33 changes: 28 additions & 5 deletions lib/util/import-target.js
Expand Up @@ -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`.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -274,24 +293,28 @@ module.exports = class ImportTarget {
extensionAlias = getTypescriptExtensionMap(this.context).backward
}

const requireResolve = resolver.create.sync({
/** @type {import('enhanced-resolve').ResolveOptionsOptionalFS} */
this.resolverConfig = {
conditionNames,
extensions,
mainFields,
mainFiles,

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)
}
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions tests/lib/configs/eslintrc.js
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
82 changes: 60 additions & 22 deletions tests/lib/rules/no-missing-import.js
Expand Up @@ -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)}'`,
},
Comment on lines +46 to +48

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
data: {
resolveError: `Can't resolve '${name}' in '${fixture(dir)}'`,
},

},
]
}

/** @type {import('eslint').RuleTester} */
const ruleTester = new RuleTester({
languageOptions: {
sourceType: "module",
Expand Down Expand Up @@ -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';",
Expand Down Expand Up @@ -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()
Expand All @@ -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"),
},
]
: []),
Expand Down