Skip to content

Commit 982a723

Browse files
authored
feat(import-target): Add resolution error reason (mysticatea#264)
1 parent 60bf29f commit 982a723

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+214
-156
lines changed

lib/util/check-existence.js

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44
*/
55
"use strict"
66

7-
const path = require("path")
8-
const exists = require("./exists")
97
const getAllowModules = require("./get-allow-modules")
10-
const isTypescript = require("./is-typescript")
11-
const { convertJsExtensionToTs } = require("../util/map-typescript-extension")
128

139
/**
1410
* Reports a missing file from ImportTarget
@@ -17,13 +13,16 @@ const { convertJsExtensionToTs } = require("../util/map-typescript-extension")
1713
* @returns {void}
1814
*/
1915
function markMissing(context, target) {
16+
// This should never happen... this is just a fallback for typescript
17+
target.resolveError ??= `"${target.name}" is not found`
18+
2019
context.report({
2120
node: target.node,
2221
loc: /** @type {import('eslint').AST.SourceLocation} */ (
2322
target.node.loc
2423
),
2524
messageId: "notFound",
26-
data: /** @type {Record<string, *>} */ (target),
25+
data: { resolveError: target.resolveError },
2726
})
2827
}
2928

@@ -38,52 +37,20 @@ function markMissing(context, target) {
3837
* @returns {void}
3938
*/
4039
exports.checkExistence = function checkExistence(context, targets) {
40+
/** @type {Set<string | undefined>} */
4141
const allowed = new Set(getAllowModules(context))
4242

43-
target: for (const target of targets) {
44-
if (
45-
target.moduleName != null &&
46-
!allowed.has(target.moduleName) &&
47-
target.filePath == null
48-
) {
49-
markMissing(context, target)
50-
continue
51-
}
52-
53-
if (
54-
target.moduleName != null ||
55-
target.filePath == null ||
56-
exists(target.filePath)
57-
) {
43+
for (const target of targets) {
44+
if (allowed.has(target.moduleName)) {
5845
continue
5946
}
6047

61-
if (isTypescript(context) === false) {
48+
if (target.resolveError != null) {
6249
markMissing(context, target)
63-
continue
6450
}
65-
66-
const parsed = path.parse(target.filePath)
67-
const pathWithoutExt = path.resolve(parsed.dir, parsed.name)
68-
69-
const reversedExtensions = convertJsExtensionToTs(
70-
context,
71-
target.filePath,
72-
parsed.ext
73-
)
74-
75-
for (const reversedExtension of reversedExtensions) {
76-
const reversedPath = pathWithoutExt + reversedExtension
77-
78-
if (exists(reversedPath)) {
79-
continue target
80-
}
81-
}
82-
83-
markMissing(context, target)
8451
}
8552
}
8653

8754
exports.messages = {
88-
notFound: '"{{name}}" is not found.',
55+
notFound: "{{resolveError}}",
8956
}

lib/util/import-target.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ module.exports = class ImportTarget {
131131
*/
132132
this.moduleName = this.getModuleName()
133133

134+
/**
135+
* This is the full resolution failure reasons
136+
* @type {string | null}
137+
*/
138+
this.resolveError = null
139+
134140
/**
135141
* The full path of this import target.
136142
* If the target is a module and it does not exist then this is `null`.
@@ -239,6 +245,19 @@ module.exports = class ImportTarget {
239245
return [this.options.basedir]
240246
}
241247

248+
/**
249+
* @param {string} baseDir
250+
* @param {unknown} error
251+
* @returns {void}
252+
*/
253+
handleResolutionError(baseDir, error) {
254+
if (error instanceof Error === false) {
255+
throw error
256+
}
257+
258+
this.resolveError = error.message
259+
}
260+
242261
/**
243262
* Resolve the given id to file paths.
244263
* @returns {string | null} The resolved path.
@@ -274,24 +293,28 @@ module.exports = class ImportTarget {
274293
extensionAlias = getTypescriptExtensionMap(this.context).backward
275294
}
276295

277-
const requireResolve = resolver.create.sync({
296+
/** @type {import('enhanced-resolve').ResolveOptionsOptionalFS} */
297+
this.resolverConfig = {
278298
conditionNames,
279299
extensions,
280300
mainFields,
281301
mainFiles,
282302

283303
extensionAlias,
284304
alias,
285-
})
305+
}
306+
307+
const requireResolve = resolver.create.sync(this.resolverConfig)
286308

287309
const cwd = this.context.settings?.cwd ?? process.cwd()
288310
for (const directory of this.getPaths()) {
311+
const baseDir = resolve(cwd, directory)
312+
289313
try {
290-
const baseDir = resolve(cwd, directory)
291314
const resolved = requireResolve(baseDir, this.name)
292315
if (typeof resolved === "string") return resolved
293-
} catch {
294-
continue
316+
} catch (error) {
317+
this.handleResolutionError(baseDir, error)
295318
}
296319
}
297320

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,6 @@
117117
"*.md": "markdownlint --fix"
118118
},
119119
"imports": {
120-
"#eslint-rule-tester": "./tests/eslint-rule-tester.js"
120+
"#test-helpers": "./tests/test-helpers.js"
121121
}
122122
}

tests/fixtures/no-missing/node_modules/misconfigured-default/build/index.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/fixtures/no-missing/node_modules/misconfigured-default/package.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/fixtures/no-missing/node_modules/misconfigured-default/src/index.ts

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/helpers.js

Lines changed: 0 additions & 10 deletions
This file was deleted.

tests/lib/configs/eslintrc.js

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
const assert = require("assert")
44
const path = require("path")
55
const { LegacyESLint } = require("eslint/use-at-your-own-risk")
6-
// const {ESLint} = require("eslint")
7-
const { gtEslintV8 } = require("../../helpers")
86
const originalCwd = process.cwd()
97

108
// this is needed as `recommended` config was cached
@@ -15,7 +13,7 @@ function clearRequireCache() {
1513
}
1614

1715
describe("node/recommended config", () => {
18-
;(gtEslintV8 ? describe : describe.skip)("in CJS directory", () => {
16+
describe("in CJS directory", () => {
1917
const root = path.resolve(__dirname, "../../fixtures/configs/cjs/")
2018

2119
/** @type {Linter} */
@@ -84,7 +82,7 @@ describe("node/recommended config", () => {
8482
endLine: 1,
8583
line: 1,
8684
messageId: "notFound",
87-
message: '"foo" is not found.',
85+
message: `Can't resolve 'foo' in '${root}'`,
8886
nodeType: "Literal",
8987
ruleId: "n/no-missing-import",
9088
severity: 2,
@@ -124,34 +122,31 @@ describe("node/recommended config", () => {
124122
endLine: 1,
125123
line: 1,
126124
messageId: "notFound",
127-
message: '"foo" is not found.',
125+
message: `Can't resolve 'foo' in '${root}'`,
128126
nodeType: "Literal",
129127
ruleId: "n/no-missing-import",
130128
severity: 2,
131129
},
132130
])
133131
})
134-
;(gtEslintV8 ? it : it.skip)(
135-
"*.cjs files should be a script.",
136-
async () => {
137-
const report = await linter.lintText("import 'foo'", {
138-
filePath: path.join(root, "test.cjs"),
139-
})
140-
141-
assert.deepStrictEqual(report[0].messages, [
142-
{
143-
column: 1,
144-
fatal: true,
145-
line: 1,
146-
message:
147-
"Parsing error: 'import' and 'export' may appear only with 'sourceType: module'",
148-
ruleId: null,
149-
nodeType: null,
150-
severity: 2,
151-
},
152-
])
153-
}
154-
)
132+
it("*.cjs files should be a script.", async () => {
133+
const report = await linter.lintText("import 'foo'", {
134+
filePath: path.join(root, "test.cjs"),
135+
})
136+
137+
assert.deepStrictEqual(report[0].messages, [
138+
{
139+
column: 1,
140+
fatal: true,
141+
line: 1,
142+
message:
143+
"Parsing error: 'import' and 'export' may appear only with 'sourceType: module'",
144+
ruleId: null,
145+
nodeType: null,
146+
severity: 2,
147+
},
148+
])
149+
})
155150

156151
it("*.mjs files should be a module.", async () => {
157152
const report = await linter.lintText("import 'foo'", {
@@ -165,7 +160,7 @@ describe("node/recommended config", () => {
165160
endLine: 1,
166161
line: 1,
167162
messageId: "notFound",
168-
message: '"foo" is not found.',
163+
message: `Can't resolve 'foo' in '${root}'`,
169164
nodeType: "Literal",
170165
ruleId: "n/no-missing-import",
171166
severity: 2,

tests/lib/rules/callback-return.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
"use strict"
66

7-
const RuleTester = require("#eslint-rule-tester").RuleTester
7+
const RuleTester = require("#test-helpers").RuleTester
88
const rule = require("../../../lib/rules/callback-return")
99
const ruleTester = new RuleTester()
1010

tests/lib/rules/exports-style.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
"use strict"
66

7-
const RuleTester = require("#eslint-rule-tester").RuleTester
7+
const RuleTester = require("#test-helpers").RuleTester
88
const rule = require("../../../lib/rules/exports-style")
99

1010
new RuleTester({ languageOptions: { ecmaVersion: 11 } }).run(

0 commit comments

Comments
 (0)