44 */
55"use strict"
66
7+ const { getStringIfConstant } = require ( "@eslint-community/eslint-utils" )
8+
79const { Range } = require ( "semver" )
810
911const 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+
1314const {
1415 NodeBuiltinModules,
1516} = require ( "../unsupported-features/node-builtins.js" )
@@ -27,6 +28,104 @@ const messageId = "preferNodeProtocol"
2728const supportedRangeForEsm = new Range ( "^12.20.0 || >= 14.13.1" )
2829const supportedRangeForCjs = new Range ( "^14.18.0 || >= 16.0.0" )
2930
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+
30129/** @type {import('eslint').Rule.RuleModule } */
31130module . exports = {
32131 meta : {
@@ -52,139 +151,36 @@ module.exports = {
52151 type : "suggestion" ,
53152 } ,
54153 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+ } ,
131171
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" )
183174 } ,
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+ }
189185 } ,
190186}
0 commit comments