4
4
*/
5
5
"use strict"
6
6
7
+ const { getStringIfConstant } = require ( "@eslint-community/eslint-utils" )
8
+
7
9
const { Range } = require ( "semver" )
8
10
9
11
const getConfiguredNodeVersion = require ( "../util/get-configured-node-version" )
10
- const visitImport = require ( "../util/visit-import" )
11
- const visitRequire = require ( "../util/visit-require" )
12
- const mergeVisitorsInPlace = require ( "../util/merge-visitors-in-place" )
12
+ const stripImportPathParams = require ( "../util/strip-import-path-params" )
13
+
13
14
const {
14
15
NodeBuiltinModules,
15
16
} = require ( "../unsupported-features/node-builtins.js" )
@@ -27,6 +28,104 @@ const messageId = "preferNodeProtocol"
27
28
const supportedRangeForEsm = new Range ( "^12.20.0 || >= 14.13.1" )
28
29
const supportedRangeForCjs = new Range ( "^14.18.0 || >= 16.0.0" )
29
30
31
+ /**
32
+ * @param {import('estree').Node } [node]
33
+ * @returns {node is import('estree').Literal }
34
+ */
35
+ function isStringLiteral ( node ) {
36
+ return node ?. type === "Literal" && typeof node . type === "string"
37
+ }
38
+
39
+ /**
40
+ * @param {import('eslint').Rule.RuleContext } context
41
+ * @param {import('../util/import-target.js').ModuleStyle } moduleStyle
42
+ * @returns {boolean }
43
+ */
44
+ function isEnablingThisRule ( context , moduleStyle ) {
45
+ const version = getConfiguredNodeVersion ( context )
46
+
47
+ // Only check Node.js version because this rule is meaningless if configured Node.js version doesn't match semver range.
48
+ if ( ! version . intersects ( supportedRangeForEsm ) ) {
49
+ return false
50
+ }
51
+
52
+ // Only check when using `require`
53
+ if (
54
+ moduleStyle === "require" &&
55
+ ! version . intersects ( supportedRangeForCjs )
56
+ ) {
57
+ return false
58
+ }
59
+
60
+ return true
61
+ }
62
+
63
+ /**
64
+ * @param {import('estree').Node } node
65
+ * @returns {boolean }
66
+ **/
67
+ function isValidRequireArgument ( node ) {
68
+ const rawName = getStringIfConstant ( node )
69
+ if ( typeof rawName !== "string" ) {
70
+ return false
71
+ }
72
+
73
+ const name = stripImportPathParams ( rawName )
74
+ if ( ! isBuiltin ( name ) ) {
75
+ return false
76
+ }
77
+
78
+ return true
79
+ }
80
+
81
+ /**
82
+ * @param {import('estree').Node | null | undefined } node
83
+ * @param {import('eslint').Rule.RuleContext } context
84
+ * @param {import('../util/import-target.js').ModuleStyle } moduleStyle
85
+ */
86
+ function validate ( node , context , moduleStyle ) {
87
+ if ( node == null ) {
88
+ return
89
+ }
90
+
91
+ if ( ! isEnablingThisRule ( context , moduleStyle ) ) {
92
+ return
93
+ }
94
+
95
+ if ( ! isStringLiteral ( node ) ) {
96
+ return
97
+ }
98
+
99
+ if ( moduleStyle === "require" && ! isValidRequireArgument ( node ) ) {
100
+ return
101
+ }
102
+
103
+ if (
104
+ ! ( "value" in node ) ||
105
+ typeof node . value !== "string" ||
106
+ node . value . startsWith ( "node:" ) ||
107
+ ! isBuiltin ( node . value ) ||
108
+ ! isBuiltin ( `node:${ node . value } ` )
109
+ ) {
110
+ return
111
+ }
112
+
113
+ context . report ( {
114
+ node,
115
+ messageId,
116
+ data : {
117
+ moduleName : node . value ,
118
+ } ,
119
+ fix ( fixer ) {
120
+ const firstCharacterIndex = ( node ?. range ?. [ 0 ] ?? 0 ) + 1
121
+ return fixer . replaceTextRange (
122
+ [ firstCharacterIndex , firstCharacterIndex ] ,
123
+ "node:"
124
+ )
125
+ } ,
126
+ } )
127
+ }
128
+
30
129
/** @type {import('eslint').Rule.RuleModule } */
31
130
module . exports = {
32
131
meta : {
@@ -52,139 +151,36 @@ module.exports = {
52
151
type : "suggestion" ,
53
152
} ,
54
153
create ( context ) {
55
- /**
56
- * @param {import('estree').Node } node
57
- * @param {object } options
58
- * @param {string } options.name
59
- * @param {number } options.argumentsLength
60
- * @returns {node is import('estree').CallExpression }
61
- */
62
- function isCallExpression ( node , { name, argumentsLength } ) {
63
- if ( node ?. type !== "CallExpression" ) {
64
- return false
65
- }
66
-
67
- if ( node . optional ) {
68
- return false
69
- }
70
-
71
- if ( node . arguments . length !== argumentsLength ) {
72
- return false
73
- }
74
-
75
- if (
76
- node . callee . type !== "Identifier" ||
77
- node . callee . name !== name
78
- ) {
79
- return false
80
- }
81
-
82
- return true
83
- }
84
-
85
- /**
86
- * @param {import('estree').Node } [node]
87
- * @returns {node is import('estree').Literal }
88
- */
89
- function isStringLiteral ( node ) {
90
- return node ?. type === "Literal" && typeof node . type === "string"
91
- }
92
-
93
- /**
94
- * @param {import('estree').Node | undefined } node
95
- * @returns {node is import('estree').CallExpression }
96
- */
97
- function isStaticRequire ( node ) {
98
- return (
99
- node != null &&
100
- isCallExpression ( node , {
101
- name : "require" ,
102
- argumentsLength : 1 ,
103
- } ) &&
104
- isStringLiteral ( node . arguments [ 0 ] )
105
- )
106
- }
107
-
108
- /**
109
- * @param {import('eslint').Rule.RuleContext } context
110
- * @param {import('../util/import-target.js').ModuleStyle } moduleStyle
111
- * @returns {boolean }
112
- */
113
- function isEnablingThisRule ( context , moduleStyle ) {
114
- const version = getConfiguredNodeVersion ( context )
115
-
116
- // Only check Node.js version because this rule is meaningless if configured Node.js version doesn't match semver range.
117
- if ( ! version . intersects ( supportedRangeForEsm ) ) {
118
- return false
119
- }
120
-
121
- // Only check when using `require`
122
- if (
123
- moduleStyle === "require" &&
124
- ! version . intersects ( supportedRangeForCjs )
125
- ) {
126
- return false
127
- }
128
-
129
- return true
130
- }
154
+ return {
155
+ CallExpression ( node ) {
156
+ if ( node . type !== "CallExpression" ) {
157
+ return
158
+ }
159
+
160
+ if (
161
+ node . optional ||
162
+ node . arguments . length !== 1 ||
163
+ node . callee . type !== "Identifier" ||
164
+ node . callee . name !== "require"
165
+ ) {
166
+ return
167
+ }
168
+
169
+ return validate ( node . arguments [ 0 ] , context , "require" )
170
+ } ,
131
171
132
- /** @type {import('../util/import-target.js')[] } */
133
- const targets = [ ]
134
- return [
135
- visitImport ( context , { includeCore : true } , importTargets => {
136
- targets . push ( ...importTargets )
137
- } ) ,
138
- visitRequire ( context , { includeCore : true } , requireTargets => {
139
- targets . push (
140
- ...requireTargets . filter ( target =>
141
- isStaticRequire ( target . node . parent )
142
- )
143
- )
144
- } ) ,
145
- {
146
- "Program:exit" ( ) {
147
- for ( const { node, moduleStyle } of targets ) {
148
- if ( ! isEnablingThisRule ( context , moduleStyle ) ) {
149
- continue
150
- }
151
-
152
- if ( node . type === "TemplateLiteral" ) {
153
- continue
154
- }
155
-
156
- if (
157
- ! ( "value" in node ) ||
158
- typeof node . value !== "string" ||
159
- node . value . startsWith ( "node:" ) ||
160
- ! isBuiltin ( node . value ) ||
161
- ! isBuiltin ( `node:${ node . value } ` )
162
- ) {
163
- continue
164
- }
165
-
166
- context . report ( {
167
- node,
168
- messageId,
169
- data : {
170
- moduleName : node . value ,
171
- } ,
172
- fix ( fixer ) {
173
- const firstCharacterIndex =
174
- ( node ?. range ?. [ 0 ] ?? 0 ) + 1
175
- return fixer . replaceTextRange (
176
- [ firstCharacterIndex , firstCharacterIndex ] ,
177
- "node:"
178
- )
179
- } ,
180
- } )
181
- }
182
- } ,
172
+ ExportAllDeclaration ( node ) {
173
+ return validate ( node . source , context , "import" )
183
174
} ,
184
- ] . reduce (
185
- ( mergedVisitor , thisVisitor ) =>
186
- mergeVisitorsInPlace ( mergedVisitor , thisVisitor ) ,
187
- { }
188
- )
175
+ ExportNamedDeclaration ( node ) {
176
+ return validate ( node . source , context , "import" )
177
+ } ,
178
+ ImportDeclaration ( node ) {
179
+ return validate ( node . source , context , "import" )
180
+ } ,
181
+ ImportExpression ( node ) {
182
+ return validate ( node . source , context , "import" )
183
+ } ,
184
+ }
189
185
} ,
190
186
}
0 commit comments