Skip to content

Commit c3f895c

Browse files
committed
feat(prefer-node-protocol): Add support for process.getBuiltinModule()
1 parent 63cbdb9 commit c3f895c

File tree

3 files changed

+118
-11
lines changed

3 files changed

+118
-11
lines changed

docs/rules/prefer-node-protocol.md

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Note that Node.js support for this feature began in:
1717

1818
> v16.0.0, v14.18.0 (`require()`)
1919
> v14.13.1, v12.20.0 (`import`)
20+
> v22.3.0, v20.16.0 (`process.getBuiltinModule()`)
2021
2122
## 📖 Rule Details
2223

@@ -32,6 +33,8 @@ import fs from "node:fs"
3233
export { promises } from "node:fs"
3334

3435
const fs = require("node:fs")
36+
37+
const fs = process.getBuiltinModule("node:fs")
3538
```
3639

3740
👎 Examples of **incorrect** code for this rule:
@@ -44,6 +47,8 @@ import fs from "fs"
4447
export { promises } from "fs"
4548

4649
const fs = require("fs")
50+
51+
const fs = process.getBuiltinModule("fs")
4752
```
4853

4954
### Configured Node.js version range

lib/rules/prefer-node-protocol.js

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

7-
const { getStringIfConstant } = require("@eslint-community/eslint-utils")
7+
const {
8+
getStringIfConstant,
9+
getPropertyName,
10+
} = require("@eslint-community/eslint-utils")
811

912
const { Range } = require("semver")
1013

@@ -15,6 +18,10 @@ const {
1518
NodeBuiltinModules,
1619
} = require("../unsupported-features/node-builtins.js")
1720

