From 273b937730e7513b327e3497a8c412c80bd3853c Mon Sep 17 00:00:00 2001 From: hiroki osame Date: Mon, 5 Dec 2022 02:54:11 -0500 Subject: [PATCH] feat: add version as a setting (#70) * feat: add version as a setting * test: check settings * docs * docs: mention shareable configs * style: lintfix --- .../no-unsupported-features/es-builtins.md | 42 ++++++++++++--- .../no-unsupported-features/es-syntax.md | 42 ++++++++++++--- .../no-unsupported-features/node-builtins.md | 42 ++++++++++++--- lib/rules/no-deprecated-api.js | 12 ++--- .../no-unsupported-features/es-builtins.js | 5 +- .../no-unsupported-features/es-syntax.js | 7 +-- .../no-unsupported-features/node-builtins.js | 7 ++- lib/util/check-unsupported-builtins.js | 3 +- lib/util/get-configured-node-version.js | 26 ++++++++- tests/lib/rules/no-deprecated-api.js | 22 ++++++++ .../no-unsupported-features/es-builtins.js | 36 ++++++++++++- .../no-unsupported-features/es-syntax.js | 53 ++++++++++++++++++- .../no-unsupported-features/node-builtins.js | 22 ++++++++ 13 files changed, 274 insertions(+), 45 deletions(-) diff --git a/docs/rules/no-unsupported-features/es-builtins.md b/docs/rules/no-unsupported-features/es-builtins.md index ac2cf3ec..3e087b0d 100644 --- a/docs/rules/no-unsupported-features/es-builtins.md +++ b/docs/rules/no-unsupported-features/es-builtins.md @@ -19,10 +19,16 @@ See also [TC39 finished proposals](https://github.com/tc39/proposals/blob/master ### Configured Node.js version range -This rule reads the [engines] field of `package.json` to detect which Node.js versions your module is supporting. +This rule gets the supported Node.js version range from the following, falling back to the next if unspecified: -I recommend the use of the [engines] field because it's the official way that indicates which Node.js versions your module is supporting. -For example of `package.json`: +1. Rule configuration `version` +2. ESLint [shared setting](http://eslint.org/docs/user-guide/configuring.html#adding-shared-settings) `node.version` +3. `package.json` [`engines`] field +4. `>=8.0.0` + +The default version is `8.0.0` because it's the minimum version the community is maintaining (see also [Node.js Release Working Group](https://github.com/nodejs/Release#readme)). + +For Node.js packages, using the [`engines`] field is recommended because it's the official way to indicate support: ```json { @@ -34,7 +40,7 @@ For example of `package.json`: } ``` -If you omit the [engines] field, this rule chooses `>=8.0.0` as the configured Node.js version since `8` is the minimum version the community is maintaining (see also [Node.js Release Working Group](https://github.com/nodejs/Release#readme)). +For [Shareable Configs](https://eslint.org/docs/latest/developer-guide/shareable-configs) or packages with a different development environment (e.g. pre-compiled, web package, etc.), you can configure ESLint with `settings.node.version` to specify support. ### Options @@ -49,7 +55,7 @@ If you omit the [engines] field, this rule chooses `>=8.0.0` as the configured N #### version -As mentioned above, this rule reads the [engines] field of `package.json`. +As mentioned above, this rule reads the [`engines`] field of `package.json`. But, you can overwrite the version by `version` option. The `version` option accepts [the valid version range of `node-semver`](https://github.com/npm/node-semver#range-grammar). @@ -140,6 +146,30 @@ The `"ignores"` option accepts an array of the following strings. +### Shared Settings + +The following options can be set by [shared settings](http://eslint.org/docs/user-guide/configuring.html#adding-shared-settings). +Several rules have the same option, but we can set this option at once. + +- `version` + +For Example: + +```json +{ + "settings": { + "node": { + "version": ">=8.0.0", + } + }, + "rules": { + "n/no-unsupported-features/es-builtins": ["error", { + "ignores": [] + }] + } +} +``` + ### Known limitations This rule cannot find non-static things. @@ -150,7 +180,7 @@ For example: - New `options` properties of function parameters. - New events. -[engines]: https://docs.npmjs.com/files/package.json#engines +[`engines`]: https://docs.npmjs.com/files/package.json#engines ## 🔎 Implementation diff --git a/docs/rules/no-unsupported-features/es-syntax.md b/docs/rules/no-unsupported-features/es-syntax.md index cc7e5d0c..cf7771db 100644 --- a/docs/rules/no-unsupported-features/es-syntax.md +++ b/docs/rules/no-unsupported-features/es-syntax.md @@ -22,10 +22,16 @@ For example, set `2020` to `parserOptions.ecmaVersion`. ### Configured Node.js version range -This rule reads the [engines] field of `package.json` to detect which Node.js versions your module is supporting. +This rule gets the supported Node.js version range from the following, falling back to the next if unspecified: -I recommend the use of the [engines] field because it's the official way that indicates which Node.js versions your module is supporting. -For example of `package.json`: +1. Rule configuration `version` +2. ESLint [shared setting](http://eslint.org/docs/user-guide/configuring.html#adding-shared-settings) `node.version` +3. `package.json` [`engines`] field +4. `>=8.0.0` + +The default version is `8.0.0` because it's the minimum version the community is maintaining (see also [Node.js Release Working Group](https://github.com/nodejs/Release#readme)). + +For Node.js packages, using the [`engines`] field is recommended because it's the official way to indicate support: ```json { @@ -37,7 +43,7 @@ For example of `package.json`: } ``` -If you omit the [engines] field, this rule chooses `>=8.0.0` as the configured Node.js version since `8` is the minimum version the community is maintaining (see also [Node.js Release Working Group](https://github.com/nodejs/Release#readme)). +For [Shareable Configs](https://eslint.org/docs/latest/developer-guide/shareable-configs) or packages with a different development environment (e.g. pre-compiled, web package, etc.), you can configure ESLint with `settings.node.version` to specify support. ### Options @@ -52,7 +58,7 @@ If you omit the [engines] field, this rule chooses `>=8.0.0` as the configured N #### version -As mentioned above, this rule reads the [engines] field of `package.json`. +As mentioned above, this rule reads the [`engines`] field of `package.json`. But, you can overwrite the version by `version` option. The `version` option accepts [the valid version range of `node-semver`](https://github.com/npm/node-semver#range-grammar). @@ -123,7 +129,31 @@ The `"ignores"` option accepts an array of the following strings. -[engines]: https://docs.npmjs.com/files/package.json#engines +[`engines`]: https://docs.npmjs.com/files/package.json#engines + +### Shared Settings + +The following options can be set by [shared settings](http://eslint.org/docs/user-guide/configuring.html#adding-shared-settings). +Several rules have the same option, but we can set this option at once. + +- `version` + +For Example: + +```json +{ + "settings": { + "node": { + "version": ">=8.0.0", + } + }, + "rules": { + "n/no-unsupported-features/es-syntax": ["error", { + "ignores": [] + }] + } +} +``` ## 🔎 Implementation diff --git a/docs/rules/no-unsupported-features/node-builtins.md b/docs/rules/no-unsupported-features/node-builtins.md index 3f091570..bbdeeab8 100644 --- a/docs/rules/no-unsupported-features/node-builtins.md +++ b/docs/rules/no-unsupported-features/node-builtins.md @@ -16,10 +16,16 @@ This rule reports APIs of Node.js built-in APIs on the basis of [Node.js v13.2.0 ### Configured Node.js version range -This rule reads the [engines] field of `package.json` to detect which Node.js versions your module is supporting. +This rule gets the supported Node.js version range from the following, falling back to the next if unspecified: -I recommend the use of the [engines] field because it's the official way that indicates which Node.js versions your module is supporting. -For example of `package.json`: +1. Rule configuration `version` +2. ESLint [shared setting](http://eslint.org/docs/user-guide/configuring.html#adding-shared-settings) `node.version` +3. `package.json` [`engines`] field +4. `>=8.0.0` + +The default version is `8.0.0` because it's the minimum version the community is maintaining (see also [Node.js Release Working Group](https://github.com/nodejs/Release#readme)). + +For Node.js packages, using the [`engines`] field is recommended because it's the official way to indicate support: ```json { @@ -31,7 +37,7 @@ For example of `package.json`: } ``` -If you omit the [engines] field, this rule chooses `>=8.0.0` as the configured Node.js version since `8` is the minimum version the community is maintaining (see also [Node.js Release Working Group](https://github.com/nodejs/Release#readme)). +For [Shareable Configs](https://eslint.org/docs/latest/developer-guide/shareable-configs) or packages with a different development environment (e.g. pre-compiled, web package, etc.), you can configure ESLint with `settings.node.version` to specify support. ### Options @@ -46,7 +52,7 @@ If you omit the [engines] field, this rule chooses `>=8.0.0` as the configured N #### version -As mentioned above, this rule reads the [engines] field of `package.json`. +As mentioned above, this rule reads the [`engines`] field of `package.json`. But, you can overwrite the version by `version` option. The `version` option accepts [the valid version range of `node-semver`](https://github.com/npm/node-semver#range-grammar). @@ -324,6 +330,30 @@ The `"ignores"` option accepts an array of the following strings. +### Shared Settings + +The following options can be set by [shared settings](http://eslint.org/docs/user-guide/configuring.html#adding-shared-settings). +Several rules have the same option, but we can set this option at once. + +- `version` + +For Example: + +```json +{ + "settings": { + "node": { + "version": ">=8.0.0", + } + }, + "rules": { + "n/no-unsupported-features/node-builtins": ["error", { + "ignores": [] + }] + } +} +``` + ### Known limitations This rule cannot find non-static things. @@ -334,7 +364,7 @@ For example: - New `options` properties of function parameters. - New events. -[engines]: https://docs.npmjs.com/files/package.json#engines +[`engines`]: https://docs.npmjs.com/files/package.json#engines ## 🔎 Implementation diff --git a/lib/rules/no-deprecated-api.js b/lib/rules/no-deprecated-api.js index 9b355863..4cc5194a 100644 --- a/lib/rules/no-deprecated-api.js +++ b/lib/rules/no-deprecated-api.js @@ -671,8 +671,7 @@ function toName(type, path) { */ function parseOptions(context) { const raw = context.options[0] || {} - const filePath = context.getFilename() - const version = getConfiguredNodeVersion(raw.version, filePath) + const version = getConfiguredNodeVersion(context) const ignoredModuleItems = new Set(raw.ignoreModuleItems || []) const ignoredGlobalItems = new Set(raw.ignoreGlobalItems || []) @@ -693,9 +692,7 @@ module.exports = { { type: "object", properties: { - version: { - type: "string", - }, + version: getConfiguredNodeVersion.schema, ignoreModuleItems: { type: "array", items: { @@ -720,8 +717,9 @@ module.exports = { }, ], messages: { - "deprecated": "{{name}} was deprecated since v{{version}}{{replace}}." - } + deprecated: + "{{name}} was deprecated since v{{version}}{{replace}}.", + }, }, create(context) { const { ignoredModuleItems, ignoredGlobalItems, version } = diff --git a/lib/rules/no-unsupported-features/es-builtins.js b/lib/rules/no-unsupported-features/es-builtins.js index 7620133e..f69151b4 100644 --- a/lib/rules/no-unsupported-features/es-builtins.js +++ b/lib/rules/no-unsupported-features/es-builtins.js @@ -10,6 +10,7 @@ const { messages, } = require("../../util/check-unsupported-builtins") const enumeratePropertyNames = require("../../util/enumerate-property-names") +const getConfiguredNodeVersion = require("../../util/get-configured-node-version") const trackMap = { globals: { @@ -152,9 +153,7 @@ module.exports = { { type: "object", properties: { - version: { - type: "string", - }, + version: getConfiguredNodeVersion.schema, ignores: { type: "array", items: { diff --git a/lib/rules/no-unsupported-features/es-syntax.js b/lib/rules/no-unsupported-features/es-syntax.js index 571fbf8c..1a093e30 100644 --- a/lib/rules/no-unsupported-features/es-syntax.js +++ b/lib/rules/no-unsupported-features/es-syntax.js @@ -412,8 +412,7 @@ const keywords = Object.keys(features) */ function parseOptions(context) { const raw = context.options[0] || {} - const filePath = context.getFilename() - const version = getConfiguredNodeVersion(raw.version, filePath) + const version = getConfiguredNodeVersion(context) const ignores = new Set(raw.ignores || []) return Object.freeze({ version, ignores }) @@ -534,9 +533,7 @@ module.exports = { { type: "object", properties: { - version: { - type: "string", - }, + version: getConfiguredNodeVersion.schema, ignores: { type: "array", items: { diff --git a/lib/rules/no-unsupported-features/node-builtins.js b/lib/rules/no-unsupported-features/node-builtins.js index 2d93e4d7..e82827da 100644 --- a/lib/rules/no-unsupported-features/node-builtins.js +++ b/lib/rules/no-unsupported-features/node-builtins.js @@ -10,6 +10,7 @@ const { messages, } = require("../../util/check-unsupported-builtins") const enumeratePropertyNames = require("../../util/enumerate-property-names") +const getConfiguredNodeVersion = require("../../util/get-configured-node-version") const trackMap = { globals: { @@ -263,7 +264,7 @@ const trackMap = { }, }, release: { [READ]: { supported: "3.0.0" } }, - report: { [READ]: { supported: "14.0.0", experimental: "11.8.0"} }, + report: { [READ]: { supported: "14.0.0", experimental: "11.8.0" } }, resourceUsage: { [READ]: { supported: "12.6.0" } }, setegid: { [READ]: { supported: "2.0.0" } }, seteuid: { [READ]: { supported: "2.0.0" } }, @@ -384,9 +385,7 @@ module.exports = { { type: "object", properties: { - version: { - type: "string", - }, + version: getConfiguredNodeVersion.schema, ignores: { type: "array", items: { diff --git a/lib/util/check-unsupported-builtins.js b/lib/util/check-unsupported-builtins.js index 97949f6c..8fd1d262 100644 --- a/lib/util/check-unsupported-builtins.js +++ b/lib/util/check-unsupported-builtins.js @@ -23,8 +23,7 @@ const getSemverRange = require("./get-semver-range") */ function parseOptions(context) { const raw = context.options[0] || {} - const filePath = context.getFilename() - const version = getConfiguredNodeVersion(raw.version, filePath) + const version = getConfiguredNodeVersion(context) const ignores = new Set(raw.ignores || []) return Object.freeze({ version, ignores }) diff --git a/lib/util/get-configured-node-version.js b/lib/util/get-configured-node-version.js index d682e3a0..ff5f9dcd 100644 --- a/lib/util/get-configured-node-version.js +++ b/lib/util/get-configured-node-version.js @@ -8,6 +8,19 @@ const { Range } = require("semver") //eslint-disable-line no-unused-vars const getPackageJson = require("./get-package-json") const getSemverRange = require("./get-semver-range") +/** + * Gets `version` property from a given option object. + * + * @param {object|undefined} option - An option object to get. + * @returns {string[]|null} The `allowModules` value, or `null`. + */ +function get(option) { + if (option && option.version) { + return option.version + } + return null +} + /** * Get the `engines.node` field of package.json. * @param {string} filename The path to the current linting file. @@ -30,10 +43,19 @@ function getEnginesNode(filename) { * This will be used to look package.json up if `version` is not a valid version range. * @returns {Range} The configured version range. */ -module.exports = function getConfiguredNodeVersion(version, filename) { +module.exports = function getConfiguredNodeVersion(context) { + const version = + get(context.options && context.options[0]) || + get(context.settings && (context.settings.n || context.settings.node)) + const filePath = context.getFilename() + return ( getSemverRange(version) || - getEnginesNode(filename) || + getEnginesNode(filePath) || getSemverRange(">=8.0.0") ) } + +module.exports.schema = { + type: "string", +} diff --git a/tests/lib/rules/no-deprecated-api.js b/tests/lib/rules/no-deprecated-api.js index b37bde71..281315a6 100644 --- a/tests/lib/rules/no-deprecated-api.js +++ b/tests/lib/rules/no-deprecated-api.js @@ -942,5 +942,27 @@ ruleTester.run("no-deprecated-api", rule, { "'Buffer()' was deprecated since v6.0.0. Use 'Buffer.alloc()' or 'Buffer.from()' instead.", ], }, + { + code: "Buffer()", + settings: { + node: { + version: "6.0.0", + }, + }, + options: [ + { + // + ignoreModuleItems: [ + "buffer.Buffer()", + "new buffer.Buffer()", + ], + ignoreGlobalItems: ["new Buffer()"], + }, + ], + env: { node: true }, + errors: [ + "'Buffer()' was deprecated since v6.0.0. Use 'Buffer.alloc()' or 'Buffer.from()' instead.", + ], + }, ], }) diff --git a/tests/lib/rules/no-unsupported-features/es-builtins.js b/tests/lib/rules/no-unsupported-features/es-builtins.js index ce90fd83..6956ef4b 100644 --- a/tests/lib/rules/no-unsupported-features/es-builtins.js +++ b/tests/lib/rules/no-unsupported-features/es-builtins.js @@ -18,8 +18,18 @@ function ignores(keyword) { const pattern = Object.assign({}, original) delete pattern.error - pattern.options = pattern.options.slice() - pattern.options[0] = Object.assign({}, pattern.options[0]) + if (pattern.options) { + pattern.options = pattern.options.slice() + } else { + pattern.options = [] + } + + if (pattern.options[0]) { + pattern.options[0] = Object.assign({}, pattern.options[0]) + } else { + pattern.options.push({}) + } + if (pattern.options[0].ignores) { pattern.options[0].ignores = pattern.options[0].ignores.concat([ keyword, @@ -2235,6 +2245,12 @@ ruleTester.run( code: "globalThis", options: [{ version: "12.0.0" }], }, + { + code: "globalThis", + settings: { + node: { version: "12.0.0" }, + }, + }, ], invalid: [ { @@ -2265,6 +2281,22 @@ ruleTester.run( }, ], }, + { + code: "function wrap() { globalThis }", + settings: { + node: { version: "11.9.9" }, + }, + errors: [ + { + messageId: "unsupported", + data: { + name: "globalThis", + supported: "12.0.0", + version: "11.9.9", + }, + }, + ], + }, ], }, ]) diff --git a/tests/lib/rules/no-unsupported-features/es-syntax.js b/tests/lib/rules/no-unsupported-features/es-syntax.js index 694b535b..1e402321 100644 --- a/tests/lib/rules/no-unsupported-features/es-syntax.js +++ b/tests/lib/rules/no-unsupported-features/es-syntax.js @@ -40,8 +40,18 @@ function ignores(keyword) { const pattern = Object.assign({}, original) delete pattern.error - pattern.options = pattern.options.slice() - pattern.options[0] = Object.assign({}, pattern.options[0]) + if (pattern.options) { + pattern.options = pattern.options.slice() + } else { + pattern.options = [] + } + + if (pattern.options[0]) { + pattern.options[0] = Object.assign({}, pattern.options[0]) + } else { + pattern.options.push({}) + } + if (pattern.options[0].ignores) { pattern.options[0].ignores = pattern.options[0].ignores.concat([ keyword, @@ -2543,6 +2553,12 @@ ruleTester.run( code: "foo ?? bar;", options: [{ version: "14.0.0" }], }, + { + code: "foo ?? bar;", + settings: { + node: { version: "14.0.0" }, + }, + }, ], invalid: [ { @@ -2558,6 +2574,21 @@ ruleTester.run( }, ], }, + { + code: "foo ?? bar", + settings: { + node: { version: "13.0.0" }, + }, + errors: [ + { + messageId: "no-nullish-coalescing-operators", + data: { + supported: "14.0.0", + version: "13.0.0", + }, + }, + ], + }, ], }, @@ -2607,6 +2638,12 @@ ruleTester.run( code: "var a = async () => 1", options: [{ version: "7.10.0" }], }, + { + code: "var a = async () => 1", + settings: { + node: { version: "7.10.0" }, + }, + }, { filename: fixture("without-node/a.js"), code: "var a = () => 1", @@ -2683,6 +2720,18 @@ ruleTester.run( }, ], }, + { + code: "var a = async () => 1", + settings: { + node: { version: "7.1.0" }, + }, + errors: [ + { + messageId: "no-async-functions", + data: { supported: "7.6.0", version: "7.1.0" }, + }, + ], + }, ], }, ]) diff --git a/tests/lib/rules/no-unsupported-features/node-builtins.js b/tests/lib/rules/no-unsupported-features/node-builtins.js index 605c2c57..219d8ed1 100644 --- a/tests/lib/rules/no-unsupported-features/node-builtins.js +++ b/tests/lib/rules/no-unsupported-features/node-builtins.js @@ -5475,6 +5475,12 @@ new RuleTester({ code: "import worker_threads from 'worker_threads'", options: [{ version: "12.11.0" }], }, + { + code: "import worker_threads from 'worker_threads'", + settings: { + node: { version: "12.11.0" }, + }, + }, ], invalid: [ { @@ -5519,6 +5525,22 @@ new RuleTester({ }, ], }, + { + code: "import { Worker } from 'worker_threads'", + settings: { + node: { version: "10.5.0" }, + }, + errors: [ + { + messageId: "unsupported", + data: { + name: "worker_threads", + supported: "12.11.0", + version: "10.5.0", + }, + }, + ], + }, ], }, ])