/** * @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, url: "https://github.com/weiran-zsd/eslint-plugin-node/blob/HEAD/docs/rules/no-unsupported-features.md", }, type: "problem", deprecated: true, replacedBy: [ "n/no-unsupported-features/es-syntax", "n/no-unsupported-features/es-builtins", ], 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, }, ], }, ], messages: { unsupported: "{{feature}} {{be}} not supported yet on Node {{version}}.", }, }, 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, messageId: "unsupported", data: { feature: feature.name, be: feature.singular ? "is" : "are", version, }, }) } else if (!normalizeScope(context.getScope(), node).isStrict) { context.report({ node, messageId: "unsupported", 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") }, } }, }