diff --git a/docs/rules/prefer-node-protocol.md b/docs/rules/prefer-node-protocol.md index 5256e449..456b4b74 100644 --- a/docs/rules/prefer-node-protocol.md +++ b/docs/rules/prefer-node-protocol.md @@ -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 @@ -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: @@ -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 diff --git a/lib/rules/prefer-node-protocol.js b/lib/rules/prefer-node-protocol.js index dba051df..89622044 100644 --- a/lib/rules/prefer-node-protocol.js +++ b/lib/rules/prefer-node-protocol.js @@ -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") @@ -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} @@ -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. @@ -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) { @@ -96,7 +108,10 @@ function validate(node, context, moduleStyle) { return } - if (moduleStyle === "require" && !isValidRequireArgument(node)) { + if ( + (moduleStyle === "require" || moduleStyle === "getBuiltinModule") && + !isValidRequireArgument(node) + ) { return } @@ -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: { @@ -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) { diff --git a/tests/lib/rules/prefer-node-protocol.js b/tests/lib/rules/prefer-node-protocol.js index 6f6762b3..e010cab6 100644 --- a/tests/lib/rules/prefer-node-protocol.js +++ b/tests/lib/rules/prefer-node-protocol.js @@ -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: [ { @@ -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`."], + }, ], })