Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/rules/prefer-node-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Note that Node.js support for this feature began in:

> v16.0.0, v14.18.0 (`require()`)
> v14.13.1, v12.20.0 (`import`)
> v22.3.0, v20.16.0 (`process.getBuiltinModule()`)

## 📖 Rule Details

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

const fs = require("node:fs")

const fs = process.getBuiltinModule("node:fs")
```

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

const fs = require("fs")

const fs = process.getBuiltinModule("fs")
```

### Configured Node.js version range
Expand Down
66 changes: 55 additions & 11 deletions lib/rules/prefer-node-protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
*/
"use strict"

const { getStringIfConstant } = require("@eslint-community/eslint-utils")
const {
getStringIfConstant,
getPropertyName,
} = require("@eslint-community/eslint-utils")

const { Range } = require("semver")

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

/**
* @typedef { 'import' | 'require' | 'getBuiltinModule' } ModuleStyle
*/

/**
* @param {string} name The name of the node module
* @returns {boolean}
Expand All @@ -38,10 +45,15 @@ function isStringLiteral(node) {

/**
* @param {import('eslint').Rule.RuleContext} context
* @param {import('../util/import-target.js').ModuleStyle} moduleStyle
* @param {ModuleStyle} moduleStyle
* @returns {boolean}
*/
function isEnablingThisRule(context, moduleStyle) {
// The availability of `process.getBuiltinModule()` means that `node:` protocol is supported.
if (moduleStyle === "getBuiltinModule") {
return true
}

const version = getConfiguredNodeVersion(context)

// Only check Node.js version because this rule is meaningless if configured Node.js version doesn't match semver range.
Expand Down Expand Up @@ -81,7 +93,7 @@ function isValidRequireArgument(node) {
/**
* @param {import('estree').Node | null | undefined} node
* @param {import('eslint').Rule.RuleContext} context
* @param {import('../util/import-target.js').ModuleStyle} moduleStyle
* @param {ModuleStyle} moduleStyle
*/
function validate(node, context, moduleStyle) {
if (node == null) {
Expand All @@ -96,7 +108,10 @@ function validate(node, context, moduleStyle) {
return
}

if (moduleStyle === "require" && !isValidRequireArgument(node)) {
if (
(moduleStyle === "require" || moduleStyle === "getBuiltinModule") &&
!isValidRequireArgument(node)
) {
return
}

Expand Down Expand Up @@ -126,6 +141,25 @@ function validate(node, context, moduleStyle) {
})
}

/**
* @param {import('estree').Expression | import('estree').Super} node
*/
function isProcess(node) {
if (node.type === "Identifier" && node.name === "process") {
return true
}
if (node.type === "MemberExpression") {
if (getPropertyName(node) !== "process") {
return false
}
return (
node.object.type === "Identifier" &&
node.object.name === "globalThis"
)
}
return false
}

/** @type {import('./rule-module').RuleModule} */
module.exports = {
meta: {
Expand Down Expand Up @@ -158,15 +192,25 @@ module.exports = {
}

if (
node.optional ||
node.arguments.length !== 1 ||
node.callee.type !== "Identifier" ||
node.callee.name !== "require"
!node.optional &&
node.arguments.length === 1 &&
node.callee.type === "Identifier" &&
node.callee.name === "require"
) {
return
return validate(node.arguments[0], context, "require")
}
if (
node.arguments.length >= 1 &&
node.callee.type === "MemberExpression" &&
isProcess(node.callee.object) &&
getPropertyName(node.callee) === "getBuiltinModule"
) {
return validate(
node.arguments[0],
context,
"getBuiltinModule"
)
}

return validate(node.arguments[0], context, "require")
},

ExportAllDeclaration(node) {
Expand Down
58 changes: 58 additions & 0 deletions tests/lib/rules/prefer-node-protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,25 @@ new RuleTester({
options: [{ version: "15.14.0" }],
code: 'const fs = require("fs");',
},

// `process.getBuiltinModule`
'const fs = process.getBuiltinModule("node:fs");',
'const fs = globalThis.process.getBuiltinModule("node:fs");',
'const fs = process.getBuiltinModule("node:fs/promises");',
'const fs = process.getNotBuiltinModule("node:fs", extra);',
"const fs = process.getBuiltinModule(fs);",
'const fs = process.getNotBuiltinModule("fs");',
'const fs = process.foo.getNotBuiltinModule("fs");',
'const fs = foo.process.getNotBuiltinModule("fs");',
'const fs = process.getNotBuiltinModule.foo("fs");',
"const fs = process.getNotBuiltinModule(`fs`);",
"const fs = process.getNotBuiltinModule();",
'const fs = process.getNotBuiltinModule(...["fs"]);',
'const fs = process.getNotBuiltinModule("eslint-plugin-n");',
{
options: [{ version: "12.19.1" }],
code: 'const fs = process.getBuiltinModule("node:fs");',
},
],
invalid: [
{
Expand Down Expand Up @@ -249,5 +268,44 @@ new RuleTester({
output: 'import https from "node:https";',
errors: ["Prefer `node:https` over `https`."],
},

// `process.getBuiltinModule`
{
code: 'const {promises} = process.getBuiltinModule("fs")',
output: 'const {promises} = process.getBuiltinModule("node:fs")',
errors: ["Prefer `node:fs` over `fs`."],
},
{
code: 'const {promises} = globalThis.process.getBuiltinModule("fs")',
output: 'const {promises} = globalThis.process.getBuiltinModule("node:fs")',
errors: ["Prefer `node:fs` over `fs`."],
},
{
code: 'const {promises} = process.getBuiltinModule("fs", extra)',
output: 'const {promises} = process.getBuiltinModule("node:fs", extra)',
errors: ["Prefer `node:fs` over `fs`."],
},
{
code: "const fs = process.getBuiltinModule('fs/promises')",
output: "const fs = process.getBuiltinModule('node:fs/promises')",
errors: ["Prefer `node:fs/promises` over `fs/promises`."],
},
{
code: `
const express = process.getBuiltinModule('express');
const fs = process.getBuiltinModule('fs/promises');
`,
output: `
const express = process.getBuiltinModule('express');
const fs = process.getBuiltinModule('node:fs/promises');
`,
errors: ["Prefer `node:fs/promises` over `fs/promises`."],
},
{
options: [{ version: "12.19.1" }],
code: 'const {promises} = process.getBuiltinModule("fs")',
output: 'const {promises} = process.getBuiltinModule("node:fs")',
errors: ["Prefer `node:fs` over `fs`."],
},
],
})