Constnium/node_modules/eslint-plugin-n/lib/rules/no-unsupported-features.js
2022-06-23 02:27:43 +02:00

1542 lines
43 KiB
JavaScript

/**
* @author Toru Nagashima
* See LICENSE file in root directory for full license.
*/
"use strict"
const semver = require("semver")
const { getInnermostScope, getPropertyName } = require("eslint-utils")
const getPackageJson = require("../util/get-package-json")
const VERSION_MAP = new Map([
[0.1, "0.10.0"],
[0.12, "0.12.0"],
[4, "4.0.0"],
[5, "5.0.0"],
[6, "6.0.0"],
[6.5, "6.5.0"],
[7, "7.0.0"],
[7.6, "7.6.0"],
[8, "8.0.0"],
[8.3, "8.3.0"],
[9, "9.0.0"],
[10, "10.0.0"],
])
const VERSION_SCHEMA = {
anyOf: [
{ enum: Array.from(VERSION_MAP.keys()) },
{
type: "string",
pattern: "^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)$",
},
],
}
const DEFAULT_VERSION = "4.0.0"
const FUNC_TYPE = /^(?:Arrow)?Function(?:Declaration|Expression)$/u
const CLASS_TYPE = /^Class(?:Declaration|Expression)$/u
const DESTRUCTURING_PARENT_TYPE =
/^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression|AssignmentExpression|VariableDeclarator)$/u
const TOPLEVEL_SCOPE_TYPE = /^(?:global|function|module)$/u
const BINARY_NUMBER = /^0[bB]/u
const OCTAL_NUMBER = /^0[oO]/u
const UNICODE_ESC = /(\\+)u\{[0-9a-fA-F]+?\}/gu
const GET_OR_SET = /^(?:g|s)et$/u
const NEW_BUILTIN_TYPES = [
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array",
"DataView",
"Map",
"Set",
"WeakMap",
"WeakSet",
"Proxy",
"Reflect",
"Promise",
"Symbol",
"SharedArrayBuffer",
"Atomics",
]
const SUBCLASSING_TEST_TARGETS = [
"Array",
"RegExp",
"Function",
"Promise",
"Boolean",
"Number",
"String",
"Map",
"Set",
]
const PROPERTY_TEST_TARGETS = {
Object: [
"assign",
"is",
"getOwnPropertySymbols",
"setPrototypeOf",
"values",
"entries",
"getOwnPropertyDescriptors",
],
String: ["raw", "fromCodePoint"],
Array: ["from", "of"],
Number: [
"isFinite",
"isInteger",
"isSafeInteger",
"isNaN",
"EPSILON",
"MIN_SAFE_INTEGER",
"MAX_SAFE_INTEGER",
],
Math: [
"clz32",
"imul",
"sign",
"log10",
"log2",
"log1p",
"expm1",
"cosh",
"sinh",
"tanh",
"acosh",
"asinh",
"atanh",
"trunc",
"fround",
"cbrt",
"hypot",
],
Symbol: [
"hasInstance",
"isConcatSpreadablec",
"iterator",
"species",
"replace",
"search",
"split",
"match",
"toPrimitive",
"toStringTag",
"unscopables",
],
Atomics: [
"add",
"and",
"compareExchange",
"exchange",
"wait",
"wake",
"isLockFree",
"load",
"or",
"store",
"sub",
"xor",
],
}
const REGEXP_NAMED_GROUP = /(\\*)\(\?<[_$\w]/u
const REGEXP_LOOKBEHIND = /(\\*)\(\?<[=!]/u
const REGEXP_UNICODE_PROPERTY = /(\\*)\\[pP]\{.+?\}/u
const FEATURES = {
defaultParameters: {
alias: ["syntax"],
name: "Default parameters",
node: "6.0.0",
},
restParameters: {
alias: ["syntax"],
name: "Rest parameters",
node: "6.0.0",
},
spreadOperators: {
alias: ["syntax"],
name: "Spread operators",
node: "5.0.0",
},
objectLiteralExtensions: {
alias: ["syntax"],
name: "Object literal extensions",
node: "4.0.0",
},
objectPropertyShorthandOfGetSet: {
alias: ["syntax", "objectLiteralExtensions"],
name: "Property shorthand of 'get' and 'set'",
node: "6.0.0",
},
forOf: {
alias: ["syntax"],
name: "'for..of' loops",
node: "0.12.0",
},
binaryNumberLiterals: {
alias: ["syntax"],
name: "Binary number literals",
node: "4.0.0",
},
octalNumberLiterals: {
alias: ["syntax"],
name: "Octal number literals",
node: "4.0.0",
},
templateStrings: {
alias: ["syntax"],
name: "Template strings",
node: "4.0.0",
},
regexpY: {
alias: ["syntax"],
name: "RegExp 'y' flags",
node: "6.0.0",
},
regexpU: {
alias: ["syntax"],
name: "RegExp 'u' flags",
node: "6.0.0",
},
destructuring: {
alias: ["syntax"],
name: "Destructuring",
node: "6.0.0",
},
unicodeCodePointEscapes: {
alias: ["syntax"],
name: "Unicode code point escapes",
node: "4.0.0",
},
"new.target": {
alias: ["syntax"],
name: "'new.target'",
node: "5.0.0",
},
const: {
alias: ["syntax"],
name: "'const' declarations",
node: {
sloppy: "6.0.0",
strict: "4.0.0",
},
},
let: {
alias: ["syntax"],
name: "'let' declarations",
node: {
sloppy: "6.0.0",
strict: "4.0.0",
},
},
blockScopedFunctions: {
alias: ["syntax"],
name: "Block-scoped functions",
node: {
sloppy: "6.0.0",
strict: "4.0.0",
},
},
arrowFunctions: {
alias: ["syntax"],
name: "Arrow functions",
node: "4.0.0",
},
generatorFunctions: {
alias: ["syntax"],
name: "Generator functions",
node: "4.0.0",
},
classes: {
alias: ["syntax"],
name: "Classes",
node: {
sloppy: "6.0.0",
strict: "4.0.0",
},
},
modules: {
alias: ["syntax"],
name: "Import and export declarations",
node: null,
},
exponentialOperators: {
alias: ["syntax"],
name: "Exponential operators (**)",
node: "7.0.0",
},
asyncAwait: {
alias: ["syntax"],
name: "Async functions",
node: "7.6.0",
},
trailingCommasInFunctions: {
alias: ["syntax"],
name: "Trailing commas in functions",
node: "8.0.0",
},
//------------------------------------------
templateLiteralRevision: {
alias: ["syntax"],
name: "Illegal escape sequences in taggled templates",
node: "9.0.0",
},
regexpS: {
alias: ["syntax"],
name: "RegExp 's' flags",
node: "9.0.0",
},
regexpNamedCaptureGroups: {
alias: ["syntax"],
name: "RegExp named capture groups",
node: "10.0.0",
},
regexpLookbehind: {
alias: ["syntax"],
name: "RegExp lookbehind assertions",
node: "9.0.0",
},
regexpUnicodeProperties: {
alias: ["syntax"],
name: "RegExp Unicode property escapes",
node: "10.0.0",
},
restProperties: {
alias: ["syntax"],
name: "Rest properties",
node: "8.3.0",
},
spreadProperties: {
alias: ["syntax"],
name: "Spread properties",
node: "8.3.0",
},
asyncGenerators: {
alias: ["syntax"],
name: "Async generators",
node: "10.0.0",
},
forAwaitOf: {
alias: ["syntax"],
name: "for-await-of loops",
node: "10.0.0",
},
Int8Array: {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Int8Array'",
singular: true,
node: "0.12.0",
},
Uint8Array: {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Uint8Array'",
singular: true,
node: "0.12.0",
},
Uint8ClampedArray: {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Uint8ClampedArray'",
singular: true,
node: "0.12.0",
},
Int16Array: {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Int16Array'",
singular: true,
node: "0.12.0",
},
Uint16Array: {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Uint16Array'",
singular: true,
node: "0.12.0",
},
Int32Array: {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Int32Array'",
singular: true,
node: "0.12.0",
},
Uint32Array: {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Uint32Array'",
singular: true,
node: "0.12.0",
},
Float32Array: {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Float32Array'",
singular: true,
node: "0.12.0",
},
Float64Array: {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Float64Array'",
singular: true,
node: "0.12.0",
},
DataView: {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'DataView'",
singular: true,
node: "0.12.0",
},
Map: {
alias: ["runtime", "globalObjects"],
name: "'Map'",
singular: true,
node: "0.12.0",
},
Set: {
alias: ["runtime", "globalObjects"],
name: "'Set'",
singular: true,
node: "0.12.0",
},
WeakMap: {
alias: ["runtime", "globalObjects"],
name: "'WeakMap'",
singular: true,
node: "0.12.0",
},
WeakSet: {
alias: ["runtime", "globalObjects"],
name: "'WeakSet'",
singular: true,
node: "0.12.0",
},
Proxy: {
alias: ["runtime", "globalObjects"],
name: "'Proxy'",
singular: true,
node: "6.0.0",
},
Reflect: {
alias: ["runtime", "globalObjects"],
name: "'Reflect'",
singular: true,
node: "6.0.0",
},
Promise: {
alias: ["runtime", "globalObjects"],
name: "'Promise'",
singular: true,
node: "0.12.0",
},
Symbol: {
alias: ["runtime", "globalObjects"],
name: "'Symbol'",
singular: true,
node: "0.12.0",
},
SharedArrayBuffer: {
alias: ["runtime", "globalObjects"],
name: "'SharedArrayBuffer'",
singular: true,
node: "9.0.0",
},
Atomics: {
alias: ["runtime", "globalObjects"],
name: "'Atomics'",
singular: true,
node: "9.0.0",
},
"Object.assign": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.assign'",
singular: true,
node: "4.0.0",
},
"Object.is": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.is'",
singular: true,
node: "0.12.0",
},
"Object.getOwnPropertySymbols": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.getOwnPropertySymbols'",
singular: true,
node: "0.12.0",
},
"Object.setPrototypeOf": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.setPrototypeOf'",
singular: true,
node: "0.12.0",
},
"Object.values": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.values'",
singular: true,
node: "7.0.0",
},
"Object.entries": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.entries'",
singular: true,
node: "7.0.0",
},
"Object.getOwnPropertyDescriptors": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.getOwnPropertyDescriptors'",
singular: true,
node: "7.0.0",
},
"String.raw": {
alias: ["runtime", "staticMethods", "String.*"],
name: "'String.raw'",
singular: true,
node: "4.0.0",
},
"String.fromCodePoint": {
alias: ["runtime", "staticMethods", "String.*"],
name: "'String.fromCodePoint'",
singular: true,
node: "4.0.0",
},
"Array.from": {
alias: ["runtime", "staticMethods", "Array.*"],
name: "'Array.from'",
singular: true,
node: "4.0.0",
},
"Array.of": {
alias: ["runtime", "staticMethods", "Array.*"],
name: "'Array.of'",
singular: true,
node: "4.0.0",
},
"Number.isFinite": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.isFinite'",
singular: true,
node: "0.10.0",
},
"Number.isInteger": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.isInteger'",
singular: true,
node: "0.12.0",
},
"Number.isSafeInteger": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.isSafeInteger'",
singular: true,
node: "0.12.0",
},
"Number.isNaN": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.isNaN'",
singular: true,
node: "0.10.0",
},
"Number.EPSILON": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.EPSILON'",
singular: true,
node: "0.12.0",
},
"Number.MIN_SAFE_INTEGER": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.MIN_SAFE_INTEGER'",
singular: true,
node: "0.12.0",
},
"Number.MAX_SAFE_INTEGER": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.MAX_SAFE_INTEGER'",
singular: true,
node: "0.12.0",
},
"Math.clz32": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.clz32'",
singular: true,
node: "0.12.0",
},
"Math.imul": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.imul'",
singular: true,
node: "0.12.0",
},
"Math.sign": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.sign'",
singular: true,
node: "0.12.0",
},
"Math.log10": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.log10'",
singular: true,
node: "0.12.0",
},
"Math.log2": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.log2'",
singular: true,
node: "0.12.0",
},
"Math.log1p": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.log1p'",
singular: true,
node: "0.12.0",
},
"Math.expm1": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.expm1'",
singular: true,
node: "0.12.0",
},
"Math.cosh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.cosh'",
singular: true,
node: "0.12.0",
},
"Math.sinh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.sinh'",
singular: true,
node: "0.12.0",
},
"Math.tanh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.tanh'",
singular: true,
node: "0.12.0",
},
"Math.acosh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.acosh'",
singular: true,
node: "0.12.0",
},
"Math.asinh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.asinh'",
singular: true,
node: "0.12.0",
},
"Math.atanh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.atanh'",
singular: true,
node: "0.12.0",
},
"Math.trunc": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.trunc'",
singular: true,
node: "0.12.0",
},
"Math.fround": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.fround'",
singular: true,
node: "0.12.0",
},
"Math.cbrt": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.cbrt'",
singular: true,
node: "0.12.0",
},
"Math.hypot": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.hypot'",
singular: true,
node: "0.12.0",
},
"Symbol.hasInstance": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.hasInstance'",
singular: true,
node: "6.5.0",
},
"Symbol.isConcatSpreadablec": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.isConcatSpreadablec'",
singular: true,
node: "6.0.0",
},
"Symbol.iterator": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.iterator'",
singular: true,
node: "0.12.0",
},
"Symbol.species": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.species'",
singular: true,
node: "6.5.0",
},
"Symbol.replace": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.replace'",
singular: true,
node: "6.0.0",
},
"Symbol.search": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.search'",
singular: true,
node: "6.0.0",
},
"Symbol.split": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.split'",
singular: true,
node: "6.0.0",
},
"Symbol.match": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.match'",
singular: true,
node: "6.0.0",
},
"Symbol.toPrimitive": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.toPrimitive'",
singular: true,
node: "6.0.0",
},
"Symbol.toStringTag": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.toStringTag'",
singular: true,
node: "6.0.0",
},
"Symbol.unscopables": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.unscopables'",
singular: true,
node: "4.0.0",
},
"Atomics.add": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.add'",
singular: true,
node: "9.0.0",
},
"Atomics.and": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.and'",
singular: true,
node: "9.0.0",
},
"Atomics.compareExchange": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.compareExchange'",
singular: true,
node: "9.0.0",
},
"Atomics.exchange": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.exchange'",
singular: true,
node: "9.0.0",
},
"Atomics.wait": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.wait'",
singular: true,
node: "9.0.0",
},
"Atomics.wake": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.wake'",
singular: true,
node: "9.0.0",
},
"Atomics.isLockFree": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.isLockFree'",
singular: true,
node: "9.0.0",
},
"Atomics.load": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.load'",
singular: true,
node: "9.0.0",
},
"Atomics.or": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.or'",
singular: true,
node: "9.0.0",
},
"Atomics.store": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.store'",
singular: true,
node: "9.0.0",
},
"Atomics.sub": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.sub'",
singular: true,
node: "9.0.0",
},
"Atomics.xor": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.xor'",
singular: true,
node: "9.0.0",
},
extendsArray: {
alias: ["runtime", "extends"],
name: "Subclassing of 'Array'",
singular: true,
node: "6.0.0",
},
extendsRegExp: {
alias: ["runtime", "extends"],
name: "Subclassing of 'RegExp'",
singular: true,
node: "5.0.0",
},
extendsFunction: {
alias: ["runtime", "extends"],
name: "Subclassing of 'Function'",
singular: true,
node: "6.0.0",
},
extendsPromise: {
alias: ["runtime", "extends"],
name: "Subclassing of 'Promise'",
singular: true,
node: "5.0.0",
},
extendsBoolean: {
alias: ["runtime", "extends"],
name: "Subclassing of 'Boolean'",
singular: true,
node: "4.0.0",
},
extendsNumber: {
alias: ["runtime", "extends"],
name: "Subclassing of 'Number'",
singular: true,
node: "4.0.0",
},
extendsString: {
alias: ["runtime", "extends"],
name: "Subclassing of 'String'",
singular: true,
node: "4.0.0",
},
extendsMap: {
alias: ["runtime", "extends"],
name: "Subclassing of 'Map'",
singular: true,
node: "4.0.0",
},
extendsSet: {
alias: ["runtime", "extends"],
name: "Subclassing of 'Set'",
singular: true,
node: "4.0.0",
},
extendsNull: {
alias: ["runtime", "extends"],
name: "'extends null'",
singular: true,
node: null,
},
}
const OPTIONS = Object.keys(FEATURES)
/**
* Gets default version configuration of this rule.
*
* This finds and reads 'package.json' file, then parses 'engines.node' field.
* If it's nothing, this returns null.
*
* @param {string} filename - The file name of the current linting file.
* @returns {string} The default version configuration.
*/
function getDefaultVersion(filename) {
const info = getPackageJson(filename)
const nodeVersion = info && info.engines && info.engines.node
return semver.validRange(nodeVersion) || DEFAULT_VERSION
}
/**
* Gets values of the `ignores` option.
*
* @returns {string[]} Values of the `ignores` option.
*/
function getIgnoresEnum() {
return Object.keys(
OPTIONS.reduce((retv, key) => {
for (const alias of FEATURES[key].alias) {
retv[alias] = true
}
retv[key] = true
return retv
}, Object.create(null))
)
}
/**
* Checks whether a given key should be ignored or not.
*
* @param {string} key - A key to check.
* @param {string[]} ignores - An array of keys and aliases to be ignored.
* @returns {boolean} `true` if the key should be ignored.
*/
function isIgnored(key, ignores) {
return (
ignores.indexOf(key) !== -1 ||
FEATURES[key].alias.some(alias => ignores.indexOf(alias) !== -1)
)
}
/**
* Parses the options.
*
* @param {number|string|object|undefined} options - An option object to parse.
* @param {number} defaultVersion - The default version to use if the version option was omitted.
* @returns {object} Parsed value.
*/
function parseOptions(options, defaultVersion) {
let version = null
let range = null
let ignores = []
if (typeof options === "number") {
version = VERSION_MAP.get(options)
} else if (typeof options === "string") {
version = options
} else if (typeof options === "object") {
version =
typeof options.version === "number"
? VERSION_MAP.get(options.version)
: options.version
ignores = options.ignores || []
}
range = semver.validRange(version ? `>=${version}` : defaultVersion)
if (!version) {
version = defaultVersion
}
return Object.freeze({
version,
features: Object.freeze(
OPTIONS.reduce((retv, key) => {
const feature = FEATURES[key]
if (isIgnored(key, ignores)) {
retv[key] = Object.freeze({
name: feature.name,
singular: Boolean(feature.singular),
supported: true,
supportedInStrict: true,
})
} else if (typeof feature.node === "string") {
retv[key] = Object.freeze({
name: feature.name,
singular: Boolean(feature.singular),
supported: !semver.intersects(
range,
`<${feature.node}`
),
supportedInStrict: !semver.intersects(
range,
`<${feature.node}`
),
})
} else {
retv[key] = Object.freeze({
name: feature.name,
singular: Boolean(feature.singular),
supported:
feature.node != null &&
feature.node.sloppy != null &&
!semver.intersects(
range,
`<${feature.node.sloppy}`
),
supportedInStrict:
feature.node != null &&
feature.node.strict != null &&
!semver.intersects(
range,
`<${feature.node.strict}`
),
})
}
return retv
}, Object.create(null))
),
})
}
/**
* Find the scope that a given node belongs to.
* @param {Scope} initialScope The initial scope to find.
* @param {Node} node The AST node.
* @returns {Scope} The scope that the node belongs to.
*/
function normalizeScope(initialScope, node) {
let scope = getInnermostScope(initialScope, node)
while (scope && scope.block === node) {
scope = scope.upper
}
return scope
}
/**
* Checks whether the given string has `\u{90ABCDEF}`-like escapes.
*
* @param {string} raw - The string to check.
* @returns {boolean} `true` if the string has Unicode code point escapes.
*/
function hasUnicodeCodePointEscape(raw) {
let match = null
UNICODE_ESC.lastIndex = 0
while ((match = UNICODE_ESC.exec(raw)) != null) {
if (match[1].length % 2 === 1) {
return true
}
}
return false
}
/**
* Check a given string has a given pattern.
* @param {string} s A string to check.
* @param {RegExp} pattern A RegExp object to check.
* @returns {boolean} `true` if the string has the pattern.
*/
function hasPattern(s, pattern) {
const m = pattern.exec(s)
return m != null && (m[1] || "").length % 2 === 0
}
module.exports = {
meta: {
docs: {
description:
"disallow unsupported ECMAScript features on the specified version",
category: "Possible Errors",
recommended: false,
replacedBy: [
"n/no-unsupported-features/es-syntax",
"n/no-unsupported-features/es-builtins",
],
url: "https://github.com/weiran-zsd/eslint-plugin-node/blob/HEAD/docs/rules/no-unsupported-features.md",
},
type: "problem",
deprecated: true,
fixable: null,
schema: [
{
anyOf: [
VERSION_SCHEMA.anyOf[0],
VERSION_SCHEMA.anyOf[1],
{
type: "object",
properties: {
version: VERSION_SCHEMA,
ignores: {
type: "array",
items: { enum: getIgnoresEnum() },
uniqueItems: true,
},
},
additionalProperties: false,
},
],
},
],
},
create(context) {
const sourceCode = context.getSourceCode()
const supportInfo = parseOptions(
context.options[0],
getDefaultVersion(context.getFilename())
)
/**
* Gets the references of the specified global variables.
*
* @param {string[]} names - Variable names to get.
* @returns {void}
*/
function* getReferences(names) {
const globalScope = context.getScope()
for (const name of names) {
const variable = globalScope.set.get(name)
if (variable && variable.defs.length === 0) {
yield* variable.references
}
}
}
/**
* Checks whether the given function has trailing commas or not.
*
* @param {ASTNode} node - The function node to check.
* @returns {boolean} `true` if the function has trailing commas.
*/
function hasTrailingCommaForFunction(node) {
const length = node.params.length
return (
length >= 1 &&
sourceCode.getTokenAfter(node.params[length - 1]).value === ","
)
}
/**
* Checks whether the given call expression has trailing commas or not.
*
* @param {ASTNode} node - The call expression node to check.
* @returns {boolean} `true` if the call expression has trailing commas.
*/
function hasTrailingCommaForCall(node) {
return (
node.arguments.length >= 1 &&
sourceCode.getLastToken(node, 1).value === ","
)
}
/**
* Checks whether the given class extends from null or not.
*
* @param {ASTNode} node - The class node to check.
* @returns {boolean} `true` if the class extends from null.
*/
function extendsNull(node) {
return (
node.superClass != null &&
node.superClass.type === "Literal" &&
node.superClass.value === null
)
}
/**
* Reports a given node if the specified feature is not supported.
*
* @param {ASTNode} node - A node to be reported.
* @param {string} key - A feature name to report.
* @returns {void}
*/
function report(node, key) {
const version = supportInfo.version
const feature = supportInfo.features[key]
if (feature.supported) {
return
}
if (!feature.supportedInStrict) {
context.report({
node,
message:
"{{feature}} {{be}} not supported yet on Node {{version}}.",
data: {
feature: feature.name,
be: feature.singular ? "is" : "are",
version,
},
})
} else if (!normalizeScope(context.getScope(), node).isStrict) {
context.report({
node,
message:
"{{feature}} {{be}} not supported yet on Node {{version}}.",
data: {
feature: `${feature.name} in non-strict mode`,
be: feature.singular ? "is" : "are",
version,
},
})
}
}
/**
* Validate RegExp syntax.
* @param {string} pattern A RegExp pattern to check.
* @param {string} flags A RegExp flags to check.
* @param {ASTNode} node A node to report.
* @returns {void}
*/
function validateRegExp(pattern, flags, node) {
if (typeof pattern === "string") {
if (hasPattern(pattern, REGEXP_NAMED_GROUP)) {
report(node, "regexpNamedCaptureGroups")
}
if (hasPattern(pattern, REGEXP_LOOKBEHIND)) {
report(node, "regexpLookbehind")
}
if (hasPattern(pattern, REGEXP_UNICODE_PROPERTY)) {
report(node, "regexpUnicodeProperties")
}
}
if (typeof flags === "string") {
if (flags.indexOf("y") !== -1) {
report(node, "regexpY")
}
if (flags.indexOf("u") !== -1) {
report(node, "regexpU")
}
if (flags.indexOf("s") !== -1) {
report(node, "regexpS")
}
}
}
/**
* Validate RegExp syntax in a RegExp literal.
* @param {ASTNode} node A Literal node to check.
* @returns {void}
*/
function validateRegExpLiteral(node) {
validateRegExp(node.regex.pattern, node.regex.flags, node)
}
/**
* Validate RegExp syntax in the first argument of `new RegExp()`.
* @param {ASTNode} node A NewExpression node to check.
* @returns {void}
*/
function validateRegExpString(node) {
const patternNode = node.arguments[0]
const flagsNode = node.arguments[1]
const pattern =
patternNode &&
patternNode.type === "Literal" &&
typeof patternNode.value === "string"
? patternNode.value
: null
const flags =
flagsNode &&
flagsNode.type === "Literal" &&
typeof flagsNode.value === "string"
? flagsNode.value
: null
validateRegExp(pattern, flags, node)
}
return {
"Program:exit"() {
// Check new global variables.
for (const name of NEW_BUILTIN_TYPES) {
for (const reference of getReferences([name])) {
// Ignore if it's using new static methods.
const node = reference.identifier
const parentNode = node.parent
const properties = PROPERTY_TEST_TARGETS[name]
if (
properties &&
parentNode.type === "MemberExpression"
) {
const propertyName = getPropertyName(parentNode)
if (properties.indexOf(propertyName) !== -1) {
continue
}
}
report(reference.identifier, name)
}
}
// Check static methods.
for (const reference of getReferences(
Object.keys(PROPERTY_TEST_TARGETS)
)) {
const node = reference.identifier
const parentNode = node.parent
if (
parentNode.type !== "MemberExpression" ||
parentNode.object !== node
) {
continue
}
const objectName = node.name
const properties = PROPERTY_TEST_TARGETS[objectName]
const propertyName = getPropertyName(parentNode)
if (
propertyName &&
properties.indexOf(propertyName) !== -1
) {
report(parentNode, `${objectName}.${propertyName}`)
}
}
// Check subclassing
for (const reference of getReferences(
SUBCLASSING_TEST_TARGETS
)) {
const node = reference.identifier
const parentNode = node.parent
if (
CLASS_TYPE.test(parentNode.type) &&
parentNode.superClass === node
) {
report(node, `extends${node.name}`)
}
}
},
ArrowFunctionExpression(node) {
report(node, "arrowFunctions")
if (node.async) {
report(node, "asyncAwait")
}
if (hasTrailingCommaForFunction(node)) {
report(node, "trailingCommasInFunctions")
}
},
AssignmentPattern(node) {
if (FUNC_TYPE.test(node.parent.type)) {
report(node, "defaultParameters")
}
},
FunctionDeclaration(node) {
const scope = context.getScope().upper
if (!TOPLEVEL_SCOPE_TYPE.test(scope.type)) {
report(node, "blockScopedFunctions")
}
if (node.generator) {
report(node, "generatorFunctions")
}
if (node.async) {
report(node, "asyncAwait")
}
if (hasTrailingCommaForFunction(node)) {
report(node, "trailingCommasInFunctions")
}
if (node.async && node.generator) {
report(node, "asyncGenerators")
}
},
FunctionExpression(node) {
if (node.generator) {
report(node, "generatorFunctions")
}
if (node.async) {
report(node, "asyncAwait")
}
if (hasTrailingCommaForFunction(node)) {
report(node, "trailingCommasInFunctions")
}
if (node.async && node.generator) {
report(node, "asyncGenerators")
}
},
MetaProperty(node) {
const meta = node.meta.name || node.meta
const property = node.property.name || node.property
if (meta === "new" && property === "target") {
report(node, "new.target")
}
},
ClassDeclaration(node) {
report(node, "classes")
if (extendsNull(node)) {
report(node, "extendsNull")
}
},
ClassExpression(node) {
report(node, "classes")
if (extendsNull(node)) {
report(node, "extendsNull")
}
},
ForOfStatement(node) {
report(node, "forOf")
if (node.await) {
report(node, "forAwaitOf")
}
},
VariableDeclaration(node) {
if (node.kind === "const") {
report(node, "const")
} else if (node.kind === "let") {
report(node, "let")
}
},
ArrayPattern(node) {
if (DESTRUCTURING_PARENT_TYPE.test(node.parent.type)) {
report(node, "destructuring")
}
},
AssignmentExpression(node) {
if (node.operator === "**=") {
report(node, "exponentialOperators")
}
},
AwaitExpression(node) {
report(node, "asyncAwait")
},
BinaryExpression(node) {
if (node.operator === "**") {
report(node, "exponentialOperators")
}
},
CallExpression(node) {
if (hasTrailingCommaForCall(node)) {
report(node, "trailingCommasInFunctions")
}
},
Identifier(node) {
const raw = sourceCode.getText(node)
if (hasUnicodeCodePointEscape(raw)) {
report(node, "unicodeCodePointEscapes")
}
},
Literal(node) {
if (typeof node.value === "number") {
if (BINARY_NUMBER.test(node.raw)) {
report(node, "binaryNumberLiterals")
} else if (OCTAL_NUMBER.test(node.raw)) {
report(node, "octalNumberLiterals")
}
} else if (typeof node.value === "string") {
if (hasUnicodeCodePointEscape(node.raw)) {
report(node, "unicodeCodePointEscapes")
}
} else if (node.regex) {
validateRegExpLiteral(node)
}
},
NewExpression(node) {
if (
node.callee.type === "Identifier" &&
node.callee.name === "RegExp"
) {
validateRegExpString(node)
}
if (hasTrailingCommaForCall(node)) {
report(node, "trailingCommasInFunctions")
}
},
ObjectPattern(node) {
if (DESTRUCTURING_PARENT_TYPE.test(node.parent.type)) {
report(node, "destructuring")
}
},
Property(node) {
if (
node.parent.type === "ObjectExpression" &&
(node.computed || node.shorthand || node.method)
) {
if (node.shorthand && GET_OR_SET.test(node.key.name)) {
report(node, "objectPropertyShorthandOfGetSet")
} else {
report(node, "objectLiteralExtensions")
}
}
},
RestElement(node) {
if (FUNC_TYPE.test(node.parent.type)) {
report(node, "restParameters")
} else if (node.parent.type === "ObjectPattern") {
report(node, "restProperties")
}
},
SpreadElement(node) {
if (node.parent.type === "ObjectExpression") {
report(node, "spreadProperties")
} else {
report(node, "spreadOperators")
}
},
TemplateElement(node) {
if (node.value.cooked == null) {
report(node, "templateLiteralRevision")
}
},
TemplateLiteral(node) {
report(node, "templateStrings")
},
ExperimentalRestProperty(node) {
report(node, "restProperties")
},
ExperimentalSpreadProperty(node) {
report(node, "spreadProperties")
},
RestProperty(node) {
report(node, "restProperties")
},
SpreadProperty(node) {
report(node, "spreadProperties")
},
ExportAllDeclaration(node) {
report(node, "modules")
},
ExportDefaultDeclaration(node) {
report(node, "modules")
},
ExportNamedDeclaration(node) {
report(node, "modules")
},
ImportDeclaration(node) {
report(node, "modules")
},
}
},
}