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

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

@ -1,7 +1,81 @@
{ /**
"parser": "espree", * @fileoverview Configuration applied when a user configuration extends from
"ecmaFeatures": {}, * eslint:recommended.
"rules": { * @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-alert": "off",
"no-array-constructor": "off", "no-array-constructor": "off",
"no-await-in-loop": "off", "no-await-in-loop": "off",
@ -10,6 +84,7 @@
"no-case-declarations": "error", "no-case-declarations": "error",
"no-catch-shadow": "off", "no-catch-shadow": "off",
"no-class-assign": "error", "no-class-assign": "error",
"no-compare-neg-zero": "off",
"no-cond-assign": "error", "no-cond-assign": "error",
"no-confusing-arrow": "off", "no-confusing-arrow": "off",
"no-console": "error", "no-console": "error",
@ -61,6 +136,7 @@
"no-mixed-operators": "off", "no-mixed-operators": "off",
"no-mixed-requires": "off", "no-mixed-requires": "off",
"no-mixed-spaces-and-tabs": "error", "no-mixed-spaces-and-tabs": "error",
"no-multi-assign": "off",
"no-multi-spaces": "off", "no-multi-spaces": "off",
"no-multi-str": "off", "no-multi-str": "off",
"no-multiple-empty-lines": "off", "no-multiple-empty-lines": "off",
@ -99,20 +175,20 @@
"no-sequences": "off", "no-sequences": "off",
"no-shadow": "off", "no-shadow": "off",
"no-shadow-restricted-names": "off", "no-shadow-restricted-names": "off",
"no-whitespace-before-property": "off",
"no-spaced-func": "off", "no-spaced-func": "off",
"no-sparse-arrays": "error", "no-sparse-arrays": "error",
"no-sync": "off", "no-sync": "off",
"no-tabs": "off", "no-tabs": "off",
"no-template-curly-in-string": "off",
"no-ternary": "off", "no-ternary": "off",
"no-trailing-spaces": "off",
"no-this-before-super": "error", "no-this-before-super": "error",
"no-throw-literal": "off", "no-throw-literal": "off",
"no-trailing-spaces": "off",
"no-undef": "error", "no-undef": "error",
"no-undef-init": "off", "no-undef-init": "off",
"no-undefined": "off", "no-undefined": "off",
"no-unexpected-multiline": "error",
"no-underscore-dangle": "off", "no-underscore-dangle": "off",
"no-unexpected-multiline": "error",
"no-unmodified-loop-condition": "off", "no-unmodified-loop-condition": "off",
"no-unneeded-ternary": "off", "no-unneeded-ternary": "off",
"no-unreachable": "error", "no-unreachable": "error",
@ -129,70 +205,12 @@
"no-useless-escape": "off", "no-useless-escape": "off",
"no-useless-rename": "off", "no-useless-rename": "off",
"no-useless-return": "off", "no-useless-return": "off",
"no-void": "off",
"no-var": "off", "no-var": "off",
"no-void": "off",
"no-warning-comments": "off", "no-warning-comments": "off",
"no-whitespace-before-property": "off",
"no-with": "off", "no-with": "off",
"array-bracket-spacing": "off", "nonblock-statement-body-position": "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",
"object-curly-newline": "off", "object-curly-newline": "off",
"object-curly-spacing": ["off", "never"], "object-curly-spacing": ["off", "never"],
"object-property-newline": "off", "object-property-newline": "off",
@ -206,6 +224,7 @@
"prefer-const": "off", "prefer-const": "off",
"prefer-destructuring": "off", "prefer-destructuring": "off",
"prefer-numeric-literals": "off", "prefer-numeric-literals": "off",
"prefer-promise-reject-errors": "off",
"prefer-reflect": "off", "prefer-reflect": "off",
"prefer-rest-params": "off", "prefer-rest-params": "off",
"prefer-spread": "off", "prefer-spread": "off",
@ -219,8 +238,8 @@
"rest-spread-spacing": "off", "rest-spread-spacing": "off",
"semi": "off", "semi": "off",
"semi-spacing": "off", "semi-spacing": "off",
"sort-keys": "off",
"sort-imports": "off", "sort-imports": "off",
"sort-keys": "off",
"sort-vars": "off", "sort-vars": "off",
"space-before-blocks": "off", "space-before-blocks": "off",
"space-before-function-paren": "off", "space-before-function-paren": "off",
@ -231,6 +250,7 @@
"strict": "off", "strict": "off",
"symbol-description": "off", "symbol-description": "off",
"template-curly-spacing": "off", "template-curly-spacing": "off",
"template-tag-spacing": "off",
"unicode-bom": "off", "unicode-bom": "off",
"use-isnan": "error", "use-isnan": "error",
"valid-jsdoc": "off", "valid-jsdoc": "off",
@ -238,8 +258,7 @@
"vars-on-top": "off", "vars-on-top": "off",
"wrap-iife": "off", "wrap-iife": "off",
"wrap-regex": "off", "wrap-regex": "off",
"no-template-curly-in-string": "off",
"yield-star-spacing": "off", "yield-star-spacing": "off",
"yoda": "off" "yoda": "off"
} }
} };

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

@ -10,7 +10,6 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const esutils = require("esutils"); const esutils = require("esutils");
const lodash = require("lodash");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
@ -24,6 +23,14 @@ const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/;
const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/; const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/;
const thisTagPattern = /^[\s*]*@this/m; 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. * Checks reference if is non initializer and writable.
* @param {Reference} reference - A reference to check. * @param {Reference} reference - A reference to check.
@ -142,7 +149,7 @@ function isInLoop(node) {
*/ */
function isNullOrUndefined(node) { function isNullOrUndefined(node) {
return ( return (
(node.type === "Literal" && node.value === null) || module.exports.isNullLiteral(node) ||
(node.type === "Identifier" && node.name === "undefined") || (node.type === "Identifier" && node.name === "undefined") ||
(node.type === "UnaryExpression" && node.operator === "void") (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. * @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) { function isReflectApply(node) {
return ( return (
@ -210,6 +217,15 @@ function isMethodWhichHasThisArg(node) {
return false; 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. * Checks whether or not a node has a `@this` tag in its comments.
* @param {ASTNode} node - A node to check. * @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 {Token} token - The token to check.
* @param {SourceCode} sourceCode - The source code object to get tokens. * @returns {boolean} `true` if the token is an arrow token.
* @returns {Token} `=>` token.
*/ */
function getArrowToken(node, sourceCode) { function isArrowToken(token) {
let token = sourceCode.getTokenBefore(node.body); 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 {Token} token - The token to check.
* @param {SourceCode} sourceCode - The source code object to get tokens. * @returns {boolean} `true` if the token is a colon token.
* @returns {Token} `(` token.
*/ */
function getOpeningParenOfParams(node, sourceCode) { function isColonToken(token) {
let token = node.id ? sourceCode.getTokenAfter(node.id) : sourceCode.getFirstToken(node); 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`. * Checks if the given token is an opening brace token or not.
* @param {SourceCode} sourceCode A sourceCode object *
* @returns {number[]} The indices of the first characters in the each of the lines of the code * @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) || []) * Checks if the given token is a closing brace token or not.
.reduce((indices, line) => indices.concat(indices[indices.length - 1] + line.length), [0]); *
* @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); * Checks if the given token is a comment token or not.
} *
return lineIndexCache.get(sourceCode); * @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 = { module.exports = {
COMMENTS_IGNORE_PATTERN,
LINEBREAKS,
LINEBREAK_MATCHER,
STATEMENT_LIST_PARENTS,
/** /**
* Determines whether two adjacent tokens are on the same line. * Determines whether two adjacent tokens are on the same line.
@ -325,6 +434,29 @@ module.exports = {
isInLoop, isInLoop,
isArrayFromMethod, isArrayFromMethod,
isParenthesised, 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. * Checks whether or not a given node is a string literal.
@ -658,6 +790,8 @@ module.exports = {
case "/": case "/":
case "%": case "%":
return 13; return 13;
case "**":
return 15;
// no default // no default
} }
@ -666,10 +800,10 @@ module.exports = {
case "UnaryExpression": case "UnaryExpression":
case "AwaitExpression": case "AwaitExpression":
return 14; return 16;
case "UpdateExpression": case "UpdateExpression":
return 15; return 17;
case "CallExpression": case "CallExpression":
@ -677,14 +811,14 @@ module.exports = {
if (node.callee.type === "FunctionExpression") { if (node.callee.type === "FunctionExpression") {
return -1; return -1;
} }
return 16; return 18;
case "NewExpression": case "NewExpression":
return 17; return 19;
// no default // no default
} }
return 18; return 20;
}, },
/** /**
@ -1023,7 +1157,7 @@ module.exports = {
let end = null; let end = null;
if (node.type === "ArrowFunctionExpression") { if (node.type === "ArrowFunctionExpression") {
const arrowToken = getArrowToken(node, sourceCode); const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken);
start = arrowToken.loc.start; start = arrowToken.loc.start;
end = arrowToken.loc.end; end = arrowToken.loc.end;
@ -1037,42 +1171,10 @@ module.exports = {
return { return {
start: Object.assign({}, start), 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 * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses
* surrounding the node. * surrounding the node.
@ -1097,5 +1199,58 @@ module.exports = {
} }
return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]); 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; 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; break;
} }
/* // Emits onCodePathSegmentStart events if updated.
* Skip updating the current segment to avoid creating useless segments if if (!dontForward) {
* 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.
forwardCurrentToHead(analyzer, node); forwardCurrentToHead(analyzer, node);
} }
debug.dumpState(node, state, true); 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. * Creates the next path from own true/false fork context.
*/ */
const prevForkContext = const prevForkContext =
context.kind === "&&" ? context.trueForkContext : context.kind === "&&" ? context.trueForkContext
/* kind === "||" */ context.falseForkContext; /* kind === "||" */ : context.falseForkContext;
forkContext.replaceHead(prevForkContext.makeNext(0, -1)); forkContext.replaceHead(prevForkContext.makeNext(0, -1));
prevForkContext.clear(); 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"; text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
} }
if (segment.internal.nodes.length > 0) { if (segment.internal.nodes.length > 0 || segment.internal.exitNodes.length > 0) {
text += segment.internal.nodes.map(node => { text += [].concat(
switch (node.type) { segment.internal.nodes.map(node => {
case "Identifier": return `${node.type} (${node.name})`; switch (node.type) {
case "Literal": return `${node.type} (${node.value})`; case "Identifier": return `${node.type} (${node.name})`;
default: return node.type; 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 => { segment.internal.exitNodes.map(node => {
switch (node.type) { switch (node.type) {
case "Identifier": return `${node.type}:exit (${node.name})`; case "Identifier": return `${node.type}:exit (${node.name})`;
case "Literal": return `${node.type}:exit (${node.value})`; case "Literal": return `${node.type}:exit (${node.value})`;
default: return `${node.type}:exit`; default: return `${node.type}:exit`;
} }
}).join("\\n"); })
).join("\\n");
} else { } else {
text += "????"; 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 * Build a config object merging the base config (conf/eslint-recommended),
* environments config (conf/environments.js) and eventually the user config. * 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 * @param {string} filePath a file in whose directory we start looking for a local config
* @returns {Object} config object * @returns {Object} config object
*/ */

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

@ -13,7 +13,7 @@ const lodash = require("lodash"),
eslint = require("../eslint"), eslint = require("../eslint"),
configRule = require("./config-rule"), configRule = require("./config-rule"),
ConfigOps = require("./config-ops"), ConfigOps = require("./config-ops"),
recConfig = require("../../conf/eslint.json"); recConfig = require("../../conf/eslint-recommended");
const debug = require("debug")("eslint:autoconfig"); 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 * 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. * any rules, only methods. This will be useful for building up registries manually.
* *
* @constructor * Registry class
* @class Registry
* @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations
*/ */
function Registry(rulesConfig) { class Registry {
this.rules = (rulesConfig) ? makeRegistryItems(rulesConfig) : {};
}
Registry.prototype = {
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. * Populate the registry with core rule configs.
@ -89,7 +88,7 @@ Registry.prototype = {
const rulesConfig = configRule.createCoreRuleConfigs(); const rulesConfig = configRule.createCoreRuleConfigs();
this.rules = makeRegistryItems(rulesConfig); this.rules = makeRegistryItems(rulesConfig);
}, }
/** /**
* Creates sets of rule configurations which can be used for linting * Creates sets of rule configurations which can be used for linting
@ -156,7 +155,7 @@ Registry.prototype = {
} }
return ruleSets; return ruleSets;
}, }
/** /**
* Remove all items from the registry with a non-zero number of errors * Remove all items from the registry with a non-zero number of errors
@ -182,7 +181,7 @@ Registry.prototype = {
}); });
return newRegistry; return newRegistry;
}, }
/** /**
* Removes rule configurations which were not included in a ruleSet * Removes rule configurations which were not included in a ruleSet
@ -199,7 +198,7 @@ Registry.prototype = {
}); });
return newRegistry; return newRegistry;
}, }
/** /**
* Creates a registry of rules which had no error-free configs. * Creates a registry of rules which had no error-free configs.
@ -221,7 +220,7 @@ Registry.prototype = {
}); });
return failingRegistry; return failingRegistry;
}, }
/** /**
* Create an eslint config for any rules which only have one configuration * Create an eslint config for any rules which only have one configuration
@ -240,7 +239,7 @@ Registry.prototype = {
}); });
return config; return config;
}, }
/** /**
* Return a cloned registry containing only configs with a desired specificity * Return a cloned registry containing only configs with a desired specificity
@ -258,7 +257,7 @@ Registry.prototype = {
}); });
return newRegistry; return newRegistry;
}, }
/** /**
* Lint SourceCodes against all configurations in the registry, and record results * 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 // It is possible that the error is from a configuration comment
// in a linted file, in which case there may not be a config // in a linted file, in which case there may not be a config
// set in this ruleSetIdx. (https://github.com/eslint/eslint/issues/5992) // set in this ruleSetIdx.
if (lintedRegistry.rules[result.ruleId][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; lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
} }
}); });
@ -316,7 +320,7 @@ Registry.prototype = {
return lintedRegistry; return lintedRegistry;
} }
}; }
/** /**
* Extract rule configuration into eslint:recommended where possible. * 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"), stripBom = require("strip-bom"),
stripComments = require("strip-json-comments"), stripComments = require("strip-json-comments"),
stringify = require("json-stable-stringify"), stringify = require("json-stable-stringify"),
defaultOptions = require("../../conf/eslint.json"), defaultOptions = require("../../conf/eslint-recommended"),
requireUncached = require("require-uncached"); requireUncached = require("require-uncached");
const debug = require("debug")("eslint:config-file"); 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 * Loads a configuration file regardless of the source. Inspects the file path
* to determine the correctly way to load the config file. * to determine the correctly way to load the config file.
@ -199,6 +215,9 @@ function loadConfigFile(file) {
config = loadJSConfigFile(filePath); config = loadJSConfigFile(filePath);
if (file.configName) { if (file.configName) {
config = config.configs[file.configName]; config = config.configs[file.configName];
if (!config) {
throw configMissingError(file.configFullName);
}
} }
break; break;
@ -340,6 +359,33 @@ function getLookupPath(configFilePath) {
return path.join(basedir, "node_modules"); 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. * Applies values from the "extends" field in a configuration file.
* @param {Object} config The configuration information. * @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 // Make the last element in an array take the highest precedence
config = configExtends.reduceRight((previousValue, parentPath) => { 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 { 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}`); debug(`Loading ${parentPath}`);
return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue); return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue);
} catch (e) { } catch (e) {
@ -455,30 +488,34 @@ function normalizePackageName(name, prefix) {
* or package name. * or package name.
* @param {string} filePath The filepath to resolve. * @param {string} filePath The filepath to resolve.
* @param {string} [relativeTo] The path to resolve relative to. * @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 * @private
*/ */
function resolve(filePath, relativeTo) { function resolve(filePath, relativeTo) {
if (isFilePath(filePath)) { if (isFilePath(filePath)) {
return { filePath: path.resolve(relativeTo || "", 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"), ConfigOps = require("./config-ops"),
getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles, getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles,
npmUtil = require("../util/npm-util"), npmUtil = require("../util/npm-util"),
recConfig = require("../../conf/eslint.json"), recConfig = require("../../conf/eslint-recommended"),
log = require("../logging"); log = require("../logging");
const debug = require("debug")("eslint:config-initializer"); const debug = require("debug")("eslint:config-initializer");
@ -317,7 +317,7 @@ function promptUser(callback) {
default: false, default: false,
when(answers) { when(answers) {
return answers.styleguide === "airbnb"; return answers.styleguide === "airbnb";
}, }
}, },
{ {
type: "input", 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"]] * ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]]
* *
* @param {ruleConfig[]} configs Valid rule configurations * Rule configuration set class
* @constructor
*/ */
function RuleConfigSet(configs) { class RuleConfigSet {
/** /**
* Stored valid rule configurations for this instance * @param {ruleConfig[]} configs Valid rule configurations
* @type {array} */
*/ constructor(configs) {
this.ruleConfigs = configs || [];
/**
} * Stored valid rule configurations for this instance
* @type {array}
RuleConfigSet.prototype = { */
this.ruleConfigs = configs || [];
constructor: RuleConfigSet, }
/** /**
* Add a severity level to the front of all configs in the instance. * 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 // Add a single config at the beginning consisting of only the severity
this.ruleConfigs.unshift(severity); this.ruleConfigs.unshift(severity);
}, }
/** /**
* Add rule configs from an array of strings (schema enums) * Add rule configs from an array of strings (schema enums)
@ -219,12 +218,12 @@ RuleConfigSet.prototype = {
*/ */
addEnums(enums) { addEnums(enums) {
this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, enums)); this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, enums));
}, }
/** /**
* Add rule configurations from a schema object * Add rule configurations from a schema object
* @param {Object} obj Schema item with type === "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) { addObject(obj) {
const objectConfigSet = { const objectConfigSet = {
@ -259,9 +258,12 @@ RuleConfigSet.prototype = {
if (objectConfigSet.objectConfigs.length > 0) { if (objectConfigSet.objectConfigs.length > 0) {
this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, objectConfigSet.objectConfigs)); this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, objectConfigSet.objectConfigs));
return true;
} }
return false;
} }
}; }
/** /**
* Generate valid rule configurations based on a schema object * Generate valid rule configurations based on a schema object
@ -272,20 +274,21 @@ function generateConfigsFromSchema(schema) {
const configSet = new RuleConfigSet(); const configSet = new RuleConfigSet();
if (Array.isArray(schema)) { if (Array.isArray(schema)) {
schema.forEach(opt => { for (const opt of schema) {
if (opt.enum) { if (opt.enum) {
configSet.addEnums(opt.enum); configSet.addEnums(opt.enum);
} } else if (opt.type && opt.type === "object") {
if (!configSet.addObject(opt)) {
if (opt.type && opt.type === "object") { break;
configSet.addObject(opt); }
}
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(); configSet.addErrorSeverity();
return configSet.ruleConfigs; return configSet.ruleConfigs;

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

@ -40,13 +40,13 @@ function getRuleOptionsSchema(id) {
minItems: 0, minItems: 0,
maxItems: schema.length 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 // Given a full schema, leave it alone

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

@ -127,14 +127,25 @@ module.exports = {
if (!plugins[shortName]) { if (!plugins[shortName]) {
try { try {
plugin = require(longName); plugin = require(longName);
} catch (err) { } catch (pluginLoadErr) {
debug(`Failed to load plugin ${longName}.`); try {
err.message = `Failed to load plugin ${pluginName}: ${err.message}`;
err.messageTemplate = "plugin-missing"; // Check whether the plugin exists
err.messageData = { require.resolve(longName);
pluginName: longName } catch (missingPluginErr) {
};
throw err; // 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); this.define(pluginName, plugin);

57
tools/eslint/lib/eslint.js

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

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

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

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

@ -17,9 +17,9 @@
function getMessageType(message) { function getMessageType(message) {
if (message.fatal || message.severity === 2) { if (message.fatal || message.severity === 2) {
return "Error"; 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) { function getMessageType(message) {
if (message.fatal || message.severity === 2) { if (message.fatal || message.severity === 2) {
return "Error"; 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) { module.exports = function(results) {
let output = "\n", let output = "\n",
total = 0,
errors = 0, errors = 0,
warnings = 0, warnings = 0,
summaryColor = "yellow"; summaryColor = "yellow";
@ -40,7 +39,9 @@ module.exports = function(results) {
return; return;
} }
total += messages.length; errors += result.errorCount;
warnings += result.warningCount;
output += `${chalk.underline(result.filePath)}\n`; output += `${chalk.underline(result.filePath)}\n`;
output += `${table( output += `${table(
@ -50,10 +51,8 @@ module.exports = function(results) {
if (message.fatal || message.severity === 2) { if (message.fatal || message.severity === 2) {
messageType = chalk.red("error"); messageType = chalk.red("error");
summaryColor = "red"; summaryColor = "red";
errors++;
} else { } else {
messageType = chalk.yellow("warning"); messageType = chalk.yellow("warning");
warnings++;
} }
return [ 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`; ).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) { if (total > 0) {
output += chalk[summaryColor].bold([ output += chalk[summaryColor].bold([
"\u2716 ", total, pluralize(" problem", total), "\u2716 ", total, pluralize(" problem", total),

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

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

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

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

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

@ -18,9 +18,9 @@
function getMessageType(message) { function getMessageType(message) {
if (message.fatal || message.severity === 2) { if (message.fatal || message.severity === 2) {
return "error"; 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); 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) { if (this.options.ignore) {
ig.add(this.ig.custom); 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 === "") { if (description === "") {
context.report({ context.report({
node: metaDocsDescription.value, node: metaDocsDescription.value,
message: "`meta.docs.description` should not be empty.", message: "`meta.docs.description` should not be empty."
}); });
return; return;
} }

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

@ -94,16 +94,6 @@ function hasMetaSchema(metaPropertyNode) {
return getPropertyFromObject("schema", metaPropertyNode.value); 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. * 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. * @param {boolean} ruleIsFixable whether the rule is fixable or not.
* @returns {void} * @returns {void}
*/ */
function checkMetaValidity(context, exportsNode, ruleIsFixable) { function checkMetaValidity(context, exportsNode) {
const metaProperty = getMetaPropertyFromExportsNode(exportsNode); const metaProperty = getMetaPropertyFromExportsNode(exportsNode);
if (!metaProperty) { if (!metaProperty) {
@ -142,11 +132,6 @@ function checkMetaValidity(context, exportsNode, ruleIsFixable) {
if (!hasMetaSchema(metaProperty)) { if (!hasMetaSchema(metaProperty)) {
context.report(metaProperty, "Rule is missing a meta.schema property."); 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) { create(context) {
let exportsNode; let exportsNode;
let ruleIsFixable = false;
return { return {
AssignmentExpression(node) { 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"() { "Program:exit"() {
if (!isCorrectExportsFormat(exportsNode)) { if (!isCorrectExportsFormat(exportsNode)) {
context.report({ node: exportsNode, message: "Rule does not export an Object. Make sure the rule follows the new rule format." }); context.report({ node: exportsNode, message: "Rule does not export an Object. Make sure the rule follows the new rule format." });
return; return;
} }
checkMetaValidity(context, exportsNode, ruleIsFixable); checkMetaValidity(context, exportsNode);
} }
}; };
} }

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

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

4
tools/eslint/lib/rules.js

@ -70,9 +70,9 @@ function importPlugin(plugin, pluginName) {
function getHandler(ruleId) { function getHandler(ruleId) {
if (typeof rules[ruleId] === "string") { if (typeof rules[ruleId] === "string") {
return require(rules[ruleId]); 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 // Requirements
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const lodash = require("lodash");
const astUtils = require("../ast-utils"); const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -147,7 +149,8 @@ module.exports = {
upper: null, upper: null,
codePath: null, codePath: null,
hasReturn: false, hasReturn: false,
shouldCheck: false shouldCheck: false,
node: null
}; };
/** /**
@ -168,8 +171,11 @@ module.exports = {
node, node,
loc: getLocation(node, context.getSourceCode()).loc.start, loc: getLocation(node, context.getSourceCode()).loc.start,
message: funcInfo.hasReturn message: funcInfo.hasReturn
? "Expected to return a value at the end of this function." ? "Expected to return a value at the end of {{name}}."
: "Expected to return a value in this function." : "Expected to return a value in {{name}}.",
data: {
name: astUtils.getFunctionNameWithKind(funcInfo.node)
}
}); });
} }
} }
@ -187,7 +193,8 @@ module.exports = {
node.body.type === "BlockStatement" && node.body.type === "BlockStatement" &&
isCallbackOfArrayMethod(node) && isCallbackOfArrayMethod(node) &&
!node.async && !node.async &&
!node.generator !node.generator,
node
}; };
}, },
@ -204,7 +211,10 @@ module.exports = {
if (!node.argument) { if (!node.argument) {
context.report({ context.report({
node, 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"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -94,7 +100,7 @@ module.exports = {
const firstValueToken = sourceCode.getTokenAfter(returnKeyword); const firstValueToken = sourceCode.getTokenAfter(returnKeyword);
let lastValueToken = sourceCode.getLastToken(blockBody[0]); 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 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 * 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 textBeforeReturn = sourceText.slice(arrowBody.range[0] + 1, returnKeyword.range[0]);
const textBetweenReturnAndValue = sourceText.slice(returnKeyword.range[1], firstValueToken.range[0]); const textBetweenReturnAndValue = sourceText.slice(returnKeyword.range[1], firstValueToken.range[0]);
const rawReturnValueText = sourceText.slice(firstValueToken.range[0], lastValueToken.range[1]); 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 textAfterValue = sourceText.slice(lastValueToken.range[1], blockBody[0].range[1] - 1);
const textAfterReturnStatement = sourceText.slice(blockBody[0].range[1], arrowBody.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, loc: arrowBody.loc.start,
message: "Expected block statement surrounding arrow body.", message: "Expected block statement surrounding arrow body.",
fix(fixer) { fix(fixer) {
const lastTokenBeforeBody = sourceCode.getTokensBetween(sourceCode.getFirstToken(node), arrowBody) const lastTokenBeforeBody = sourceCode.getLastTokenBetween(sourceCode.getFirstToken(node), arrowBody, astUtils.isNotOpeningParenToken);
.reverse()
.find(token => token.value !== "(");
const firstBodyToken = sourceCode.getTokenAfter(lastTokenBeforeBody); const firstBodyToken = sourceCode.getTokenAfter(lastTokenBeforeBody);
return fixer.replaceTextRange( return fixer.replaceTextRange(

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

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

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

@ -4,6 +4,12 @@
*/ */
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -51,12 +57,7 @@ module.exports = {
* @returns {Object} Tokens of arrow and before/after arrow. * @returns {Object} Tokens of arrow and before/after arrow.
*/ */
function getTokens(node) { function getTokens(node) {
let arrow = sourceCode.getTokenBefore(node.body); const arrow = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
// skip '(' tokens.
while (arrow.value !== "=>") {
arrow = sourceCode.getTokenBefore(arrow);
}
return { return {
before: sourceCode.getTokenBefore(arrow), 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. // Gets braces and the first/last token of content.
const openBrace = getOpenBrace(node); const openBrace = getOpenBrace(node);
const closeBrace = sourceCode.getLastToken(node); const closeBrace = sourceCode.getLastToken(node);
const firstToken = sourceCode.getTokenOrCommentAfter(openBrace); const firstToken = sourceCode.getTokenAfter(openBrace, { includeComments: true });
const lastToken = sourceCode.getTokenOrCommentBefore(closeBrace); const lastToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
// Skip if the node is invalid or empty. // Skip if the node is invalid or empty.
if (openBrace.type !== "Punctuator" || if (openBrace.type !== "Punctuator" ||

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

@ -5,6 +5,8 @@
"use strict"; "use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -52,215 +54,86 @@ module.exports = {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
/** /**
* Determines if a given node is a block statement. * Fixes a place where a newline unexpectedly appears
* @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
* @param {Token} firstToken The token before the unexpected newline * @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) { function removeNewlineBetween(firstToken, secondToken) {
context.report({ const textRange = [firstToken.range[1], secondToken.range[0]];
node, const textBetween = sourceCode.text.slice(textRange[0], textRange[1]);
message, const NEWLINE_REGEX = astUtils.createGlobalLinebreakMatcher();
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;
// Don't do a fix if there is a comment between the tokens. // 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, "")); 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 * Validates a pair of curly brackets based on the user's config
* curly brace is on the same line as its controlling statement of a given * @param {Token} openingCurly The opening curly bracket
* node. * @param {Token} closingCurly The closing curly bracket
* @param {...string} The properties to check on the node. * @returns {void}
* @returns {Function} A function that will perform the check on a node */
* @private function validateCurlyPair(openingCurly, closingCurly) {
*/ const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly);
function checkBlock() { const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly);
const blockProperties = arguments; const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly);
const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly);
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")
});
}
if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.end.line) { if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) {
context.report({ context.report({
node: block.body[block.body.length - 1], node: openingCurly,
message: CLOSE_MESSAGE_SINGLE, message: OPEN_MESSAGE,
fix: fixer => fixer.insertTextBefore(curlyTokenEnd, "\n") 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)) { if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) {
const tokens = sourceCode.getTokensBefore(node.finalizer, 2); context.report({
node: openingCurly,
if (style === "1tbs") { message: OPEN_MESSAGE_ALLMAN,
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) { fix: fixer => fixer.insertTextBefore(openingCurly, "\n")
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")
});
}
} }
}
/**
* 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 (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) {
if (style === "1tbs") { context.report({
if (previousToken.loc.start.line !== firstToken.loc.start.line) { node: closingCurly,
reportExtraNewline(node, CLOSE_MESSAGE, previousToken); message: CLOSE_MESSAGE_SINGLE,
} fix: fixer => fixer.insertTextBefore(closingCurly, "\n")
} else { });
if (previousToken.loc.start.line === firstToken.loc.start.line) {
context.report({
node,
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
fix: fixer => fixer.insertTextAfter(previousToken, "\n")
});
}
}
} }
} }
/** /**
* Enforces the configured brace style on SwitchStatements * Validates the location of a token that appears before a keyword (e.g. a newline before `else`)
* @param {ASTNode} node A SwitchStatement node. * @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`).
* @returns {void} * @returns {void}
* @private */
*/ function validateCurlyBeforeKeyword(curlyToken) {
function checkSwitchStatement(node) { const keywordToken = sourceCode.getTokenAfter(curlyToken);
let tokens;
if (node.cases && node.cases.length) { if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
tokens = sourceCode.getTokensBefore(node.cases[0], 2); context.report({
} else { node: curlyToken,
tokens = sourceCode.getLastTokens(node, 3); message: CLOSE_MESSAGE,
fix: removeNewlineBetween(curlyToken, keywordToken)
});
} }
if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) { if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
reportExtraNewline(node, OPEN_MESSAGE, tokens[0]);
} else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report({ context.report({
node, node: curlyToken,
message: OPEN_MESSAGE_ALLMAN, message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
fix: fixer => fixer.insertTextBefore(tokens[1], "\n") fix: fixer => fixer.insertTextAfter(curlyToken, "\n")
}); });
} }
} }
@ -270,20 +143,38 @@ module.exports = {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
return { return {
FunctionDeclaration: checkBlock("body"), BlockStatement(node) {
FunctionExpression: checkBlock("body"), if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
ArrowFunctionExpression: checkBlock("body"), validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
IfStatement: checkIfStatement, }
TryStatement: checkTryStatement, },
CatchClause: checkCatchClause, ClassBody(node) {
DoWhileStatement: checkBlock("body"), validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
WhileStatement: checkBlock("body"), },
WithStatement: checkBlock("body"), SwitchStatement(node) {
ForStatement: checkBlock("body"), const closingCurly = sourceCode.getLastToken(node);
ForInStatement: checkBlock("body"), const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly);
ForOfStatement: checkBlock("body"),
SwitchStatement: checkSwitchStatement 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 LETTER_PATTERN = require("../util/patterns/letters");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
@ -16,7 +17,7 @@ const LETTER_PATTERN = require("../util/patterns/letters");
const ALWAYS_MESSAGE = "Comments should not begin with a lowercase character", const ALWAYS_MESSAGE = "Comments should not begin with a lowercase character",
NEVER_MESSAGE = "Comments should not begin with an uppercase 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, WHITESPACE = /\s/g,
MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern? MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern?
DEFAULTS = { DEFAULTS = {
@ -163,8 +164,8 @@ module.exports = {
* otherwise. * otherwise.
*/ */
function isInlineComment(comment) { function isInlineComment(comment) {
const previousToken = sourceCode.getTokenOrCommentBefore(comment), const previousToken = sourceCode.getTokenBefore(comment, { includeComments: true }),
nextToken = sourceCode.getTokenOrCommentAfter(comment); nextToken = sourceCode.getTokenAfter(comment, { includeComments: true });
return Boolean( return Boolean(
previousToken && previousToken &&
@ -181,7 +182,7 @@ module.exports = {
* @returns {boolean} True if the comment follows a valid comment. * @returns {boolean} True if the comment follows a valid comment.
*/ */
function isConsecutiveComment(comment) { function isConsecutiveComment(comment) {
const previousTokenOrComment = sourceCode.getTokenOrCommentBefore(comment); const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true });
return Boolean( return Boolean(
previousTokenOrComment && previousTokenOrComment &&
@ -264,9 +265,9 @@ module.exports = {
commentValid = isCommentValid(comment, options); commentValid = isCommentValid(comment, options);
if (!commentValid) { if (!commentValid) {
const message = capitalize === "always" ? const message = capitalize === "always"
ALWAYS_MESSAGE : ? ALWAYS_MESSAGE
NEVER_MESSAGE; : NEVER_MESSAGE;
context.report({ context.report({
node: null, // Intentionally using loc instead node: null, // Intentionally using loc instead

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

@ -10,6 +10,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const lodash = require("lodash"); const lodash = require("lodash");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
@ -20,7 +21,7 @@ const DEFAULT_OPTIONS = Object.freeze({
objects: "never", objects: "never",
imports: "never", imports: "never",
exports: "never", exports: "never",
functions: "ignore", functions: "ignore"
}); });
/** /**
@ -53,7 +54,7 @@ function normalizeOptions(optionValue) {
exports: optionValue, exports: optionValue,
// For backward compatibility, always ignore functions. // For backward compatibility, always ignore functions.
functions: "ignore", functions: "ignore"
}; };
} }
if (typeof optionValue === "object" && optionValue !== null) { if (typeof optionValue === "object" && optionValue !== null) {
@ -62,7 +63,7 @@ function normalizeOptions(optionValue) {
objects: optionValue.objects || DEFAULT_OPTIONS.objects, objects: optionValue.objects || DEFAULT_OPTIONS.objects,
imports: optionValue.imports || DEFAULT_OPTIONS.imports, imports: optionValue.imports || DEFAULT_OPTIONS.imports,
exports: optionValue.exports || DEFAULT_OPTIONS.exports, 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 additionalProperties: false
} }
] ]
}, }
] ]
}, },
@ -178,7 +179,7 @@ module.exports = {
default: { default: {
const nextToken = sourceCode.getTokenAfter(lastItem); const nextToken = sourceCode.getTokenAfter(lastItem);
if (nextToken.value === ",") { if (astUtils.isCommaToken(nextToken)) {
return nextToken; return nextToken;
} }
return sourceCode.getLastToken(lastItem); return sourceCode.getLastToken(lastItem);
@ -224,7 +225,7 @@ module.exports = {
const trailingToken = getTrailingToken(node, lastItem); const trailingToken = getTrailingToken(node, lastItem);
if (trailingToken.value === ",") { if (astUtils.isCommaToken(trailingToken)) {
context.report({ context.report({
node: lastItem, node: lastItem,
loc: trailingToken.loc.start, loc: trailingToken.loc.start,
@ -312,7 +313,7 @@ module.exports = {
"always-multiline": forceTrailingCommaIfMultiline, "always-multiline": forceTrailingCommaIfMultiline,
"only-multiline": allowTrailingCommaIfMultiline, "only-multiline": allowTrailingCommaIfMultiline,
never: forbidTrailingComma, never: forbidTrailingComma,
ignore: lodash.noop, ignore: lodash.noop
}; };
return { return {
@ -330,7 +331,7 @@ module.exports = {
FunctionExpression: predicate[options.functions], FunctionExpression: predicate[options.functions],
ArrowFunctionExpression: predicate[options.functions], ArrowFunctionExpression: predicate[options.functions],
CallExpression: 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 // list of comma tokens to ignore for the check of leading whitespace
const commaTokensToIgnore = []; 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. * Reports a spacing error with an appropriate message.
* @param {ASTNode} node The binary expression node to report. * @param {ASTNode} node The binary expression node to report.
@ -78,27 +68,27 @@ module.exports = {
if (options[dir]) { if (options[dir]) {
if (dir === "before") { if (dir === "before") {
return fixer.insertTextBefore(node, " "); return fixer.insertTextBefore(node, " ");
} else {
return fixer.insertTextAfter(node, " ");
} }
} else { return fixer.insertTextAfter(node, " ");
let start, end;
const newText = "";
if (dir === "before") { }
start = otherNode.range[1]; let start, end;
end = node.range[0]; const newText = "";
} else {
start = node.range[1];
end = otherNode.range[0];
}
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] ? message: options[dir]
"A space is required {{dir}} ','." : ? "A space is required {{dir}} ','."
"There should be no space {{dir}} ','.", : "There should be no space {{dir}} ','.",
data: { data: {
dir dir
} }
@ -147,7 +137,7 @@ module.exports = {
if (element === null) { if (element === null) {
token = sourceCode.getTokenAfter(previousToken); token = sourceCode.getTokenAfter(previousToken);
if (isComma(token)) { if (astUtils.isCommaToken(token)) {
commaTokensToIgnore.push(token); commaTokensToIgnore.push(token);
} }
} else { } else {
@ -166,7 +156,7 @@ module.exports = {
"Program:exit"() { "Program:exit"() {
tokensAndComments.forEach((token, i) => { tokensAndComments.forEach((token, i) => {
if (!isComma(token)) { if (!astUtils.isCommaToken(token)) {
return; return;
} }
@ -179,8 +169,8 @@ module.exports = {
validateCommaItemSpacing({ validateCommaItemSpacing({
comma: token, comma: token,
left: isComma(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken, left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
right: isComma(nextToken) ? null : nextToken right: astUtils.isCommaToken(nextToken) ? null : nextToken
}, token); }, token);
}); });
}, },

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

@ -48,7 +48,7 @@ module.exports = {
FunctionDeclaration: true, FunctionDeclaration: true,
FunctionExpression: true, FunctionExpression: true,
ImportDeclaration: true, ImportDeclaration: true,
ObjectPattern: true, ObjectPattern: true
}; };
if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) { if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
@ -63,16 +63,6 @@ module.exports = {
// Helpers // 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 * Modified text based on the style
* @param {string} styleType Style type * @param {string} styleType Style type
@ -192,7 +182,7 @@ module.exports = {
tokenBeforeComma = sourceCode.getTokenBefore(commaToken); tokenBeforeComma = sourceCode.getTokenBefore(commaToken);
// Check if previous token is wrapped in parentheses // Check if previous token is wrapped in parentheses
if (tokenBeforeComma && tokenBeforeComma.value === ")") { if (tokenBeforeComma && astUtils.isClosingParenToken(tokenBeforeComma)) {
previousItemToken = tokenBeforeComma; previousItemToken = tokenBeforeComma;
} }
@ -210,12 +200,16 @@ module.exports = {
* All comparisons are done based on these tokens directly, so * All comparisons are done based on these tokens directly, so
* they are always valid regardless of an undefined item. * they are always valid regardless of an undefined item.
*/ */
if (isComma(commaToken)) { if (astUtils.isCommaToken(commaToken)) {
validateCommaItemSpacing(previousItemToken, commaToken, validateCommaItemSpacing(previousItemToken, commaToken,
currentItemToken, reportItem); 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), const lastToken = sourceCode.getLastToken(node),
nextToLastToken = sourceCode.getTokenBefore(lastToken); nextToLastToken = sourceCode.getTokenBefore(lastToken);
if (isComma(nextToLastToken)) { if (astUtils.isCommaToken(nextToLastToken)) {
validateCommaItemSpacing( validateCommaItemSpacing(
sourceCode.getTokenBefore(nextToLastToken), sourceCode.getTokenBefore(nextToLastToken),
nextToLastToken, nextToLastToken,

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

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

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

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

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

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

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

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

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

@ -4,6 +4,12 @@
*/ */
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -64,20 +70,20 @@ module.exports = {
propertyValue: JSON.stringify(node.property.value) propertyValue: JSON.stringify(node.property.value)
}, },
fix(fixer) { fix(fixer) {
const leftBracket = sourceCode.getTokenBefore(node.property); const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
const rightBracket = sourceCode.getTokenAfter(node.property); const rightBracket = sourceCode.getLastToken(node);
const textBeforeProperty = sourceCode.text.slice(leftBracket.range[1], node.property.range[0]);
const textAfterProperty = sourceCode.text.slice(node.property.range[1], rightBracket.range[0]);
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. // Don't perform any fixes if there are comments inside the brackets.
return null; return null;
} }
const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
return fixer.replaceTextRange( return fixer.replaceTextRange(
[leftBracket.range[0], rightBracket.range[1]], [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"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -57,9 +63,9 @@ module.exports = {
const options = context.options[1] || {}; const options = context.options[1] || {};
const sourceCode = context.getSourceCode(); const sourceCode = context.getSourceCode();
const nullOption = (config === "always") ? const nullOption = (config === "always")
options.null || "always" : ? options.null || "always"
"ignore"; : "ignore";
const enforceRuleForNull = (nullOption === "always"); const enforceRuleForNull = (nullOption === "always");
const enforceInverseRuleForNull = (nullOption === "never"); const enforceInverseRuleForNull = (nullOption === "never");
@ -100,8 +106,7 @@ module.exports = {
* @private * @private
*/ */
function isNullCheck(node) { function isNullCheck(node) {
return (node.right.type === "Literal" && node.right.value === null) || return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left);
(node.left.type === "Literal" && node.left.value === null);
} }
/** /**
@ -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 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)) { 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); return fixer.replaceText(operatorToken, expectedOperator);
} }

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

@ -5,6 +5,12 @@
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -67,28 +73,19 @@ module.exports = {
* @private * @private
*/ */
function checkSpacing(node) { function checkSpacing(node) {
const lastToken = sourceCode.getLastToken(node);
const lastCalleeToken = sourceCode.getLastToken(node.callee); const lastCalleeToken = sourceCode.getLastToken(node.callee);
let prevToken = lastCalleeToken; const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
let parenToken = sourceCode.getTokenAfter(lastCalleeToken); const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
// advances to an open parenthesis.
while (
parenToken &&
parenToken.range[1] < node.range[1] &&
parenToken.value !== "("
) {
prevToken = parenToken;
parenToken = sourceCode.getTokenAfter(parenToken);
}
// Parens in NewExpression are optional // Parens in NewExpression are optional
if (!(parenToken && parenToken.range[1] < node.range[1])) { if (!(parenToken && parenToken.range[1] < node.range[1])) {
return; return;
} }
const hasWhitespace = sourceCode.isSpaceBetweenTokens(prevToken, parenToken); const textBetweenTokens = text.slice(prevToken.range[1], parenToken.range[0]).replace(/\/\*.*?\*\//g, "");
const hasNewline = hasWhitespace && const hasWhitespace = /\s/.test(textBetweenTokens);
/\n/.test(text.slice(prevToken.range[1], parenToken.range[0]).replace(/\/\*.*?\*\//g, "")); const hasNewline = hasWhitespace && astUtils.LINEBREAK_MATCHER.test(textBetweenTokens);
/* /*
* never allowNewlines hasWhitespace hasNewline message * 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 // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -139,7 +148,7 @@ module.exports = {
return { return {
VariableDeclarator(node) { VariableDeclarator(node) {
if (!node.init || node.init.type !== "FunctionExpression") { if (!node.init || node.init.type !== "FunctionExpression" || node.id.type !== "Identifier") {
return; return;
} }
if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) { if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) {
@ -148,14 +157,16 @@ module.exports = {
}, },
AssignmentExpression(node) { AssignmentExpression(node) {
if (node.right.type !== "FunctionExpression" || if (
(node.left.computed && node.left.property.type !== "Literal") || node.right.type !== "FunctionExpression" ||
(!includeModuleExports && isModuleExports(node.left)) (node.left.computed && node.left.property.type !== "Literal") ||
) { (!includeModuleExports && isModuleExports(node.left)) ||
(node.left.type !== "Identifier" && node.left.type !== "MemberExpression")
) {
return; 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; const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name;
if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) { if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
@ -164,13 +175,13 @@ module.exports = {
}, },
Property(node) { 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; return;
} }
if (node.key.type === "Identifier" && shouldWarn(node.key.name, node.value.id.name)) { if (node.key.type === "Identifier" && shouldWarn(node.key.name, node.value.id.name)) {
report(node, node.key.name, node.value.id.name, true); report(node, node.key.name, node.value.id.name, true);
} else if ( } else if (
node.key.type === "Literal" && isStringLiteral(node.key) &&
isIdentifier(node.key.value, ecmaVersion) && isIdentifier(node.key.value, ecmaVersion) &&
shouldWarn(node.key.value, node.value.id.name) shouldWarn(node.key.value, node.value.id.name)
) { ) {

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

@ -5,6 +5,12 @@
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
/** /**
* Checks whether or not a given variable is a function name. * Checks whether or not a given variable is a function name.
* @param {escope.Variable} variable - A variable to check. * @param {escope.Variable} variable - A variable to check.
@ -82,15 +88,24 @@ module.exports = {
return; return;
} }
const name = node.id && node.id.name; const hasName = Boolean(node.id && node.id.name);
const name = astUtils.getFunctionNameWithKind(node);
if (never) { if (never) {
if (name) { if (hasName) {
context.report({ node, message: "Unexpected function expression name." }); context.report({
node,
message: "Unexpected named {{name}}.",
data: { name }
});
} }
} else { } else {
if (!name && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) { if (!hasName && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
context.report({ node, message: "Missing function expression name." }); 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(); 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 * @param {Token} token - The token to check.
* FunctionDeclaration, FunctionExpression, Property, and * @returns {boolean} `true` if the token is a star token.
* MethodDefinition.
* @returns {Token} `*` token.
*/ */
function getStarToken(node) { function isStarToken(token) {
let token = sourceCode.getFirstToken(node); return token.value === "*" && token.type === "Punctuator";
}
while (token.value !== "*") {
token = sourceCode.getTokenAfter(token);
}
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} * @returns {void}
*/ */
function checkFunction(node) { function checkFunction(node) {
let starToken;
if (!node.generator) { if (!node.generator) {
return; return;
} }
if (node.parent.method || node.parent.type === "MethodDefinition") { const starToken = getStarToken(node);
starToken = getStarToken(node.parent);
} else {
starToken = getStarToken(node);
}
// Only check before when preceded by `function`|`static` keyword // Only check before when preceded by `function`|`static` keyword
const prevToken = sourceCode.getTokenBefore(starToken); 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 */ /* istanbul ignore else: correctly returns null */
if (references.length === 1) { if (references.length === 1) {
return references[0]; 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 * @returns {boolean} whether an error should be reported or not
*/ */
function shouldReport(effectiveParent, name) { function shouldReport(effectiveParent, name) {
return effectiveParent.type !== "CallExpression" return effectiveParent.type !== "CallExpression" &&
&& effectiveParent.type !== "NewExpression" && effectiveParent.type !== "NewExpression" &&
isInvalid(name); isInvalid(name);
} }

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

@ -104,9 +104,9 @@ module.exports = {
if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) { if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) {
context.report({ context.report({
node, node,
message: isShort ? message: isShort
"Identifier name '{{name}}' is too short (< {{min}})." : ? "Identifier name '{{name}}' is too short (< {{min}})."
"Identifier name '{{name}}' is too long (> {{max}}).", : "Identifier name '{{name}}' is too long (> {{max}}).",
data: { name, min: minLength, max: maxLength } 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 * @returns {boolean} whether an error should be reported or not
*/ */
function shouldReport(effectiveParent, name) { function shouldReport(effectiveParent, name) {
return effectiveParent.type !== "CallExpression" return effectiveParent.type !== "CallExpression" &&
&& effectiveParent.type !== "NewExpression" && effectiveParent.type !== "NewExpression" &&
isInvalid(name); isInvalid(name);
} }

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

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

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

@ -4,6 +4,12 @@
*/ */
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -15,7 +21,7 @@
* @returns {boolean} True if str contains a line terminator. * @returns {boolean} True if str contains a line terminator.
*/ */
function containsLineTerminator(str) { 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. * @returns {ASTNode} The last token before a colon punctuator.
*/ */
function getLastTokenBeforeColon(node) { function getLastTokenBeforeColon(node) {
let prevNode; const colonToken = sourceCode.getTokenAfter(node, astUtils.isColonToken);
while (node && (node.type !== "Punctuator" || node.value !== ":")) { return sourceCode.getTokenBefore(colonToken);
prevNode = node;
node = sourceCode.getTokenAfter(node);
}
return prevNode;
} }
/** /**
@ -381,12 +382,7 @@ module.exports = {
* @returns {ASTNode} The colon punctuator. * @returns {ASTNode} The colon punctuator.
*/ */
function getNextColon(node) { function getNextColon(node) {
return sourceCode.getTokenAfter(node, astUtils.isColonToken);
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
node = sourceCode.getTokenAfter(node);
}
return node;
} }
/** /**
@ -417,8 +413,8 @@ module.exports = {
function report(property, side, whitespace, expected, mode) { function report(property, side, whitespace, expected, mode) {
const diff = whitespace.length - expected, const diff = whitespace.length - expected,
nextColon = getNextColon(property.key), nextColon = getNextColon(property.key),
tokenBeforeColon = sourceCode.getTokenOrCommentBefore(nextColon), tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
tokenAfterColon = sourceCode.getTokenOrCommentAfter(nextColon), tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
isKeySide = side === "key", isKeySide = side === "key",
locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start, locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
isExtra = diff > 0, 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) { function checkSpacingAroundTokenBefore(node) {
if (node) { if (node) {
let token = sourceCode.getTokenBefore(node); const token = sourceCode.getTokenBefore(node, astUtils.isKeywordToken);
while (token.type !== "Keyword") {
token = sourceCode.getTokenBefore(token);
}
checkSpacingAround(token); checkSpacingAround(token);
} }
@ -363,7 +359,8 @@ module.exports = {
const firstToken = node && sourceCode.getFirstToken(node); const firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && if (firstToken &&
(firstToken.type === "Keyword" || firstToken.value === "async") ((firstToken.type === "Keyword" && firstToken.value === "function") ||
firstToken.value === "async")
) { ) {
checkSpacingBefore(firstToken); checkSpacingBefore(firstToken);
} }
@ -439,14 +436,7 @@ module.exports = {
*/ */
function checkSpacingForForOfStatement(node) { function checkSpacingForForOfStatement(node) {
checkSpacingAroundFirstToken(node); checkSpacingAroundFirstToken(node);
checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken));
// `of` is not a keyword token.
let token = sourceCode.getTokenBefore(node.right);
while (token.value !== "of") {
token = sourceCode.getTokenBefore(token);
}
checkSpacingAround(token);
} }
/** /**
@ -506,11 +496,25 @@ module.exports = {
node.value.async node.value.async
) )
) { ) {
const token = sourceCode.getFirstToken( const token = sourceCode.getTokenBefore(
node, node.key,
node.static ? 1 : 0 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); checkSpacingAround(token);
} }
} }

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

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

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

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

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

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

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

@ -31,7 +31,7 @@ module.exports = {
}, },
after: { after: {
enum: ["always", "never"] enum: ["always", "never"]
}, }
}, },
additionalProperties: false, additionalProperties: false,
minProperties: 2 minProperties: 2
@ -57,7 +57,7 @@ module.exports = {
* @returns {boolean} Whether or not the passed in node is preceded by a blank newline. * @returns {boolean} Whether or not the passed in node is preceded by a blank newline.
*/ */
function hasNewlineBefore(node) { function hasNewlineBefore(node) {
const tokenBefore = sourceCode.getTokenOrCommentBefore(node); const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true });
const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0; const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0;
return node.loc.start.line - tokenLineBefore >= 2; return node.loc.start.line - tokenLineBefore >= 2;
@ -74,7 +74,7 @@ module.exports = {
const lastToken = sourceCode.getLastToken(node); const lastToken = sourceCode.getLastToken(node);
const secondToLastToken = sourceCode.getTokenBefore(lastToken); 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 ? secondToLastToken
: lastToken; : lastToken;
} }
@ -86,7 +86,7 @@ module.exports = {
*/ */
function hasNewlineAfter(node) { function hasNewlineAfter(node) {
const lastToken = getLastTokenOnLine(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; return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2;
} }
@ -131,7 +131,7 @@ module.exports = {
} }
const firstDirective = directives[0]; 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 // 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 // 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; token = comment;
do { do {
token = sourceCode.getTokenOrCommentBefore(token); token = sourceCode.getTokenBefore(token, { includeComments: true });
} while (isCommentNodeType(token)); } while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(token, comment)) { if (token && astUtils.isTokenOnSameLine(token, comment)) {
@ -99,7 +99,7 @@ module.exports = {
token = comment; token = comment;
do { do {
token = sourceCode.getTokenOrCommentAfter(token); token = sourceCode.getTokenAfter(token, { includeComments: true });
} while (isCommentNodeType(token)); } while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(comment, 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.", message: "File must be at most {{max}} lines long. It's {{actual}} lines long.",
data: { data: {
max, max,
actual: lines.length, actual: lines.length
} }
}); });
} }

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

@ -5,6 +5,14 @@
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const lodash = require("lodash");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -66,10 +74,15 @@ module.exports = {
*/ */
function checkFunction(node) { function checkFunction(node) {
if (node.params.length > numParams) { if (node.params.length > numParams) {
context.report({ node, message: "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", data: { context.report({
count: node.params.length, node,
max: numParams 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"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -60,7 +66,7 @@ module.exports = {
data: { data: {
numberOfStatementsOnThisLine, numberOfStatementsOnThisLine,
maxStatementsPerLine, maxStatementsPerLine,
statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements", statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements"
} }
}); });
} }
@ -74,12 +80,7 @@ module.exports = {
* @returns {Token} The actual last token. * @returns {Token} The actual last token.
*/ */
function getActualLastToken(node) { function getActualLastToken(node) {
let lastToken = sourceCode.getLastToken(node); return sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
if (lastToken.value === ";") {
lastToken = sourceCode.getTokenBefore(lastToken);
}
return lastToken;
} }
/** /**

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

@ -5,6 +5,14 @@
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const lodash = require("lodash");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -84,19 +92,12 @@ module.exports = {
*/ */
function reportIfTooManyStatements(node, count, max) { function reportIfTooManyStatements(node, count, max) {
if (count > max) { if (count > max) {
const messageEnd = " has too many statements ({{count}}). Maximum allowed is {{max}}."; const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
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)}'`;
}
context.report({ context.report({
node, node,
message: name + messageEnd, message: "{{name}} has too many statements ({{count}}). Maximum allowed is {{max}}.",
data: { count, max } data: { name, count, max }
}); });
} }
} }

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

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

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

@ -6,28 +6,14 @@
"use strict"; "use strict";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Requirements
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** const astUtils = require("../ast-utils");
* 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 === "(";
}
/** //------------------------------------------------------------------------------
* Checks whether the given token is an closing parenthesis or not. // Helpers
* //------------------------------------------------------------------------------
* @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 === ")";
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
@ -56,8 +42,8 @@ module.exports = {
} }
const lastToken = sourceCode.getLastToken(node); const lastToken = sourceCode.getLastToken(node);
const hasLastParen = lastToken && isClosingParen(lastToken); const hasLastParen = lastToken && astUtils.isClosingParenToken(lastToken);
const hasParens = hasLastParen && isOpeningParen(sourceCode.getTokenBefore(lastToken)); const hasParens = hasLastParen && astUtils.isOpeningParenToken(sourceCode.getTokenBefore(lastToken));
if (!hasParens) { if (!hasParens) {
context.report({ context.report({

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

@ -5,6 +5,12 @@
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -201,8 +207,7 @@ module.exports = {
message: NEVER_MESSAGE, message: NEVER_MESSAGE,
data: { identifier: node.name }, data: { identifier: node.name },
fix(fixer) { fix(fixer) {
const NEWLINE_REGEX = /\r\n|\r|\n|\u2028|\u2029/; const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER);
const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(NEWLINE_REGEX);
return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween[linesBetween.length - 1]}`); 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"]); return isPrecededByTokens(node, ["do"]);
} else if (parentType === "SwitchCase") { } else if (parentType === "SwitchCase") {
return isPrecededByTokens(node, [":"]); 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"; "use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -47,7 +49,7 @@ module.exports = {
*/ */
function getPropertyText(node) { function getPropertyText(node) {
const prefix = node.computed ? "[" : "."; 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 ? "]" : ""; const suffix = node.computed && lines.length === 1 ? "]" : "";
return prefix + lines[0] + suffix; return prefix + lines[0] + suffix;

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

@ -10,7 +10,7 @@ const loopTypes = new Set([
"ForOfStatement", "ForOfStatement",
"ForInStatement", "ForInStatement",
"WhileStatement", "WhileStatement",
"DoWhileStatement", "DoWhileStatement"
]); ]);
// Node types at which we should stop looking for loops. For example, it is fine to declare an async // 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([ const boundaryTypes = new Set([
"FunctionDeclaration", "FunctionDeclaration",
"FunctionExpression", "FunctionExpression",
"ArrowFunctionExpression", "ArrowFunctionExpression"
]); ]);
module.exports = { module.exports = {
@ -26,9 +26,9 @@ module.exports = {
docs: { docs: {
description: "disallow `await` inside of loops", description: "disallow `await` inside of loops",
category: "Possible Errors", category: "Possible Errors",
recommended: false, recommended: false
}, },
schema: [], schema: []
}, },
create(context) { create(context) {
return { 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; 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. * Check whether the code represented by an AST node is enclosed in two sets of parentheses.
* @param {!Object} node The node to test. * @param {!Object} node The node to test.
@ -88,9 +75,9 @@ module.exports = {
const previousToken = sourceCode.getTokenBefore(node, 1), const previousToken = sourceCode.getTokenBefore(node, 1),
nextToken = sourceCode.getTokenAfter(node, 1); nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) && return astUtils.isParenthesised(sourceCode, node) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] && astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1]; astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
} }
/** /**
@ -101,9 +88,9 @@ module.exports = {
function testForAssign(node) { function testForAssign(node) {
if (node.test && if (node.test &&
(node.test.type === "AssignmentExpression") && (node.test.type === "AssignmentExpression") &&
(node.type === "ForStatement" ? (node.type === "ForStatement"
!isParenthesised(node.test) : ? !astUtils.isParenthesised(sourceCode, node.test)
!isParenthesisedTwice(node.test) : !isParenthesisedTwice(node.test)
) )
) { ) {

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

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

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

@ -5,6 +5,13 @@
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
const FixTracker = require("../util/fix-tracker");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -17,7 +24,9 @@ module.exports = {
recommended: false recommended: false
}, },
schema: [] schema: [],
fixable: "code"
}, },
create(context) { create(context) {
@ -33,7 +42,66 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function displayReport(node) { 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); return checkForReturnOrIf(node);
} }
//-------------------------------------------------------------------------- /**
// Public API * Check the if statement
//-------------------------------------------------------------------------- * @returns {void}
* @param {Node} node The node for the if statement to check
return { * @private
*/
function IfStatement(node) {
const parent = context.getAncestors().pop();
let consequents,
alternate;
IfStatement(node) { /*
const parent = context.getAncestors().pop(); * Fixing this would require splitting one statement into two, so no error should
let consequents, * be reported if this node is in a position where only one statement is allowed.
alternate; */
if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) {
return;
}
// Only "top-level" if statements are checked, meaning the first `if` for (consequents = []; node.type === "IfStatement"; node = node.alternate) {
// in a `if-else-if-...` chain. if (!node.alternate) {
if (parent.type === "IfStatement" && parent.alternate === node) {
return; return;
} }
consequents.push(node.consequent);
alternate = node.alternate;
}
for (consequents = []; node.type === "IfStatement"; node = node.alternate) { if (consequents.every(alwaysReturns)) {
if (!node.alternate) { displayReport(alternate);
return;
}
consequents.push(node.consequent);
alternate = node.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"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -19,18 +25,6 @@ const ALLOW_OPTIONS = Object.freeze([
"setters", "setters",
"constructors" "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. * Gets the kind of a given function node.
@ -137,6 +131,7 @@ module.exports = {
*/ */
function reportIfEmpty(node) { function reportIfEmpty(node) {
const kind = getKind(node); const kind = getKind(node);
const name = astUtils.getFunctionNameWithKind(node);
if (allowed.indexOf(kind) === -1 && if (allowed.indexOf(kind) === -1 &&
node.body.type === "BlockStatement" && node.body.type === "BlockStatement" &&
@ -146,10 +141,8 @@ module.exports = {
context.report({ context.report({
node, node,
loc: node.body.loc.start, loc: node.body.loc.start,
message: "Unexpected empty {{kind}}.", message: "Unexpected empty {{name}}.",
data: { data: { name }
kind: SHOW_KIND[kind]
}
}); });
} }
} }

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

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

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

@ -8,7 +8,7 @@
// Requirements // Requirements
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const getPropertyName = require("../ast-utils").getStaticPropertyName; const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
@ -44,8 +44,7 @@ module.exports = {
loc: node.parent.property.loc.start, loc: node.parent.property.loc.start,
fix(fixer) { fix(fixer) {
const firstTokenToRemove = context.getSourceCode() const firstTokenToRemove = context.getSourceCode()
.getTokensBetween(node.parent.object, node.parent.property) .getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
.find(token => token.value !== ")");
return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]); return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]);
} }
@ -73,7 +72,7 @@ module.exports = {
grandparent.arguments.length === 1 && grandparent.arguments.length === 1 &&
parent.type === "MemberExpression" && parent.type === "MemberExpression" &&
parent.object === node && 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"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -91,7 +97,22 @@ module.exports = {
context.report({ context.report({
node, node,
message: "Redundant Boolean call.", 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, node: labelNode,
message: "This label '{{name}}' is unnecessary.", message: "This label '{{name}}' is unnecessary.",
data: labelNode, data: labelNode,
fix(fixer) { fix: fixer => fixer.removeRange([sourceCode.getFirstToken(node).range[1], labelNode.range[1]])
return fixer.replaceTextRange(
[info.label.range[0], labelNode.range[1]],
sourceCode.text.slice(info.label.parent.body.range[0], sourceCode.getFirstToken(node).range[1])
);
}
}); });
} }
return; return;

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

@ -44,7 +44,8 @@ module.exports = {
properties: { properties: {
conditionalAssign: { type: "boolean" }, conditionalAssign: { type: "boolean" },
nestedBinaryExpressions: { type: "boolean" }, nestedBinaryExpressions: { type: "boolean" },
returnAssign: { type: "boolean" } returnAssign: { type: "boolean" },
ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] }
}, },
additionalProperties: false additionalProperties: false
} }
@ -59,12 +60,16 @@ module.exports = {
create(context) { create(context) {
const sourceCode = context.getSourceCode(); const sourceCode = context.getSourceCode();
const tokensToIgnore = new WeakSet();
const isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode); const isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode);
const precedence = astUtils.getPrecedence; const precedence = astUtils.getPrecedence;
const ALL_NODES = context.options[0] !== "functions"; const ALL_NODES = context.options[0] !== "functions";
const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false; 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 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 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. * Determines if this rule should be enforced for a node given the current configuration.
@ -73,6 +78,31 @@ module.exports = {
* @private * @private
*/ */
function ruleApplies(node) { 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"; return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
} }
@ -87,8 +117,8 @@ module.exports = {
nextToken = sourceCode.getTokenAfter(node, 1); nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) && previousToken && nextToken && return isParenthesised(node) && previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] && astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1]; astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
} }
/** /**
@ -140,6 +170,19 @@ module.exports = {
return false; 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 * Determines if a node is or contains an assignment expression
* @param {ASTNode} node - The node to be checked. * @param {ASTNode} node - The node to be checked.
@ -175,9 +218,9 @@ module.exports = {
return node.argument && containsAssignment(node.argument); return node.argument && containsAssignment(node.argument);
} else if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") { } else if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
return containsAssignment(node.body); return containsAssignment(node.body);
} else {
return containsAssignment(node);
} }
return containsAssignment(node);
} }
/** /**
@ -196,69 +239,6 @@ module.exports = {
return hasDoubleExcessParens(node); 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 * 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 * @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`) // 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 // 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[]` // e.g. `typeof([])` should be fixed to `typeof[]`
@ -289,7 +269,7 @@ module.exports = {
const startsWithUnaryPlus = firstToken.type === "Punctuator" && firstToken.value === "+"; const startsWithUnaryPlus = firstToken.type === "Punctuator" && firstToken.value === "+";
const startsWithUnaryMinus = firstToken.type === "Punctuator" && firstToken.value === "-"; const startsWithUnaryMinus = firstToken.type === "Punctuator" && firstToken.value === "-";
return (precededByKeyword && startsWithIdentifierPart) || return (precededByIdentiferPart && startsWithIdentifierPart) ||
(precededByUnaryPlus && startsWithUnaryPlus) || (precededByUnaryPlus && startsWithUnaryPlus) ||
(precededByUnaryMinus && startsWithUnaryMinus); (precededByUnaryMinus && startsWithUnaryMinus);
} }
@ -304,6 +284,10 @@ module.exports = {
const leftParenToken = sourceCode.getTokenBefore(node); const leftParenToken = sourceCode.getTokenBefore(node);
const rightParenToken = sourceCode.getTokenAfter(node); const rightParenToken = sourceCode.getTokenAfter(node);
if (tokensToIgnore.has(sourceCode.getFirstToken(node)) && !isParenthesisedTwice(node)) {
return;
}
context.report({ context.report({
node, node,
loc: leftParenToken.loc.start, loc: leftParenToken.loc.start,
@ -325,7 +309,11 @@ module.exports = {
* @returns {void} * @returns {void}
* @private * @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)) { if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) {
report(node.argument); report(node.argument);
} }
@ -337,10 +325,11 @@ module.exports = {
* @returns {void} * @returns {void}
* @private * @private
*/ */
function dryCallNew(node) { function checkCallNew(node) {
if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !( if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !(
node.type === "CallExpression" && 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 // One set of parentheses are allowed for a function expression
!hasDoubleExcessParens(node.callee) !hasDoubleExcessParens(node.callee)
@ -348,12 +337,12 @@ module.exports = {
report(node.callee); report(node.callee);
} }
if (node.arguments.length === 1) { 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]); report(node.arguments[0]);
} }
} else { } else {
[].forEach.call(node.arguments, arg => { [].forEach.call(node.arguments, arg => {
if (hasExcessParens(arg) && precedence(arg) >= precedence({ type: "AssignmentExpression" })) { if (hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
report(arg); report(arg);
} }
}); });
@ -366,23 +355,91 @@ module.exports = {
* @returns {void} * @returns {void}
* @private * @private
*/ */
function dryBinaryLogical(node) { function checkBinaryLogical(node) {
const prec = precedence(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"); 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); 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); 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 { return {
ArrayExpression(node) { ArrayExpression(node) {
[].forEach.call(node.elements, e => { [].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); report(e);
} }
}); });
@ -394,13 +451,13 @@ module.exports = {
} }
if (node.body.type !== "BlockStatement") { if (node.body.type !== "BlockStatement") {
if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({ type: "AssignmentExpression" })) { const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken);
report(node.body); const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);
return;
}
// Object literals *must* be parenthesised if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) {
if (node.body.type === "ObjectExpression" && hasDoubleExcessParens(node.body)) { tokensToIgnore.add(firstBodyToken);
}
if (hasExcessParens(node.body) && precedence(node.body) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
report(node.body); report(node.body);
} }
} }
@ -416,8 +473,8 @@ module.exports = {
} }
}, },
BinaryExpression: dryBinaryLogical, BinaryExpression: checkBinaryLogical,
CallExpression: dryCallNew, CallExpression: checkCallNew,
ConditionalExpression(node) { ConditionalExpression(node) {
if (isReturnAssignException(node)) { if (isReturnAssignException(node)) {
@ -428,11 +485,11 @@ module.exports = {
report(node.test); 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); 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); report(node.alternate);
} }
}, },
@ -443,27 +500,8 @@ module.exports = {
} }
}, },
ExpressionStatement(node) { ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
if (hasExcessParens(node.expression)) { ExpressionStatement: node => checkExpressionOrExportStatement(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);
}
}
},
ForInStatement(node) { ForInStatement(node) {
if (hasExcessParens(node.right)) { if (hasExcessParens(node.right)) {
@ -497,7 +535,7 @@ module.exports = {
} }
}, },
LogicalExpression: dryBinaryLogical, LogicalExpression: checkBinaryLogical,
MemberExpression(node) { MemberExpression(node) {
if ( if (
@ -506,19 +544,11 @@ module.exports = {
( (
node.computed || node.computed ||
!( !(
(node.object.type === "Literal" && astUtils.isDecimalInteger(node.object) ||
typeof node.object.value === "number" &&
astUtils.isDecimalInteger(node.object))
||
// RegExp literal is allowed to have parens (#1589) // RegExp literal is allowed to have parens (#1589)
(node.object.type === "Literal" && node.object.regex) (node.object.type === "Literal" && node.object.regex)
) )
) &&
!(
(node.object.type === "FunctionExpression" || node.object.type === "ClassExpression") &&
isHeadOfExpressionStatement(node) &&
!hasDoubleExcessParens(node.object)
) )
) { ) {
report(node.object); report(node.object);
@ -528,13 +558,13 @@ module.exports = {
} }
}, },
NewExpression: dryCallNew, NewExpression: checkCallNew,
ObjectExpression(node) { ObjectExpression(node) {
[].forEach.call(node.properties, e => { [].forEach.call(node.properties, e => {
const v = e.value; 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); report(v);
} }
}); });
@ -584,13 +614,13 @@ module.exports = {
} }
}, },
UnaryExpression: dryUnaryUpdate, UnaryExpression: checkUnaryUpdate,
UpdateExpression: dryUnaryUpdate, UpdateExpression: checkUnaryUpdate,
AwaitExpression: dryUnaryUpdate, AwaitExpression: checkUnaryUpdate,
VariableDeclarator(node) { VariableDeclarator(node) {
if (node.init && hasExcessParens(node.init) && 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) // RegExp literal is allowed to have parens (#1589)
!(node.init.type === "Literal" && node.init.regex)) { !(node.init.type === "Literal" && node.init.regex)) {
@ -620,7 +650,14 @@ module.exports = {
report(node.argument); 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"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const FixTracker = require("../util/fix-tracker");
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -34,7 +41,13 @@ module.exports = {
node: nodeOrToken, node: nodeOrToken,
message: "Unnecessary semicolon.", message: "Unnecessary semicolon.",
fix(fixer) { 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) { function checkForPartOfClassBody(firstToken) {
for (let token = firstToken; for (let token = firstToken;
token.type === "Punctuator" && token.value !== "}"; token.type === "Punctuator" && !astUtils.isClosingBraceToken(token);
token = sourceCode.getTokenAfter(token) token = sourceCode.getTokenAfter(token)
) { ) {
if (token.value === ";") { if (astUtils.isSemicolonToken(token)) {
report(token); report(token);
} }
} }

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

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

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

@ -6,6 +6,7 @@
"use strict"; "use strict";
const astUtils = require("../ast-utils"); const astUtils = require("../ast-utils");
const esUtils = require("esutils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
@ -197,19 +198,31 @@ module.exports = {
*/ */
function report(node, recommendation, shouldFix) { function report(node, recommendation, shouldFix) {
shouldFix = typeof shouldFix === "undefined" ? true : shouldFix; shouldFix = typeof shouldFix === "undefined" ? true : shouldFix;
const reportObj = {
context.report({
node, node,
message: "use `{{recommendation}}` instead.", message: "use `{{recommendation}}` instead.",
data: { data: {
recommendation 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 { return {

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

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

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

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

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

@ -6,6 +6,12 @@
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Constants // 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 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_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 IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mg;
const LINE_BREAK = /\r\n|\r|\n|\u2028|\u2029/g; const LINE_BREAK = astUtils.createGlobalLinebreakMatcher();
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition

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

@ -32,22 +32,22 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function report(node) { 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" ? context.report({ node, message });
"Block is redundant." :
"Nested block is redundant."
});
} }
/** /**
* Checks for any ocurrence of BlockStatement > BlockStatement or Program > BlockStatement * Checks for any ocurrence of a BlockStatement in a place where lists of statements can appear
* @returns {boolean} True if the current node is a lone block. * @param {ASTNode} node The node to check
* @returns {boolean} True if the node is a lone block.
*/ */
function isLoneBlock() { function isLoneBlock(node) {
const parent = context.getAncestors().pop(); 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. * @returns {Token} The operator token of the node.
*/ */
function getOperatorToken(node) { function getOperatorToken(node) {
let token = sourceCode.getTokenAfter(node.left); return sourceCode.getTokenAfter(node.left, astUtils.isNotClosingParenToken);
while (token.value === ")") {
token = sourceCode.getTokenAfter(token);
}
return token;
} }
/** /**

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

@ -153,11 +153,11 @@ module.exports = {
// "var utils = require('./utils');" // "var utils = require('./utils');"
return REQ_FILE; 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"; "use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -93,7 +95,8 @@ module.exports = {
const sourceCode = context.getSourceCode(), const sourceCode = context.getSourceCode(),
source = sourceCode.getText(), source = sourceCode.getText(),
allComments = sourceCode.getAllComments(), 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; let parent;

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

@ -5,6 +5,12 @@
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -39,9 +45,7 @@ module.exports = {
return { return {
Literal(node) { Literal(node) {
const lineBreak = /\n/; if (astUtils.LINEBREAK_MATCHER.test(node.raw) && !isJSXElement(node.parent)) {
if (lineBreak.test(node.raw) && !isJSXElement(node.parent)) {
context.report({ node, message: "Multiline support is limited to browsers supporting ES5 only." }); 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"; "use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -114,8 +112,8 @@ module.exports = {
data: { max: maxAllowed, pluralizedLines: maxAllowed === 1 ? "line" : "lines" }, data: { max: maxAllowed, pluralizedLines: maxAllowed === 1 ? "line" : "lines" },
fix(fixer) { fix(fixer) {
return fixer.removeRange([ return fixer.removeRange([
astUtils.getRangeIndexFromLocation(sourceCode, { line: lastLineNumber + 1, column: 0 }), sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 }),
astUtils.getRangeIndexFromLocation(sourceCode, { line: lineNumber - maxAllowed, 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: { docs: {
description: "disallow assignments to native objects or read-only global variables", description: "disallow assignments to native objects or read-only global variables",
category: "Best Practices", category: "Best Practices",
recommended: true, recommended: false,
replacedBy: ["no-global-assign"] replacedBy: ["no-global-assign"]
}, },

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

@ -15,7 +15,7 @@ module.exports = {
docs: { docs: {
description: "disallow negating the left operand in `in` expressions", description: "disallow negating the left operand in `in` expressions",
category: "Possible Errors", category: "Possible Errors",
recommended: true, recommended: false,
replacedBy: ["no-unsafe-negation"] replacedBy: ["no-unsafe-negation"]
}, },
deprecated: true, 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. * Reports a node.
* @param {ASTNode} node The node to check and report on * @param {ASTNode} node The node to report
* @returns {void} * @returns {void}
* @private * @private
*/ */
function validateCallee(node) { function report(node) {
if (node.callee.name === "Function") { context.report({ node, message: "The Function constructor is eval." });
context.report({ node, message: "The Function constructor is eval." });
}
} }
return { return {
NewExpression: validateCallee, "NewExpression[callee.name = 'Function']": report,
CallExpression: validateCallee "CallExpression[callee.name = 'Function']": report
}; };
} }

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

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

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

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

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

Loading…
Cancel
Save