Skip to content

Commit dc9f473

Browse files
scagoodaladdin-add
andauthored
feat!: Start using enhanced-resolve to improve ts support (#139)
* feat: Use enhanced-resolve for imports * chore: Improve the metadata from "ImportTarget" * chore: remove "enhanced-resolve" from "no-hide-core-modules" * test: Add a test for #66 * feat!: Allow ts paths aliases (#84) * feat: Allow for "allowImportingTsExtensions" (#134) * feat: Add test for import maps (#147) * Update lib/util/import-target.js Co-authored-by: Sebastian Good <[email protected]> * test: Add test for n/no-missing-require eslint/use-at-your-own-risk * chore: Remove esbuild * feat: Allow for settings.cwd to be used before process.cwd * chore: replace reference to eslint/use-at-your-own-risk * chore: Remove more unused packages * chore: update rule test options to flat config * fix: incorrect env in tests --------- Co-authored-by: 唯然 <[email protected]>
1 parent 5449752 commit dc9f473

31 files changed

+492
-1499
lines changed

lib/converted-esm/import-meta-resolve.js

-1,274
This file was deleted.

lib/rules/file-extension-in-import.js

+41-32
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ const visitImport = require("../util/visit-import")
1515
* @returns {string[]} File extensions.
1616
*/
1717
function getExistingExtensions(filePath) {
18-
const basename = path.basename(filePath, path.extname(filePath))
18+
const directory = path.dirname(filePath)
19+
const extension = path.extname(filePath)
20+
const basename = path.basename(filePath, extension)
21+
1922
try {
2023
return fs
21-
.readdirSync(path.dirname(filePath))
22-
.filter(
23-
filename =>
24-
path.basename(filename, path.extname(filename)) === basename
25-
)
24+
.readdirSync(directory)
25+
.filter(filename => filename.startsWith(`${basename}.`))
2626
.map(filename => path.extname(filename))
2727
} catch (_error) {
2828
return []
@@ -74,47 +74,56 @@ module.exports = {
7474
}
7575

7676
// Get extension.
77-
const originalExt = path.extname(name)
78-
const existingExts = getExistingExtensions(filePath)
79-
const ext = path.extname(filePath) || existingExts.join(" or ")
80-
const style = overrideStyle[ext] || defaultStyle
77+
const currentExt = path.extname(name)
78+
const actualExt = path.extname(filePath)
79+
const style = overrideStyle[actualExt] || defaultStyle
80+
81+
const expectedExt = mapTypescriptExtension(
82+
context,
83+
filePath,
84+
actualExt
85+
)
8186

8287
// Verify.
83-
if (style === "always" && ext !== originalExt) {
84-
const fileExtensionToAdd = mapTypescriptExtension(
85-
context,
86-
filePath,
87-
ext
88-
)
88+
if (style === "always" && currentExt !== expectedExt) {
8989
context.report({
9090
node,
9191
messageId: "requireExt",
92-
data: { ext: fileExtensionToAdd },
92+
data: { ext: expectedExt },
9393
fix(fixer) {
94-
if (existingExts.length !== 1) {
95-
return null
96-
}
9794
const index = node.range[1] - 1
9895
return fixer.insertTextBeforeRange(
9996
[index, index],
100-
fileExtensionToAdd
97+
expectedExt
10198
)
10299
},
103100
})
104-
} else if (style === "never" && ext === originalExt) {
101+
}
102+
103+
if (
104+
style === "never" &&
105+
currentExt !== "" &&
106+
expectedExt !== "" &&
107+
currentExt === expectedExt
108+
) {
109+
const otherExtensions = getExistingExtensions(filePath)
110+
111+
let fix = fixer => {
112+
const index = name.lastIndexOf(currentExt)
113+
const start = node.range[0] + 1 + index
114+
const end = start + currentExt.length
115+
return fixer.removeRange([start, end])
116+
}
117+
118+
if (otherExtensions.length > 1) {
119+
fix = undefined
120+
}
121+
105122
context.report({
106123
node,
107124
messageId: "forbidExt",
108-
data: { ext },
109-
fix(fixer) {
110-
if (existingExts.length !== 1) {
111-
return null
112-
}
113-
const index = name.lastIndexOf(ext)
114-
const start = node.range[0] + 1 + index
115-
const end = start + ext.length
116-
return fixer.removeRange([start, end])
117-
},
125+
data: { ext: currentExt },
126+
fix,
118127
})
119128
}
120129
}

lib/rules/no-hide-core-modules.js

+7-24
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@
99
"use strict"
1010

1111
const path = require("path")
12-
const resolve = require("resolve")
13-
const { pathToFileURL, fileURLToPath } = require("url")
14-
const {
15-
defaultResolve: importResolve,
16-
} = require("../converted-esm/import-meta-resolve")
1712
const getPackageJson = require("../util/get-package-json")
1813
const mergeVisitorsInPlace = require("../util/merge-visitors-in-place")
1914
const visitImport = require("../util/visit-import")
@@ -29,15 +24,17 @@ const CORE_MODULES = new Set([
2924
"crypto",
3025
"dgram",
3126
"dns",
32-
/* "domain", */ "events",
27+
/* "domain", */
28+
"events",
3329
"fs",
3430
"http",
3531
"https",
3632
"module",
3733
"net",
3834
"os",
3935
"path",
40-
/* "punycode", */ "querystring",
36+
/* "punycode", */
37+
"querystring",
4138
"readline",
4239
"repl",
4340
"stream",
@@ -132,22 +129,8 @@ module.exports = {
132129
continue
133130
}
134131

135-
let resolved = ""
136-
const moduleId = `${name}/`
137-
try {
138-
resolved = resolve.sync(moduleId, {
139-
basedir: dirPath,
140-
})
141-
} catch (_error) {
142-
try {
143-
const { url } = importResolve(moduleId, {
144-
parentURL: pathToFileURL(dirPath).href,
145-
})
146-
147-
resolved = fileURLToPath(url)
148-
} catch (_error) {
149-
continue
150-
}
132+
if (target.filePath == null) {
133+
continue
151134
}
152135

153136
context.report({
@@ -156,7 +139,7 @@ module.exports = {
156139
messageId: "unexpectedImport",
157140
data: {
158141
name: path
159-
.relative(dirPath, resolved)
142+
.relative(dirPath, target.filePath)
160143
.replace(BACK_SLASH, "/"),
161144
},
162145
})

lib/util/check-existence.js

+34-12
Original file line numberDiff line numberDiff line change
@@ -10,50 +10,72 @@ const getAllowModules = require("./get-allow-modules")
1010
const isTypescript = require("./is-typescript")
1111
const mapTypescriptExtension = require("../util/map-typescript-extension")
1212

13+
/**
14+
* Reports a missing file from ImportTarget
15+
* @param {RuleContext} context - A context to report.
16+
* @param {import('../util/import-target.js')} target - A list of target information to check.
17+
* @returns {void}
18+
*/
19+
function markMissing(context, target) {
20+
context.report({
21+
node: target.node,
22+
loc: target.node.loc,
23+
messageId: "notFound",
24+
data: target,
25+
})
26+
}
27+
1328
/**
1429
* Checks whether or not each requirement target exists.
1530
*
1631
* It looks up the target according to the logic of Node.js.
1732
* See Also: https://nodejs.org/api/modules.html
1833
*
1934
* @param {RuleContext} context - A context to report.
20-
* @param {ImportTarget[]} targets - A list of target information to check.
35+
* @param {import('../util/import-target.js')[]} targets - A list of target information to check.
2136
* @returns {void}
2237
*/
2338
exports.checkExistence = function checkExistence(context, targets) {
2439
const allowed = new Set(getAllowModules(context))
2540

2641
for (const target of targets) {
27-
const missingModule =
42+
if (
2843
target.moduleName != null &&
2944
!allowed.has(target.moduleName) &&
3045
target.filePath == null
46+
) {
47+
markMissing(context, target)
48+
continue
49+
}
50+
51+
if (target.moduleName != null) {
52+
continue
53+
}
54+
55+
let missingFile =
56+
target.filePath == null ? false : !exists(target.filePath)
3157

32-
let missingFile = target.moduleName == null && !exists(target.filePath)
3358
if (missingFile && isTypescript(context)) {
3459
const parsed = path.parse(target.filePath)
60+
const pathWithoutExt = path.resolve(parsed.dir, parsed.name)
61+
3562
const reversedExts = mapTypescriptExtension(
3663
context,
3764
target.filePath,
3865
parsed.ext,
3966
true
4067
)
4168
const reversedPaths = reversedExts.map(
42-
reversedExt =>
43-
path.resolve(parsed.dir, parsed.name) + reversedExt
69+
reversedExt => pathWithoutExt + reversedExt
4470
)
4571
missingFile = reversedPaths.every(
4672
reversedPath =>
4773
target.moduleName == null && !exists(reversedPath)
4874
)
4975
}
50-
if (missingModule || missingFile) {
51-
context.report({
52-
node: target.node,
53-
loc: target.node.loc,
54-
messageId: "notFound",
55-
data: target,
56-
})
76+
77+
if (missingFile) {
78+
markMissing(context, target)
5779
}
5880
}
5981
}

lib/util/check-publish.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ exports.checkPublish = function checkPublish(context, filePath, targets) {
5959
if (target.moduleName != null) {
6060
return false
6161
}
62-
const relativeTargetPath = toRelative(target.filePath)
62+
const relativeTargetPath = toRelative(target.filePath ?? "")
6363
return (
6464
relativeTargetPath !== "" &&
6565
npmignore.match(relativeTargetPath)
@@ -70,6 +70,7 @@ exports.checkPublish = function checkPublish(context, filePath, targets) {
7070
devDependencies.has(target.moduleName) &&
7171
!dependencies.has(target.moduleName) &&
7272
!allowed.has(target.moduleName)
73+
7374
if (isPrivateFile() || isDevPackage()) {
7475
context.report({
7576
node: target.node,

lib/util/exists.js

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ function existsCaseSensitive(filePath) {
3838
* @returns {boolean} `true` if the file of a given path exists.
3939
*/
4040
module.exports = function exists(filePath) {
41+
if (filePath == null) {
42+
return false
43+
}
44+
4145
let result = cache.get(filePath)
4246
if (result == null) {
4347
try {

lib/util/get-try-extensions.js

+39-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,26 @@
44
*/
55
"use strict"
66

7-
const DEFAULT_VALUE = Object.freeze([".js", ".json", ".node"])
7+
const { getTSConfigForContext } = require("./get-tsconfig")
8+
const isTypescript = require("./is-typescript")
9+
10+
const DEFAULT_JS_VALUE = Object.freeze([
11+
".js",
12+
".json",
13+
".node",
14+
".mjs",
15+
".cjs",
16+
])
17+
const DEFAULT_TS_VALUE = Object.freeze([
18+
".js",
19+
".ts",
20+
".mjs",
21+
".mts",
22+
".cjs",
23+
".cts",
24+
".json",
25+
".node",
26+
])
827

928
/**
1029
* Gets `tryExtensions` property from a given option object.
@@ -13,7 +32,7 @@ const DEFAULT_VALUE = Object.freeze([".js", ".json", ".node"])
1332
* @returns {string[]|null} The `tryExtensions` value, or `null`.
1433
*/
1534
function get(option) {
16-
if (option && option.tryExtensions && Array.isArray(option.tryExtensions)) {
35+
if (Array.isArray(option?.tryExtensions)) {
1736
return option.tryExtensions.map(String)
1837
}
1938
return null
@@ -24,19 +43,29 @@ function get(option) {
2443
*
2544
* 1. This checks `options` property, then returns it if exists.
2645
* 2. This checks `settings.n` | `settings.node` property, then returns it if exists.
27-
* 3. This returns `[".js", ".json", ".node"]`.
46+
* 3. This returns `[".js", ".json", ".node", ".mjs", ".cjs"]`.
2847
*
2948
* @param {RuleContext} context - The rule context.
3049
* @returns {string[]} A list of extensions.
3150
*/
3251
module.exports = function getTryExtensions(context, optionIndex = 0) {
33-
return (
34-
get(context.options && context.options[optionIndex]) ||
35-
get(
36-
context.settings && (context.settings.n || context.settings.node)
37-
) ||
38-
DEFAULT_VALUE
39-
)
52+
const configured =
53+
get(context.options?.[optionIndex]) ??
54+
get(context.settings?.n) ??
55+
get(context.settings?.node)
56+
57+
if (configured != null) {
58+
return configured
59+
}
60+
61+
if (isTypescript(context)) {
62+
const tsconfig = getTSConfigForContext(context)
63+
if (tsconfig?.config?.compilerOptions?.allowImportingTsExtensions) {
64+
return DEFAULT_TS_VALUE
65+
}
66+
}
67+
68+
return DEFAULT_JS_VALUE
4069
}
4170

4271
module.exports.schema = {

lib/util/get-tsconfig.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,33 @@ function getTSConfig(filename) {
1717
* Attempts to get the ExtensionMap from the tsconfig of a given file.
1818
*
1919
* @param {string} filename - The path to the file we need to find the tsconfig.json of
20-
* @returns {import("get-tsconfig").TsConfigResult}
20+
* @returns {import("get-tsconfig").TsConfigResult | null}
2121
*/
2222
function getTSConfigForFile(filename) {
2323
return getTsconfig(filename, "tsconfig.json", fsCache)
2424
}
2525

26+
/**
27+
* Attempts to get the ExtensionMap from the tsconfig of a given file.
28+
*
29+
* @param {import('eslint').Rule.RuleContext} context - The current eslint context
30+
* @returns {import("get-tsconfig").TsConfigResult | null}
31+
*/
32+
function getTSConfigForContext(context) {
33+
// TODO: remove context.get(PhysicalFilename|Filename) when dropping eslint < v10
34+
const filename =
35+
context.physicalFilename ??
36+
context.getPhysicalFilename?.() ??
37+
context.filename ??
38+
context.getFilename?.()
39+
40+
return getTSConfigForFile(filename)
41+
}
42+
2643
module.exports = {
2744
getTSConfig,
2845
getTSConfigForFile,
46+
getTSConfigForContext,
2947
}
3048

3149
module.exports.schema = { type: "string" }

0 commit comments

Comments
 (0)