Browse Source

tools: update ESLint to 3.19.0

PR-URL: https://github.com/nodejs/node/pull/12162
Reviewed-By: Teddy Katz <teddy.katz@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Yuta Hiroto <hello@about-hiroppy.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
v6
Rich Trott 8 years ago
committed by James M Snell
parent
commit
316665235c
  1. 3939
      tools/eslint/CHANGELOG.md
  2. 3
      tools/eslint/README.md
  3. 161
      tools/eslint/conf/eslint-recommended.js
  4. 305
      tools/eslint/lib/ast-utils.js
  5. 4
      tools/eslint/lib/cli.js
  6. 9
      tools/eslint/lib/code-path-analysis/code-path-analyzer.js
  7. 4
      tools/eslint/lib/code-path-analysis/code-path-state.js
  8. 33
      tools/eslint/lib/code-path-analysis/debug-helpers.js
  9. 5
      tools/eslint/lib/config.js
  10. 44
      tools/eslint/lib/config/autoconfig.js
  11. 127
      tools/eslint/lib/config/config-file.js
  12. 4
      tools/eslint/lib/config/config-initializer.js
  13. 55
      tools/eslint/lib/config/config-rule.js
  14. 12
      tools/eslint/lib/config/config-validator.js
  15. 27
      tools/eslint/lib/config/plugins.js
  16. 57
      tools/eslint/lib/eslint.js
  17. 4
      tools/eslint/lib/formatters/checkstyle.js
  18. 15
      tools/eslint/lib/formatters/codeframe.js
  19. 4
      tools/eslint/lib/formatters/compact.js
  20. 4
      tools/eslint/lib/formatters/junit.js
  21. 9
      tools/eslint/lib/formatters/stylish.js
  22. 4
      tools/eslint/lib/formatters/tap.js
  23. 4
      tools/eslint/lib/formatters/unix.js
  24. 4
      tools/eslint/lib/formatters/visualstudio.js
  25. 6
      tools/eslint/lib/ignored-paths.js
  26. 2
      tools/eslint/lib/internal-rules/internal-consistent-docs-description.js
  27. 42
      tools/eslint/lib/internal-rules/internal-no-invalid-meta.js
  28. 4
      tools/eslint/lib/rule-context.js
  29. 4
      tools/eslint/lib/rules.js
  30. 20
      tools/eslint/lib/rules/array-callback-return.js
  31. 15
      tools/eslint/lib/rules/arrow-body-style.js
  32. 12
      tools/eslint/lib/rules/arrow-parens.js
  33. 13
      tools/eslint/lib/rules/arrow-spacing.js
  34. 4
      tools/eslint/lib/rules/block-spacing.js
  35. 295
      tools/eslint/lib/rules/brace-style.js
  36. 15
      tools/eslint/lib/rules/capitalized-comments.js
  37. 17
      tools/eslint/lib/rules/comma-dangle.js
  38. 50
      tools/eslint/lib/rules/comma-spacing.js
  39. 24
      tools/eslint/lib/rules/comma-style.js
  40. 22
      tools/eslint/lib/rules/complexity.js
  41. 29
      tools/eslint/lib/rules/consistent-return.js
  42. 6
      tools/eslint/lib/rules/constructor-super.js
  43. 22
      tools/eslint/lib/rules/curly.js
  44. 6
      tools/eslint/lib/rules/default-case.js
  45. 18
      tools/eslint/lib/rules/dot-notation.js
  46. 21
      tools/eslint/lib/rules/eqeqeq.js
  47. 27
      tools/eslint/lib/rules/func-call-spacing.js
  48. 27
      tools/eslint/lib/rules/func-name-matching.js
  49. 25
      tools/eslint/lib/rules/func-names.js
  50. 37
      tools/eslint/lib/rules/generator-star-spacing.js
  51. 4
      tools/eslint/lib/rules/global-require.js
  52. 4
      tools/eslint/lib/rules/id-blacklist.js
  53. 6
      tools/eslint/lib/rules/id-length.js
  54. 4
      tools/eslint/lib/rules/id-match.js
  55. 41
      tools/eslint/lib/rules/indent.js
  56. 43
      tools/eslint/lib/rules/key-spacing.js
  57. 38
      tools/eslint/lib/rules/keyword-spacing.js
  58. 22
      tools/eslint/lib/rules/line-comment-position.js
  59. 8
      tools/eslint/lib/rules/linebreak-style.js
  60. 27
      tools/eslint/lib/rules/lines-around-comment.js
  61. 10
      tools/eslint/lib/rules/lines-around-directive.js
  62. 6
      tools/eslint/lib/rules/max-lines.js
  63. 21
      tools/eslint/lib/rules/max-params.js
  64. 15
      tools/eslint/lib/rules/max-statements-per-line.js
  65. 21
      tools/eslint/lib/rules/max-statements.js
  66. 4
      tools/eslint/lib/rules/new-cap.js
  67. 28
      tools/eslint/lib/rules/new-parens.js
  68. 9
      tools/eslint/lib/rules/newline-after-var.js
  69. 4
      tools/eslint/lib/rules/newline-before-return.js
  70. 4
      tools/eslint/lib/rules/newline-per-chained-call.js
  71. 10
      tools/eslint/lib/rules/no-await-in-loop.js
  72. 53
      tools/eslint/lib/rules/no-compare-neg-zero.js
  73. 25
      tools/eslint/lib/rules/no-cond-assign.js
  74. 2
      tools/eslint/lib/rules/no-dupe-keys.js
  75. 128
      tools/eslint/lib/rules/no-else-return.js
  76. 25
      tools/eslint/lib/rules/no-empty-function.js
  77. 6
      tools/eslint/lib/rules/no-extend-native.js
  78. 7
      tools/eslint/lib/rules/no-extra-bind.js
  79. 23
      tools/eslint/lib/rules/no-extra-boolean-cast.js
  80. 7
      tools/eslint/lib/rules/no-extra-label.js
  81. 293
      tools/eslint/lib/rules/no-extra-parens.js
  82. 19
      tools/eslint/lib/rules/no-extra-semi.js
  83. 2
      tools/eslint/lib/rules/no-global-assign.js
  84. 29
      tools/eslint/lib/rules/no-implicit-coercion.js
  85. 8
      tools/eslint/lib/rules/no-inner-declarations.js
  86. 3
      tools/eslint/lib/rules/no-invalid-regexp.js
  87. 8
      tools/eslint/lib/rules/no-irregular-whitespace.js
  88. 20
      tools/eslint/lib/rules/no-lone-blocks.js
  89. 8
      tools/eslint/lib/rules/no-mixed-operators.js
  90. 8
      tools/eslint/lib/rules/no-mixed-requires.js
  91. 41
      tools/eslint/lib/rules/no-multi-assign.js
  92. 5
      tools/eslint/lib/rules/no-multi-spaces.js
  93. 10
      tools/eslint/lib/rules/no-multi-str.js
  94. 6
      tools/eslint/lib/rules/no-multiple-empty-lines.js
  95. 2
      tools/eslint/lib/rules/no-native-reassign.js
  96. 2
      tools/eslint/lib/rules/no-negated-in-lhs.js
  97. 14
      tools/eslint/lib/rules/no-new-func.js
  98. 8
      tools/eslint/lib/rules/no-new.js
  99. 44
      tools/eslint/lib/rules/no-param-reassign.js
  100. 12
      tools/eslint/lib/rules/no-process-exit.js

3939
tools/eslint/CHANGELOG.md

File diff suppressed because it is too large

3
tools/eslint/README.md

