/** * @author Toru Nagashima * See LICENSE file in root directory for full license. */ "use strict" const { rules: esRules } = require("eslint-plugin-es") const { getInnermostScope } = require("eslint-utils") const { Range } = require("semver") const getConfiguredNodeVersion = require("../../util/get-configured-node-version") const getSemverRange = require("../../util/get-semver-range") const mergeVisitorsInPlace = require("../../util/merge-visitors-in-place") const getOrSet = /^(?:g|s)et$/u const features = { //-------------------------------------------------------------------------- // ES2015 //-------------------------------------------------------------------------- arrowFunctions: { ruleId: "no-arrow-functions", cases: [ { supported: "4.0.0", messageId: "no-arrow-functions", }, ], }, binaryNumericLiterals: { ruleId: "no-binary-numeric-literals", cases: [ { supported: "4.0.0", messageId: "no-binary-numeric-literals", }, ], }, blockScopedFunctions: { ruleId: "no-block-scoped-functions", cases: [ { supported: "6.0.0", test: info => !info.isStrict, messageId: "no-block-scoped-functions-sloppy", }, { supported: "4.0.0", messageId: "no-block-scoped-functions-strict", }, ], }, blockScopedVariables: { ruleId: "no-block-scoped-variables", cases: [ { supported: "6.0.0", test: info => !info.isStrict, messageId: "no-block-scoped-variables-sloppy", }, { supported: "4.0.0", messageId: "no-block-scoped-variables-strict", }, ], }, classes: { ruleId: "no-classes", cases: [ { supported: "6.0.0", test: info => !info.isStrict, messageId: "no-classes-sloppy", }, { supported: "4.0.0", messageId: "no-classes-strict", }, ], }, computedProperties: { ruleId: "no-computed-properties", cases: [ { supported: "4.0.0", messageId: "no-computed-properties", }, ], }, defaultParameters: { ruleId: "no-default-parameters", cases: [ { supported: "6.0.0", messageId: "no-default-parameters", }, ], }, destructuring: { ruleId: "no-destructuring", cases: [ { supported: "6.0.0", messageId: "no-destructuring", }, ], }, forOfLoops: { ruleId: "no-for-of-loops", cases: [ { supported: "0.12.0", messageId: "no-for-of-loops", }, ], }, generators: { ruleId: "no-generators", cases: [ { supported: "4.0.0", messageId: "no-generators", }, ], }, modules: { ruleId: "no-modules", cases: [ { supported: null, messageId: "no-modules", }, ], }, "new.target": { ruleId: "no-new-target", cases: [ { supported: "5.0.0", messageId: "no-new-target", }, ], }, objectSuperProperties: { ruleId: "no-object-super-properties", cases: [ { supported: "4.0.0", messageId: "no-object-super-properties", }, ], }, octalNumericLiterals: { ruleId: "no-octal-numeric-literals", cases: [ { supported: "4.0.0", messageId: "no-octal-numeric-literals", }, ], }, propertyShorthands: { ruleId: "no-property-shorthands", cases: [ { supported: "6.0.0", test: info => info.node.shorthand && getOrSet.test(info.node.key.name), messageId: "no-property-shorthands-getset", }, { supported: "4.0.0", messageId: "no-property-shorthands", }, ], }, regexpU: { ruleId: "no-regexp-u-flag", cases: [ { supported: "6.0.0", messageId: "no-regexp-u-flag", }, ], }, regexpY: { ruleId: "no-regexp-y-flag", cases: [ { supported: "6.0.0", messageId: "no-regexp-y-flag", }, ], }, restParameters: { ruleId: "no-rest-parameters", cases: [ { supported: "6.0.0", messageId: "no-rest-parameters", }, ], }, spreadElements: { ruleId: "no-spread-elements", cases: [ { supported: "5.0.0", messageId: "no-spread-elements", }, ], }, templateLiterals: { ruleId: "no-template-literals", cases: [ { supported: "4.0.0", messageId: "no-template-literals", }, ], }, unicodeCodePointEscapes: { ruleId: "no-unicode-codepoint-escapes", cases: [ { supported: "4.0.0", messageId: "no-unicode-codepoint-escapes", }, ], }, //-------------------------------------------------------------------------- // ES2016 //-------------------------------------------------------------------------- exponentialOperators: { ruleId: "no-exponential-operators", cases: [ { supported: "7.0.0", messageId: "no-exponential-operators", }, ], }, //-------------------------------------------------------------------------- // ES2017 //-------------------------------------------------------------------------- asyncFunctions: { ruleId: "no-async-functions", cases: [ { supported: "7.6.0", messageId: "no-async-functions", }, ], }, trailingCommasInFunctions: { ruleId: "no-trailing-function-commas", cases: [ { supported: "8.0.0", messageId: "no-trailing-function-commas", }, ], }, //-------------------------------------------------------------------------- // ES2018 //-------------------------------------------------------------------------- asyncIteration: { ruleId: "no-async-iteration", cases: [ { supported: "10.0.0", messageId: "no-async-iteration", }, ], }, malformedTemplateLiterals: { ruleId: "no-malformed-template-literals", cases: [ { supported: "8.10.0", messageId: "no-malformed-template-literals", }, ], }, regexpLookbehind: { ruleId: "no-regexp-lookbehind-assertions", cases: [ { supported: "8.10.0", messageId: "no-regexp-lookbehind-assertions", }, ], }, regexpNamedCaptureGroups: { ruleId: "no-regexp-named-capture-groups", cases: [ { supported: "10.0.0", messageId: "no-regexp-named-capture-groups", }, ], }, regexpS: { ruleId: "no-regexp-s-flag", cases: [ { supported: "8.10.0", messageId: "no-regexp-s-flag", }, ], }, regexpUnicodeProperties: { ruleId: "no-regexp-unicode-property-escapes", cases: [ { supported: "10.0.0", messageId: "no-regexp-unicode-property-escapes", }, ], }, restSpreadProperties: { ruleId: "no-rest-spread-properties", cases: [ { supported: "8.3.0", messageId: "no-rest-spread-properties", }, ], }, //-------------------------------------------------------------------------- // ES2019 //-------------------------------------------------------------------------- jsonSuperset: { ruleId: "no-json-superset", cases: [ { supported: "10.0.0", messageId: "no-json-superset", }, ], }, optionalCatchBinding: { ruleId: "no-optional-catch-binding", cases: [ { supported: "10.0.0", messageId: "no-optional-catch-binding", }, ], }, //-------------------------------------------------------------------------- // ES2020 //-------------------------------------------------------------------------- bigint: { ruleId: "no-bigint", cases: [ { supported: "10.4.0", test: info => info.node.type === "Literal", messageId: "no-bigint", }, { supported: null, test: ({ node }) => node.type === "Literal" && (node.parent.type === "Property" || node.parent.type === "MethodDefinition") && !node.parent.computed && node.parent.key === node, messageId: "no-bigint-property-names", }, ], }, dynamicImport: { ruleId: "no-dynamic-import", cases: [ { supported: new Range(">=12.17 <13 || >=13.2"), messageId: "no-dynamic-import", }, ], }, optionalChaining: { ruleId: "no-optional-chaining", cases: [ { supported: "14.0.0", messageId: "no-optional-chaining", }, ], }, nullishCoalescingOperators: { ruleId: "no-nullish-coalescing-operators", cases: [ { supported: "14.0.0", messageId: "no-nullish-coalescing-operators", }, ], }, } const keywords = Object.keys(features) /** * Parses the options. * @param {RuleContext} context The rule context. * @returns {{version:Range,ignores:Set}} Parsed value. */ function parseOptions(context) { const raw = context.options[0] || {} const filePath = context.getFilename() const version = getConfiguredNodeVersion(raw.version, filePath) const ignores = new Set(raw.ignores || []) return Object.freeze({ version, ignores }) } /** * 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 } /** * Define the visitor object as merging the rules of eslint-plugin-es. * @param {RuleContext} context The rule context. * @param {{version:Range,ignores:Set}} options The options. * @returns {object} The defined visitor. */ function defineVisitor(context, options) { const testInfoPrototype = { get isStrict() { return normalizeScope(context.getScope(), this.node).isStrict }, } /** * Check whether a given case object is full-supported on the configured node version. * @param {{supported:string}} aCase The case object to check. * @returns {boolean} `true` if it's supporting. */ function isNotSupportingVersion(aCase) { if (!aCase.supported) { return true } if (aCase.supported instanceof Range) { return !options.version.intersects(aCase.supported) } return options.version.intersects(getSemverRange(`<${aCase.supported}`)) } /** * Define the predicate function to check whether a given case object is supported on the configured node version. * @param {Node} node The node which is reported. * @returns {function(aCase:{supported:string}):boolean} The predicate function. */ function isNotSupportingOn(node) { return aCase => isNotSupportingVersion(aCase) && (!aCase.test || aCase.test({ node, __proto__: testInfoPrototype })) } return ( keywords // Omit full-supported features and ignored features by options // because this rule never reports those. .filter( keyword => !options.ignores.has(keyword) && features[keyword].cases.some(isNotSupportingVersion) ) // Merge remaining features with overriding `context.report()`. .reduce((visitor, keyword) => { const { ruleId, cases } = features[keyword] const rule = esRules[ruleId] const thisContext = { __proto__: context, // Override `context.report()` then: // - ignore if it's supported. // - override reporting messages. report(descriptor) { // Set additional information. if (descriptor.data) { descriptor.data.version = options.version.raw } else { descriptor.data = { version: options.version.raw } } descriptor.fix = undefined // Test and report. const node = descriptor.node const hitCase = cases.find(isNotSupportingOn(node)) if (hitCase) { descriptor.messageId = hitCase.messageId descriptor.data.supported = hitCase.supported super.report(descriptor) } }, } return mergeVisitorsInPlace(visitor, rule.create(thisContext)) }, {}) ) } module.exports = { meta: { docs: { description: "disallow unsupported ECMAScript syntax on the specified version", category: "Possible Errors", recommended: true, url: "https://github.com/weiran-zsd/eslint-plugin-node/blob/HEAD/docs/rules/no-unsupported-features/es-syntax.md", }, type: "problem", fixable: null, schema: [ { type: "object", properties: { version: { type: "string", }, ignores: { type: "array", items: { enum: Object.keys(features), }, uniqueItems: true, }, }, additionalProperties: false, }, ], messages: { //------------------------------------------------------------------ // ES2015 //------------------------------------------------------------------ "no-arrow-functions": "Arrow functions are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-binary-numeric-literals": "Binary numeric literals are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-block-scoped-functions-strict": "Block-scoped functions in strict mode are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-block-scoped-functions-sloppy": "Block-scoped functions in non-strict mode are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-block-scoped-variables-strict": "Block-scoped variables in strict mode are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-block-scoped-variables-sloppy": "Block-scoped variables in non-strict mode are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-classes-strict": "Classes in strict mode are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-classes-sloppy": "Classes in non-strict mode are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-computed-properties": "Computed properties are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-default-parameters": "Default parameters are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-destructuring": "Destructuring is not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-for-of-loops": "'for-of' loops are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-generators": "Generator functions are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-modules": "Import and export declarations are not supported yet.", "no-new-target": "'new.target' is not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-object-super-properties": "'super' in object literals is not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-octal-numeric-literals": "Octal numeric literals are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-property-shorthands": "Property shorthands are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-property-shorthands-getset": "Property shorthands of 'get' and 'set' are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-regexp-u-flag": "RegExp 'u' flag is not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-regexp-y-flag": "RegExp 'y' flag is not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-rest-parameters": "Rest parameters are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-spread-elements": "Spread elements are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-template-literals": "Template literals are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-unicode-codepoint-escapes": "Unicode code point escapes are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", //------------------------------------------------------------------ // ES2016 //------------------------------------------------------------------ "no-exponential-operators": "Exponential operators are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", //------------------------------------------------------------------ // ES2017 //------------------------------------------------------------------ "no-async-functions": "Async functions are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-trailing-function-commas": "Trailing commas in function syntax are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", //------------------------------------------------------------------ // ES2018 //------------------------------------------------------------------ "no-async-iteration": "Async iteration is not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-malformed-template-literals": "Malformed template literals are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-regexp-lookbehind-assertions": "RegExp lookbehind assertions are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-regexp-named-capture-groups": "RegExp named capture groups are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-regexp-s-flag": "RegExp 's' flag is not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-regexp-unicode-property-escapes": "RegExp Unicode property escapes are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-rest-spread-properties": "Rest/spread properties are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", //------------------------------------------------------------------ // ES2019 //------------------------------------------------------------------ "no-json-superset": "'\\u{{code}}' in string literals is not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-optional-catch-binding": "The omission of 'catch' binding is not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", //------------------------------------------------------------------ // ES2020 //------------------------------------------------------------------ "no-bigint": "Bigint literals are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-bigint-property-names": "Bigint literal property names are not supported yet.", "no-dynamic-import": "'import()' expressions are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-optional-chaining": "Optional chainings are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", "no-nullish-coalescing-operators": "Nullish coalescing operators are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.", }, }, create(context) { return defineVisitor(context, parseOptions(context)) }, }