Skip to content

Commit

Permalink
feat: Add support for ignoring experemental features (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
scagood committed May 10, 2024
1 parent c3e5a19 commit c046376
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 21 deletions.
6 changes: 6 additions & 0 deletions docs/rules/no-unsupported-features/node-builtins.md
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions lib/rules/no-unsupported-features/node-builtins.js
Expand Up @@ -37,6 +37,7 @@ module.exports = {
type: "object",
properties: {
version: getConfiguredNodeVersion.schema,
allowExperimental: { type: "boolean" },
ignores: {
type: "array",
items: {
Expand Down
86 changes: 65 additions & 21 deletions lib/util/check-unsupported-builtins.js
Expand Up @@ -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<string>
* version: import('semver').Range;
* ignores: Set<string>;
* 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
Expand Down Expand Up @@ -92,27 +97,66 @@ 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,
},
})
}
}

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}}.",
Expand Down
39 changes: 39 additions & 0 deletions tests/lib/rules/no-unsupported-features/node-builtins.js
Expand Up @@ -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",
},
},
],
},
],
},
])
)

0 comments on commit c046376

Please sign in to comment.