@ -112,6 +112,7 @@ These folks keep the project moving and are resources for help.
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea))
* Alberto Rodríguez ([@alberto](https://github.com/alberto))
* Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo))
* Teddy Katz ([@not-an-aardvark](https://github.com/not-an-aardvark))
### Development Team
@ -129,7 +130,7 @@ These folks keep the project moving and are resources for help.
* Kevin Partington ([@platinumazure](https://github.com/platinumazure))
* Vitor Balocco ([@vitorbal](https://github.com/vitorbal))
* James Henry ([@JamesHenry](https://github.com/JamesHenry))
* Teddy Katz ([@not-an-aardvark](https://github.com/not-an-aardvark))
* Reyad Attiyat ([@soda0289](https://github.com/soda0289))
## Releases

161
tools/eslint/conf/eslint.json → tools/eslint/conf/eslint-recommended.js

@ -1,7 +1,81 @@
{
"parser": "espree",
"ecmaFeatures": {},
"rules": {
/**
* @fileoverview Configuration applied when a user configuration extends from
* eslint:recommended.
* @author Nicholas C. Zakas
*/
"use strict";
/* eslint sort-keys: ["error", "asc"], quote-props: ["error", "consistent"] */
/* eslint-disable sort-keys */
module.exports = {
parser: "espree",
ecmaFeatures: {},
rules: {
/* eslint-enable sort-keys */
"accessor-pairs": "off",
"array-bracket-spacing": "off",
"array-callback-return": "off",
"arrow-body-style": "off",
"arrow-parens": "off",
"arrow-spacing": "off",
"block-scoped-var": "off",
"block-spacing": "off",
"brace-style": "off",
"callback-return": "off",
"camelcase": "off",
"capitalized-comments": "off",
"class-methods-use-this": "off",
"comma-dangle": "off",
"comma-spacing": "off",
"comma-style": "off",
"complexity": "off",
"computed-property-spacing": "off",
"consistent-return": "off",
"consistent-this": "off",
"constructor-super": "error",
"curly": "off",
"default-case": "off",
"dot-location": "off",
"dot-notation": "off",
"eol-last": "off",
"eqeqeq": "off",
"func-call-spacing": "off",
"func-name-matching": "off",
"func-names": "off",
"func-style": "off",
"generator-star-spacing": "off",
"global-require": "off",
"guard-for-in": "off",
"handle-callback-err": "off",
"id-blacklist": "off",
"id-length": "off",
"id-match": "off",
"indent": "off",
"init-declarations": "off",
"jsx-quotes": "off",
"key-spacing": "off",
"keyword-spacing": "off",
"line-comment-position": "off",
"linebreak-style": "off",
"lines-around-comment": "off",
"lines-around-directive": "off",
"max-depth": "off",
"max-len": "off",
"max-lines": "off",
"max-nested-callbacks": "off",
"max-params": "off",
"max-statements": "off",
"max-statements-per-line": "off",
"multiline-ternary": "off",
"new-cap": "off",
"new-parens": "off",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"no-alert": "off",
"no-array-constructor": "off",
"no-await-in-loop": "off",
@ -10,6 +84,7 @@
"no-case-declarations": "error",
"no-catch-shadow": "off",
"no-class-assign": "error",
"no-compare-neg-zero": "off",
"no-cond-assign": "error",
"no-confusing-arrow": "off",
"no-console": "error",
@ -61,6 +136,7 @@
"no-mixed-operators": "off",
"no-mixed-requires": "off",
"no-mixed-spaces-and-tabs": "error",
"no-multi-assign": "off",
"no-multi-spaces": "off",
"no-multi-str": "off",
"no-multiple-empty-lines": "off",
@ -99,20 +175,20 @@
"no-sequences": "off",
"no-shadow": "off",
"no-shadow-restricted-names": "off",
"no-whitespace-before-property": "off",
"no-spaced-func": "off",
"no-sparse-arrays": "error",
"no-sync": "off",
"no-tabs": "off",
"no-template-curly-in-string": "off",
"no-ternary": "off",
"no-trailing-spaces": "off",
"no-this-before-super": "error",
"no-throw-literal": "off",
"no-trailing-spaces": "off",
"no-undef": "error",
"no-undef-init": "off",
"no-undefined": "off",
"no-unexpected-multiline": "error",
"no-underscore-dangle": "off",
"no-unexpected-multiline": "error",
"no-unmodified-loop-condition": "off",
"no-unneeded-ternary": "off",
"no-unreachable": "error",
@ -129,70 +205,12 @@
"no-useless-escape": "off",
"no-useless-rename": "off",
"no-useless-return": "off",
"no-void": "off",
"no-var": "off",
"no-void": "off",
"no-warning-comments": "off",
"no-whitespace-before-property": "off",
"no-with": "off",
"array-bracket-spacing": "off",
"array-callback-return": "off",
"arrow-body-style": "off",
"arrow-parens": "off",
"arrow-spacing": "off",
"accessor-pairs": "off",
"block-scoped-var": "off",
"block-spacing": "off",
"brace-style": "off",
"callback-return": "off",
"camelcase": "off",
"capitalized-comments": "off",
"class-methods-use-this": "off",
"comma-dangle": "off",
"comma-spacing": "off",
"comma-style": "off",
"complexity": "off",
"computed-property-spacing": "off",
"consistent-return": "off",
"consistent-this": "off",
"constructor-super": "error",
"curly": "off",
"default-case": "off",
"dot-location": "off",
"dot-notation": "off",
"eol-last": "off",
"eqeqeq": "off",
"func-call-spacing": "off",
"func-names": "off",
"func-name-matching": "off",
"func-style": "off",
"generator-star-spacing": "off",
"global-require": "off",
"guard-for-in": "off",
"handle-callback-err": "off",
"id-blacklist": "off",
"id-length": "off",
"id-match": "off",
"indent": "off",
"init-declarations": "off",
"jsx-quotes": "off",
"key-spacing": "off",
"keyword-spacing": "off",
"linebreak-style": "off",
"line-comment-position": "off",
"lines-around-comment": "off",
"lines-around-directive": "off",
"max-depth": "off",
"max-len": "off",
"max-lines": "off",
"max-nested-callbacks": "off",
"max-params": "off",
"max-statements": "off",
"max-statements-per-line": "off",
"multiline-ternary": "off",
"new-cap": "off",
"new-parens": "off",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"nonblock-statement-body-position": "off",
"object-curly-newline": "off",
"object-curly-spacing": ["off", "never"],
"object-property-newline": "off",
@ -206,6 +224,7 @@
"prefer-const": "off",
"prefer-destructuring": "off",
"prefer-numeric-literals": "off",
"prefer-promise-reject-errors": "off",
"prefer-reflect": "off",
"prefer-rest-params": "off",
"prefer-spread": "off",
@ -219,8 +238,8 @@
"rest-spread-spacing": "off",
"semi": "off",
"semi-spacing": "off",
"sort-keys": "off",
"sort-imports": "off",
"sort-keys": "off",
"sort-vars": "off",
"space-before-blocks": "off",
"space-before-function-paren": "off",
@ -231,6 +250,7 @@
"strict": "off",
"symbol-description": "off",
"template-curly-spacing": "off",
"template-tag-spacing": "off",
"unicode-bom": "off",
"use-isnan": "error",
"valid-jsdoc": "off",
@ -238,8 +258,7 @@
"vars-on-top": "off",
"wrap-iife": "off",
"wrap-regex": "off",
"no-template-curly-in-string": "off",
"yield-star-spacing": "off",
"yoda": "off"
}
}
};

305
tools/eslint/lib/ast-utils.js

@ -10,7 +10,6 @@
//------------------------------------------------------------------------------
const esutils = require("esutils");
const lodash = require("lodash");
//------------------------------------------------------------------------------
// Helpers
@ -24,6 +23,14 @@ const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/;
const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/;
const thisTagPattern = /^[\s*]*@this/m;
const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/;
const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/;
// A set of node types that can contain a list of statements
const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
/**
* Checks reference if is non initializer and writable.
* @param {Reference} reference - A reference to check.
@ -142,7 +149,7 @@ function isInLoop(node) {
*/
function isNullOrUndefined(node) {
return (
(node.type === "Literal" && node.value === null) ||
module.exports.isNullLiteral(node) ||
(node.type === "Identifier" && node.name === "undefined") ||
(node.type === "UnaryExpression" && node.operator === "void")
);
@ -158,9 +165,9 @@ function isCallee(node) {
}
/**
* Checks whether or not a node is `Reclect.apply`.
* Checks whether or not a node is `Reflect.apply`.
* @param {ASTNode} node - A node to check.
* @returns {boolean} Whether or not the node is a `Reclect.apply`.
* @returns {boolean} Whether or not the node is a `Reflect.apply`.
*/
function isReflectApply(node) {
return (
@ -210,6 +217,15 @@ function isMethodWhichHasThisArg(node) {
return false;
}
/**
* Creates the negate function of the given function.
* @param {Function} f - The function to negate.
* @returns {Function} Negated function.
*/
function negate(f) {
return token => !f(token);
}
/**
* Checks whether or not a node has a `@this` tag in its comments.
* @param {ASTNode} node - A node to check.
@ -247,56 +263,145 @@ function isParenthesised(sourceCode, node) {
}
/**
* Gets the `=>` token of the given arrow function node.
* Checks if the given token is an arrow token or not.
*
* @param {ASTNode} node - The arrow function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {Token} `=>` token.
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is an arrow token.
*/
function getArrowToken(node, sourceCode) {
let token = sourceCode.getTokenBefore(node.body);
function isArrowToken(token) {
return token.value === "=>" && token.type === "Punctuator";
}
while (token.value !== "=>") {
token = sourceCode.getTokenBefore(token);
}
/**
* Checks if the given token is a comma token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a comma token.
*/
function isCommaToken(token) {
return token.value === "," && token.type === "Punctuator";
}
return token;
/**
* Checks if the given token is a semicolon token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a semicolon token.
*/
function isSemicolonToken(token) {
return token.value === ";" && token.type === "Punctuator";
}
/**
* Gets the `(` token of the given function node.
* Checks if the given token is a colon token or not.
*
* @param {ASTNode} node - The function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {Token} `(` token.
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a colon token.
*/
function getOpeningParenOfParams(node, sourceCode) {
let token = node.id ? sourceCode.getTokenAfter(node.id) : sourceCode.getFirstToken(node);
function isColonToken(token) {
return token.value === ":" && token.type === "Punctuator";
}
while (token.value !== "(") {
token = sourceCode.getTokenAfter(token);
}
/**
* Checks if the given token is an opening parenthesis token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is an opening parenthesis token.
*/
function isOpeningParenToken(token) {
return token.value === "(" && token.type === "Punctuator";
}
/**
* Checks if the given token is a closing parenthesis token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a closing parenthesis token.
*/
function isClosingParenToken(token) {
return token.value === ")" && token.type === "Punctuator";
}
return token;
/**
* Checks if the given token is an opening square bracket token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is an opening square bracket token.
*/
function isOpeningBracketToken(token) {
return token.value === "[" && token.type === "Punctuator";
}
const lineIndexCache = new WeakMap();
/**
* Checks if the given token is a closing square bracket token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a closing square bracket token.
*/
function isClosingBracketToken(token) {
return token.value === "]" && token.type === "Punctuator";
}
/**
* Gets the range index for the first character in each of the lines of `sourceCode`.
* @param {SourceCode} sourceCode A sourceCode object
* @returns {number[]} The indices of the first characters in the each of the lines of the code
* Checks if the given token is an opening brace token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is an opening brace token.
*/
function getLineIndices(sourceCode) {
function isOpeningBraceToken(token) {
return token.value === "{" && token.type === "Punctuator";
}
if (!lineIndexCache.has(sourceCode)) {
const lineIndices = (sourceCode.text.match(/[^\r\n\u2028\u2029]*(\r\n|\r|\n|\u2028|\u2029)/g) || [])
.reduce((indices, line) => indices.concat(indices[indices.length - 1] + line.length), [0]);
/**
* Checks if the given token is a closing brace token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a closing brace token.
*/
function isClosingBraceToken(token) {
return token.value === "}" && token.type === "Punctuator";
}
// Store the sourceCode object in a WeakMap to avoid iterating over all of the lines every time a sourceCode object is passed in.
lineIndexCache.set(sourceCode, lineIndices);
}
return lineIndexCache.get(sourceCode);
/**
* Checks if the given token is a comment token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a comment token.
*/
function isCommentToken(token) {
return token.type === "Line" || token.type === "Block" || token.type === "Shebang";
}
/**
* Checks if the given token is a keyword token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a keyword token.
*/
function isKeywordToken(token) {
return token.type === "Keyword";
}
/**
* Gets the `(` token of the given function node.
*
* @param {ASTNode} node - The function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {Token} `(` token.
*/
function getOpeningParenOfParams(node, sourceCode) {
return node.id
? sourceCode.getTokenAfter(node.id, isOpeningParenToken)
: sourceCode.getFirstToken(node, isOpeningParenToken);
}
/**
* Creates a version of the LINEBREAK_MATCHER regex with the global flag.
* Global regexes are mutable, so this needs to be a function instead of a constant.
* @returns {RegExp} A global regular expression that matches line terminators
*/
function createGlobalLinebreakMatcher() {
return new RegExp(LINEBREAK_MATCHER.source, "g");
}
//------------------------------------------------------------------------------
@ -304,6 +409,10 @@ function getLineIndices(sourceCode) {
//------------------------------------------------------------------------------
module.exports = {
COMMENTS_IGNORE_PATTERN,
LINEBREAKS,
LINEBREAK_MATCHER,
STATEMENT_LIST_PARENTS,
/**
* Determines whether two adjacent tokens are on the same line.
@ -325,6 +434,29 @@ module.exports = {
isInLoop,
isArrayFromMethod,
isParenthesised,
createGlobalLinebreakMatcher,
isArrowToken,
isClosingBraceToken,
isClosingBracketToken,
isClosingParenToken,
isColonToken,
isCommaToken,
isCommentToken,
isKeywordToken,
isNotClosingBraceToken: negate(isClosingBraceToken),
isNotClosingBracketToken: negate(isClosingBracketToken),
isNotClosingParenToken: negate(isClosingParenToken),
isNotColonToken: negate(isColonToken),
isNotCommaToken: negate(isCommaToken),
isNotOpeningBraceToken: negate(isOpeningBraceToken),
isNotOpeningBracketToken: negate(isOpeningBracketToken),
isNotOpeningParenToken: negate(isOpeningParenToken),
isNotSemicolonToken: negate(isSemicolonToken),
isOpeningBraceToken,
isOpeningBracketToken,
isOpeningParenToken,
isSemicolonToken,
/**
* Checks whether or not a given node is a string literal.
@ -658,6 +790,8 @@ module.exports = {
case "/":
case "%":
return 13;
case "**":
return 15;
// no default
}
@ -666,10 +800,10 @@ module.exports = {
case "UnaryExpression":
case "AwaitExpression":
return 14;
return 16;
case "UpdateExpression":
return 15;
return 17;
case "CallExpression":
@ -677,14 +811,14 @@ module.exports = {
if (node.callee.type === "FunctionExpression") {
return -1;
}
return 16;
return 18;
case "NewExpression":
return 17;
return 19;
// no default
}
return 18;
return 20;
},
/**
@ -1023,7 +1157,7 @@ module.exports = {
let end = null;
if (node.type === "ArrowFunctionExpression") {
const arrowToken = getArrowToken(node, sourceCode);
const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken);
start = arrowToken.loc.start;
end = arrowToken.loc.end;
@ -1037,42 +1171,10 @@ module.exports = {
return {
start: Object.assign({}, start),
end: Object.assign({}, end),
end: Object.assign({}, end)
};
},
/*
* Converts a range index into a (line, column) pair.
* @param {SourceCode} sourceCode A SourceCode object
* @param {number} rangeIndex The range index of a character in a file
* @returns {Object} A {line, column} location object with a 0-indexed column
*/
getLocationFromRangeIndex(sourceCode, rangeIndex) {
const lineIndices = getLineIndices(sourceCode);
/*
* lineIndices is a sorted list of indices of the first character of each line.
* To figure out which line rangeIndex is on, determine the last index at which rangeIndex could
* be inserted into lineIndices to keep the list sorted.
*/
const lineNumber = lodash.sortedLastIndex(lineIndices, rangeIndex);
return { line: lineNumber, column: rangeIndex - lineIndices[lineNumber - 1] };
},
/**
* Converts a (line, column) pair into a range index.
* @param {SourceCode} sourceCode A SourceCode object
* @param {Object} loc A line/column location
* @param {number} loc.line The line number of the location (1-indexed)
* @param {number} loc.column The column number of the location (0-indexed)
* @returns {number} The range index of the location in the file.
*/
getRangeIndexFromLocation(sourceCode, loc) {
return getLineIndices(sourceCode)[loc.line - 1] + loc.column;
},
/**
* Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses
* surrounding the node.
@ -1097,5 +1199,58 @@ module.exports = {
}
return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]);
},
/*
* Determine if a node has a possiblity to be an Error object
* @param {ASTNode} node ASTNode to check
* @returns {boolean} True if there is a chance it contains an Error obj
*/
couldBeError(node) {
switch (node.type) {
case "Identifier":
case "CallExpression":
case "NewExpression":
case "MemberExpression":
case "TaggedTemplateExpression":
case "YieldExpression":
case "AwaitExpression":
return true; // possibly an error object.
case "AssignmentExpression":
return module.exports.couldBeError(node.right);
case "SequenceExpression": {
const exprs = node.expressions;
return exprs.length !== 0 && module.exports.couldBeError(exprs[exprs.length - 1]);
}
case "LogicalExpression":
return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
case "ConditionalExpression":
return module.exports.couldBeError(node.consequent) || module.exports.couldBeError(node.alternate);
default:
return false;
}
},
/**
* Determines whether the given node is a `null` literal.
* @param {ASTNode} node The node to check
* @returns {boolean} `true` if the node is a `null` literal
*/
isNullLiteral(node) {
/*
* Checking `node.value === null` does not guarantee that a literal is a null literal.
* When parsing values that cannot be represented in the current environment (e.g. unicode
* regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
* set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
* `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
*/
return node.type === "Literal" && node.value === null && !node.regex;
}
};

4
tools/eslint/lib/cli.js

@ -188,9 +188,9 @@ const cli = {
}
return (report.errorCount || tooManyWarnings) ? 1 : 0;
} else {
return 1;
}
return 1;
}

9
tools/eslint/lib/code-path-analysis/code-path-analyzer.js

@ -512,13 +512,8 @@ function processCodePathToExit(analyzer, node) {
break;
}
/*
* Skip updating the current segment to avoid creating useless segments if
* the node type is the same as the parent node type.
*/
if (!dontForward && (!node.parent || node.type !== node.parent.type)) {
// Emits onCodePathSegmentStart events if updated.
// Emits onCodePathSegmentStart events if updated.
if (!dontForward) {
forwardCurrentToHead(analyzer, node);
}
debug.dumpState(node, state, true);

4
tools/eslint/lib/code-path-analysis/code-path-state.js

@ -467,8 +467,8 @@ class CodePathState {
* Creates the next path from own true/false fork context.
*/
const prevForkContext =
context.kind === "&&" ? context.trueForkContext :
/* kind === "||" */ context.falseForkContext;
context.kind === "&&" ? context.trueForkContext
/* kind === "||" */ : context.falseForkContext;
forkContext.replaceHead(prevForkContext.makeNext(0, -1));
prevForkContext.clear();

33
tools/eslint/lib/code-path-analysis/debug-helpers.js

@ -107,22 +107,23 @@ module.exports = {
text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
}
if (segment.internal.nodes.length > 0) {
text += segment.internal.nodes.map(node => {
switch (node.type) {
case "Identifier": return `${node.type} (${node.name})`;
case "Literal": return `${node.type} (${node.value})`;
default: return node.type;
}
}).join("\\n");
} else if (segment.internal.exitNodes.length > 0) {
text += segment.internal.exitNodes.map(node => {
switch (node.type) {
case "Identifier": return `${node.type}:exit (${node.name})`;
case "Literal": return `${node.type}:exit (${node.value})`;
default: return `${node.type}:exit`;
}
}).join("\\n");
if (segment.internal.nodes.length > 0 || segment.internal.exitNodes.length > 0) {
text += [].concat(
segment.internal.nodes.map(node => {
switch (node.type) {
case "Identifier": return `${node.type} (${node.name})`;
case "Literal": return `${node.type} (${node.value})`;
default: return node.type;
}
}),
segment.internal.exitNodes.map(node => {
switch (node.type) {
case "Identifier": return `${node.type}:exit (${node.name})`;
case "Literal": return `${node.type}:exit (${node.value})`;
default: return `${node.type}:exit`;
}
})
).join("\\n");
} else {
text += "????";
}

5
tools/eslint/lib/config.js

@ -234,8 +234,9 @@ class Config {
}
/**
* Build a config object merging the base config (conf/eslint.json), the
* environments config (conf/environments.js) and eventually the user config.
* Build a config object merging the base config (conf/eslint-recommended),
* the environments config (conf/environments.js) and eventually the user
* config.
* @param {string} filePath a file in whose directory we start looking for a local config
* @returns {Object} config object
*/

44
tools/eslint/lib/config/autoconfig.js

@ -13,7 +13,7 @@ const lodash = require("lodash"),
eslint = require("../eslint"),
configRule = require("./config-rule"),
ConfigOps = require("./config-ops"),
recConfig = require("../../conf/eslint.json");
recConfig = require("../../conf/eslint-recommended");
const debug = require("debug")("eslint:autoconfig");
@ -65,17 +65,16 @@ function makeRegistryItems(rulesConfig) {
* Unless a rulesConfig is provided at construction, the registry will not contain
* any rules, only methods. This will be useful for building up registries manually.
*
* @constructor
* @class Registry
* @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations
* Registry class
*/
function Registry(rulesConfig) {
this.rules = (rulesConfig) ? makeRegistryItems(rulesConfig) : {};
}
Registry.prototype = {
class Registry {
constructor: Registry,
/**
* @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations
*/
constructor(rulesConfig) {
this.rules = (rulesConfig) ? makeRegistryItems(rulesConfig) : {};
}
/**
* Populate the registry with core rule configs.
@ -89,7 +88,7 @@ Registry.prototype = {
const rulesConfig = configRule.createCoreRuleConfigs();
this.rules = makeRegistryItems(rulesConfig);
},
}
/**
* Creates sets of rule configurations which can be used for linting
@ -156,7 +155,7 @@ Registry.prototype = {
}
return ruleSets;
},
}
/**
* Remove all items from the registry with a non-zero number of errors
@ -182,7 +181,7 @@ Registry.prototype = {
});
return newRegistry;
},
}
/**
* Removes rule configurations which were not included in a ruleSet
@ -199,7 +198,7 @@ Registry.prototype = {
});
return newRegistry;
},
}
/**
* Creates a registry of rules which had no error-free configs.
@ -221,7 +220,7 @@ Registry.prototype = {
});
return failingRegistry;
},
}
/**
* Create an eslint config for any rules which only have one configuration
@ -240,7 +239,7 @@ Registry.prototype = {
});
return config;
},
}
/**
* Return a cloned registry containing only configs with a desired specificity
@ -258,7 +257,7 @@ Registry.prototype = {
});
return newRegistry;
},
}
/**
* Lint SourceCodes against all configurations in the registry, and record results
@ -297,8 +296,13 @@ Registry.prototype = {
// It is possible that the error is from a configuration comment
// in a linted file, in which case there may not be a config
// set in this ruleSetIdx. (https://github.com/eslint/eslint/issues/5992)
if (lintedRegistry.rules[result.ruleId][ruleSetIdx]) {
// set in this ruleSetIdx.
// (https://github.com/eslint/eslint/issues/5992)
// (https://github.com/eslint/eslint/issues/7860)
if (
lintedRegistry.rules[result.ruleId] &&
lintedRegistry.rules[result.ruleId][ruleSetIdx]
) {
lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
}
});
@ -316,7 +320,7 @@ Registry.prototype = {
return lintedRegistry;
}
};
}
/**
* Extract rule configuration into eslint:recommended where possible.

127
tools/eslint/lib/config/config-file.js

@ -23,7 +23,7 @@ const fs = require("fs"),
stripBom = require("strip-bom"),
stripComments = require("strip-json-comments"),
stringify = require("json-stable-stringify"),
defaultOptions = require("../../conf/eslint.json"),
defaultOptions = require("../../conf/eslint-recommended"),
requireUncached = require("require-uncached");
const debug = require("debug")("eslint:config-file");
@ -183,6 +183,22 @@ function loadPackageJSONConfigFile(filePath) {
}
}
/**
* Creates an error to notify about a missing config to extend from.
* @param {string} configName The name of the missing config.
* @returns {Error} The error object to throw
* @private
*/
function configMissingError(configName) {
const error = new Error(`Failed to load config "${configName}" to extend from.`);
error.messageTemplate = "extend-config-missing";
error.messageData = {
configName
};
return error;
}
/**
* Loads a configuration file regardless of the source. Inspects the file path
* to determine the correctly way to load the config file.
@ -199,6 +215,9 @@ function loadConfigFile(file) {
config = loadJSConfigFile(filePath);
if (file.configName) {
config = config.configs[file.configName];
if (!config) {
throw configMissingError(file.configFullName);
}
}
break;
@ -340,6 +359,33 @@ function getLookupPath(configFilePath) {
return path.join(basedir, "node_modules");
}
/**
* Resolves a eslint core config path
* @param {string} name The eslint config name.
* @returns {string} The resolved path of the config.
* @private
*/
function getEslintCoreConfigPath(name) {
if (name === "eslint:recommended") {
/*
* Add an explicit substitution for eslint:recommended to
* conf/eslint-recommended.js.
*/
return path.resolve(__dirname, "../../conf/eslint-recommended.js");
}
if (name === "eslint:all") {
/*
* Add an explicit substitution for eslint:all to conf/eslint-all.js
*/
return path.resolve(__dirname, "../../conf/eslint-all.js");
}
throw configMissingError(name);
}
/**
* Applies values from the "extends" field in a configuration file.
* @param {Object} config The configuration information.
@ -360,33 +406,20 @@ function applyExtends(config, filePath, relativeTo) {
// Make the last element in an array take the highest precedence
config = configExtends.reduceRight((previousValue, parentPath) => {
if (parentPath === "eslint:recommended") {
/*
* Add an explicit substitution for eslint:recommended to conf/eslint.json
* this lets us use the eslint.json file as the recommended rules
*/
parentPath = path.resolve(__dirname, "../../conf/eslint.json");
} else if (parentPath === "eslint:all") {
/*
* Add an explicit substitution for eslint:all to conf/eslint-all.js
*/
parentPath = path.resolve(__dirname, "../../conf/eslint-all.js");
} else if (isFilePath(parentPath)) {
/*
* If the `extends` path is relative, use the directory of the current configuration
* file as the reference point. Otherwise, use as-is.
*/
parentPath = (!path.isAbsolute(parentPath) ?
path.join(relativeTo || path.dirname(filePath), parentPath) :
parentPath
);
}
try {
if (parentPath.startsWith("eslint:")) {
parentPath = getEslintCoreConfigPath(parentPath);
} else if (isFilePath(parentPath)) {
/*
* If the `extends` path is relative, use the directory of the current configuration
* file as the reference point. Otherwise, use as-is.
*/
parentPath = (path.isAbsolute(parentPath)
? parentPath
: path.join(relativeTo || path.dirname(filePath), parentPath)
);
}
debug(`Loading ${parentPath}`);
return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue);
} catch (e) {
@ -455,30 +488,34 @@ function normalizePackageName(name, prefix) {
* or package name.
* @param {string} filePath The filepath to resolve.
* @param {string} [relativeTo] The path to resolve relative to.
* @returns {Object} A path that can be used directly to load the configuration.
* @returns {Object} An object containing 3 properties:
* - 'filePath' (required) the resolved path that can be used directly to load the configuration.
* - 'configName' the name of the configuration inside the plugin.
* - 'configFullName' the name of the configuration as used in the eslint config (e.g. 'plugin:node/recommended').
* @private
*/
function resolve(filePath, relativeTo) {
if (isFilePath(filePath)) {
return { filePath: path.resolve(relativeTo || "", filePath) };
} else {
let normalizedPackageName;
if (filePath.indexOf("plugin:") === 0) {
const packagePath = filePath.substr(7, filePath.lastIndexOf("/") - 7);
const configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1);
normalizedPackageName = normalizePackageName(packagePath, "eslint-plugin");
debug(`Attempting to resolve ${normalizedPackageName}`);
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
return { filePath, configName };
} else {
normalizedPackageName = normalizePackageName(filePath, "eslint-config");
debug(`Attempting to resolve ${normalizedPackageName}`);
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
return { filePath };
}
}
let normalizedPackageName;
if (filePath.startsWith("plugin:")) {
const configFullName = filePath;
const pluginName = filePath.substr(7, filePath.lastIndexOf("/") - 7);
const configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1);
normalizedPackageName = normalizePackageName(pluginName, "eslint-plugin");
debug(`Attempting to resolve ${normalizedPackageName}`);
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
return { filePath, configName, configFullName };
}
normalizedPackageName = normalizePackageName(filePath, "eslint-config");
debug(`Attempting to resolve ${normalizedPackageName}`);
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
return { filePath };
}

4
tools/eslint/lib/config/config-initializer.js

@ -17,7 +17,7 @@ const util = require("util"),
ConfigOps = require("./config-ops"),
getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles,
npmUtil = require("../util/npm-util"),
recConfig = require("../../conf/eslint.json"),
recConfig = require("../../conf/eslint-recommended"),
log = require("../logging");
const debug = require("debug")("eslint:config-initializer");
@ -317,7 +317,7 @@ function promptUser(callback) {
default: false,
when(answers) {
return answers.styleguide === "airbnb";
},
}
},
{
type: "input",

55
tools/eslint/lib/config/config-rule.js

@ -176,22 +176,21 @@ function combinePropertyObjects(objArr1, objArr2) {
*
* ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]]
*
* @param {ruleConfig[]} configs Valid rule configurations
* @constructor
* Rule configuration set class
*/
function RuleConfigSet(configs) {
class RuleConfigSet {
/**
* Stored valid rule configurations for this instance
* @type {array}
*/
this.ruleConfigs = configs || [];
}
RuleConfigSet.prototype = {
constructor: RuleConfigSet,
* @param {ruleConfig[]} configs Valid rule configurations
*/
constructor(configs) {
/**
* Stored valid rule configurations for this instance
* @type {array}
*/
this.ruleConfigs = configs || [];
}
/**
* Add a severity level to the front of all configs in the instance.
@ -210,7 +209,7 @@ RuleConfigSet.prototype = {
// Add a single config at the beginning consisting of only the severity
this.ruleConfigs.unshift(severity);
},
}
/**
* Add rule configs from an array of strings (schema enums)
@ -219,12 +218,12 @@ RuleConfigSet.prototype = {
*/
addEnums(enums) {
this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, enums));
},
}
/**
* Add rule configurations from a schema object
* @param {Object} obj Schema item with type === "object"
* @returns {void}
* @returns {boolean} true if at least one schema for the object could be generated, false otherwise
*/
addObject(obj) {
const objectConfigSet = {
@ -259,9 +258,12 @@ RuleConfigSet.prototype = {
if (objectConfigSet.objectConfigs.length > 0) {
this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, objectConfigSet.objectConfigs));
return true;
}
return false;
}
};
}
/**
* Generate valid rule configurations based on a schema object
@ -272,20 +274,21 @@ function generateConfigsFromSchema(schema) {
const configSet = new RuleConfigSet();
if (Array.isArray(schema)) {
schema.forEach(opt => {
for (const opt of schema) {
if (opt.enum) {
configSet.addEnums(opt.enum);
}
if (opt.type && opt.type === "object") {
configSet.addObject(opt);
}
} else if (opt.type && opt.type === "object") {
if (!configSet.addObject(opt)) {
break;
}
if (opt.oneOf) {
// TODO (IanVS): support oneOf
} else {
// TODO (IanVS): not yet implemented
// If we don't know how to fill in this option, don't fill in any of the following options.
break;
}
});
}
}
configSet.addErrorSeverity();
return configSet.ruleConfigs;

12
tools/eslint/lib/config/config-validator.js

@ -40,13 +40,13 @@ function getRuleOptionsSchema(id) {
minItems: 0,
maxItems: schema.length
};
} else {
return {
type: "array",
minItems: 0,
maxItems: 0
};
}
return {
type: "array",
minItems: 0,
maxItems: 0
};
}
// Given a full schema, leave it alone

27
tools/eslint/lib/config/plugins.js

@ -127,14 +127,25 @@ module.exports = {
if (!plugins[shortName]) {
try {
plugin = require(longName);
} catch (err) {
debug(`Failed to load plugin ${longName}.`);
err.message = `Failed to load plugin ${pluginName}: ${err.message}`;
err.messageTemplate = "plugin-missing";
err.messageData = {
pluginName: longName
};
throw err;
} catch (pluginLoadErr) {
try {
// Check whether the plugin exists
require.resolve(longName);
} catch (missingPluginErr) {
// If the plugin can't be resolved, display the missing plugin error (usually a config or install error)
debug(`Failed to load plugin ${longName}.`);
missingPluginErr.message = `Failed to load plugin ${pluginName}: ${missingPluginErr.message}`;
missingPluginErr.messageTemplate = "plugin-missing";
missingPluginErr.messageData = {
pluginName: longName
};
throw missingPluginErr;
}
// Otherwise, the plugin exists and is throwing on module load for some reason, so print the stack trace.
throw pluginLoadErr;
}
this.define(pluginName, plugin);

57
tools/eslint/lib/eslint.js

@ -14,7 +14,7 @@ const assert = require("assert"),
escope = require("escope"),
levn = require("levn"),
blankScriptAST = require("../conf/blank-script.json"),
DEFAULT_PARSER = require("../conf/eslint.json").parser,
DEFAULT_PARSER = require("../conf/eslint-recommended").parser,
replacements = require("../conf/replacements.json"),
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
ConfigOps = require("./config/config-ops"),
@ -528,9 +528,9 @@ function createStubRule(message) {
if (message) {
return createRuleModule;
} else {
throw new Error("No message passed to stub rule");
}
throw new Error("No message passed to stub rule");
}
/**
@ -660,9 +660,9 @@ module.exports = (function() {
try {
if (typeof parser.parseForESLint === "function") {
return parser.parseForESLint(text, parserOptions);
} else {
return parser.parse(text, parserOptions);
}
return parser.parse(text, parserOptions);
} catch (ex) {
// If the message includes a leading line number, strip it:
@ -695,9 +695,9 @@ module.exports = (function() {
return ruleConfig;
} else if (Array.isArray(ruleConfig)) {
return ruleConfig[0];
} else {
return 0;
}
return 0;
}
/**
@ -708,9 +708,9 @@ module.exports = (function() {
function getRuleOptions(ruleConfig) {
if (Array.isArray(ruleConfig)) {
return ruleConfig.slice(1);
} else {
return [];
}
return [];
}
// set unlimited listeners (see https://github.com/eslint/eslint/issues/524)
@ -778,17 +778,18 @@ module.exports = (function() {
// search and apply "eslint-env *".
const envInFile = findEslintEnv(text || textOrSourceCode.text);
config = Object.assign({}, config);
if (envInFile) {
if (!config || !config.env) {
config = Object.assign({}, config || {}, { env: envInFile });
} else {
config = Object.assign({}, config);
if (config.env) {
config.env = Object.assign({}, config.env, envInFile);
} else {
config.env = envInFile;
}
}
// process initial config to make it safe to extend
config = prepareConfig(config || {});
config = prepareConfig(config);
// only do this for text
if (text !== null) {
@ -864,14 +865,14 @@ module.exports = (function() {
(parseResult && parseResult.services ? parseResult.services : {})
);
const rule = ruleCreator.create ? ruleCreator.create(ruleContext) :
ruleCreator(ruleContext);
const rule = ruleCreator.create ? ruleCreator.create(ruleContext)
: ruleCreator(ruleContext);
// add all the node types as listeners
Object.keys(rule).forEach(nodeType => {
api.on(nodeType, timing.enabled
? timing.time(key, rule[nodeType])
: rule[nodeType]
// add all the selectors from the rule as listeners
Object.keys(rule).forEach(selector => {
api.on(selector, timing.enabled
? timing.time(key, rule[selector])
: rule[selector]
);
});
} catch (ex) {
@ -939,9 +940,9 @@ module.exports = (function() {
if (lineDiff === 0) {
return a.column - b.column;
} else {
return lineDiff;
}
return lineDiff;
});
return messages;
@ -1109,9 +1110,9 @@ module.exports = (function() {
if (scope) {
if (scope.type === "function-expression-name") {
return scope.childScopes[0];
} else {
return scope;
}
return scope;
}
}
@ -1161,9 +1162,9 @@ module.exports = (function() {
api.getFilename = function() {
if (typeof currentFilename === "string") {
return currentFilename;
} else {
return "<input>";
}
return "<input>";
};
/**
@ -1192,7 +1193,7 @@ module.exports = (function() {
* @returns {Object} Object mapping rule IDs to their default configurations
*/
api.defaults = function() {
return require("../conf/eslint.json");
return require("../conf/eslint-recommended");
};
/**

4
tools/eslint/lib/formatters/checkstyle.js

@ -19,9 +19,9 @@ const xmlEscape = require("../util/xml-escape");
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "error";
} else {
return "warning";
}
return "warning";
}
//------------------------------------------------------------------------------

15
tools/eslint/lib/formatters/codeframe.js

@ -56,7 +56,7 @@ function formatMessage(message, parentResult) {
`${type}:`,
`${msg}`,
ruleId ? `${ruleId}` : "",
sourceCode ? `at ${filePath}:` : `at ${filePath}`,
sourceCode ? `at ${filePath}:` : `at ${filePath}`
].filter(String).join(" ");
const result = [firstLine];
@ -101,15 +101,10 @@ module.exports = function(results) {
const resultsWithMessages = results.filter(result => result.messages.length > 0);
let output = resultsWithMessages.reduce((resultsOutput, result) => {
const messages = result.messages.map(message => {
if (message.fatal || message.severity === 2) {
errors++;
} else {
warnings++;
}
return `${formatMessage(message, result)}\n\n`;
});
const messages = result.messages.map(message => `${formatMessage(message, result)}\n\n`);
errors += result.errorCount;
warnings += result.warningCount;
return resultsOutput.concat(messages);
}, []).join("\n");

4
tools/eslint/lib/formatters/compact.js

@ -17,9 +17,9 @@
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "Error";
} else {
return "Warning";
}
return "Warning";
}

4
tools/eslint/lib/formatters/junit.js

@ -19,9 +19,9 @@ const xmlEscape = require("../util/xml-escape");
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "Error";
} else {
return "Warning";
}
return "Warning";
}
//------------------------------------------------------------------------------

9
tools/eslint/lib/formatters/stylish.js

@ -28,7 +28,6 @@ function pluralize(word, count) {
module.exports = function(results) {
let output = "\n",
total = 0,
errors = 0,
warnings = 0,
summaryColor = "yellow";
@ -40,7 +39,9 @@ module.exports = function(results) {
return;
}
total += messages.length;
errors += result.errorCount;
warnings += result.warningCount;
output += `${chalk.underline(result.filePath)}\n`;
output += `${table(
@ -50,10 +51,8 @@ module.exports = function(results) {
if (message.fatal || message.severity === 2) {
messageType = chalk.red("error");
summaryColor = "red";
errors++;
} else {
messageType = chalk.yellow("warning");
warnings++;
}
return [
@ -74,6 +73,8 @@ module.exports = function(results) {
).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
});
const total = errors + warnings;
if (total > 0) {
output += chalk[summaryColor].bold([
"\u2716 ", total, pluralize(" problem", total),

4
tools/eslint/lib/formatters/tap.js

@ -18,9 +18,9 @@ const yaml = require("js-yaml");
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "error";
} else {
return "warning";
}
return "warning";
}
/**

4
tools/eslint/lib/formatters/unix.js

@ -16,9 +16,9 @@
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "Error";
} else {
return "Warning";
}
return "Warning";
}

4
tools/eslint/lib/formatters/visualstudio.js

@ -18,9 +18,9 @@
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "error";
} else {
return "warning";
}
return "warning";
}

6
tools/eslint/lib/ignored-paths.js

@ -201,6 +201,12 @@ class IgnoredPaths {
const ig = ignore().add(DEFAULT_IGNORE_DIRS);
if (this.options.dotfiles !== true) {
// Ignore hidden folders. (This cannot be ".*", or else it's not possible to unignore hidden files)
ig.add([".*/*", "!../"]);
}
if (this.options.ignore) {
ig.add(this.ig.custom);
}

2
tools/eslint/lib/internal-rules/internal-consistent-docs-description.js

@ -71,7 +71,7 @@ function checkMetaDocsDescription(context, exportsNode) {
if (description === "") {
context.report({
node: metaDocsDescription.value,
message: "`meta.docs.description` should not be empty.",
message: "`meta.docs.description` should not be empty."
});
return;
}

42
tools/eslint/lib/internal-rules/internal-no-invalid-meta.js

@ -94,16 +94,6 @@ function hasMetaSchema(metaPropertyNode) {
return getPropertyFromObject("schema", metaPropertyNode.value);
}
/**
* Whether this `meta` ObjectExpression has a `fixable` property defined or not.
*
* @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
* @returns {boolean} `true` if a `fixable` property exists.
*/
function hasMetaFixable(metaPropertyNode) {
return getPropertyFromObject("fixable", metaPropertyNode.value);
}
/**
* Checks the validity of the meta definition of this rule and reports any errors found.
*
@ -112,7 +102,7 @@ function hasMetaFixable(metaPropertyNode) {
* @param {boolean} ruleIsFixable whether the rule is fixable or not.
* @returns {void}
*/
function checkMetaValidity(context, exportsNode, ruleIsFixable) {
function checkMetaValidity(context, exportsNode) {
const metaProperty = getMetaPropertyFromExportsNode(exportsNode);
if (!metaProperty) {
@ -142,11 +132,6 @@ function checkMetaValidity(context, exportsNode, ruleIsFixable) {
if (!hasMetaSchema(metaProperty)) {
context.report(metaProperty, "Rule is missing a meta.schema property.");
return;
}
if (ruleIsFixable && !hasMetaFixable(metaProperty)) {
context.report(metaProperty, "Rule is fixable, but is missing a meta.fixable property.");
}
}
@ -177,7 +162,6 @@ module.exports = {
create(context) {
let exportsNode;
let ruleIsFixable = false;
return {
AssignmentExpression(node) {
@ -191,35 +175,13 @@ module.exports = {
}
},
CallExpression(node) {
// If the rule has a call for `context.report` and a property `fix`
// is being passed in, then we consider that the rule is fixable.
//
// Note that we only look for context.report() calls in the new
// style (with single MessageDescriptor argument), because only
// calls in the new style can specify a fix.
if (node.callee.type === "MemberExpression" &&
node.callee.object.type === "Identifier" &&
node.callee.object.name === "context" &&
node.callee.property.type === "Identifier" &&
node.callee.property.name === "report" &&
node.arguments.length === 1 &&
node.arguments[0].type === "ObjectExpression") {
if (getPropertyFromObject("fix", node.arguments[0])) {
ruleIsFixable = true;
}
}
},
"Program:exit"() {
if (!isCorrectExportsFormat(exportsNode)) {
context.report({ node: exportsNode, message: "Rule does not export an Object. Make sure the rule follows the new rule format." });
return;
}
checkMetaValidity(context, exportsNode, ruleIsFixable);
checkMetaValidity(context, exportsNode);
}
};
}

4
tools/eslint/lib/rule-context.js

@ -8,7 +8,7 @@
// Requirements
//------------------------------------------------------------------------------
const RuleFixer = require("./util/rule-fixer");
const ruleFixer = require("./util/rule-fixer");
//------------------------------------------------------------------------------
// Constants
@ -124,7 +124,7 @@ class RuleContext {
// if there's a fix specified, get it
if (typeof descriptor.fix === "function") {
fix = descriptor.fix(new RuleFixer());
fix = descriptor.fix(ruleFixer);
}
this.eslint.report(

4
tools/eslint/lib/rules.js

@ -70,9 +70,9 @@ function importPlugin(plugin, pluginName) {
function getHandler(ruleId) {
if (typeof rules[ruleId] === "string") {
return require(rules[ruleId]);
} else {
return rules[ruleId];
}
return rules[ruleId];
}
/**

20
tools/eslint/lib/rules/array-callback-return.js

@ -9,6 +9,8 @@
// Requirements
//------------------------------------------------------------------------------
const lodash = require("lodash");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
@ -147,7 +149,8 @@ module.exports = {
upper: null,
codePath: null,
hasReturn: false,
shouldCheck: false
shouldCheck: false,
node: null
};
/**
@ -168,8 +171,11 @@ module.exports = {
node,
loc: getLocation(node, context.getSourceCode()).loc.start,
message: funcInfo.hasReturn
? "Expected to return a value at the end of this function."
: "Expected to return a value in this function."
? "Expected to return a value at the end of {{name}}."
: "Expected to return a value in {{name}}.",
data: {
name: astUtils.getFunctionNameWithKind(funcInfo.node)
}
});
}
}
@ -187,7 +193,8 @@ module.exports = {
node.body.type === "BlockStatement" &&
isCallbackOfArrayMethod(node) &&
!node.async &&
!node.generator
!node.generator,
node
};
},
@ -204,7 +211,10 @@ module.exports = {
if (!node.argument) {
context.report({
node,
message: "Expected a return value."
message: "{{name}} expected a return value.",
data: {
name: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
}
});
}
}

15
tools/eslint/lib/rules/arrow-body-style.js

@ -4,6 +4,12 @@
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -94,7 +100,7 @@ module.exports = {
const firstValueToken = sourceCode.getTokenAfter(returnKeyword);
let lastValueToken = sourceCode.getLastToken(blockBody[0]);
if (lastValueToken.type === "Punctuator" && lastValueToken.value === ";") {
if (astUtils.isSemicolonToken(lastValueToken)) {
/* The last token of the returned value is the last token of the ReturnExpression (if
* the ReturnExpression has no semicolon), or the second-to-last token (if the ReturnExpression
@ -114,7 +120,7 @@ module.exports = {
const textBeforeReturn = sourceText.slice(arrowBody.range[0] + 1, returnKeyword.range[0]);
const textBetweenReturnAndValue = sourceText.slice(returnKeyword.range[1], firstValueToken.range[0]);
const rawReturnValueText = sourceText.slice(firstValueToken.range[0], lastValueToken.range[1]);
const returnValueText = firstValueToken.value === "{" ? `(${rawReturnValueText})` : rawReturnValueText;
const returnValueText = astUtils.isOpeningBraceToken(firstValueToken) ? `(${rawReturnValueText})` : rawReturnValueText;
const textAfterValue = sourceText.slice(lastValueToken.range[1], blockBody[0].range[1] - 1);
const textAfterReturnStatement = sourceText.slice(blockBody[0].range[1], arrowBody.range[1] - 1);
@ -136,10 +142,7 @@ module.exports = {
loc: arrowBody.loc.start,
message: "Expected block statement surrounding arrow body.",
fix(fixer) {
const lastTokenBeforeBody = sourceCode.getTokensBetween(sourceCode.getFirstToken(node), arrowBody)
.reverse()
.find(token => token.value !== "(");
const lastTokenBeforeBody = sourceCode.getLastTokenBetween(sourceCode.getFirstToken(node), arrowBody, astUtils.isNotOpeningParenToken);
const firstBodyToken = sourceCode.getTokenAfter(lastTokenBeforeBody);
return fixer.replaceTextRange(

12
tools/eslint/lib/rules/arrow-parens.js

@ -4,6 +4,12 @@
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -62,7 +68,7 @@ module.exports = {
node.body.type !== "BlockStatement" &&
!node.returnType
) {
if (token.type === "Punctuator" && token.value === "(") {
if (astUtils.isOpeningParenToken(token)) {
context.report({
node,
message: requireForBlockBodyMessage,
@ -84,7 +90,7 @@ module.exports = {
requireForBlockBody &&
node.body.type === "BlockStatement"
) {
if (token.type !== "Punctuator" || token.value !== "(") {
if (!astUtils.isOpeningParenToken(token)) {
context.report({
node,
message: requireForBlockBodyNoParensMessage,
@ -103,7 +109,7 @@ module.exports = {
!node.params[0].typeAnnotation &&
!node.returnType
) {
if (token.type === "Punctuator" && token.value === "(") {
if (astUtils.isOpeningParenToken(token)) {
context.report({
node,
message: asNeededMessage,

13
tools/eslint/lib/rules/arrow-spacing.js

@ -4,6 +4,12 @@
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -51,12 +57,7 @@ module.exports = {
* @returns {Object} Tokens of arrow and before/after arrow.
*/
function getTokens(node) {
let arrow = sourceCode.getTokenBefore(node.body);
// skip '(' tokens.
while (arrow.value !== "=>") {
arrow = sourceCode.getTokenBefore(arrow);
}
const arrow = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
return {
before: sourceCode.getTokenBefore(arrow),

4
tools/eslint/lib/rules/block-spacing.js

@ -74,8 +74,8 @@ module.exports = {
// Gets braces and the first/last token of content.
const openBrace = getOpenBrace(node);
const closeBrace = sourceCode.getLastToken(node);
const firstToken = sourceCode.getTokenOrCommentAfter(openBrace);
const lastToken = sourceCode.getTokenOrCommentBefore(closeBrace);
const firstToken = sourceCode.getTokenAfter(openBrace, { includeComments: true });
const lastToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
// Skip if the node is invalid or empty.
if (openBrace.type !== "Punctuator" ||

295
tools/eslint/lib/rules/brace-style.js

@ -5,6 +5,8 @@
"use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -52,215 +54,86 @@ module.exports = {
//--------------------------------------------------------------------------
/**
* Determines if a given node is a block statement.
* @param {ASTNode} node The node to check.
* @returns {boolean} True if the node is a block statement, false if not.
* @private
*/
function isBlock(node) {
return node && node.type === "BlockStatement";
}
/**
* Check if the token is an punctuator with a value of curly brace
* @param {Object} token - Token to check
* @returns {boolean} true if its a curly punctuator
* @private
*/
function isCurlyPunctuator(token) {
return token.value === "{" || token.value === "}";
}
/**
* Reports a place where a newline unexpectedly appears
* @param {ASTNode} node The node to report
* @param {string} message The message to report
* Fixes a place where a newline unexpectedly appears
* @param {Token} firstToken The token before the unexpected newline
* @returns {void}
* @param {Token} secondToken The token after the unexpected newline
* @returns {Function} A fixer function to remove the newlines between the tokens
*/
function reportExtraNewline(node, message, firstToken) {
context.report({
node,
message,
fix(fixer) {
const secondToken = sourceCode.getTokenAfter(firstToken);
const textBetween = sourceCode.getText().slice(firstToken.range[1], secondToken.range[0]);
const NEWLINE_REGEX = /\r\n|\r|\n|\u2028|\u2029/g;
function removeNewlineBetween(firstToken, secondToken) {
const textRange = [firstToken.range[1], secondToken.range[0]];
const textBetween = sourceCode.text.slice(textRange[0], textRange[1]);
const NEWLINE_REGEX = astUtils.createGlobalLinebreakMatcher();
// Don't do a fix if there is a comment between the tokens.
return textBetween.trim() ? null : fixer.replaceTextRange([firstToken.range[1], secondToken.range[0]], textBetween.replace(NEWLINE_REGEX, ""));
}
});
// Don't do a fix if there is a comment between the tokens
return fixer => fixer.replaceTextRange(textRange, textBetween.trim() ? null : textBetween.replace(NEWLINE_REGEX, ""));
}
/**
* Binds a list of properties to a function that verifies that the opening
* curly brace is on the same line as its controlling statement of a given
* node.
* @param {...string} The properties to check on the node.
* @returns {Function} A function that will perform the check on a node
* @private
*/
function checkBlock() {
const blockProperties = arguments;
return function(node) {
Array.prototype.forEach.call(blockProperties, blockProp => {
const block = node[blockProp];
if (!isBlock(block)) {
return;
}
const previousToken = sourceCode.getTokenBefore(block);
const curlyToken = sourceCode.getFirstToken(block);
const curlyTokenEnd = sourceCode.getLastToken(block);
const allOnSameLine = previousToken.loc.start.line === curlyTokenEnd.loc.start.line;
if (allOnSameLine && params.allowSingleLine) {
return;
}
if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) {
reportExtraNewline(node, OPEN_MESSAGE, previousToken);
} else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) {
context.report({
node,
message: OPEN_MESSAGE_ALLMAN,
fix: fixer => fixer.insertTextBefore(curlyToken, "\n")
});
}
if (!block.body.length) {
return;
}
if (curlyToken.loc.start.line === block.body[0].loc.start.line) {
context.report({
node: block.body[0],
message: BODY_MESSAGE,
fix: fixer => fixer.insertTextAfter(curlyToken, "\n")
});
}
* Validates a pair of curly brackets based on the user's config
* @param {Token} openingCurly The opening curly bracket
* @param {Token} closingCurly The closing curly bracket
* @returns {void}
*/
function validateCurlyPair(openingCurly, closingCurly) {
const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly);
const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly);
const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly);
const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly);
if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.end.line) {
context.report({
node: block.body[block.body.length - 1],
message: CLOSE_MESSAGE_SINGLE,
fix: fixer => fixer.insertTextBefore(curlyTokenEnd, "\n")
});
}
if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) {
context.report({
node: openingCurly,
message: OPEN_MESSAGE,
fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly)
});
};
}
/**
* Enforces the configured brace style on IfStatements
* @param {ASTNode} node An IfStatement node.
* @returns {void}
* @private
*/
function checkIfStatement(node) {
checkBlock("consequent", "alternate")(node);
if (node.alternate) {
const tokens = sourceCode.getTokensBefore(node.alternate, 2);
if (style === "1tbs") {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line &&
node.consequent.type === "BlockStatement" &&
isCurlyPunctuator(tokens[0])) {
reportExtraNewline(node.alternate, CLOSE_MESSAGE, tokens[0]);
}
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report({
node: node.alternate,
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
fix: fixer => fixer.insertTextAfter(tokens[0], "\n")
});
}
}
}
/**
* Enforces the configured brace style on TryStatements
* @param {ASTNode} node A TryStatement node.
* @returns {void}
* @private
*/
function checkTryStatement(node) {
checkBlock("block", "finalizer")(node);
if (isBlock(node.finalizer)) {
const tokens = sourceCode.getTokensBefore(node.finalizer, 2);
if (style === "1tbs") {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
reportExtraNewline(node.finalizer, CLOSE_MESSAGE, tokens[0]);
}
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report({
node: node.finalizer,
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
fix: fixer => fixer.insertTextAfter(tokens[0], "\n")
});
}
if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) {
context.report({
node: openingCurly,
message: OPEN_MESSAGE_ALLMAN,
fix: fixer => fixer.insertTextBefore(openingCurly, "\n")
});
}
}
/**
* Enforces the configured brace style on CatchClauses
* @param {ASTNode} node A CatchClause node.
* @returns {void}
* @private
*/
function checkCatchClause(node) {
const previousToken = sourceCode.getTokenBefore(node),
firstToken = sourceCode.getFirstToken(node);
checkBlock("body")(node);
if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) {
context.report({
node: openingCurly,
message: BODY_MESSAGE,
fix: fixer => fixer.insertTextAfter(openingCurly, "\n")
});
}
if (isBlock(node.body)) {
if (style === "1tbs") {
if (previousToken.loc.start.line !== firstToken.loc.start.line) {
reportExtraNewline(node, CLOSE_MESSAGE, previousToken);
}
} else {
if (previousToken.loc.start.line === firstToken.loc.start.line) {
context.report({
node,
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
fix: fixer => fixer.insertTextAfter(previousToken, "\n")
});
}
}
if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) {
context.report({
node: closingCurly,
message: CLOSE_MESSAGE_SINGLE,
fix: fixer => fixer.insertTextBefore(closingCurly, "\n")
});
}
}
/**
* Enforces the configured brace style on SwitchStatements
* @param {ASTNode} node A SwitchStatement node.
* @returns {void}
* @private
*/
function checkSwitchStatement(node) {
let tokens;
* Validates the location of a token that appears before a keyword (e.g. a newline before `else`)
* @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`).
* @returns {void}
*/
function validateCurlyBeforeKeyword(curlyToken) {
const keywordToken = sourceCode.getTokenAfter(curlyToken);
if (node.cases && node.cases.length) {
tokens = sourceCode.getTokensBefore(node.cases[0], 2);
} else {
tokens = sourceCode.getLastTokens(node, 3);
if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
context.report({
node: curlyToken,
message: CLOSE_MESSAGE,
fix: removeNewlineBetween(curlyToken, keywordToken)
});
}
if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) {
reportExtraNewline(node, OPEN_MESSAGE, tokens[0]);
} else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) {
if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
context.report({
node,
message: OPEN_MESSAGE_ALLMAN,
fix: fixer => fixer.insertTextBefore(tokens[1], "\n")
node: curlyToken,
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
fix: fixer => fixer.insertTextAfter(curlyToken, "\n")
});
}
}
@ -270,20 +143,38 @@ module.exports = {
//--------------------------------------------------------------------------
return {
FunctionDeclaration: checkBlock("body"),
FunctionExpression: checkBlock("body"),
ArrowFunctionExpression: checkBlock("body"),
IfStatement: checkIfStatement,
TryStatement: checkTryStatement,
CatchClause: checkCatchClause,
DoWhileStatement: checkBlock("body"),
WhileStatement: checkBlock("body"),
WithStatement: checkBlock("body"),
ForStatement: checkBlock("body"),
ForInStatement: checkBlock("body"),
ForOfStatement: checkBlock("body"),
SwitchStatement: checkSwitchStatement
};
BlockStatement(node) {
if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
}
},
ClassBody(node) {
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
},
SwitchStatement(node) {
const closingCurly = sourceCode.getLastToken(node);
const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly);
validateCurlyPair(openingCurly, closingCurly);
},
IfStatement(node) {
if (node.consequent.type === "BlockStatement" && node.alternate) {
// Handle the keyword after the `if` block (before `else`)
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent));
}
},
TryStatement(node) {
// Handle the keyword after the `try` block (before `catch` or `finally`)
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block));
if (node.handler && node.finalizer) {
// Handle the keyword after the `catch` block (before `finally`)
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body));
}
}
};
}
};

15
tools/eslint/lib/rules/capitalized-comments.js

@ -9,6 +9,7 @@
//------------------------------------------------------------------------------
const LETTER_PATTERN = require("../util/patterns/letters");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Helpers
@ -16,7 +17,7 @@ const LETTER_PATTERN = require("../util/patterns/letters");
const ALWAYS_MESSAGE = "Comments should not begin with a lowercase character",
NEVER_MESSAGE = "Comments should not begin with an uppercase character",
DEFAULT_IGNORE_PATTERN = /^\s*(?:eslint|istanbul|jscs|jshint|globals?|exported)\b/,
DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN,
WHITESPACE = /\s/g,
MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern?
DEFAULTS = {
@ -163,8 +164,8 @@ module.exports = {
* otherwise.
*/
function isInlineComment(comment) {
const previousToken = sourceCode.getTokenOrCommentBefore(comment),
nextToken = sourceCode.getTokenOrCommentAfter(comment);
const previousToken = sourceCode.getTokenBefore(comment, { includeComments: true }),
nextToken = sourceCode.getTokenAfter(comment, { includeComments: true });
return Boolean(
previousToken &&
@ -181,7 +182,7 @@ module.exports = {
* @returns {boolean} True if the comment follows a valid comment.
*/
function isConsecutiveComment(comment) {
const previousTokenOrComment = sourceCode.getTokenOrCommentBefore(comment);
const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true });
return Boolean(
previousTokenOrComment &&
@ -264,9 +265,9 @@ module.exports = {
commentValid = isCommentValid(comment, options);
if (!commentValid) {
const message = capitalize === "always" ?
ALWAYS_MESSAGE :
NEVER_MESSAGE;
const message = capitalize === "always"
? ALWAYS_MESSAGE
: NEVER_MESSAGE;
context.report({
node: null, // Intentionally using loc instead

17
tools/eslint/lib/rules/comma-dangle.js

@ -10,6 +10,7 @@
//------------------------------------------------------------------------------
const lodash = require("lodash");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Helpers
@ -20,7 +21,7 @@ const DEFAULT_OPTIONS = Object.freeze({
objects: "never",
imports: "never",
exports: "never",
functions: "ignore",
functions: "ignore"
});
/**
@ -53,7 +54,7 @@ function normalizeOptions(optionValue) {
exports: optionValue,
// For backward compatibility, always ignore functions.
functions: "ignore",
functions: "ignore"
};
}
if (typeof optionValue === "object" && optionValue !== null) {
@ -62,7 +63,7 @@ function normalizeOptions(optionValue) {
objects: optionValue.objects || DEFAULT_OPTIONS.objects,
imports: optionValue.imports || DEFAULT_OPTIONS.imports,
exports: optionValue.exports || DEFAULT_OPTIONS.exports,
functions: optionValue.functions || DEFAULT_OPTIONS.functions,
functions: optionValue.functions || DEFAULT_OPTIONS.functions
};
}
@ -121,7 +122,7 @@ module.exports = {
additionalProperties: false
}
]
},
}
]
},
@ -178,7 +179,7 @@ module.exports = {
default: {
const nextToken = sourceCode.getTokenAfter(lastItem);
if (nextToken.value === ",") {
if (astUtils.isCommaToken(nextToken)) {
return nextToken;
}
return sourceCode.getLastToken(lastItem);
@ -224,7 +225,7 @@ module.exports = {
const trailingToken = getTrailingToken(node, lastItem);
if (trailingToken.value === ",") {
if (astUtils.isCommaToken(trailingToken)) {
context.report({
node: lastItem,
loc: trailingToken.loc.start,
@ -312,7 +313,7 @@ module.exports = {
"always-multiline": forceTrailingCommaIfMultiline,
"only-multiline": allowTrailingCommaIfMultiline,
never: forbidTrailingComma,
ignore: lodash.noop,
ignore: lodash.noop
};
return {
@ -330,7 +331,7 @@ module.exports = {
FunctionExpression: predicate[options.functions],
ArrowFunctionExpression: predicate[options.functions],
CallExpression: predicate[options.functions],
NewExpression: predicate[options.functions],
NewExpression: predicate[options.functions]
};
}
};

50
tools/eslint/lib/rules/comma-spacing.js

@ -53,16 +53,6 @@ module.exports = {
// list of comma tokens to ignore for the check of leading whitespace
const commaTokensToIgnore = [];
/**
* Determines if a given token is a comma operator.
* @param {ASTNode} token The token to check.
* @returns {boolean} True if the token is a comma, false if not.
* @private
*/
function isComma(token) {
return !!token && (token.type === "Punctuator") && (token.value === ",");
}
/**
* Reports a spacing error with an appropriate message.
* @param {ASTNode} node The binary expression node to report.
@ -78,27 +68,27 @@ module.exports = {
if (options[dir]) {
if (dir === "before") {
return fixer.insertTextBefore(node, " ");
} else {
return fixer.insertTextAfter(node, " ");
}
} else {
let start, end;
const newText = "";
return fixer.insertTextAfter(node, " ");
if (dir === "before") {
start = otherNode.range[1];
end = node.range[0];
} else {
start = node.range[1];
end = otherNode.range[0];
}
}
let start, end;
const newText = "";
return fixer.replaceTextRange([start, end], newText);
if (dir === "before") {
start = otherNode.range[1];
end = node.range[0];
} else {
start = node.range[1];
end = otherNode.range[0];
}
return fixer.replaceTextRange([start, end], newText);
},
message: options[dir] ?
"A space is required {{dir}} ','." :
"There should be no space {{dir}} ','.",
message: options[dir]
? "A space is required {{dir}} ','."
: "There should be no space {{dir}} ','.",
data: {
dir
}
@ -147,7 +137,7 @@ module.exports = {
if (element === null) {
token = sourceCode.getTokenAfter(previousToken);
if (isComma(token)) {
if (astUtils.isCommaToken(token)) {
commaTokensToIgnore.push(token);
}
} else {
@ -166,7 +156,7 @@ module.exports = {
"Program:exit"() {
tokensAndComments.forEach((token, i) => {
if (!isComma(token)) {
if (!astUtils.isCommaToken(token)) {
return;
}
@ -179,8 +169,8 @@ module.exports = {
validateCommaItemSpacing({
comma: token,
left: isComma(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
right: isComma(nextToken) ? null : nextToken
left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
right: astUtils.isCommaToken(nextToken) ? null : nextToken
}, token);
});
},

24
tools/eslint/lib/rules/comma-style.js

@ -48,7 +48,7 @@ module.exports = {
FunctionDeclaration: true,
FunctionExpression: true,
ImportDeclaration: true,
ObjectPattern: true,
ObjectPattern: true
};
if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
@ -63,16 +63,6 @@ module.exports = {
// Helpers
//--------------------------------------------------------------------------
/**
* Determines if a given token is a comma operator.
* @param {ASTNode} token The token to check.
* @returns {boolean} True if the token is a comma, false if not.
* @private
*/
function isComma(token) {
return !!token && (token.type === "Punctuator") && (token.value === ",");
}
/**
* Modified text based on the style
* @param {string} styleType Style type
@ -192,7 +182,7 @@ module.exports = {
tokenBeforeComma = sourceCode.getTokenBefore(commaToken);
// Check if previous token is wrapped in parentheses
if (tokenBeforeComma && tokenBeforeComma.value === ")") {
if (tokenBeforeComma && astUtils.isClosingParenToken(tokenBeforeComma)) {
previousItemToken = tokenBeforeComma;
}
@ -210,12 +200,16 @@ module.exports = {
* All comparisons are done based on these tokens directly, so
* they are always valid regardless of an undefined item.
*/
if (isComma(commaToken)) {
if (astUtils.isCommaToken(commaToken)) {
validateCommaItemSpacing(previousItemToken, commaToken,
currentItemToken, reportItem);
}
previousItemToken = item ? sourceCode.getLastToken(item) : previousItemToken;
if (item) {
const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken);
previousItemToken = tokenAfterItem ? sourceCode.getTokenBefore(tokenAfterItem) : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
}
});
/*
@ -229,7 +223,7 @@ module.exports = {
const lastToken = sourceCode.getLastToken(node),
nextToLastToken = sourceCode.getTokenBefore(lastToken);
if (isComma(nextToLastToken)) {
if (astUtils.isCommaToken(nextToLastToken)) {
validateCommaItemSpacing(
sourceCode.getTokenBefore(nextToLastToken),
nextToLastToken,

22
tools/eslint/lib/rules/complexity.js

@ -6,6 +6,14 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const lodash = require("lodash");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -81,17 +89,15 @@ module.exports = {
* @private
*/
function endFunction(node) {
const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
const complexity = fns.pop();
let name = "anonymous";
if (node.id) {
name = node.id.name;
} else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") {
name = node.parent.key.name;
}
if (complexity > THRESHOLD) {
context.report({ node, message: "Function '{{name}}' has a complexity of {{complexity}}.", data: { name, complexity } });
context.report({
node,
message: "{{name}} has a complexity of {{complexity}}.",
data: { name, complexity }
});
}
}

29
tools/eslint/lib/rules/consistent-return.js

@ -8,6 +8,8 @@
// Requirements
//------------------------------------------------------------------------------
const lodash = require("lodash");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
@ -81,7 +83,7 @@ module.exports = {
* @returns {void}
*/
function checkLastSegment(node) {
let loc, type;
let loc, name;
/*
* Skip if it expected no return value or unreachable.
@ -100,12 +102,11 @@ module.exports = {
// The head of program.
loc = { line: 1, column: 0 };
type = "program";
name = "program";
} else if (node.type === "ArrowFunctionExpression") {
// `=>` token
loc = context.getSourceCode().getTokenBefore(node.body).loc.start;
type = "function";
loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc.start;
} else if (
node.parent.type === "MethodDefinition" ||
(node.parent.type === "Property" && node.parent.method)
@ -113,33 +114,36 @@ module.exports = {
// Method name.
loc = node.parent.key.loc.start;
type = "method";
} else {
// Function name or `function` keyword.
loc = (node.id || node).loc.start;
type = "function";
}
if (!name) {
name = astUtils.getFunctionNameWithKind(node);
}
// Reports.
context.report({
node,
loc,
message: "Expected to return a value at the end of this {{type}}.",
data: { type }
message: "Expected to return a value at the end of {{name}}.",
data: { name }
});
}
return {
// Initializes/Disposes state of each code path.
onCodePathStart(codePath) {
onCodePathStart(codePath, node) {
funcInfo = {
upper: funcInfo,
codePath,
hasReturn: false,
hasReturnValue: false,
message: ""
message: "",
node
};
},
onCodePathEnd() {
@ -158,8 +162,11 @@ module.exports = {
if (!funcInfo.hasReturn) {
funcInfo.hasReturn = true;
funcInfo.hasReturnValue = hasReturnValue;
funcInfo.message = "Expected {{which}} return value.";
funcInfo.message = "{{name}} expected {{which}} return value.";
funcInfo.data = {
name: funcInfo.node.type === "Program"
? "Program"
: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node)),
which: hasReturnValue ? "a" : "no"
};
} else if (funcInfo.hasReturnValue !== hasReturnValue) {

6
tools/eslint/lib/rules/constructor-super.js

@ -209,9 +209,9 @@ module.exports = {
if (!calledInEveryPaths) {
context.report({
message: calledInSomePaths ?
"Lacked a call of 'super()' in some code paths." :
"Expected to call 'super()'.",
message: calledInSomePaths
? "Lacked a call of 'super()' in some code paths."
: "Expected to call 'super()'.",
node: node.parent
});
}

22
tools/eslint/lib/rules/curly.js

@ -76,7 +76,7 @@ module.exports = {
function isCollapsedOneLiner(node) {
const before = sourceCode.getTokenBefore(node);
const last = sourceCode.getLastToken(node);
const lastExcludingSemicolon = last.type === "Punctuator" && last.value === ";" ? sourceCode.getTokenBefore(last) : last;
const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
return before.loc.start.line === lastExcludingSemicolon.loc.end.line;
}
@ -94,19 +94,23 @@ module.exports = {
return first.loc.start.line === last.loc.end.line;
}
/**
* Checks if the given token is an `else` token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is an `else` token.
*/
function isElseKeywordToken(token) {
return token.value === "else" && token.type === "Keyword";
}
/**
* Gets the `else` keyword token of a given `IfStatement` node.
* @param {ASTNode} node - A `IfStatement` node to get.
* @returns {Token} The `else` keyword token.
*/
function getElseKeyword(node) {
let token = sourceCode.getTokenAfter(node.consequent);
while (token.type !== "Keyword" || token.value !== "else") {
token = sourceCode.getTokenAfter(token);
}
return token;
return node.alternate && sourceCode.getFirstTokenBetween(node.consequent, node.alternate, isElseKeywordToken);
}
/**
@ -170,7 +174,7 @@ module.exports = {
const tokenAfter = sourceCode.getTokenAfter(closingBracket);
const lastBlockNode = sourceCode.getNodeByRangeIndex(tokenBefore.range[0]);
if (tokenBefore.value === ";") {
if (astUtils.isSemicolonToken(tokenBefore)) {
// If the last statement already has a semicolon, don't add another one.
return false;

6
tools/eslint/lib/rules/default-case.js

@ -31,9 +31,9 @@ module.exports = {
create(context) {
const options = context.options[0] || {};
const commentPattern = options.commentPattern ?
new RegExp(options.commentPattern) :
DEFAULT_COMMENT_PATTERN;
const commentPattern = options.commentPattern
? new RegExp(options.commentPattern)
: DEFAULT_COMMENT_PATTERN;
const sourceCode = context.getSourceCode();

18
tools/eslint/lib/rules/dot-notation.js

@ -4,6 +4,12 @@
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -64,20 +70,20 @@ module.exports = {
propertyValue: JSON.stringify(node.property.value)
},
fix(fixer) {
const leftBracket = sourceCode.getTokenBefore(node.property);
const rightBracket = sourceCode.getTokenAfter(node.property);
const textBeforeProperty = sourceCode.text.slice(leftBracket.range[1], node.property.range[0]);
const textAfterProperty = sourceCode.text.slice(node.property.range[1], rightBracket.range[0]);
const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
const rightBracket = sourceCode.getLastToken(node);
if (textBeforeProperty.trim() || textAfterProperty.trim()) {
if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
// Don't perform any fixes if there are comments inside the brackets.
return null;
}
const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
return fixer.replaceTextRange(
[leftBracket.range[0], rightBracket.range[1]],
`.${node.property.value}`
`${textBeforeDot}.${node.property.value}`
);
}
});

21
tools/eslint/lib/rules/eqeqeq.js

@ -5,6 +5,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -57,9 +63,9 @@ module.exports = {
const options = context.options[1] || {};
const sourceCode = context.getSourceCode();
const nullOption = (config === "always") ?
options.null || "always" :
"ignore";
const nullOption = (config === "always")
? options.null || "always"
: "ignore";
const enforceRuleForNull = (nullOption === "always");
const enforceInverseRuleForNull = (nullOption === "never");
@ -100,8 +106,7 @@ module.exports = {
* @private
*/
function isNullCheck(node) {
return (node.right.type === "Literal" && node.right.value === null) ||
(node.left.type === "Literal" && node.left.value === null);
return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left);
}
/**
@ -134,7 +139,11 @@ module.exports = {
// If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix.
if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {
const operatorToken = sourceCode.getTokensBetween(node.left, node.right).find(token => token.value === node.operator);
const operatorToken = sourceCode.getFirstTokenBetween(
node.left,
node.right,
token => token.value === node.operator
);
return fixer.replaceText(operatorToken, expectedOperator);
}

27
tools/eslint/lib/rules/func-call-spacing.js

@ -5,6 +5,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -67,28 +73,19 @@ module.exports = {
* @private
*/
function checkSpacing(node) {
const lastToken = sourceCode.getLastToken(node);
const lastCalleeToken = sourceCode.getLastToken(node.callee);
let prevToken = lastCalleeToken;
let parenToken = sourceCode.getTokenAfter(lastCalleeToken);
// advances to an open parenthesis.
while (
parenToken &&
parenToken.range[1] < node.range[1] &&
parenToken.value !== "("
) {
prevToken = parenToken;
parenToken = sourceCode.getTokenAfter(parenToken);
}
const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
// Parens in NewExpression are optional
if (!(parenToken && parenToken.range[1] < node.range[1])) {
return;
}
const hasWhitespace = sourceCode.isSpaceBetweenTokens(prevToken, parenToken);
const hasNewline = hasWhitespace &&
/\n/.test(text.slice(prevToken.range[1], parenToken.range[0]).replace(/\/\*.*?\*\//g, ""));
const textBetweenTokens = text.slice(prevToken.range[1], parenToken.range[0]).replace(/\/\*.*?\*\//g, "");
const hasWhitespace = /\s/.test(textBetweenTokens);
const hasNewline = hasWhitespace && astUtils.LINEBREAK_MATCHER.test(textBetweenTokens);
/*
* never allowNewlines hasWhitespace hasNewline message

27
tools/eslint/lib/rules/func-name-matching.js

@ -132,6 +132,15 @@ module.exports = {
});
}
/**
* Determines whether a given node is a string literal
* @param {ASTNode} node The node to check
* @returns {boolean} `true` if the node is a string literal
*/
function isStringLiteral(node) {
return node.type === "Literal" && typeof node.value === "string";
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@ -139,7 +148,7 @@ module.exports = {
return {
VariableDeclarator(node) {
if (!node.init || node.init.type !== "FunctionExpression") {
if (!node.init || node.init.type !== "FunctionExpression" || node.id.type !== "Identifier") {
return;
}
if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) {
@ -148,14 +157,16 @@ module.exports = {
},
AssignmentExpression(node) {
if (node.right.type !== "FunctionExpression" ||
(node.left.computed && node.left.property.type !== "Literal") ||
(!includeModuleExports && isModuleExports(node.left))
) {
if (
node.right.type !== "FunctionExpression" ||
(node.left.computed && node.left.property.type !== "Literal") ||
(!includeModuleExports && isModuleExports(node.left)) ||
(node.left.type !== "Identifier" && node.left.type !== "MemberExpression")
) {
return;
}
const isProp = node.left.type === "MemberExpression" ? true : false;
const isProp = node.left.type === "MemberExpression";
const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name;
if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
@ -164,13 +175,13 @@ module.exports = {
},
Property(node) {
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && node.key.type !== "Literal") {
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) {
return;
}
if (node.key.type === "Identifier" && shouldWarn(node.key.name, node.value.id.name)) {
report(node, node.key.name, node.value.id.name, true);
} else if (
node.key.type === "Literal" &&
isStringLiteral(node.key) &&
isIdentifier(node.key.value, ecmaVersion) &&
shouldWarn(node.key.value, node.value.id.name)
) {

25
tools/eslint/lib/rules/func-names.js

@ -5,6 +5,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
/**
* Checks whether or not a given variable is a function name.
* @param {escope.Variable} variable - A variable to check.
@ -82,15 +88,24 @@ module.exports = {
return;
}
const name = node.id && node.id.name;
const hasName = Boolean(node.id && node.id.name);
const name = astUtils.getFunctionNameWithKind(node);
if (never) {
if (name) {
context.report({ node, message: "Unexpected function expression name." });
if (hasName) {
context.report({
node,
message: "Unexpected named {{name}}.",
data: { name }
});
}
} else {
if (!name && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
context.report({ node, message: "Missing function expression name." });
if (!hasName && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
context.report({
node,
message: "Unexpected unnamed {{name}}.",
data: { name }
});
}
}
}

37
tools/eslint/lib/rules/generator-star-spacing.js

@ -55,21 +55,26 @@ module.exports = {
const sourceCode = context.getSourceCode();
/**
* Gets `*` token from a given node.
* Checks if the given token is a star token or not.
*
* @param {ASTNode} node - A node to get `*` token. This is one of
* FunctionDeclaration, FunctionExpression, Property, and
* MethodDefinition.
* @returns {Token} `*` token.
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a star token.
*/
function getStarToken(node) {
let token = sourceCode.getFirstToken(node);
while (token.value !== "*") {
token = sourceCode.getTokenAfter(token);
}
function isStarToken(token) {
return token.value === "*" && token.type === "Punctuator";
}
return token;
/**
* Gets the generator star token of the given function node.
*
* @param {ASTNode} node - The function node to get.
* @returns {Token} Found star token.
*/
function getStarToken(node) {
return sourceCode.getFirstToken(
(node.parent.method || node.parent.type === "MethodDefinition") ? node.parent : node,
isStarToken
);
}
/**
@ -116,17 +121,11 @@ module.exports = {
* @returns {void}
*/
function checkFunction(node) {
let starToken;
if (!node.generator) {
return;
}
if (node.parent.method || node.parent.type === "MethodDefinition") {
starToken = getStarToken(node.parent);
} else {
starToken = getStarToken(node);
}
const starToken = getStarToken(node);
// Only check before when preceded by `function`|`static` keyword
const prevToken = sourceCode.getTokenBefore(starToken);

4
tools/eslint/lib/rules/global-require.js

@ -29,9 +29,9 @@ function findReference(scope, node) {
/* istanbul ignore else: correctly returns null */
if (references.length === 1) {
return references[0];
} else {
return null;
}
return null;
}
/**

4
tools/eslint/lib/rules/id-blacklist.js

@ -55,8 +55,8 @@ module.exports = {
* @returns {boolean} whether an error should be reported or not
*/
function shouldReport(effectiveParent, name) {
return effectiveParent.type !== "CallExpression"
&& effectiveParent.type !== "NewExpression" &&
return effectiveParent.type !== "CallExpression" &&
effectiveParent.type !== "NewExpression" &&
isInvalid(name);
}

6
tools/eslint/lib/rules/id-length.js

@ -104,9 +104,9 @@ module.exports = {
if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) {
context.report({
node,
message: isShort ?
"Identifier name '{{name}}' is too short (< {{min}})." :
"Identifier name '{{name}}' is too long (> {{max}}).",
message: isShort
? "Identifier name '{{name}}' is too short (< {{min}})."
: "Identifier name '{{name}}' is too long (> {{max}}).",
data: { name, min: minLength, max: maxLength }
});
}

4
tools/eslint/lib/rules/id-match.js

@ -63,8 +63,8 @@ module.exports = {
* @returns {boolean} whether an error should be reported or not
*/
function shouldReport(effectiveParent, name) {
return effectiveParent.type !== "CallExpression"
&& effectiveParent.type !== "NewExpression" &&
return effectiveParent.type !== "CallExpression" &&
effectiveParent.type !== "NewExpression" &&
isInvalid(name);
}

41
tools/eslint/lib/rules/indent.js

@ -8,6 +8,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -435,15 +441,10 @@ module.exports = {
* @returns {void}
*/
function checkLastReturnStatementLineIndent(node, firstLineIndent) {
const nodeLastToken = sourceCode.getLastToken(node);
let lastToken = nodeLastToken;
// in case if return statement ends with ');' we have traverse back to ')'
// otherwise we'll measure indent for ';' and replace ')'
while (lastToken.value !== ")") {
lastToken = sourceCode.getTokenBefore(lastToken);
}
const lastToken = sourceCode.getLastToken(node, astUtils.isClosingParenToken);
const textBeforeClosingParenthesis = sourceCode.getText(lastToken, lastToken.loc.start.column).slice(0, -1);
if (textBeforeClosingParenthesis.trim()) {
@ -691,9 +692,9 @@ module.exports = {
function isFirstArrayElementOnSameLine(node) {
if (node.type === "ArrayExpression" && node.elements[0]) {
return node.elements[0].loc.start.line === node.loc.start.line && node.elements[0].type === "ObjectExpression";
} else {
return false;
}
return false;
}
/**
@ -729,7 +730,7 @@ module.exports = {
} else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
if (parentElements[0].loc.start.line === parent.loc.start.line && parentElements[0].loc.end.line !== parent.loc.start.line) {
if (parentElements[0] && parentElements[0].loc.start.line === parent.loc.start.line && parentElements[0].loc.end.line !== parent.loc.start.line) {
/*
* If the first element of the array spans multiple lines, don't increase the expected indentation of the rest.
@ -936,20 +937,20 @@ module.exports = {
if (caseIndentStore[switchNode.loc.start.line]) {
return caseIndentStore[switchNode.loc.start.line];
}
if (typeof switchIndent === "undefined") {
switchIndent = getNodeIndent(switchNode).goodChar;
}
if (switchNode.cases.length > 0 && options.SwitchCase === 0) {
caseIndent = switchIndent;
} else {
if (typeof switchIndent === "undefined") {
switchIndent = getNodeIndent(switchNode).goodChar;
}
caseIndent = switchIndent + (indentSize * options.SwitchCase);
}
if (switchNode.cases.length > 0 && options.SwitchCase === 0) {
caseIndent = switchIndent;
} else {
caseIndent = switchIndent + (indentSize * options.SwitchCase);
}
caseIndentStore[switchNode.loc.start.line] = caseIndent;
return caseIndent;
caseIndentStore[switchNode.loc.start.line] = caseIndent;
return caseIndent;
}
}
/**

43
tools/eslint/lib/rules/key-spacing.js

@ -4,6 +4,12 @@
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
@ -15,7 +21,7 @@
* @returns {boolean} True if str contains a line terminator.
*/
function containsLineTerminator(str) {
return /[\n\r\u2028\u2029]/.test(str);
return astUtils.LINEBREAK_MATCHER.test(str);
}
/**
@ -364,14 +370,9 @@ module.exports = {
* @returns {ASTNode} The last token before a colon punctuator.
*/
function getLastTokenBeforeColon(node) {
let prevNode;
const colonToken = sourceCode.getTokenAfter(node, astUtils.isColonToken);
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
prevNode = node;
node = sourceCode.getTokenAfter(node);
}
return prevNode;
return sourceCode.getTokenBefore(colonToken);
}
/**
@ -381,12 +382,7 @@ module.exports = {
* @returns {ASTNode} The colon punctuator.
*/
function getNextColon(node) {
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
node = sourceCode.getTokenAfter(node);
}
return node;
return sourceCode.getTokenAfter(node, astUtils.isColonToken);
}
/**
@ -417,8 +413,8 @@ module.exports = {
function report(property, side, whitespace, expected, mode) {
const diff = whitespace.length - expected,
nextColon = getNextColon(property.key),
tokenBeforeColon = sourceCode.getTokenOrCommentBefore(nextColon),
tokenAfterColon = sourceCode.getTokenOrCommentAfter(nextColon),
tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
isKeySide = side === "key",
locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
isExtra = diff > 0,
@ -628,15 +624,16 @@ module.exports = {
}
};
} else { // Obey beforeColon and afterColon in each property as configured
}
// Obey beforeColon and afterColon in each property as configured
return {
Property(node) {
verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
}
};
return {
Property(node) {
verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
}
};
}
}
};

38
tools/eslint/lib/rules/keyword-spacing.js

@ -342,11 +342,7 @@ module.exports = {
*/
function checkSpacingAroundTokenBefore(node) {
if (node) {
let token = sourceCode.getTokenBefore(node);
while (token.type !== "Keyword") {
token = sourceCode.getTokenBefore(token);
}
const token = sourceCode.getTokenBefore(node, astUtils.isKeywordToken);
checkSpacingAround(token);
}
@ -363,7 +359,8 @@ module.exports = {
const firstToken = node && sourceCode.getFirstToken(node);
if (firstToken &&
(firstToken.type === "Keyword" || firstToken.value === "async")
((firstToken.type === "Keyword" && firstToken.value === "function") ||
firstToken.value === "async")
) {
checkSpacingBefore(firstToken);
}
@ -439,14 +436,7 @@ module.exports = {
*/
function checkSpacingForForOfStatement(node) {
checkSpacingAroundFirstToken(node);
// `of` is not a keyword token.
let token = sourceCode.getTokenBefore(node.right);
while (token.value !== "of") {
token = sourceCode.getTokenBefore(token);
}
checkSpacingAround(token);
checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken));
}
/**
@ -506,11 +496,25 @@ module.exports = {
node.value.async
)
) {
const token = sourceCode.getFirstToken(
node,
node.static ? 1 : 0
const token = sourceCode.getTokenBefore(
node.key,
tok => {
switch (tok.value) {
case "get":
case "set":
case "async":
return true;
default:
return false;
}
}
);
if (!token) {
throw new Error("Failed to find token get, set, or async beside method name");
}
checkSpacingAround(token);
}
}

22
tools/eslint/lib/rules/line-comment-position.js

@ -4,6 +4,8 @@
*/
"use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -33,6 +35,9 @@ module.exports = {
},
applyDefaultPatterns: {
type: "boolean"
},
applyDefaultIgnorePatterns: {
type: "boolean"
}
},
additionalProperties: false
@ -43,12 +48,11 @@ module.exports = {
},
create(context) {
const DEFAULT_IGNORE_PATTERN = "^\\s*(?:eslint|jshint\\s+|jslint\\s+|istanbul\\s+|globals?\\s+|exported\\s+|jscs|falls?\\s?through)";
const options = context.options[0];
let above,
ignorePattern,
applyDefaultPatterns = true;
applyDefaultIgnorePatterns = true;
if (!options || typeof options === "string") {
above = !options || options === "above";
@ -56,10 +60,16 @@ module.exports = {
} else {
above = options.position === "above";
ignorePattern = options.ignorePattern;
applyDefaultPatterns = options.applyDefaultPatterns !== false;
if (options.hasOwnProperty("applyDefaultIgnorePatterns")) {
applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false;
} else {
applyDefaultIgnorePatterns = options.applyDefaultPatterns !== false;
}
}
const defaultIgnoreRegExp = new RegExp(DEFAULT_IGNORE_PATTERN);
const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN;
const fallThroughRegExp = /^\s*falls?\s?through/;
const customIgnoreRegExp = new RegExp(ignorePattern);
const sourceCode = context.getSourceCode();
@ -69,7 +79,7 @@ module.exports = {
return {
LineComment(node) {
if (applyDefaultPatterns && defaultIgnoreRegExp.test(node.value)) {
if (applyDefaultIgnorePatterns && (defaultIgnoreRegExp.test(node.value) || fallThroughRegExp.test(node.value))) {
return;
}
@ -77,7 +87,7 @@ module.exports = {
return;
}
const previous = sourceCode.getTokenOrCommentBefore(node);
const previous = sourceCode.getTokenBefore(node, { includeComments: true });
const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line;
if (above) {

8
tools/eslint/lib/rules/linebreak-style.js

@ -5,6 +5,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -60,7 +66,7 @@ module.exports = {
expectedLF = linebreakStyle === "unix",
expectedLFChars = expectedLF ? "\n" : "\r\n",
source = sourceCode.getText(),
pattern = /\r\n|\r|\n|\u2028|\u2029/g;
pattern = astUtils.createGlobalLinebreakMatcher();
let match;
let i = 0;

27
tools/eslint/lib/rules/lines-around-comment.js

@ -93,6 +93,12 @@ module.exports = {
},
allowArrayEnd: {
type: "boolean"
},
ignorePattern: {
type: "string"
},
applyDefaultIgnorePatterns: {
type: "boolean"
}
},
additionalProperties: false
@ -103,6 +109,11 @@ module.exports = {
create(context) {
const options = context.options[0] ? Object.assign({}, context.options[0]) : {};
const ignorePattern = options.ignorePattern;
const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN;
const customIgnoreRegExp = new RegExp(ignorePattern);
const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false;
options.beforeLineComment = options.beforeLineComment || false;
options.afterLineComment = options.afterLineComment || false;
@ -139,7 +150,7 @@ module.exports = {
token = node;
do {
token = sourceCode.getTokenOrCommentBefore(token);
token = sourceCode.getTokenBefore(token, { includeComments: true });
} while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(token, node)) {
@ -148,7 +159,7 @@ module.exports = {
token = node;
do {
token = sourceCode.getTokenOrCommentAfter(token);
token = sourceCode.getTokenAfter(token, { includeComments: true });
} while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(node, token)) {
@ -270,6 +281,14 @@ module.exports = {
* @returns {void}
*/
function checkForEmptyLine(node, opts) {
if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(node.value)) {
return;
}
if (ignorePattern && customIgnoreRegExp.test(node.value)) {
return;
}
let after = opts.after,
before = opts.before;
@ -300,8 +319,8 @@ module.exports = {
return;
}
const previousTokenOrComment = sourceCode.getTokenOrCommentBefore(node);
const nextTokenOrComment = sourceCode.getTokenOrCommentAfter(node);
const previousTokenOrComment = sourceCode.getTokenBefore(node, { includeComments: true });
const nextTokenOrComment = sourceCode.getTokenAfter(node, { includeComments: true });
// check for newline before
if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) &&

10
tools/eslint/lib/rules/lines-around-directive.js

@ -31,7 +31,7 @@ module.exports = {
},
after: {
enum: ["always", "never"]
},
}
},
additionalProperties: false,
minProperties: 2
@ -57,7 +57,7 @@ module.exports = {
* @returns {boolean} Whether or not the passed in node is preceded by a blank newline.
*/
function hasNewlineBefore(node) {
const tokenBefore = sourceCode.getTokenOrCommentBefore(node);
const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true });
const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0;
return node.loc.start.line - tokenLineBefore >= 2;
@ -74,7 +74,7 @@ module.exports = {
const lastToken = sourceCode.getLastToken(node);
const secondToLastToken = sourceCode.getTokenBefore(lastToken);
return lastToken.type === "Punctuator" && lastToken.value === ";" && lastToken.loc.start.line > secondToLastToken.loc.end.line
return astUtils.isSemicolonToken(lastToken) && lastToken.loc.start.line > secondToLastToken.loc.end.line
? secondToLastToken
: lastToken;
}
@ -86,7 +86,7 @@ module.exports = {
*/
function hasNewlineAfter(node) {
const lastToken = getLastTokenOnLine(node);
const tokenAfter = sourceCode.getTokenOrCommentAfter(lastToken);
const tokenAfter = sourceCode.getTokenAfter(lastToken, { includeComments: true });
return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2;
}
@ -131,7 +131,7 @@ module.exports = {
}
const firstDirective = directives[0];
const hasTokenOrCommentBefore = !!sourceCode.getTokenOrCommentBefore(firstDirective);
const hasTokenOrCommentBefore = !!sourceCode.getTokenBefore(firstDirective, { includeComments: true });
// Only check before the first directive if it is preceded by a comment or if it is at the top of
// the file and expectLineBefore is set to "never". This is to not force a newline at the top of

6
tools/eslint/lib/rules/max-lines.js

@ -90,7 +90,7 @@ module.exports = {
token = comment;
do {
token = sourceCode.getTokenOrCommentBefore(token);
token = sourceCode.getTokenBefore(token, { includeComments: true });
} while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(token, comment)) {
@ -99,7 +99,7 @@ module.exports = {
token = comment;
do {
token = sourceCode.getTokenOrCommentAfter(token);
token = sourceCode.getTokenAfter(token, { includeComments: true });
} while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(comment, token)) {
@ -134,7 +134,7 @@ module.exports = {
message: "File must be at most {{max}} lines long. It's {{actual}} lines long.",
data: {
max,
actual: lines.length,
actual: lines.length
}
});
}

21
tools/eslint/lib/rules/max-params.js

@ -5,6 +5,14 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const lodash = require("lodash");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -66,10 +74,15 @@ module.exports = {
*/
function checkFunction(node) {
if (node.params.length > numParams) {
context.report({ node, message: "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", data: {
count: node.params.length,
max: numParams
} });
context.report({
node,
message: "{{name}} has too many parameters ({{count}}). Maximum allowed is {{max}}.",
data: {
name: lodash.upperFirst(astUtils.getFunctionNameWithKind(node)),
count: node.params.length,
max: numParams
}
});
}
}

15
tools/eslint/lib/rules/max-statements-per-line.js

@ -4,6 +4,12 @@
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -60,7 +66,7 @@ module.exports = {
data: {
numberOfStatementsOnThisLine,
maxStatementsPerLine,
statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements",
statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements"
}
});
}
@ -74,12 +80,7 @@ module.exports = {
* @returns {Token} The actual last token.
*/
function getActualLastToken(node) {
let lastToken = sourceCode.getLastToken(node);
if (lastToken.value === ";") {
lastToken = sourceCode.getTokenBefore(lastToken);
}
return lastToken;
return sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
}
/**

21
tools/eslint/lib/rules/max-statements.js

@ -5,6 +5,14 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const lodash = require("lodash");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -84,19 +92,12 @@ module.exports = {
*/
function reportIfTooManyStatements(node, count, max) {
if (count > max) {
const messageEnd = " has too many statements ({{count}}). Maximum allowed is {{max}}.";
let name = "This function";
if (node.id) {
name = `Function '${node.id.name}'`;
} else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") {
name = `Function '${context.getSource(node.parent.key)}'`;
}
const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
context.report({
node,
message: name + messageEnd,
data: { count, max }
message: "{{name}} has too many statements ({{count}}). Maximum allowed is {{max}}.",
data: { name, count, max }
});
}
}

4
tools/eslint/lib/rules/new-cap.js

@ -180,9 +180,9 @@ module.exports = {
return "non-alpha";
} else if (firstChar === firstCharLower) {
return "lower";
} else {
return "upper";
}
return "upper";
}
/**

28
tools/eslint/lib/rules/new-parens.js

@ -6,28 +6,14 @@
"use strict";
//------------------------------------------------------------------------------
// Helpers
// Requirements
//------------------------------------------------------------------------------
/**
* Checks whether the given token is an opening parenthesis or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is an opening parenthesis.
*/
function isOpeningParen(token) {
return token.type === "Punctuator" && token.value === "(";
}
const astUtils = require("../ast-utils");
/**
* Checks whether the given token is an closing parenthesis or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is an closing parenthesis.
*/
function isClosingParen(token) {
return token.type === "Punctuator" && token.value === ")";
}
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Rule Definition
@ -56,8 +42,8 @@ module.exports = {
}
const lastToken = sourceCode.getLastToken(node);
const hasLastParen = lastToken && isClosingParen(lastToken);
const hasParens = hasLastParen && isOpeningParen(sourceCode.getTokenBefore(lastToken));
const hasLastParen = lastToken && astUtils.isClosingParenToken(lastToken);
const hasParens = hasLastParen && astUtils.isOpeningParenToken(sourceCode.getTokenBefore(lastToken));
if (!hasParens) {
context.report({

9
tools/eslint/lib/rules/newline-after-var.js

@ -5,6 +5,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -201,8 +207,7 @@ module.exports = {
message: NEVER_MESSAGE,
data: { identifier: node.name },
fix(fixer) {
const NEWLINE_REGEX = /\r\n|\r|\n|\u2028|\u2029/;
const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(NEWLINE_REGEX);
const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER);
return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween[linesBetween.length - 1]}`);
}

4
tools/eslint/lib/rules/newline-before-return.js

@ -60,9 +60,9 @@ module.exports = {
return isPrecededByTokens(node, ["do"]);
} else if (parentType === "SwitchCase") {
return isPrecededByTokens(node, [":"]);
} else {
return isPrecededByTokens(node, [")"]);
}
return isPrecededByTokens(node, [")"]);
}
/**

4
tools/eslint/lib/rules/newline-per-chained-call.js

@ -6,6 +6,8 @@
"use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -47,7 +49,7 @@ module.exports = {
*/
function getPropertyText(node) {
const prefix = node.computed ? "[" : ".";
const lines = sourceCode.getText(node.property).split(/\r\n|\r|\n/g);
const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER);
const suffix = node.computed && lines.length === 1 ? "]" : "";
return prefix + lines[0] + suffix;

10
tools/eslint/lib/rules/no-await-in-loop.js

@ -10,7 +10,7 @@ const loopTypes = new Set([
"ForOfStatement",
"ForInStatement",
"WhileStatement",
"DoWhileStatement",
"DoWhileStatement"
]);
// Node types at which we should stop looking for loops. For example, it is fine to declare an async
@ -18,7 +18,7 @@ const loopTypes = new Set([
const boundaryTypes = new Set([
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
"ArrowFunctionExpression"
]);
module.exports = {
@ -26,9 +26,9 @@ module.exports = {
docs: {
description: "disallow `await` inside of loops",
category: "Possible Errors",
recommended: false,
recommended: false
},
schema: [],
schema: []
},
create(context) {
return {
@ -69,7 +69,7 @@ module.exports = {
}
}
}
},
}
};
}
};

53
tools/eslint/lib/rules/no-compare-neg-zero.js

@ -0,0 +1,53 @@
/**
* @fileoverview The rule should warn against code that tries to compare against -0.
* @author Aladdin-ADD <hh_2013@foxmail.com>
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "disallow comparing against -0",
category: "Possible Errors",
recommended: false
},
fixable: null,
schema: []
},
create(context) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Checks a given node is -0
*
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is -0.
*/
function isNegZero(node) {
return node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "Literal" && node.argument.value === 0;
}
const OPERATORS_TO_CHECK = new Set([">", ">=", "<", "<=", "==", "===", "!=", "!=="]);
return {
BinaryExpression(node) {
if (OPERATORS_TO_CHECK.has(node.operator)) {
if (isNegZero(node.left) || isNegZero(node.right)) {
context.report({
node,
message: "Do not use the '{{operator}}' operator to compare against -0.",
data: { operator: node.operator }
});
}
}
}
};
}
};

25
tools/eslint/lib/rules/no-cond-assign.js

@ -66,19 +66,6 @@ module.exports = {
return null;
}
/**
* Check whether the code represented by an AST node is enclosed in parentheses.
* @param {!Object} node The node to test.
* @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`.
*/
function isParenthesised(node) {
const previousToken = sourceCode.getTokenBefore(node),
nextToken = sourceCode.getTokenAfter(node);
return previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
}
/**
* Check whether the code represented by an AST node is enclosed in two sets of parentheses.
* @param {!Object} node The node to test.
@ -88,9 +75,9 @@ module.exports = {
const previousToken = sourceCode.getTokenBefore(node, 1),
nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
return astUtils.isParenthesised(sourceCode, node) &&
astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
}
/**
@ -101,9 +88,9 @@ module.exports = {
function testForAssign(node) {
if (node.test &&
(node.test.type === "AssignmentExpression") &&
(node.type === "ForStatement" ?
!isParenthesised(node.test) :
!isParenthesisedTwice(node.test)
(node.type === "ForStatement"
? !astUtils.isParenthesised(sourceCode, node.test)
: !isParenthesisedTwice(node.test)
)
) {

2
tools/eslint/lib/rules/no-dupe-keys.js

@ -123,7 +123,7 @@ module.exports = {
node: info.node,
loc: node.key.loc,
message: "Duplicate key '{{name}}'.",
data: { name },
data: { name }
});
}

128
tools/eslint/lib/rules/no-else-return.js

@ -5,6 +5,13 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
const FixTracker = require("../util/fix-tracker");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -17,7 +24,9 @@ module.exports = {
recommended: false
},
schema: []
schema: [],
fixable: "code"
},
create(context) {
@ -33,7 +42,66 @@ module.exports = {
* @returns {void}
*/
function displayReport(node) {
context.report({ node, message: "Unnecessary 'else' after 'return'." });
context.report({
node,
message: "Unnecessary 'else' after 'return'.",
fix: fixer => {
const sourceCode = context.getSourceCode();
const startToken = sourceCode.getFirstToken(node);
const elseToken = sourceCode.getTokenBefore(startToken);
const source = sourceCode.getText(node);
const lastIfToken = sourceCode.getTokenBefore(elseToken);
let fixedSource, firstTokenOfElseBlock;
if (startToken.type === "Punctuator" && startToken.value === "{") {
firstTokenOfElseBlock = sourceCode.getTokenAfter(startToken);
} else {
firstTokenOfElseBlock = startToken;
}
// If the if block does not have curly braces and does not end in a semicolon
// and the else block starts with (, [, /, +, ` or -, then it is not
// safe to remove the else keyword, because ASI will not add a semicolon
// after the if block
const ifBlockMaybeUnsafe = node.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";";
const elseBlockUnsafe = /^[([/+`-]/.test(firstTokenOfElseBlock.value);
if (ifBlockMaybeUnsafe && elseBlockUnsafe) {
return null;
}
const endToken = sourceCode.getLastToken(node);
const lastTokenOfElseBlock = sourceCode.getTokenBefore(endToken);
if (lastTokenOfElseBlock.value !== ";") {
const nextToken = sourceCode.getTokenAfter(endToken);
const nextTokenUnsafe = nextToken && /^[([/+`-]/.test(nextToken.value);
const nextTokenOnSameLine = nextToken && nextToken.loc.start.line === lastTokenOfElseBlock.loc.start.line;
// If the else block contents does not end in a semicolon,
// and the else block starts with (, [, /, +, ` or -, then it is not
// safe to remove the else block, because ASI will not add a semicolon
// after the remaining else block contents
if (nextTokenUnsafe || (nextTokenOnSameLine && nextToken.value !== "}")) {
return null;
}
}
if (startToken.type === "Punctuator" && startToken.value === "{") {
fixedSource = source.slice(1, -1);
} else {
fixedSource = source;
}
// Extend the replacement range to include the entire
// function to avoid conflicting with no-useless-return.
// https://github.com/eslint/eslint/issues/8026
return new FixTracker(fixer, sourceCode)
.retainEnclosingFunction(node)
.replaceTextRange([elseToken.start, node.end], fixedSource);
}
});
}
/**
@ -121,35 +189,45 @@ module.exports = {
return checkForReturnOrIf(node);
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
/**
* Check the if statement
* @returns {void}
* @param {Node} node The node for the if statement to check
* @private
*/
function IfStatement(node) {
const parent = context.getAncestors().pop();
let consequents,
alternate;
IfStatement(node) {
const parent = context.getAncestors().pop();
let consequents,
alternate;
/*
* Fixing this would require splitting one statement into two, so no error should
* be reported if this node is in a position where only one statement is allowed.
*/
if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) {
return;
}
// Only "top-level" if statements are checked, meaning the first `if`
// in a `if-else-if-...` chain.
if (parent.type === "IfStatement" && parent.alternate === node) {
for (consequents = []; node.type === "IfStatement"; node = node.alternate) {
if (!node.alternate) {
return;
}
consequents.push(node.consequent);
alternate = node.alternate;
}
for (consequents = []; node.type === "IfStatement"; node = node.alternate) {
if (!node.alternate) {
return;
}
consequents.push(node.consequent);
alternate = node.alternate;
}
if (consequents.every(alwaysReturns)) {
displayReport(alternate);
}
if (consequents.every(alwaysReturns)) {
displayReport(alternate);
}
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
"IfStatement:exit": IfStatement
};

25
tools/eslint/lib/rules/no-empty-function.js

@ -5,6 +5,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
@ -19,18 +25,6 @@ const ALLOW_OPTIONS = Object.freeze([
"setters",
"constructors"
]);
const SHOW_KIND = Object.freeze({
functions: "function",
arrowFunctions: "arrow function",
generatorFunctions: "generator function",
asyncFunctions: "async function",
methods: "method",
generatorMethods: "generator method",
asyncMethods: "async method",
getters: "getter",
setters: "setter",
constructors: "constructor"
});
/**
* Gets the kind of a given function node.
@ -137,6 +131,7 @@ module.exports = {
*/
function reportIfEmpty(node) {
const kind = getKind(node);
const name = astUtils.getFunctionNameWithKind(node);
if (allowed.indexOf(kind) === -1 &&
node.body.type === "BlockStatement" &&
@ -146,10 +141,8 @@ module.exports = {
context.report({
node,
loc: node.body.loc.start,
message: "Unexpected empty {{kind}}.",
data: {
kind: SHOW_KIND[kind]
}
message: "Unexpected empty {{name}}.",
data: { name }
});
}
}

6
tools/eslint/lib/rules/no-extend-native.js

@ -60,9 +60,9 @@ module.exports = {
return;
}
const affectsProto = lhs.object.computed ?
lhs.object.property.type === "Literal" && lhs.object.property.value === "prototype" :
lhs.object.property.name === "prototype";
const affectsProto = lhs.object.computed
? lhs.object.property.type === "Literal" && lhs.object.property.value === "prototype"
: lhs.object.property.name === "prototype";
if (!affectsProto) {
return;

7
tools/eslint/lib/rules/no-extra-bind.js

@ -8,7 +8,7 @@
// Requirements
//------------------------------------------------------------------------------
const getPropertyName = require("../ast-utils").getStaticPropertyName;
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
@ -44,8 +44,7 @@ module.exports = {
loc: node.parent.property.loc.start,
fix(fixer) {
const firstTokenToRemove = context.getSourceCode()
.getTokensBetween(node.parent.object, node.parent.property)
.find(token => token.value !== ")");
.getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]);
}
@ -73,7 +72,7 @@ module.exports = {
grandparent.arguments.length === 1 &&
parent.type === "MemberExpression" &&
parent.object === node &&
getPropertyName(parent) === "bind"
astUtils.getStaticPropertyName(parent) === "bind"
);
}

23
tools/eslint/lib/rules/no-extra-boolean-cast.js

@ -5,6 +5,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -91,7 +97,22 @@ module.exports = {
context.report({
node,
message: "Redundant Boolean call.",
fix: fixer => fixer.replaceText(node, sourceCode.getText(node.arguments[0]))
fix: fixer => {
if (!node.arguments.length) {
return fixer.replaceText(parent, "true");
}
if (node.arguments.length > 1 || node.arguments[0].type === "SpreadElement") {
return null;
}
const argument = node.arguments[0];
if (astUtils.getPrecedence(argument) < astUtils.getPrecedence(node.parent)) {
return fixer.replaceText(node, `(${sourceCode.getText(argument)})`);
}
return fixer.replaceText(node, sourceCode.getText(argument));
}
});
}
}

7
tools/eslint/lib/rules/no-extra-label.js

@ -110,12 +110,7 @@ module.exports = {
node: labelNode,
message: "This label '{{name}}' is unnecessary.",
data: labelNode,
fix(fixer) {
return fixer.replaceTextRange(
[info.label.range[0], labelNode.range[1]],
sourceCode.text.slice(info.label.parent.body.range[0], sourceCode.getFirstToken(node).range[1])
);
}
fix: fixer => fixer.removeRange([sourceCode.getFirstToken(node).range[1], labelNode.range[1]])
});
}
return;

293
tools/eslint/lib/rules/no-extra-parens.js

@ -44,7 +44,8 @@ module.exports = {
properties: {
conditionalAssign: { type: "boolean" },
nestedBinaryExpressions: { type: "boolean" },
returnAssign: { type: "boolean" }
returnAssign: { type: "boolean" },
ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] }
},
additionalProperties: false
}
@ -59,12 +60,16 @@ module.exports = {
create(context) {
const sourceCode = context.getSourceCode();
const tokensToIgnore = new WeakSet();
const isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode);
const precedence = astUtils.getPrecedence;
const ALL_NODES = context.options[0] !== "functions";
const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
/**
* Determines if this rule should be enforced for a node given the current configuration.
@ -73,6 +78,31 @@ module.exports = {
* @private
*/
function ruleApplies(node) {
if (node.type === "JSXElement") {
const isSingleLine = node.loc.start.line === node.loc.end.line;
switch (IGNORE_JSX) {
// Exclude this JSX element from linting
case "all":
return false;
// Exclude this JSX element if it is multi-line element
case "multi-line":
return isSingleLine;
// Exclude this JSX element if it is single-line element
case "single-line":
return !isSingleLine;
// Nothing special to be done for JSX elements
case "none":
break;
// no default
}
}
return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
}
@ -87,8 +117,8 @@ module.exports = {
nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) && previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
}
/**
@ -140,6 +170,19 @@ module.exports = {
return false;
}
/**
* Determines if a constructor function is newed-up with parens
* @param {ASTNode} newExpression - The NewExpression node to be checked.
* @returns {boolean} True if the constructor is called with parens.
* @private
*/
function isNewExpressionWithParens(newExpression) {
const lastToken = sourceCode.getLastToken(newExpression);
const penultimateToken = sourceCode.getTokenBefore(lastToken);
return newExpression.arguments.length > 0 || astUtils.isOpeningParenToken(penultimateToken) && astUtils.isClosingParenToken(lastToken);
}
/**
* Determines if a node is or contains an assignment expression
* @param {ASTNode} node - The node to be checked.
@ -175,9 +218,9 @@ module.exports = {
return node.argument && containsAssignment(node.argument);
} else if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
return containsAssignment(node.body);
} else {
return containsAssignment(node);
}
return containsAssignment(node);
}
/**
@ -196,69 +239,6 @@ module.exports = {
return hasDoubleExcessParens(node);
}
/**
* Checks whether or not a given node is located at the head of ExpressionStatement.
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is located at the head of ExpressionStatement.
*/
function isHeadOfExpressionStatement(node) {
let parent = node.parent;
while (parent) {
switch (parent.type) {
case "SequenceExpression":
if (parent.expressions[0] !== node || isParenthesised(node)) {
return false;
}
break;
case "UnaryExpression":
case "UpdateExpression":
if (parent.prefix || isParenthesised(node)) {
return false;
}
break;
case "BinaryExpression":
case "LogicalExpression":
if (parent.left !== node || isParenthesised(node)) {
return false;
}
break;
case "ConditionalExpression":
if (parent.test !== node || isParenthesised(node)) {
return false;
}
break;
case "CallExpression":
if (parent.callee !== node || isParenthesised(node)) {
return false;
}
break;
case "MemberExpression":
if (parent.object !== node || isParenthesised(node)) {
return false;
}
break;
case "ExpressionStatement":
return true;
default:
return false;
}
node = parent;
parent = parent.parent;
}
/* istanbul ignore next */
throw new Error("unreachable");
}
/**
* Determines whether a node should be preceded by an additional space when removing parens
* @param {ASTNode} node node to evaluate; must be surrounded by parentheses
@ -276,7 +256,7 @@ module.exports = {
}
// If the parens are preceded by a keyword (e.g. `typeof(0)`), a space should be inserted (`typeof 0`)
const precededByKeyword = tokenBeforeLeftParen.type === "Keyword";
const precededByIdentiferPart = esUtils.code.isIdentifierPartES6(tokenBeforeLeftParen.value.slice(-1).charCodeAt(0));
// However, a space should not be inserted unless the first character of the token is an identifier part
// e.g. `typeof([])` should be fixed to `typeof[]`
@ -289,7 +269,7 @@ module.exports = {
const startsWithUnaryPlus = firstToken.type === "Punctuator" && firstToken.value === "+";
const startsWithUnaryMinus = firstToken.type === "Punctuator" && firstToken.value === "-";
return (precededByKeyword && startsWithIdentifierPart) ||
return (precededByIdentiferPart && startsWithIdentifierPart) ||
(precededByUnaryPlus && startsWithUnaryPlus) ||
(precededByUnaryMinus && startsWithUnaryMinus);
}
@ -304,6 +284,10 @@ module.exports = {
const leftParenToken = sourceCode.getTokenBefore(node);
const rightParenToken = sourceCode.getTokenAfter(node);
if (tokensToIgnore.has(sourceCode.getFirstToken(node)) && !isParenthesisedTwice(node)) {
return;
}
context.report({
node,
loc: leftParenToken.loc.start,
@ -325,7 +309,11 @@ module.exports = {
* @returns {void}
* @private
*/
function dryUnaryUpdate(node) {
function checkUnaryUpdate(node) {
if (node.type === "UnaryExpression" && node.argument.type === "BinaryExpression" && node.argument.operator === "**") {
return;
}
if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) {
report(node.argument);
}
@ -337,10 +325,11 @@ module.exports = {
* @returns {void}
* @private
*/
function dryCallNew(node) {
function checkCallNew(node) {
if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !(
node.type === "CallExpression" &&
node.callee.type === "FunctionExpression" &&
(node.callee.type === "FunctionExpression" ||
node.callee.type === "NewExpression" && !isNewExpressionWithParens(node.callee)) &&
// One set of parentheses are allowed for a function expression
!hasDoubleExcessParens(node.callee)
@ -348,12 +337,12 @@ module.exports = {
report(node.callee);
}
if (node.arguments.length === 1) {
if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({ type: "AssignmentExpression" })) {
if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
report(node.arguments[0]);
}
} else {
[].forEach.call(node.arguments, arg => {
if (hasExcessParens(arg) && precedence(arg) >= precedence({ type: "AssignmentExpression" })) {
if (hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
report(arg);
}
});
@ -366,23 +355,91 @@ module.exports = {
* @returns {void}
* @private
*/
function dryBinaryLogical(node) {
function checkBinaryLogical(node) {
const prec = precedence(node);
const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression");
const leftPrecedence = precedence(node.left);
const rightPrecedence = precedence(node.right);
const isExponentiation = node.operator === "**";
const shouldSkipLeft = (NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression")) ||
node.left.type === "UnaryExpression" && isExponentiation;
const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");
if (!shouldSkipLeft && hasExcessParens(node.left) && precedence(node.left) >= prec) {
if (!shouldSkipLeft && hasExcessParens(node.left) && (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation))) {
report(node.left);
}
if (!shouldSkipRight && hasExcessParens(node.right) && precedence(node.right) > prec) {
if (!shouldSkipRight && hasExcessParens(node.right) && (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation))) {
report(node.right);
}
}
/**
* Check the parentheses around the super class of the given class definition.
* @param {ASTNode} node The node of class declarations to check.
* @returns {void}
*/
function checkClass(node) {
if (!node.superClass) {
return;
}
// If `node.superClass` is a LeftHandSideExpression, parentheses are extra.
// Otherwise, parentheses are needed.
const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR
? hasExcessParens(node.superClass)
: hasDoubleExcessParens(node.superClass);
if (hasExtraParens) {
report(node.superClass);
}
}
/**
* Check the parentheses around the argument of the given spread operator.
* @param {ASTNode} node The node of spread elements/properties to check.
* @returns {void}
*/
function checkSpreadOperator(node) {
const hasExtraParens = precedence(node.argument) >= PRECEDENCE_OF_ASSIGNMENT_EXPR
? hasExcessParens(node.argument)
: hasDoubleExcessParens(node.argument);
if (hasExtraParens) {
report(node.argument);
}
}
/**
* Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration
* @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node
* @returns {void}
*/
function checkExpressionOrExportStatement(node) {
const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);
const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken);
if (
astUtils.isOpeningParenToken(firstToken) &&
(
astUtils.isOpeningBraceToken(secondToken) ||
secondToken.type === "Keyword" && (
secondToken.value === "function" ||
secondToken.value === "class" ||
secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken))
)
)
) {
tokensToIgnore.add(secondToken);
}
if (hasExcessParens(node)) {
report(node);
}
}
return {
ArrayExpression(node) {
[].forEach.call(node.elements, e => {
if (e && hasExcessParens(e) && precedence(e) >= precedence({ type: "AssignmentExpression" })) {
if (e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
report(e);
}
});
@ -394,13 +451,13 @@ module.exports = {
}
if (node.body.type !== "BlockStatement") {
if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({ type: "AssignmentExpression" })) {
report(node.body);
return;
}
const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken);
const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);
// Object literals *must* be parenthesised
if (node.body.type === "ObjectExpression" && hasDoubleExcessParens(node.body)) {
if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) {
tokensToIgnore.add(firstBodyToken);
}
if (hasExcessParens(node.body) && precedence(node.body) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
report(node.body);
}
}
@ -416,8 +473,8 @@ module.exports = {
}
},
BinaryExpression: dryBinaryLogical,
CallExpression: dryCallNew,
BinaryExpression: checkBinaryLogical,
CallExpression: checkCallNew,
ConditionalExpression(node) {
if (isReturnAssignException(node)) {
@ -428,11 +485,11 @@ module.exports = {
report(node.test);
}
if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({ type: "AssignmentExpression" })) {
if (hasExcessParens(node.consequent) && precedence(node.consequent) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
report(node.consequent);
}
if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({ type: "AssignmentExpression" })) {
if (hasExcessParens(node.alternate) && precedence(node.alternate) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
report(node.alternate);
}
},
@ -443,27 +500,8 @@ module.exports = {
}
},
ExpressionStatement(node) {
if (hasExcessParens(node.expression)) {
const firstTokens = sourceCode.getFirstTokens(node.expression, 2);
const firstToken = firstTokens[0];
const secondToken = firstTokens[1];
if (
!firstToken ||
firstToken.value !== "{" &&
firstToken.value !== "function" &&
firstToken.value !== "class" &&
(
firstToken.value !== "let" ||
!secondToken ||
secondToken.value !== "["
)
) {
report(node.expression);
}
}
},
ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
ForInStatement(node) {
if (hasExcessParens(node.right)) {
@ -497,7 +535,7 @@ module.exports = {
}
},
LogicalExpression: dryBinaryLogical,
LogicalExpression: checkBinaryLogical,
MemberExpression(node) {
if (
@ -506,19 +544,11 @@ module.exports = {
(
node.computed ||
!(
(node.object.type === "Literal" &&
typeof node.object.value === "number" &&
astUtils.isDecimalInteger(node.object))
||
astUtils.isDecimalInteger(node.object) ||
// RegExp literal is allowed to have parens (#1589)
(node.object.type === "Literal" && node.object.regex)
)
) &&
!(
(node.object.type === "FunctionExpression" || node.object.type === "ClassExpression") &&
isHeadOfExpressionStatement(node) &&
!hasDoubleExcessParens(node.object)
)
) {
report(node.object);
@ -528,13 +558,13 @@ module.exports = {
}
},
NewExpression: dryCallNew,
NewExpression: checkCallNew,
ObjectExpression(node) {
[].forEach.call(node.properties, e => {
const v = e.value;
if (v && hasExcessParens(v) && precedence(v) >= precedence({ type: "AssignmentExpression" })) {
if (v && hasExcessParens(v) && precedence(v) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
report(v);
}
});
@ -584,13 +614,13 @@ module.exports = {
}
},
UnaryExpression: dryUnaryUpdate,
UpdateExpression: dryUnaryUpdate,
AwaitExpression: dryUnaryUpdate,
UnaryExpression: checkUnaryUpdate,
UpdateExpression: checkUnaryUpdate,
AwaitExpression: checkUnaryUpdate,
VariableDeclarator(node) {
if (node.init && hasExcessParens(node.init) &&
precedence(node.init) >= precedence({ type: "AssignmentExpression" }) &&
precedence(node.init) >= PRECEDENCE_OF_ASSIGNMENT_EXPR &&
// RegExp literal is allowed to have parens (#1589)
!(node.init.type === "Literal" && node.init.regex)) {
@ -620,7 +650,14 @@ module.exports = {
report(node.argument);
}
}
}
},
ClassDeclaration: checkClass,
ClassExpression: checkClass,
SpreadElement: checkSpreadOperator,
SpreadProperty: checkSpreadOperator,
ExperimentalSpreadProperty: checkSpreadOperator
};
}

19
tools/eslint/lib/rules/no-extra-semi.js

@ -5,6 +5,13 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const FixTracker = require("../util/fix-tracker");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -34,7 +41,13 @@ module.exports = {
node: nodeOrToken,
message: "Unnecessary semicolon.",
fix(fixer) {
return fixer.remove(nodeOrToken);
// Expand the replacement range to include the surrounding
// tokens to avoid conflicting with semi.
// https://github.com/eslint/eslint/issues/7928
return new FixTracker(fixer, context.getSourceCode())
.retainSurroundingTokens(nodeOrToken)
.remove(nodeOrToken);
}
});
}
@ -48,10 +61,10 @@ module.exports = {
*/
function checkForPartOfClassBody(firstToken) {
for (let token = firstToken;
token.type === "Punctuator" && token.value !== "}";
token.type === "Punctuator" && !astUtils.isClosingBraceToken(token);
token = sourceCode.getTokenAfter(token)
) {
if (token.value === ";") {
if (astUtils.isSemicolonToken(token)) {
report(token);
}
}

2
tools/eslint/lib/rules/no-global-assign.js

@ -14,7 +14,7 @@ module.exports = {
docs: {
description: "disallow assignments to native objects or read-only global variables",
category: "Best Practices",
recommended: false
recommended: true
},
schema: [

29
tools/eslint/lib/rules/no-implicit-coercion.js

@ -6,6 +6,7 @@
"use strict";
const astUtils = require("../ast-utils");
const esUtils = require("esutils");
//------------------------------------------------------------------------------
// Helpers
@ -197,19 +198,31 @@ module.exports = {
*/
function report(node, recommendation, shouldFix) {
shouldFix = typeof shouldFix === "undefined" ? true : shouldFix;
const reportObj = {
context.report({
node,
message: "use `{{recommendation}}` instead.",
data: {
recommendation
},
fix(fixer) {
if (!shouldFix) {
return null;
}
const tokenBefore = sourceCode.getTokenBefore(node);
if (
tokenBefore &&
tokenBefore.range[1] === node.range[0] &&
esUtils.code.isIdentifierPartES6(tokenBefore.value.slice(-1).charCodeAt(0)) &&
esUtils.code.isIdentifierPartES6(recommendation.charCodeAt(0))
) {
return fixer.replaceText(node, ` ${recommendation}`);
}
return fixer.replaceText(node, recommendation);
}
};
if (shouldFix) {
reportObj.fix = fixer => fixer.replaceText(node, recommendation);
}
context.report(reportObj);
});
}
return {

8
tools/eslint/lib/rules/no-inner-declarations.js

@ -64,10 +64,10 @@ module.exports = {
if (!valid) {
context.report({ node, message: "Move {{type}} declaration to {{body}} root.", data: {
type: (node.type === "FunctionDeclaration" ?
"function" : "variable"),
body: (body.type === "Program" ?
"program" : "function body")
type: (node.type === "FunctionDeclaration"
? "function" : "variable"),
body: (body.type === "Program"
? "program" : "function body")
} });
}
}

3
tools/eslint/lib/rules/no-invalid-regexp.js

@ -74,7 +74,8 @@ module.exports = {
} catch (e) {
context.report({
node,
message: `${e.message}.`
message: "{{message}}.",
data: e
});
}

8
tools/eslint/lib/rules/no-irregular-whitespace.js

@ -6,6 +6,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
@ -13,7 +19,7 @@
const ALL_IRREGULARS = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/;
const IRREGULAR_WHITESPACE = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg;
const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mg;
const LINE_BREAK = /\r\n|\r|\n|\u2028|\u2029/g;
const LINE_BREAK = astUtils.createGlobalLinebreakMatcher();
//------------------------------------------------------------------------------
// Rule Definition

20
tools/eslint/lib/rules/no-lone-blocks.js

@ -32,22 +32,22 @@ module.exports = {
* @returns {void}
*/
function report(node) {
const parent = context.getAncestors().pop();
const message = node.parent.type === "BlockStatement" ? "Nested block is redundant." : "Block is redundant.";
context.report({ node, message: parent.type === "Program" ?
"Block is redundant." :
"Nested block is redundant."
});
context.report({ node, message });
}
/**
* Checks for any ocurrence of BlockStatement > BlockStatement or Program > BlockStatement
* @returns {boolean} True if the current node is a lone block.
* Checks for any ocurrence of a BlockStatement in a place where lists of statements can appear
* @param {ASTNode} node The node to check
* @returns {boolean} True if the node is a lone block.
*/
function isLoneBlock() {
const parent = context.getAncestors().pop();
function isLoneBlock(node) {
return node.parent.type === "BlockStatement" ||
node.parent.type === "Program" ||
return parent.type === "BlockStatement" || parent.type === "Program";
// Don't report blocks in switch cases if the block is the only statement of the case.
node.parent.type === "SwitchCase" && !(node.parent.consequent[0] === node && node.parent.consequent.length === 1);
}
/**

8
tools/eslint/lib/rules/no-mixed-operators.js

@ -148,13 +148,7 @@ module.exports = {
* @returns {Token} The operator token of the node.
*/
function getOperatorToken(node) {
let token = sourceCode.getTokenAfter(node.left);
while (token.value === ")") {
token = sourceCode.getTokenAfter(token);
}
return token;
return sourceCode.getTokenAfter(node.left, astUtils.isNotClosingParenToken);
}
/**

8
tools/eslint/lib/rules/no-mixed-requires.js

@ -153,11 +153,11 @@ module.exports = {
// "var utils = require('./utils');"
return REQ_FILE;
} else {
// "var async = require('async');"
return REQ_MODULE;
}
// "var async = require('async');"
return REQ_MODULE;
}
/**

41
tools/eslint/lib/rules/no-multi-assign.js

@ -0,0 +1,41 @@
/**
* @fileoverview Rule to check use of chained assignment expressions
* @author Stewart Rand
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "disallow use of chained assignment expressions",
category: "Stylistic Issues",
recommended: false
},
schema: []
},
create(context) {
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
AssignmentExpression(node) {
if (["AssignmentExpression", "VariableDeclarator"].indexOf(node.parent.type) !== -1) {
context.report({
node,
message: "Unexpected chained assignment."
});
}
}
};
}
};

5
tools/eslint/lib/rules/no-multi-spaces.js

@ -5,6 +5,8 @@
"use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -93,7 +95,8 @@ module.exports = {
const sourceCode = context.getSourceCode(),
source = sourceCode.getText(),
allComments = sourceCode.getAllComments(),
pattern = /[^\n\r\u2028\u2029\t ].? {2,}/g; // note: repeating space
JOINED_LINEBEAKS = Array.from(astUtils.LINEBREAKS).join(""),
pattern = new RegExp(String.raw`[^ \t${JOINED_LINEBEAKS}].? {2,}`, "g"); // note: repeating space
let parent;

10
tools/eslint/lib/rules/no-multi-str.js

@ -5,6 +5,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -39,9 +45,7 @@ module.exports = {
return {
Literal(node) {
const lineBreak = /\n/;
if (lineBreak.test(node.raw) && !isJSXElement(node.parent)) {
if (astUtils.LINEBREAK_MATCHER.test(node.raw) && !isJSXElement(node.parent)) {
context.report({ node, message: "Multiline support is limited to browsers supporting ES5 only." });
}
}

6
tools/eslint/lib/rules/no-multiple-empty-lines.js

@ -5,8 +5,6 @@
*/
"use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -114,8 +112,8 @@ module.exports = {
data: { max: maxAllowed, pluralizedLines: maxAllowed === 1 ? "line" : "lines" },
fix(fixer) {
return fixer.removeRange([
astUtils.getRangeIndexFromLocation(sourceCode, { line: lastLineNumber + 1, column: 0 }),
astUtils.getRangeIndexFromLocation(sourceCode, { line: lineNumber - maxAllowed, column: 0 })
sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 }),
sourceCode.getIndexFromLoc({ line: lineNumber - maxAllowed, column: 0 })
]);
}
});

2
tools/eslint/lib/rules/no-native-reassign.js

@ -15,7 +15,7 @@ module.exports = {
docs: {
description: "disallow assignments to native objects or read-only global variables",
category: "Best Practices",
recommended: true,
recommended: false,
replacedBy: ["no-global-assign"]
},

2
tools/eslint/lib/rules/no-negated-in-lhs.js

@ -15,7 +15,7 @@ module.exports = {
docs: {
description: "disallow negating the left operand in `in` expressions",
category: "Possible Errors",
recommended: true,
recommended: false,
replacedBy: ["no-unsafe-negation"]
},
deprecated: true,

14
tools/eslint/lib/rules/no-new-func.js

@ -27,20 +27,18 @@ module.exports = {
//--------------------------------------------------------------------------
/**
* Checks if the callee is the Function constructor, and if so, reports an issue.
* @param {ASTNode} node The node to check and report on
* Reports a node.
* @param {ASTNode} node The node to report
* @returns {void}
* @private
*/
function validateCallee(node) {
if (node.callee.name === "Function") {
context.report({ node, message: "The Function constructor is eval." });
}
function report(node) {
context.report({ node, message: "The Function constructor is eval." });
}
return {
NewExpression: validateCallee,
CallExpression: validateCallee
"NewExpression[callee.name = 'Function']": report,
"CallExpression[callee.name = 'Function']": report
};
}

8
tools/eslint/lib/rules/no-new.js

@ -24,12 +24,8 @@ module.exports = {
create(context) {
return {
ExpressionStatement(node) {
if (node.expression.type === "NewExpression") {
context.report({ node, message: "Do not use 'new' for side effects." });
}
"ExpressionStatement > NewExpression"(node) {
context.report({ node: node.parent, message: "Do not use 'new' for side effects." });
}
};

44
tools/eslint/lib/rules/no-param-reassign.js

@ -20,17 +20,40 @@ module.exports = {
schema: [
{
type: "object",
properties: {
props: { type: "boolean" }
},
additionalProperties: false
oneOf: [
{
type: "object",
properties: {
props: {
enum: [false]
}
},
additionalProperties: false
},
{
type: "object",
properties: {
props: {
enum: [true]
},
ignorePropertyModificationsFor: {
type: "array",
items: {
type: "string"
},
uniqueItems: true
}
},
additionalProperties: false
}
]
}
]
},
create(context) {
const props = context.options[0] && Boolean(context.options[0].props);
const ignoredPropertyAssignmentsFor = context.options[0] && context.options[0].ignorePropertyModificationsFor || [];
/**
* Checks whether or not the reference modifies properties of its variable.
@ -73,8 +96,15 @@ module.exports = {
}
break;
default:
// EXCLUDES: e.g. ({ [foo]: a }) = bar;
case "Property":
if (parent.key === node) {
return false;
}
break;
// no default
}
node = parent;
@ -103,7 +133,7 @@ module.exports = {
) {
if (reference.isWrite()) {
context.report({ node: identifier, message: "Assignment to function parameter '{{name}}'.", data: { name: identifier.name } });
} else if (props && isModifyingProp(reference)) {
} else if (props && isModifyingProp(reference) && ignoredPropertyAssignmentsFor.indexOf(identifier.name) === -1) {
context.report({ node: identifier, message: "Assignment to property of function parameter '{{name}}'.", data: { name: identifier.name } });
}
}

12
tools/eslint/lib/rules/no-process-exit.js

@ -26,17 +26,9 @@ module.exports = {
//--------------------------------------------------------------------------
return {
CallExpression(node) {
const callee = node.callee;
if (callee.type === "MemberExpression" && callee.object.name === "process" &&
callee.property.name === "exit"
) {
context.report({ node, message: "Don't use process.exit(); throw an error instead." });
}
"CallExpression > MemberExpression.callee[object.name = 'process'][property.name = 'exit']"(node) {
context.report({ node: node.parent, message: "Don't use process.exit(); throw an error instead." });
}
};
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save