Skip to content

Commit

Permalink
Breaking: no-unsupported-features supports ES2018 (fixes #94, fixes #103
Browse files Browse the repository at this point in the history
)
  • Loading branch information
mysticatea committed Feb 4, 2018
1 parent 2e2d464 commit 49c060a
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 71 deletions.
28 changes: 20 additions & 8 deletions 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
Expand All @@ -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}
}
```

Expand All @@ -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"
}
}
```
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
150 changes: 125 additions & 25 deletions lib/rules/no-unsupported-features.js
Expand Up @@ -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: [
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -467,6 +539,9 @@ function create(context) {
if (hasTrailingCommaForFunction(node)) {
report(node, "trailingCommasInFunctions")
}
if (node.async && node.generator) {
report(node, "asyncGenerators")
}
},

"FunctionExpression"(node) {
Expand All @@ -479,6 +554,9 @@ function create(context) {
if (hasTrailingCommaForFunction(node)) {
report(node, "trailingCommasInFunctions")
}
if (node.async && node.generator) {
report(node, "asyncGenerators")
}
},

"MetaProperty"(node) {
Expand All @@ -489,12 +567,6 @@ function create(context) {
}
},

"RestElement"(node) {
if (FUNC_TYPE.test(node.parent.type)) {
report(node, "restParameters")
}
},

//----------------------------------------------------------------------
// Classes
//----------------------------------------------------------------------
Expand All @@ -521,6 +593,9 @@ function create(context) {

"ForOfStatement"(node) {
report(node, "forOf")
if (node.await) {
report(node, "forAwaitOf")
}
},

"VariableDeclaration"(node) {
Expand Down Expand Up @@ -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")
Expand All @@ -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
//----------------------------------------------------------------------
Expand Down

0 comments on commit 49c060a

Please sign in to comment.