diff --git a/docs/rules/no-unsupported-features/node-builtins.md b/docs/rules/no-unsupported-features/node-builtins.md index bc68812e..d3bd2259 100644 --- a/docs/rules/no-unsupported-features/node-builtins.md +++ b/docs/rules/no-unsupported-features/node-builtins.md @@ -36,6 +36,12 @@ 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). +#### allowExperimental + +This allows you to enable experimental features that are available in your configured node version + +The `"allowExperimental"` option accepts a boolean value (the default value is `false`). + #### ignores If you are using transpilers, maybe you want to ignore the warnings about some features. diff --git a/lib/rules/no-unsupported-features/node-builtins.js b/lib/rules/no-unsupported-features/node-builtins.js index 68edaea6..1891f994 100644 --- a/lib/rules/no-unsupported-features/node-builtins.js +++ b/lib/rules/no-unsupported-features/node-builtins.js @@ -37,6 +37,7 @@ module.exports = { type: "object", properties: { version: getConfiguredNodeVersion.schema, + allowExperimental: { type: "boolean" }, ignores: { type: "array", items: { diff --git a/lib/util/check-unsupported-builtins.js b/lib/util/check-unsupported-builtins.js index 5d4a742e..51af9d3e 100644 --- a/lib/util/check-unsupported-builtins.js +++ b/lib/util/check-unsupported-builtins.js @@ -15,51 +15,56 @@ const semverRangeSubset = require("semver/ranges/subset") * Parses the options. * @param {import('eslint').Rule.RuleContext} context The rule context. * @returns {Readonly<{ - * version: import('semver').Range, - * ignores: Set + * version: import('semver').Range; + * ignores: Set; + * allowExperimental: boolean; * }>} Parsed value. */ function parseOptions(context) { const raw = context.options[0] || {} const version = getConfiguredNodeVersion(context) const ignores = new Set(raw.ignores || []) + const allowExperimental = raw.allowExperimental ?? false - return Object.freeze({ version, ignores }) + return Object.freeze({ version, ignores, allowExperimental }) } /** * Check if it has been supported. - * @param {import('../unsupported-features/types.js').SupportInfo} info The support info. - * @param {import('semver').Range} configured The configured version range. + * @param {string[] | undefined} featureRange The target features supported range + * @param {import('semver').Range} requestedRange The configured version range. + * @returns {boolean} */ -function isSupported({ supported }, configured) { - if (supported == null || supported.length === 0) { +function isInRange(featureRange, requestedRange) { + if (featureRange == null || featureRange.length === 0) { return false } - const [latest] = rsort(supported) + const [latest] = rsort(featureRange) const range = getSemverRange( - [...supported.map(version => `^${version}`), `>= ${latest}`].join("||") + [...featureRange.map(version => `^${version}`), `>= ${latest}`].join( + "||" + ) ) if (range == null) { return false } - return semverRangeSubset(configured, range) + return semverRangeSubset(requestedRange, range) } /** * Get the formatted text of a given supported version. - * @param {import('../unsupported-features/types.js').SupportInfo} info The support info. + * @param {string[] | undefined} versions The support info. * @returns {string | undefined} */ -function supportedVersionToString({ supported }) { - if (supported == null || supported.length === 0) { +function versionsToString(versions) { + if (versions == null) { return } - const [latest, ...backported] = rsort(supported) + const [latest, ...backported] = rsort(versions) if (backported.length === 0) { return latest @@ -92,20 +97,54 @@ module.exports.checkUnsupportedBuiltins = function checkUnsupportedBuiltins( for (const { node, path, info } of references) { const name = unprefixNodeColon(path.join(".")) - const supported = isSupported(info, options.version) - if (supported === true || options.ignores.has(name)) { + if (options.ignores.has(name)) { continue } - const supportedVersion = supportedVersionToString(info) + + if (options.allowExperimental) { + if (isInRange(info.experimental, options.version)) { + continue + } + + const experimentalVersion = versionsToString(info.experimental) + if (experimentalVersion) { + context.report({ + node, + messageId: "not-experimental-till", + data: { + name: path.join("."), + experimental: experimentalVersion, + version: options.version.raw, + }, + }) + continue + } + } + + if (isInRange(info.supported, options.version)) { + continue + } + + const supportedVersion = versionsToString(info.supported) + if (supportedVersion) { + context.report({ + node, + messageId: "not-supported-till", + data: { + name: path.join("."), + supported: supportedVersion, + version: options.version.raw, + }, + }) + continue + } + context.report({ node, - messageId: supportedVersion - ? "not-supported-till" - : "not-supported-yet", + messageId: "not-supported-yet", data: { name: path.join("."), - supported: /** @type string */ (supportedVersion), version: options.version.raw, }, }) @@ -113,6 +152,11 @@ module.exports.checkUnsupportedBuiltins = function checkUnsupportedBuiltins( } exports.messages = { + "not-experimental-till": [ + "The '{{name}}' is not an experimental feature", + "until Node.js {{experimental}}.", + "The configured version range is '{{version}}'.", + ].join(" "), "not-supported-till": [ "The '{{name}}' is still an experimental feature", "and is not supported until Node.js {{supported}}.", diff --git a/tests/lib/rules/no-unsupported-features/node-builtins.js b/tests/lib/rules/no-unsupported-features/node-builtins.js index 2fcf72ce..9b3eb220 100644 --- a/tests/lib/rules/no-unsupported-features/node-builtins.js +++ b/tests/lib/rules/no-unsupported-features/node-builtins.js @@ -5371,5 +5371,44 @@ new RuleTester({ languageOptions: { sourceType: "module" } }).run( }, ], }, + + { + valid: [ + { + code: "fetch('/asd')", + options: [{ version: "16.16.0", allowExperimental: true }], + }, + ], + invalid: [ + { + code: "fetch('/asd')", + options: [{ version: "16.0.0", allowExperimental: true }], + errors: [ + { + messageId: "not-experimental-till", + data: { + name: "fetch", + experimental: "17.5.0 (backported: ^16.15.0)", + version: "16.0.0", + }, + }, + ], + }, + { + code: "fetch('/asd')", + options: [{ version: "16.16.0", allowExperimental: false }], + errors: [ + { + messageId: "not-supported-till", + data: { + name: "fetch", + supported: "21.0.0", + version: "16.16.0", + }, + }, + ], + }, + ], + }, ]) )