Skip to content

Commit 8202c24

Browse files
committed
chore: Improve the metadata from "ImportTarget"
1 parent 8e0dcb7 commit 8202c24

File tree

4 files changed

+156
-63
lines changed

4 files changed

+156
-63
lines changed

lib/util/check-existence.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const mapTypescriptExtension = require("../util/map-typescript-extension")
1717
* See Also: https://nodejs.org/api/modules.html
1818
*
1919
* @param {RuleContext} context - A context to report.
20-
* @param {ImportTarget[]} targets - A list of target information to check.
20+
* @param {import('../util/import-target.js')[]} targets - A list of target information to check.
2121
* @returns {void}
2222
*/
2323
exports.checkExistence = function checkExistence(context, targets) {

lib/util/check-publish.js

Lines changed: 2 additions & 1 deletion
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

Lines changed: 4 additions & 0 deletions
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/import-target.js

Lines changed: 149 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,44 @@
44
*/
55
"use strict"
66

7-
const { resolve, sep } = require("path")
7+
const { resolve } = require("path")
88
const isBuiltin = require("is-builtin-module")
99
const resolver = require("enhanced-resolve")
1010

11+
/**
12+
* @typedef {Object} Options
13+
* @property {string[]} [extensions]
14+
* @property {string[]} [paths]
15+
* @property {string} basedir
16+
*/
17+
/**
18+
* @typedef { 'unknown' | 'relative' | 'absolute' | 'node' | 'npm' | 'http' } ModuleType
19+
* @typedef { 'import' | 'require' | 'type' } ModuleStyle
20+
*/
21+
1122
/**
1223
* Resolve the given id to file paths.
13-
* @param {boolean} isModule The flag which indicates this id is a module.
1424
* @param {string} id The id to resolve.
15-
* @param {object} options The options of node-resolve module.
16-
* It requires `options.basedir`.
17-
* @param {'import' | 'require'} moduleType - whether the target was require-ed or imported
18-
* @returns {string|null} The resolved path.
25+
* @param {Options} options The options of node-resolve module.
26+
* @param {ModuleType} moduleType - whether the target was require-ed or imported
27+
* @param {ModuleStyle} moduleStyle - whether the target was require-ed or imported
28+
* @returns {string | null} The resolved path.
1929
*/
20-
function getFilePath(isModule, id, options, moduleType) {
30+
function getFilePath(id, options, moduleType, moduleStyle) {
2131
const conditionNames = ["node", "require"]
2232
const { extensions } = options
2333
const mainFields = []
2434
const mainFiles = []
2535

26-
if (moduleType === "import") {
36+
if (moduleStyle === "import") {
2737
conditionNames.push("import")
2838
}
2939

30-
if (moduleType === "require" || isModule === true) {
40+
if (moduleStyle === "type") {
41+
conditionNames.push("import", "types")
42+
}
43+
44+
if (moduleStyle === "require" || moduleType === "npm") {
3145
mainFields.push("main")
3246
mainFiles.push("index")
3347
}
@@ -52,36 +66,15 @@ function getFilePath(isModule, id, options, moduleType) {
5266
}
5367
}
5468

55-
if (isModule) {
56-
return null
69+
if (moduleType === "absolute" || moduleType === "relative") {
70+
return resolve(options.basedir, id)
5771
}
5872

59-
return resolve(options.basedir, id)
73+
return null
6074
}
6175

62-
function isNodeModule(name, options) {
63-
try {
64-
return require.resolve(name, options).startsWith(sep)
65-
} catch {
66-
return false
67-
}
68-
}
69-
70-
/**
71-
* Gets the module name of a given path.
72-
*
73-
* e.g. `eslint/lib/ast-utils` -> `eslint`
74-
*
75-
* @param {string} nameOrPath - A path to get.
76-
* @returns {string} The module name of the path.
77-
*/
78-
function getModuleName(nameOrPath) {
79-
let end = nameOrPath.indexOf("/")
80-
if (end !== -1 && nameOrPath[0] === "@") {
81-
end = nameOrPath.indexOf("/", 1 + end)
82-
}
83-
84-
return end === -1 ? nameOrPath : nameOrPath.slice(0, end)
76+
function trimAfter(string, matcher, count = 1) {
77+
return string.split(matcher).slice(0, count).join(matcher)
8578
}
8679

8780
/**
@@ -90,17 +83,15 @@ function getModuleName(nameOrPath) {
9083
module.exports = class ImportTarget {
9184
/**
9285
* Initialize this instance.
93-
* @param {ASTNode} node - The node of a `require()` or a module declaraiton.
86+
* @param {import('eslint').Rule.Node} node - The node of a `require()` or a module declaraiton.
9487
* @param {string} name - The name of an import target.
95-
* @param {object} options - The options of `node-resolve` module.
88+
* @param {Options} options - The options of `node-resolve` module.
9689
* @param {'import' | 'require'} moduleType - whether the target was require-ed or imported
9790
*/
9891
constructor(node, name, options, moduleType) {
99-
const isModule = !/^(?:[./\\]|\w+:)/u.test(name)
100-
10192
/**
10293
* The node of a `require()` or a module declaraiton.
103-
* @type {ASTNode}
94+
* @type {import('eslint').Rule.Node}
10495
*/
10596
this.node = node
10697

@@ -111,35 +102,132 @@ module.exports = class ImportTarget {
111102
this.name = name
112103

113104
/**
114-
* What type of module is this
115-
* @type {'unknown'|'relative'|'absolute'|'node'|'npm'|'http'|void}
105+
* The import target options.
106+
* @type {Options}
116107
*/
117-
this.moduleType = "unknown"
118-
119-
if (name.startsWith("./") || name.startsWith(".\\")) {
120-
this.moduleType = "relative"
121-
} else if (name.startsWith("/") || name.startsWith("\\")) {
122-
this.moduleType = "absolute"
123-
} else if (isBuiltin(name)) {
124-
this.moduleType = "node"
125-
} else if (isNodeModule(name, options)) {
126-
this.moduleType = "npm"
127-
} else if (name.startsWith("http://") || name.startsWith("https://")) {
128-
this.moduleType = "http"
129-
}
108+
this.options = options
130109

131110
/**
132-
* The full path of this import target.
133-
* If the target is a module and it does not exist then this is `null`.
134-
* @type {string|null}
111+
* What type of module are we looking for?
112+
* @type {ModuleType}
113+
*/
114+
this.moduleType = this.getModuleType()
115+
116+
/**
117+
* What import style are we using
118+
* @type {ModuleStyle}
135119
*/
136-
this.filePath = getFilePath(isModule, name, options, moduleType)
120+
this.moduleStyle = this.getModuleStyle(moduleType)
137121

138122
/**
139123
* The module name of this import target.
140124
* If the target is a relative path then this is `null`.
141-
* @type {string|null}
125+
* @type {string | null}
142126
*/
143-
this.moduleName = isModule ? getModuleName(name) : null
127+
this.moduleName = this.getModuleName()
128+
129+
/**
130+
* The full path of this import target.
131+
* If the target is a module and it does not exist then this is `null`.
132+
* @type {string | null}
133+
*/
134+
this.filePath = getFilePath(
135+
name,
136+
options,
137+
this.moduleType,
138+
this.moduleStyle
139+
)
140+
}
141+
142+
/**
143+
* What type of module is this
144+
* @returns {ModuleType}
145+
*/
146+
getModuleType() {
147+
if (/^\.{1,2}([\\/]|$)/.test(this.name)) {
148+
return "relative"
149+
}
150+
151+
if (/^[\\/]/.test(this.name)) {
152+
return "absolute"
153+
}
154+
155+
if (isBuiltin(this.name)) {
156+
return "node"
157+
}
158+
159+
if (/^(@[\w~-][\w.~-]*\/)?[\w~-][\w.~-]*/.test(this.name)) {
160+
return "npm"
161+
}
162+
163+
if (/^https?:\/\//.test(this.name)) {
164+
return "http"
165+
}
166+
167+
return "unknown"
168+
}
169+
170+
/**
171+
* What module import style is used
172+
* @param {'import' | 'require'} fallback
173+
* @returns {ModuleStyle}
174+
*/
175+
getModuleStyle(fallback) {
176+
/** @type {import('eslint').Rule.Node} */
177+
let node = { parent: this.node }
178+
179+
do {
180+
node = node.parent
181+
182+
// `const {} = require('')`
183+
if (
184+
node.type === "CallExpression" &&
185+
node.callee.name === "require"
186+
) {
187+
return "require"
188+
}
189+
190+
// `import type {} from '';`
191+
if (
192+
node.type === "ImportDeclaration" &&
193+
node.importKind === "type"
194+
) {
195+
return "type"
196+
}
197+
198+
// `import {} from '';`
199+
if (
200+
node.type === "ImportDeclaration" &&
201+
node.importKind === "value"
202+
) {
203+
return "import"
204+
}
205+
} while (node.parent)
206+
207+
return fallback
208+
}
209+
210+
/**
211+
* Get the node or npm module name
212+
* @returns {string}
213+
*/
214+
getModuleName() {
215+
if (this.moduleType === "relative") return
216+
217+
if (this.moduleType === "npm") {
218+
if (this.name.startsWith("@")) {
219+
return trimAfter(this.name, "/", 2)
220+
}
221+
222+
return trimAfter(this.name, "/")
223+
}
224+
225+
if (this.moduleType === "node") {
226+
if (this.name.startsWith("node:")) {
227+
return trimAfter(this.name.slice(5), "/")
228+
}
229+
230+
return trimAfter(this.name, "/")
231+
}
144232
}
145233
}

0 commit comments

Comments
 (0)