diff --git a/docs/rules/no-unsupported-features.md b/docs/rules/no-unsupported-features.md
index 6b2d9108..27ee5072 100644
--- a/docs/rules/no-unsupported-features.md
+++ b/docs/rules/no-unsupported-features.md
@@ -1,9 +1,9 @@
# Disallow unsupported ECMAScript features on the specified version (no-unsupported-features)
Node.js doesn't support all ECMAScript standard features.
-This rule reports when you used unsupported ECMAScript 2015-2017 features on the specified Node.js version.
+This rule reports when you used unsupported ECMAScript 2015-2018 features on the specified Node.js version.
-> ※ About ECMAScript 2017, this rule reports only features which have arrived at stage 4 until 2017-06-01.
+> ※ About ECMAScript 2018, this rule reports only features which have arrived at stage 4 until 2018-02-01.
> It needs a major version bump in order to cover newer features.
## Rule Details
@@ -13,7 +13,7 @@ This rule reports when you used unsupported ECMAScript 2015-2017 features on the
```json
{
"env": {"es6": true},
- "parserOptions": {"ecmaVersion": 2017}
+ "parserOptions": {"ecmaVersion": 2018}
}
```
@@ -27,7 +27,7 @@ For example of `package.json`:
"name": "your-module",
"version": "1.0.0",
"engines": {
- "node": ">=4.0.0"
+ "node": ">=6.0.0"
}
}
```
@@ -125,9 +125,13 @@ The `version` option accepts the following version number:
- `4`
- `5`
- `6`
-- `7`
-- `7.6` ... supports async functions.
-- `8` ... supports trailing commas in functions.
+- `6.5` ... `Symbol.hasInstance` and `Symbol.species`.
+- `7` ... Exponential operators, `Object.values`, `Object.entries`, and `Object.getOwnPropertyDescriptors`.
+- `7.6` ... Async functions.
+- `8` ... Trailing commas in functions.
+- `8.3` ... Rest/Spread proeprties.
+- `9.0` ... Illegal escape sequences in taggled templates, RegExp 's' flags, RegExp lookbehind assertions, `SharedArrayBuffer`, and `Atomics`.
+- `10.0` ... RegExp named capture groups, RegExp Unicode property escapes, Async generators, and `for-await-of` loops.
### ignores
@@ -159,6 +163,15 @@ The `"ignores"` option accepts an array of the following strings.
- `"exponentialOperators"`
- `"asyncAwait"`
- `"trailingCommasInFunctions"`
+ - `"templateLiteralRevision"`
+ - `"regexpS"`
+ - `"regexpNamedCaptureGroups"`
+ - `"regexpLookbehind"`
+ - `"regexpUnicodeProperties"`
+ - `"restProperties"`
+ - `"spreadProperties"`
+ - `"asyncGenerators"`
+ - `"forAwaitOf"`
- `"runtime"` (group)
- `"globalObjects"` (group)
- `"typedArrays"` (group)
@@ -282,6 +295,5 @@ E.g., a use of instance methods.
## Further Reading
- http://node.green/
-- http://kangax.github.io/compat-table/es6/
[engines]: https://docs.npmjs.com/files/package.json#engines
diff --git a/lib/rules/no-unsupported-features.js b/lib/rules/no-unsupported-features.js
index 0ed1493d..989ada24 100644
--- a/lib/rules/no-unsupported-features.js
+++ b/lib/rules/no-unsupported-features.js
@@ -25,9 +25,13 @@ const VERSION_MAP = new Map([
[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: [
@@ -83,6 +87,9 @@ const PROPERTY_TEST_TARGETS = {
"isLockFree", "load", "or", "store", "sub", "xor",
],
}
+const REGEXP_NAMED_GROUP = /(\\*)\(\?<[_$\w]/
+const REGEXP_LOOKBEHIND = /(\\*)\(\?<[=!]/
+const REGEXP_UNICODE_PROPERTY = /(\\*)\\[pP]{.+?}/
/**
* Gets default version configuration of this rule.
@@ -252,6 +259,17 @@ function hasUnicodeCodePointEscape(raw) {
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
+}
+
/**
* The definition of this rule.
*
@@ -378,6 +396,60 @@ function create(context) {
}
}
+ /**
+ * 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
@@ -467,6 +539,9 @@ function create(context) {
if (hasTrailingCommaForFunction(node)) {
report(node, "trailingCommasInFunctions")
}
+ if (node.async && node.generator) {
+ report(node, "asyncGenerators")
+ }
},
"FunctionExpression"(node) {
@@ -479,6 +554,9 @@ function create(context) {
if (hasTrailingCommaForFunction(node)) {
report(node, "trailingCommasInFunctions")
}
+ if (node.async && node.generator) {
+ report(node, "asyncGenerators")
+ }
},
"MetaProperty"(node) {
@@ -489,12 +567,6 @@ function create(context) {
}
},
- "RestElement"(node) {
- if (FUNC_TYPE.test(node.parent.type)) {
- report(node, "restParameters")
- }
- },
-
//----------------------------------------------------------------------
// Classes
//----------------------------------------------------------------------
@@ -521,6 +593,9 @@ function create(context) {
"ForOfStatement"(node) {
report(node, "forOf")
+ if (node.await) {
+ report(node, "forAwaitOf")
+ }
},
"VariableDeclaration"(node) {
@@ -586,28 +661,13 @@ function create(context) {
}
}
else if (node.regex) {
- if (node.regex.flags.indexOf("y") !== -1) {
- report(node, "regexpY")
- }
- if (node.regex.flags.indexOf("u") !== -1) {
- report(node, "regexpU")
- }
+ validateRegExpLiteral(node)
}
},
"NewExpression"(node) {
- if (node.callee.type === "Identifier" &&
- node.callee.name === "RegExp" &&
- node.arguments.length === 2 &&
- node.arguments[1].type === "Literal" &&
- typeof node.arguments[1].value === "string"
- ) {
- if (node.arguments[1].value.indexOf("y") !== -1) {
- report(node, "regexpY")
- }
- if (node.arguments[1].value.indexOf("u") !== -1) {
- report(node, "regexpU")
- }
+ if (node.callee.type === "Identifier" && node.callee.name === "RegExp") {
+ validateRegExpString(node)
}
if (hasTrailingCommaForCall(node)) {
report(node, "trailingCommasInFunctions")
@@ -633,14 +693,54 @@ function create(context) {
}
},
+ "RestElement"(node) {
+ if (FUNC_TYPE.test(node.parent.type)) {
+ report(node, "restParameters")
+ }
+ else if (node.parent.type === "ObjectPattern") {
+ report(node, "restProperties")
+ }
+ },
+
"SpreadElement"(node) {
- report(node, "spreadOperators")
+ 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")
},
+ //----------------------------------------------------------------------
+ // Legacy
+ //----------------------------------------------------------------------
+
+ "ExperimentalRestProperty"(node) {
+ report(node, "restProperties")
+ },
+
+ "ExperimentalSpreadProperty"(node) {
+ report(node, "spreadProperties")
+ },
+
+ "RestProperty"(node) {
+ report(node, "restProperties")
+ },
+
+ "SpreadProperty"(node) {
+ report(node, "spreadProperties")
+ },
+
//----------------------------------------------------------------------
// Modules
//----------------------------------------------------------------------
diff --git a/lib/util/features.js b/lib/util/features.js
index 759baadb..874e6408 100644
--- a/lib/util/features.js
+++ b/lib/util/features.js
@@ -142,11 +142,56 @@ module.exports = {
node: "7.6.0",
},
"trailingCommasInFunctions": {
- // trailingCommasInFunctionSyntax is for backward compatibility.
- alias: ["syntax", "trailingCommasInFunctionSyntax"],
+ 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",
+ },
//--------------------------------------------------------------------------
// Runtime
@@ -264,13 +309,13 @@ module.exports = {
alias: ["runtime", "globalObjects"],
name: "'SharedArrayBuffer'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics": {
alias: ["runtime", "globalObjects"],
name: "'Atomics'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Object.assign": {
@@ -492,7 +537,7 @@ module.exports = {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.hasInstance'",
singular: true,
- node: null,
+ node: "6.5.0",
},
"Symbol.isConcatSpreadablec": {
alias: ["runtime", "staticMethods", "Symbol.*"],
@@ -510,7 +555,7 @@ module.exports = {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.species'",
singular: true,
- node: null,
+ node: "6.5.0",
},
"Symbol.replace": {
alias: ["runtime", "staticMethods", "Symbol.*"],
@@ -559,73 +604,73 @@ module.exports = {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.add'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.and": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.and'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.compareExchange": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.compareExchange'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.exchange": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.exchange'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.wait": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.wait'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.wake": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.wake'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.isLockFree": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.isLockFree'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.load": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.load'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.or": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.or'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.store": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.store'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.sub": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.sub'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"Atomics.xor": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.xor'",
singular: true,
- node: null,
+ node: "9.0.0",
},
"extendsArray": {
diff --git a/tests/lib/rules/no-unsupported-features.js b/tests/lib/rules/no-unsupported-features.js
index a9435ede..dff1e356 100644
--- a/tests/lib/rules/no-unsupported-features.js
+++ b/tests/lib/rules/no-unsupported-features.js
@@ -23,9 +23,13 @@ const VERSION_MAP = new Map([
[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"],
])
/**
@@ -75,7 +79,7 @@ function convertPattern(retv, pattern) {
globals: { SharedArrayBuffer: false, Atomics: false },
options: [version],
parserOptions: {
- ecmaVersion: 8,
+ ecmaVersion: 2018,
sourceType: pattern.modules ? "module" : "script",
},
})
@@ -88,7 +92,7 @@ function convertPattern(retv, pattern) {
globals: { SharedArrayBuffer: false, Atomics: false },
options: [{ version, ignores: [key] }],
parserOptions: {
- ecmaVersion: 8,
+ ecmaVersion: 2018,
sourceType: pattern.modules ? "module" : "script",
},
})))
@@ -100,7 +104,7 @@ function convertPattern(retv, pattern) {
globals: { SharedArrayBuffer: false, Atomics: false },
options: [version],
parserOptions: {
- ecmaVersion: 8,
+ ecmaVersion: 2018,
sourceType: pattern.modules ? "module" : "script",
},
errors: errors.map(message => `${message + versionText}.`),
@@ -378,7 +382,7 @@ ruleTester.run("no-unsupported-features", rule, [
ignores: [0.10, 0.12, 4, 5],
},
{
- keys: ["trailingCommasInFunctionSyntax", "trailingCommasInFunctions", "syntax"],
+ keys: ["trailingCommasInFunctions", "syntax"],
name: "Trailing commas in functions",
code: [
"function foo(a,) {}",
@@ -393,6 +397,105 @@ ruleTester.run("no-unsupported-features", rule, [
supported: 8,
ignores: [0.10, 0.12, 4, 5],
},
+ {
+ keys: ["templateLiteralRevision", "syntax"],
+ name: "Illegal escape sequences in taggled templates",
+ code: [
+ //eslint-disable-next-line no-template-curly-in-string
+ "tag`\\01\\1\\xg\\xAg\\u0\\u0g\\u00g\\u000g\\u{g\\u{0\\u{110000}${0}\\0`",
+ ].join("\n"),
+ errors: 1,
+ supported: 9,
+ ignores: [0.10, 0.12, 4, 5],
+ },
+ {
+ keys: ["regexpS", "syntax"],
+ name: "RegExp 's' flags",
+ code: "new RegExp('', 's'); (/a/s)",
+ errors: 2,
+ supported: 9,
+ },
+ {
+ keys: ["regexpNamedCaptureGroups", "syntax"],
+ name: "RegExp named capture groups",
+ code: [
+ "new RegExp('(?b)');",
+ //TODO: Espree has not supported this syntax yet.
+ // ";(/(?b)/)",
+ ].join("\n"),
+ errors: 1,
+ supported: 10,
+ },
+ {
+ keys: ["regexpLookbehind", "syntax"],
+ name: "RegExp lookbehind assertions",
+ code: [
+ "new RegExp('(?<=a)b')",
+ "new RegExp('(?