From fde2ba6cc1aafcc7880a62b2684b68cd877154b6 Mon Sep 17 00:00:00 2001 From: Teppei Sato Date: Tue, 22 Aug 2023 11:28:57 +0900 Subject: [PATCH] fix: support `node:` prefix (#109) --- lib/rules/no-deprecated-api.js | 12 +- .../no-unsupported-features/node-builtins.js | 4 + lib/rules/prefer-promises/dns.js | 1 + lib/rules/prefer-promises/fs.js | 1 + lib/util/check-prefer-global.js | 11 +- lib/util/check-unsupported-builtins.js | 3 +- lib/util/enumerate-property-names.js | 9 +- lib/util/extend-trackmap-with-node-prefix.js | 21 +++ lib/util/unprefix-node-colon.js | 13 ++ tests/lib/rules/no-deprecated-api.js | 47 +++++++ .../no-unsupported-features/node-builtins.js | 133 ++++++++++++++++++ tests/lib/rules/prefer-promises/dns.js | 10 ++ tests/lib/rules/prefer-promises/fs.js | 10 ++ 13 files changed, 258 insertions(+), 17 deletions(-) create mode 100644 lib/util/extend-trackmap-with-node-prefix.js create mode 100644 lib/util/unprefix-node-colon.js diff --git a/lib/rules/no-deprecated-api.js b/lib/rules/no-deprecated-api.js index a80b77e1..42b4144a 100644 --- a/lib/rules/no-deprecated-api.js +++ b/lib/rules/no-deprecated-api.js @@ -13,8 +13,10 @@ const { const enumeratePropertyNames = require("../util/enumerate-property-names") const getConfiguredNodeVersion = require("../util/get-configured-node-version") const getSemverRange = require("../util/get-semver-range") +const extendTrackmapWithNodePrefix = require("../util/extend-trackmap-with-node-prefix") +const unprefixNodeColon = require("../util/unprefix-node-colon") -const modules = { +const rawModules = { _linklist: { [READ]: { since: "5.0.0", replacedBy: null }, }, @@ -567,6 +569,8 @@ const modules = { }, }, } +const modules = extendTrackmapWithNodePrefix(rawModules) + const globals = { Buffer: { [CONSTRUCT]: { @@ -660,7 +664,7 @@ function toReplaceMessage(replacedBy, version) { * @returns {string} The name. */ function toName(type, path) { - const baseName = path.join(".") + const baseName = unprefixNodeColon(path.join(".")) return type === ReferenceTracker.CALL ? `${baseName}()` : type === ReferenceTracker.CONSTRUCT @@ -701,7 +705,9 @@ module.exports = { ignoreModuleItems: { type: "array", items: { - enum: Array.from(enumeratePropertyNames(modules)), + enum: Array.from( + enumeratePropertyNames(rawModules) + ), }, additionalItems: false, uniqueItems: true, diff --git a/lib/rules/no-unsupported-features/node-builtins.js b/lib/rules/no-unsupported-features/node-builtins.js index 5c4bb06c..7f94bbef 100644 --- a/lib/rules/no-unsupported-features/node-builtins.js +++ b/lib/rules/no-unsupported-features/node-builtins.js @@ -11,6 +11,7 @@ const { } = require("../../util/check-unsupported-builtins") const enumeratePropertyNames = require("../../util/enumerate-property-names") const getConfiguredNodeVersion = require("../../util/get-configured-node-version") +const extendTrackMapWithNodePrefix = require("../../util/extend-trackmap-with-node-prefix") const trackMap = { globals: { @@ -368,6 +369,9 @@ Object.assign(trackMap.globals, { console: trackMap.modules.console, process: trackMap.modules.process, }) + +trackMap.modules = extendTrackMapWithNodePrefix(trackMap.modules) + /*eslint-enable camelcase */ module.exports = { diff --git a/lib/rules/prefer-promises/dns.js b/lib/rules/prefer-promises/dns.js index 17ef24d4..f2d9b912 100644 --- a/lib/rules/prefer-promises/dns.js +++ b/lib/rules/prefer-promises/dns.js @@ -32,6 +32,7 @@ const trackMap = { setServers: { [CALL]: true }, }, } +trackMap["node:dns"] = trackMap.dns module.exports = { meta: { diff --git a/lib/rules/prefer-promises/fs.js b/lib/rules/prefer-promises/fs.js index 70b26138..f6bcbddc 100644 --- a/lib/rules/prefer-promises/fs.js +++ b/lib/rules/prefer-promises/fs.js @@ -34,6 +34,7 @@ const trackMap = { readFile: { [CALL]: true }, }, } +trackMap["node:fs"] = trackMap.fs module.exports = { meta: { diff --git a/lib/util/check-prefer-global.js b/lib/util/check-prefer-global.js index cf1d67b6..7eaf442a 100644 --- a/lib/util/check-prefer-global.js +++ b/lib/util/check-prefer-global.js @@ -5,6 +5,7 @@ "use strict" const { ReferenceTracker } = require("@eslint-community/eslint-utils") +const extendTrackmapWithNodePrefix = require("./extend-trackmap-with-node-prefix") /** * Verifier for `prefer-global/*` rules. @@ -34,15 +35,7 @@ class Verifier { mode: "legacy", }) - const modules = { - ...trackMap.modules, - ...Object.fromEntries( - Object.entries(trackMap.modules).map(([name, value]) => [ - `node:${name}`, - value, - ]) - ), - } + const modules = extendTrackmapWithNodePrefix(trackMap.modules) for (const { node } of [ ...tracker.iterateCjsReferences(modules), diff --git a/lib/util/check-unsupported-builtins.js b/lib/util/check-unsupported-builtins.js index 79b8f1e4..32a14e8e 100644 --- a/lib/util/check-unsupported-builtins.js +++ b/lib/util/check-unsupported-builtins.js @@ -8,6 +8,7 @@ const { Range, lt, major } = require("semver") // eslint-disable-line no-unused- const { ReferenceTracker } = require("@eslint-community/eslint-utils") const getConfiguredNodeVersion = require("./get-configured-node-version") const getSemverRange = require("./get-semver-range") +const unprefixNodeColon = require("./unprefix-node-colon") /** * @typedef {Object} SupportInfo @@ -92,7 +93,7 @@ module.exports.checkUnsupportedBuiltins = function checkUnsupportedBuiltins( ] for (const { node, path, info } of references) { - const name = path.join(".") + const name = unprefixNodeColon(path.join(".")) const supported = isSupported(info, options.version) if (!supported && !options.ignores.has(name)) { diff --git a/lib/util/enumerate-property-names.js b/lib/util/enumerate-property-names.js index 2dfccb74..e7e72bab 100644 --- a/lib/util/enumerate-property-names.js +++ b/lib/util/enumerate-property-names.js @@ -5,6 +5,7 @@ "use strict" const { CALL, CONSTRUCT, READ } = require("@eslint-community/eslint-utils") +const unprefixNodeColon = require("./unprefix-node-colon") /** * Enumerate property names of a given object recursively. @@ -18,17 +19,17 @@ function* enumeratePropertyNames(trackMap, path = []) { if (typeof value !== "object") { continue } - path.push(key) + const name = unprefixNodeColon(path.join(".")) if (value[CALL]) { - yield `${path.join(".")}()` + yield `${name}()` } if (value[CONSTRUCT]) { - yield `new ${path.join(".")}()` + yield `new ${name}()` } if (value[READ]) { - yield path.join(".") + yield name } yield* enumeratePropertyNames(value, path) diff --git a/lib/util/extend-trackmap-with-node-prefix.js b/lib/util/extend-trackmap-with-node-prefix.js new file mode 100644 index 00000000..d103e9d1 --- /dev/null +++ b/lib/util/extend-trackmap-with-node-prefix.js @@ -0,0 +1,21 @@ +"use strict" + +const isCoreModule = require("is-core-module") + +/** + * Extend trackMap.modules with `node:` prefixed modules + * @param {Object} modules Like `{assert: foo}` + * @returns {Object} Like `{assert: foo}, "node:assert": foo}` + */ +module.exports = function extendTrackMapWithNodePrefix(modules) { + const ret = { + ...modules, + ...Object.fromEntries( + Object.entries(modules) + .map(([name, value]) => [`node:${name}`, value]) + // Note: "999" arbitrary to check current/future Node.js version + .filter(([name]) => isCoreModule(name, "999")) + ), + } + return ret +} diff --git a/lib/util/unprefix-node-colon.js b/lib/util/unprefix-node-colon.js new file mode 100644 index 00000000..b5c1b49a --- /dev/null +++ b/lib/util/unprefix-node-colon.js @@ -0,0 +1,13 @@ +"use strict" + +/** + * Remove `node:` prefix from module name + * @param {string} name The module name such as `node:assert` or `assert`. + * @returns {string} The unprefixed module name like `assert`. + */ +module.exports = function unprefixNodeColon(name) { + if (name.startsWith("node:")) { + return name.slice(5) + } + return name +} diff --git a/tests/lib/rules/no-deprecated-api.js b/tests/lib/rules/no-deprecated-api.js index 281315a6..d221e91a 100644 --- a/tests/lib/rules/no-deprecated-api.js +++ b/tests/lib/rules/no-deprecated-api.js @@ -14,6 +14,10 @@ ruleTester.run("no-deprecated-api", rule, { code: "require('buffer').Buffer", env: { node: true }, }, + { + code: "require('node:buffer').Buffer", + env: { node: true }, + }, { code: "foo(require('buffer').Buffer)", env: { node: true }, @@ -93,6 +97,16 @@ ruleTester.run("no-deprecated-api", rule, { ], env: { node: true }, }, + { + code: "require('node:buffer').Buffer()", + options: [ + { + // + ignoreModuleItems: ["buffer.Buffer()"], + }, + ], + env: { node: true }, + }, { code: "require('domain');", options: [ @@ -189,6 +203,14 @@ ruleTester.run("no-deprecated-api", rule, { "'new buffer.Buffer()' was deprecated since v6.0.0. Use 'buffer.Buffer.alloc()' or 'buffer.Buffer.from()' instead.", ], }, + { + code: "new (require('node:buffer').Buffer)()", + options: [{ version: "6.0.0" }], + env: { node: true }, + errors: [ + "'new buffer.Buffer()' was deprecated since v6.0.0. Use 'buffer.Buffer.alloc()' or 'buffer.Buffer.from()' instead.", + ], + }, { code: "require('buffer').Buffer()", options: [{ version: "6.0.0" }], @@ -197,6 +219,14 @@ ruleTester.run("no-deprecated-api", rule, { "'buffer.Buffer()' was deprecated since v6.0.0. Use 'buffer.Buffer.alloc()' or 'buffer.Buffer.from()' instead.", ], }, + { + code: "require('node:buffer').Buffer()", + options: [{ version: "6.0.0" }], + env: { node: true }, + errors: [ + "'buffer.Buffer()' was deprecated since v6.0.0. Use 'buffer.Buffer.alloc()' or 'buffer.Buffer.from()' instead.", + ], + }, { code: "var b = require('buffer'); new b.Buffer()", options: [{ version: "6.0.0" }], @@ -278,6 +308,14 @@ ruleTester.run("no-deprecated-api", rule, { "'buffer.SlowBuffer' was deprecated since v6.0.0. Use 'buffer.Buffer.allocUnsafeSlow()' instead.", ], }, + { + code: "require('node:buffer').SlowBuffer", + options: [{ version: "6.0.0" }], + env: { node: true }, + errors: [ + "'buffer.SlowBuffer' was deprecated since v6.0.0. Use 'buffer.Buffer.allocUnsafeSlow()' instead.", + ], + }, { code: "var b = require('buffer'); b.SlowBuffer", options: [{ version: "6.0.0" }], @@ -713,6 +751,15 @@ ruleTester.run("no-deprecated-api", rule, { "'new buffer.Buffer()' was deprecated since v6.0.0. Use 'buffer.Buffer.alloc()' or 'buffer.Buffer.from()' instead.", ], }, + { + code: "import b from 'node:buffer'; new b.Buffer()", + options: [{ version: "6.0.0" }], + parserOptions: { sourceType: "module" }, + env: { es6: true }, + errors: [ + "'new buffer.Buffer()' was deprecated since v6.0.0. Use 'buffer.Buffer.alloc()' or 'buffer.Buffer.from()' instead.", + ], + }, { code: "import * as b from 'buffer'; new b.Buffer()", options: [{ version: "6.0.0" }], diff --git a/tests/lib/rules/no-unsupported-features/node-builtins.js b/tests/lib/rules/no-unsupported-features/node-builtins.js index 219d8ed1..3f690d61 100644 --- a/tests/lib/rules/no-unsupported-features/node-builtins.js +++ b/tests/lib/rules/no-unsupported-features/node-builtins.js @@ -52,6 +52,10 @@ new RuleTester({ code: "require('assert').strictEqual()", options: [{ version: "0.12.0" }], }, + { + code: "require('node:assert').strictEqual()", + options: [{ version: "0.12.0" }], + }, { code: "var assert = require('assert'); assert(); assert.strictEqual()", options: [{ version: "0.12.0" }], @@ -72,6 +76,10 @@ new RuleTester({ code: "import assert from 'assert'; assert.deepStrictEqual()", options: [{ version: "4.0.0" }], }, + { + code: "import assert from 'node:assert'; assert.deepStrictEqual()", + options: [{ version: "4.0.0" }], + }, { code: "import { deepStrictEqual } from 'assert'; deepStrictEqual()", options: [{ version: "4.0.0" }], @@ -113,6 +121,15 @@ new RuleTester({ }, ], }, + { + code: "require('node:assert').deepStrictEqual()", + options: [ + { + version: "3.9.9", + ignores: ["assert.deepStrictEqual"], + }, + ], + }, { code: "var assert = require('assert'); assert.deepStrictEqual()", options: [ @@ -229,6 +246,20 @@ new RuleTester({ }, ], }, + { + code: "require('node:assert').deepStrictEqual()", + options: [{ version: "3.9.9" }], + errors: [ + { + messageId: "unsupported", + data: { + name: "assert.deepStrictEqual", + supported: "4.0.0", + version: "3.9.9", + }, + }, + ], + }, { code: "var assert = require('assert'); assert.deepStrictEqual()", options: [{ version: "3.9.9" }], @@ -271,6 +302,20 @@ new RuleTester({ }, ], }, + { + code: "import assert from 'node:assert'; assert.deepStrictEqual()", + options: [{ version: "3.9.9" }], + errors: [ + { + messageId: "unsupported", + data: { + name: "assert.deepStrictEqual", + supported: "4.0.0", + version: "3.9.9", + }, + }, + ], + }, { code: "import { deepStrictEqual } from 'assert'; deepStrictEqual()", options: [{ version: "3.9.9" }], @@ -431,10 +476,18 @@ new RuleTester({ code: "require('async_hooks')", options: [{ version: "8.0.0" }], }, + { + code: "require('node:async_hooks')", + options: [{ version: "8.0.0" }], + }, { code: "import hooks from 'async_hooks'", options: [{ version: "8.0.0" }], }, + { + code: "import hooks from 'node:async_hooks'", + options: [{ version: "8.0.0" }], + }, { code: "require('async_hooks').createHook()", options: [{ version: "8.1.0" }], @@ -477,10 +530,18 @@ new RuleTester({ code: "require('async_hooks')", options: [{ version: "7.9.9", ignores: ["async_hooks"] }], }, + { + code: "require('node:async_hooks')", + options: [{ version: "7.9.9", ignores: ["async_hooks"] }], + }, { code: "import hooks from 'async_hooks'", options: [{ version: "7.9.9", ignores: ["async_hooks"] }], }, + { + code: "import hooks from 'node:async_hooks'", + options: [{ version: "7.9.9", ignores: ["async_hooks"] }], + }, { code: "import { createHook } from 'async_hooks'", options: [ @@ -587,6 +648,20 @@ new RuleTester({ }, ], }, + { + code: "require('node:async_hooks')", + options: [{ version: "7.9.9" }], + errors: [ + { + messageId: "unsupported", + data: { + name: "async_hooks", + supported: "8.0.0", + version: "7.9.9", + }, + }, + ], + }, { code: "import hooks from 'async_hooks'", options: [{ version: "7.9.9" }], @@ -601,6 +676,20 @@ new RuleTester({ }, ], }, + { + code: "import hooks from 'node:async_hooks'", + options: [{ version: "7.9.9" }], + errors: [ + { + messageId: "unsupported", + data: { + name: "async_hooks", + supported: "8.0.0", + version: "7.9.9", + }, + }, + ], + }, { code: "import { createHook } from 'async_hooks'", options: [{ version: "7.9.9" }], @@ -3078,18 +3167,34 @@ new RuleTester({ code: "import * as fs from 'fs/promises';", options: [{ version: "14.0.0" }], }, + { + code: "import * as fs from 'node:fs/promises';", + options: [{ version: "14.0.0" }], + }, { code: "require('fs/promise')", options: [{ version: "14.0.0" }], }, + { + code: "require('node:fs/promise')", + options: [{ version: "14.0.0" }], + }, { code: "import * as fs from 'fs/promises';", options: [{ version: "13.14.0", ignores: ["fs/promises"] }], }, + { + code: "import * as fs from 'node:fs/promises';", + options: [{ version: "13.14.0", ignores: ["fs/promises"] }], + }, { code: "require('fs/promise')", options: [{ version: "13.14.0", ignores: ["fs/promises"] }], }, + { + code: "require('node:fs/promise')", + options: [{ version: "13.14.0", ignores: ["fs/promises"] }], + }, ], invalid: [ { @@ -3106,6 +3211,20 @@ new RuleTester({ }, ], }, + { + code: "import * as fs from 'node:fs/promises';", + options: [{ version: "13.14.0" }], + errors: [ + { + messageId: "unsupported", + data: { + name: "fs/promises", + supported: "14.0.0", + version: "13.14.0", + }, + }, + ], + }, { code: "require('fs/promises');", options: [{ version: "13.14.0" }], @@ -3120,6 +3239,20 @@ new RuleTester({ }, ], }, + { + code: "require('node:fs/promises');", + options: [{ version: "13.14.0" }], + errors: [ + { + messageId: "unsupported", + data: { + name: "fs/promises", + supported: "14.0.0", + version: "13.14.0", + }, + }, + ], + }, { code: "const fs = require('fs/promises');", options: [{ version: "13.14.0" }], diff --git a/tests/lib/rules/prefer-promises/dns.js b/tests/lib/rules/prefer-promises/dns.js index 53de056f..281b1e74 100644 --- a/tests/lib/rules/prefer-promises/dns.js +++ b/tests/lib/rules/prefer-promises/dns.js @@ -19,10 +19,12 @@ new RuleTester({ valid: [ "const dns = require('dns'); dns.lookupSync()", "const dns = require('dns'); dns.promises.lookup()", + "const dns = require('node:dns'); dns.promises.lookup()", "const {promises} = require('dns'); promises.lookup()", "const {promises: dns} = require('dns'); dns.lookup()", "const {promises: {lookup}} = require('dns'); lookup()", "import dns from 'dns'; dns.promises.lookup()", + "import dns from 'node:dns'; dns.promises.lookup()", "import * as dns from 'dns'; dns.promises.lookup()", "import {promises} from 'dns'; promises.lookup()", "import {promises as dns} from 'dns'; dns.lookup()", @@ -32,6 +34,10 @@ new RuleTester({ code: "const dns = require('dns'); dns.lookup()", errors: [{ messageId: "preferPromises", data: { name: "lookup" } }], }, + { + code: "const dns = require('node:dns'); dns.lookup()", + errors: [{ messageId: "preferPromises", data: { name: "lookup" } }], + }, { code: "const {lookup} = require('dns'); lookup()", errors: [{ messageId: "preferPromises", data: { name: "lookup" } }], @@ -40,6 +46,10 @@ new RuleTester({ code: "import dns from 'dns'; dns.lookup()", errors: [{ messageId: "preferPromises", data: { name: "lookup" } }], }, + { + code: "import dns from 'node:dns'; dns.lookup()", + errors: [{ messageId: "preferPromises", data: { name: "lookup" } }], + }, { code: "import * as dns from 'dns'; dns.lookup()", errors: [{ messageId: "preferPromises", data: { name: "lookup" } }], diff --git a/tests/lib/rules/prefer-promises/fs.js b/tests/lib/rules/prefer-promises/fs.js index b9c66a92..4bc9b60e 100644 --- a/tests/lib/rules/prefer-promises/fs.js +++ b/tests/lib/rules/prefer-promises/fs.js @@ -20,10 +20,12 @@ new RuleTester({ "const fs = require('fs'); fs.createReadStream()", "const fs = require('fs'); fs.accessSync()", "const fs = require('fs'); fs.promises.access()", + "const fs = require('node:fs'); fs.promises.access()", "const {promises} = require('fs'); promises.access()", "const {promises: fs} = require('fs'); fs.access()", "const {promises: {access}} = require('fs'); access()", "import fs from 'fs'; fs.promises.access()", + "import fs from 'node:fs'; fs.promises.access()", "import * as fs from 'fs'; fs.promises.access()", "import {promises} from 'fs'; promises.access()", "import {promises as fs} from 'fs'; fs.access()", @@ -33,6 +35,10 @@ new RuleTester({ code: "const fs = require('fs'); fs.access()", errors: [{ messageId: "preferPromises", data: { name: "access" } }], }, + { + code: "const fs = require('node:fs'); fs.access()", + errors: [{ messageId: "preferPromises", data: { name: "access" } }], + }, { code: "const {access} = require('fs'); access()", errors: [{ messageId: "preferPromises", data: { name: "access" } }], @@ -41,6 +47,10 @@ new RuleTester({ code: "import fs from 'fs'; fs.access()", errors: [{ messageId: "preferPromises", data: { name: "access" } }], }, + { + code: "import fs from 'node:fs'; fs.access()", + errors: [{ messageId: "preferPromises", data: { name: "access" } }], + }, { code: "import * as fs from 'fs'; fs.access()", errors: [{ messageId: "preferPromises", data: { name: "access" } }],