21+
/**
22+
* @typedef { 'import' | 'require' | 'getBuiltinModule' } ModuleStyle
23+
*/
24+
1825
/**
1926
* @param {string} name The name of the node module
2027
* @returns {boolean}
@@ -38,10 +45,15 @@ function isStringLiteral(node) {
3845

3946
/**
4047
* @param {import('eslint').Rule.RuleContext} context
41-
* @param {import('../util/import-target.js').ModuleStyle} moduleStyle
48+
* @param {ModuleStyle} moduleStyle
4249
* @returns {boolean}
4350
*/
4451
function isEnablingThisRule(context, moduleStyle) {
52+
// The availability of `process.getBuiltinModule()` means that `node:` protocol is supported.
53+
if (moduleStyle === "getBuiltinModule") {
54+
return true
55+
}
56+
4557
const version = getConfiguredNodeVersion(context)
4658

4759
// Only check Node.js version because this rule is meaningless if configured Node.js version doesn't match semver range.
@@ -81,7 +93,7 @@ function isValidRequireArgument(node) {
8193
/**
8294
* @param {import('estree').Node | null | undefined} node
8395
* @param {import('eslint').Rule.RuleContext} context
84-
* @param {import('../util/import-target.js').ModuleStyle} moduleStyle
96+
* @param {ModuleStyle} moduleStyle
8597
*/
8698
function validate(node, context, moduleStyle) {
8799
if (node == null) {
@@ -96,7 +108,10 @@ function validate(node, context, moduleStyle) {
96108
return
97109
}
98110

99-
if (moduleStyle === "require" && !isValidRequireArgument(node)) {
111+
if (
112+
(moduleStyle === "require" || moduleStyle === "getBuiltinModule") &&
113+
!isValidRequireArgument(node)
114+
) {
100115
return
101116
}
102117

@@ -126,6 +141,25 @@ function validate(node, context, moduleStyle) {
126141
})
127142
}
128143

144+
/**
145+
* @param {import('estree').Expression | import('estree').Super} node
146+
*/
147+
function isProcess(node) {
148+
if (node.type === "Identifier" && node.name === "process") {
149+
return true
150+
}
151+
if (node.type === "MemberExpression") {
152+
if (getPropertyName(node) !== "process") {
153+
return false
154+
}
155+
return (
156+
node.object.type === "Identifier" &&
157+
node.object.name === "globalThis"
158+
)
159+
}
160+
return false
161+
}
162+
129163
/** @type {import('./rule-module').RuleModule} */
130164
module.exports = {
131165
meta: {
@@ -158,15 +192,25 @@ module.exports = {
158192
}
159193

160194
if (
161-
node.optional ||
162-
node.arguments.length !== 1 ||
163-
node.callee.type !== "Identifier" ||
164-
node.callee.name !== "require"
195+
!node.optional &&
196+
node.arguments.length === 1 &&
197+
node.callee.type === "Identifier" &&
198+
node.callee.name === "require"
165199
) {
166-
return
200+
return validate(node.arguments[0], context, "require")
201+
}
202+
if (
203+
node.arguments.length >= 1 &&
204+
node.callee.type === "MemberExpression" &&
205+
isProcess(node.callee.object) &&
206+
getPropertyName(node.callee) === "getBuiltinModule"
207+
) {
208+
return validate(
209+
node.arguments[0],
210+
context,
211+
"getBuiltinModule"
212+
)
167213
}
168-
169-
return validate(node.arguments[0], context, "require")
170214
},
171215

172216
ExportAllDeclaration(node) {

tests/lib/rules/prefer-node-protocol.js

+58
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,25 @@ new RuleTester({
7777
options: [{ version: "15.14.0" }],
7878
code: 'const fs = require("fs");',
7979
},
80+
81+
// `process.getBuiltinModule`
82+
'const fs = process.getBuiltinModule("node:fs");',
83+
'const fs = globalThis.process.getBuiltinModule("node:fs");',
84+
'const fs = process.getBuiltinModule("node:fs/promises");',
85+
'const fs = process.getNotBuiltinModule("node:fs", extra);',
86+
"const fs = process.getBuiltinModule(fs);",
87+
'const fs = process.getNotBuiltinModule("fs");',
88+
'const fs = process.foo.getNotBuiltinModule("fs");',
89+
'const fs = foo.process.getNotBuiltinModule("fs");',
90+
'const fs = process.getNotBuiltinModule.foo("fs");',
91+
"const fs = process.getNotBuiltinModule(`fs`);",
92+
"const fs = process.getNotBuiltinModule();",
93+
'const fs = process.getNotBuiltinModule(...["fs"]);',
94+
'const fs = process.getNotBuiltinModule("eslint-plugin-n");',
95+
{
96+
options: [{ version: "12.19.1" }],
97+
code: 'const fs = process.getBuiltinModule("node:fs");',
98+
},
8099
],
81100
invalid: [
82101
{
@@ -242,5 +261,44 @@ new RuleTester({
242261
`,
243262
errors: ["Prefer `node:buffer` over `buffer`."],
244263
},
264+
265+
// `process.getBuiltinModule`
266+
{
267+
code: 'const {promises} = process.getBuiltinModule("fs")',
268+
output: 'const {promises} = process.getBuiltinModule("node:fs")',
269+
errors: ["Prefer `node:fs` over `fs`."],
270+
},
271+
{
272+
code: 'const {promises} = globalThis.process.getBuiltinModule("fs")',
273+
output: 'const {promises} = globalThis.process.getBuiltinModule("node:fs")',
274+
errors: ["Prefer `node:fs` over `fs`."],
275+
},
276+
{
277+
code: 'const {promises} = process.getBuiltinModule("fs", extra)',
278+
output: 'const {promises} = process.getBuiltinModule("node:fs", extra)',
279+
errors: ["Prefer `node:fs` over `fs`."],
280+
},
281+
{
282+
code: "const fs = process.getBuiltinModule('fs/promises')",
283+
output: "const fs = process.getBuiltinModule('node:fs/promises')",
284+
errors: ["Prefer `node:fs/promises` over `fs/promises`."],
285+
},
286+
{
287+
code: `
288+
const express = process.getBuiltinModule('express');
289+
const fs = process.getBuiltinModule('fs/promises');
290+
`,
291+
output: `
292+
const express = process.getBuiltinModule('express');
293+
const fs = process.getBuiltinModule('node:fs/promises');
294+
`,
295+
errors: ["Prefer `node:fs/promises` over `fs/promises`."],
296+
},
297+
{
298+
options: [{ version: "12.19.1" }],
299+
code: 'const {promises} = process.getBuiltinModule("fs")',
300+
output: 'const {promises} = process.getBuiltinModule("node:fs")',
301+
errors: ["Prefer `node:fs` over `fs`."],
302+
},
245303
],
246304
})

0 commit comments

Comments
 (0)