Browse Source

tools: update ESLint to current version

We have been stalled on ESLint 3.8.0 for some time. Current ESLint is
3.13.0. We have been unable to upgrade because of more aggressive
reporting on some rules, including indentation.

ESLint configuration options and bugfixes are now such that we can
reasonably upgrade.

PR-URL: https://github.com/nodejs/node/pull/10561
Reviewed-By: Teddy Katz <teddy.katz@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
v6
Rich Trott 8 years ago
parent
commit
f44969a5ab
  1. 6
      .eslintrc
  2. 2
      tools/eslint/LICENSE
  3. 7
      tools/eslint/README.md
  4. 10
      tools/eslint/bin/eslint.js
  5. 14
      tools/eslint/conf/eslint.json
  6. 411
      tools/eslint/lib/ast-utils.js
  7. 30
      tools/eslint/lib/cli-engine.js
  8. 31
      tools/eslint/lib/code-path-analysis/code-path-analyzer.js
  9. 279
      tools/eslint/lib/code-path-analysis/code-path-segment.js
  10. 131
      tools/eslint/lib/code-path-analysis/code-path-state.js
  11. 93
      tools/eslint/lib/code-path-analysis/code-path.js
  12. 8
      tools/eslint/lib/code-path-analysis/debug-helpers.js
  13. 93
      tools/eslint/lib/code-path-analysis/fork-context.js
  14. 43
      tools/eslint/lib/code-path-analysis/id-generator.js
  15. 236
      tools/eslint/lib/config.js
  16. 62
      tools/eslint/lib/config/autoconfig.js
  17. 12
      tools/eslint/lib/config/config-file.js
  18. 84
      tools/eslint/lib/config/config-initializer.js
  19. 20
      tools/eslint/lib/config/config-ops.js
  20. 32
      tools/eslint/lib/config/config-rule.js
  21. 94
      tools/eslint/lib/config/config-validator.js
  22. 6
      tools/eslint/lib/config/environments.js
  23. 102
      tools/eslint/lib/eslint.js
  24. 130
      tools/eslint/lib/file-finder.js
  25. 4
      tools/eslint/lib/formatters/checkstyle.js
  26. 121
      tools/eslint/lib/formatters/codeframe.js
  27. 4
      tools/eslint/lib/formatters/compact.js
  28. 20
      tools/eslint/lib/formatters/html.js
  29. 4
      tools/eslint/lib/formatters/jslint-xml.js
  30. 4
      tools/eslint/lib/formatters/junit.js
  31. 10
      tools/eslint/lib/formatters/stylish.js
  32. 10
      tools/eslint/lib/formatters/table.js
  33. 4
      tools/eslint/lib/formatters/tap.js
  34. 4
      tools/eslint/lib/formatters/unix.js
  35. 4
      tools/eslint/lib/formatters/visualstudio.js
  36. 247
      tools/eslint/lib/ignored-paths.js
  37. 1
      tools/eslint/lib/internal-rules/internal-consistent-docs-description.js
  38. 3
      tools/eslint/lib/internal-rules/internal-no-invalid-meta.js
  39. 2
      tools/eslint/lib/load-rules.js
  40. 57
      tools/eslint/lib/rule-context.js
  41. 20
      tools/eslint/lib/rules.js
  42. 4
      tools/eslint/lib/rules/accessor-pairs.js
  43. 6
      tools/eslint/lib/rules/array-bracket-spacing.js
  44. 98
      tools/eslint/lib/rules/arrow-body-style.js
  45. 11
      tools/eslint/lib/rules/arrow-parens.js
  46. 5
      tools/eslint/lib/rules/block-scoped-var.js
  47. 2
      tools/eslint/lib/rules/block-spacing.js
  48. 82
      tools/eslint/lib/rules/brace-style.js
  49. 2
      tools/eslint/lib/rules/callback-return.js
  50. 7
      tools/eslint/lib/rules/camelcase.js
  51. 301
      tools/eslint/lib/rules/capitalized-comments.js
  52. 15
      tools/eslint/lib/rules/comma-dangle.js
  53. 4
      tools/eslint/lib/rules/comma-spacing.js
  54. 57
      tools/eslint/lib/rules/comma-style.js
  55. 2
      tools/eslint/lib/rules/complexity.js
  56. 19
      tools/eslint/lib/rules/consistent-return.js
  57. 21
      tools/eslint/lib/rules/consistent-this.js
  58. 4
      tools/eslint/lib/rules/constructor-super.js
  59. 19
      tools/eslint/lib/rules/curly.js
  60. 8
      tools/eslint/lib/rules/default-case.js
  61. 28
      tools/eslint/lib/rules/eqeqeq.js
  62. 9
      tools/eslint/lib/rules/func-call-spacing.js
  63. 77
      tools/eslint/lib/rules/func-name-matching.js
  64. 31
      tools/eslint/lib/rules/func-names.js
  65. 6
      tools/eslint/lib/rules/func-style.js
  66. 4
      tools/eslint/lib/rules/generator-star-spacing.js
  67. 12
      tools/eslint/lib/rules/global-require.js
  68. 2
      tools/eslint/lib/rules/guard-for-in.js
  69. 6
      tools/eslint/lib/rules/handle-callback-err.js
  70. 4
      tools/eslint/lib/rules/id-blacklist.js
  71. 10
      tools/eslint/lib/rules/id-length.js
  72. 4
      tools/eslint/lib/rules/id-match.js
  73. 268
      tools/eslint/lib/rules/indent.js
  74. 2
      tools/eslint/lib/rules/jsx-quotes.js
  75. 8
      tools/eslint/lib/rules/key-spacing.js
  76. 18
      tools/eslint/lib/rules/keyword-spacing.js
  77. 16
      tools/eslint/lib/rules/lines-around-comment.js
  78. 27
      tools/eslint/lib/rules/lines-around-directive.js
  79. 3
      tools/eslint/lib/rules/max-depth.js
  80. 32
      tools/eslint/lib/rules/max-len.js
  81. 16
      tools/eslint/lib/rules/max-lines.js
  82. 4
      tools/eslint/lib/rules/max-nested-callbacks.js
  83. 4
      tools/eslint/lib/rules/max-params.js
  84. 20
      tools/eslint/lib/rules/max-statements.js
  85. 2
      tools/eslint/lib/rules/new-cap.js
  86. 27
      tools/eslint/lib/rules/new-parens.js
  87. 63
      tools/eslint/lib/rules/newline-after-var.js
  88. 6
      tools/eslint/lib/rules/newline-before-return.js
  89. 6
      tools/eslint/lib/rules/no-alert.js
  90. 2
      tools/eslint/lib/rules/no-array-constructor.js
  91. 75
      tools/eslint/lib/rules/no-await-in-loop.js
  92. 2
      tools/eslint/lib/rules/no-bitwise.js
  93. 2
      tools/eslint/lib/rules/no-caller.js
  94. 3
      tools/eslint/lib/rules/no-catch-shadow.js
  95. 7
      tools/eslint/lib/rules/no-class-assign.js
  96. 4
      tools/eslint/lib/rules/no-cond-assign.js
  97. 4
      tools/eslint/lib/rules/no-confusing-arrow.js
  98. 7
      tools/eslint/lib/rules/no-const-assign.js
  99. 2
      tools/eslint/lib/rules/no-constant-condition.js
  100. 2
      tools/eslint/lib/rules/no-continue.js

6
.eslintrc

@ -84,7 +84,11 @@ rules:
eol-last: 2 eol-last: 2
func-call-spacing: 2 func-call-spacing: 2
func-name-matching: 2 func-name-matching: 2
indent: [2, 2, {SwitchCase: 1, MemberExpression: 1}] indent: [2, 2, {ArrayExpression: first,
CallExpression: {arguments: first},
MemberExpression: 1,
ObjectExpression: first,
SwitchCase: 1}]
key-spacing: [2, {mode: minimum}] key-spacing: [2, {mode: minimum}]
keyword-spacing: 2 keyword-spacing: 2
linebreak-style: [2, unix] linebreak-style: [2, unix]

2
tools/eslint/LICENSE

@ -1,5 +1,5 @@
ESLint ESLint
Copyright jQuery Foundation and other contributors, https://jquery.org/ Copyright JS Foundation and other contributors, https://js.foundation
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

7
tools/eslint/README.md

@ -13,7 +13,7 @@
[Rules](http://eslint.org/docs/rules/) | [Rules](http://eslint.org/docs/rules/) |
[Contributing](http://eslint.org/docs/developer-guide/contributing) | [Contributing](http://eslint.org/docs/developer-guide/contributing) |
[Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) | [Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) |
[Code of Conduct](https://jquery.org/conduct/) | [Code of Conduct](https://js.foundation/conduct/) |
[Twitter](https://twitter.com/geteslint) | [Twitter](https://twitter.com/geteslint) |
[Mailing List](https://groups.google.com/group/eslint) | [Mailing List](https://groups.google.com/group/eslint) |
[Chat Room](https://gitter.im/eslint/eslint) [Chat Room](https://gitter.im/eslint/eslint)
@ -210,10 +210,7 @@ ESLint has full support for ECMAScript 6. By default, this support is off. You c
ESLint doesn't natively support experimental ECMAScript language features. You can use [babel-eslint](https://github.com/babel/babel-eslint) to use any option available in Babel. ESLint doesn't natively support experimental ECMAScript language features. You can use [babel-eslint](https://github.com/babel/babel-eslint) to use any option available in Babel.
Once a language feature has been adopted into the ECMAScript standard, we will accept Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](http://eslint.org/docs/developer-guide/contributing). Until then, please use the appropriate parser and plugin(s) for your experimental feature.
issues and pull requests related to the new feature, subject to our [contributing
guidelines](http://eslint.org/docs/developer-guide/contributing). Until then, please use
the appropriate parser and plugin(s) for your experimental feature.
### Where to ask for help? ### Where to ask for help?

10
tools/eslint/bin/eslint.js

@ -5,7 +5,7 @@
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
*/ */
/* eslint no-console:off, no-process-exit:off */ /* eslint no-console:off */
"use strict"; "use strict";
@ -36,7 +36,7 @@ const concat = require("concat-stream"),
// Execution // Execution
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
process.on("uncaughtException", function(err) { process.once("uncaughtException", err => {
// lazy load // lazy load
const lodash = require("lodash"); const lodash = require("lodash");
@ -51,17 +51,17 @@ process.on("uncaughtException", function(err) {
console.log(err.stack); console.log(err.stack);
} }
process.exit(1); process.exitCode = 1;
}); });
if (useStdIn) { if (useStdIn) {
process.stdin.pipe(concat({ encoding: "string" }, function(text) { process.stdin.pipe(concat({ encoding: "string" }, text => {
process.exitCode = cli.execute(process.argv, text); process.exitCode = cli.execute(process.argv, text);
})); }));
} else if (init) { } else if (init) {
const configInit = require("../lib/config/config-initializer"); const configInit = require("../lib/config/config-initializer");
configInit.initializeConfig(function(err) { configInit.initializeConfig(err => {
if (err) { if (err) {
process.exitCode = 1; process.exitCode = 1;
console.error(err.message); console.error(err.message);

14
tools/eslint/conf/eslint.json

@ -4,6 +4,7 @@
"rules": { "rules": {
"no-alert": "off", "no-alert": "off",
"no-array-constructor": "off", "no-array-constructor": "off",
"no-await-in-loop": "off",
"no-bitwise": "off", "no-bitwise": "off",
"no-caller": "off", "no-caller": "off",
"no-case-declarations": "error", "no-case-declarations": "error",
@ -41,7 +42,7 @@
"no-fallthrough": "error", "no-fallthrough": "error",
"no-floating-decimal": "off", "no-floating-decimal": "off",
"no-func-assign": "error", "no-func-assign": "error",
"no-global-assign": "off", "no-global-assign": "error",
"no-implicit-coercion": "off", "no-implicit-coercion": "off",
"no-implicit-globals": "off", "no-implicit-globals": "off",
"no-implied-eval": "off", "no-implied-eval": "off",
@ -63,9 +64,9 @@
"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",
"no-native-reassign": "error", "no-native-reassign": "off",
"no-negated-condition": "off", "no-negated-condition": "off",
"no-negated-in-lhs": "error", "no-negated-in-lhs": "off",
"no-nested-ternary": "off", "no-nested-ternary": "off",
"no-new": "off", "no-new": "off",
"no-new-func": "off", "no-new-func": "off",
@ -91,6 +92,7 @@
"no-restricted-properties": "off", "no-restricted-properties": "off",
"no-restricted-syntax": "off", "no-restricted-syntax": "off",
"no-return-assign": "off", "no-return-assign": "off",
"no-return-await": "off",
"no-script-url": "off", "no-script-url": "off",
"no-self-assign": "error", "no-self-assign": "error",
"no-self-compare": "off", "no-self-compare": "off",
@ -115,7 +117,7 @@
"no-unneeded-ternary": "off", "no-unneeded-ternary": "off",
"no-unreachable": "error", "no-unreachable": "error",
"no-unsafe-finally": "error", "no-unsafe-finally": "error",
"no-unsafe-negation": "off", "no-unsafe-negation": "error",
"no-unused-expressions": "off", "no-unused-expressions": "off",
"no-unused-labels": "error", "no-unused-labels": "error",
"no-unused-vars": "error", "no-unused-vars": "error",
@ -126,6 +128,7 @@
"no-useless-constructor": "off", "no-useless-constructor": "off",
"no-useless-escape": "off", "no-useless-escape": "off",
"no-useless-rename": "off", "no-useless-rename": "off",
"no-useless-return": "off",
"no-void": "off", "no-void": "off",
"no-var": "off", "no-var": "off",
"no-warning-comments": "off", "no-warning-comments": "off",
@ -141,6 +144,7 @@
"brace-style": "off", "brace-style": "off",
"callback-return": "off", "callback-return": "off",
"camelcase": "off", "camelcase": "off",
"capitalized-comments": "off",
"class-methods-use-this": "off", "class-methods-use-this": "off",
"comma-dangle": "off", "comma-dangle": "off",
"comma-spacing": "off", "comma-spacing": "off",
@ -200,6 +204,7 @@
"padded-blocks": "off", "padded-blocks": "off",
"prefer-arrow-callback": "off", "prefer-arrow-callback": "off",
"prefer-const": "off", "prefer-const": "off",
"prefer-destructuring": "off",
"prefer-numeric-literals": "off", "prefer-numeric-literals": "off",
"prefer-reflect": "off", "prefer-reflect": "off",
"prefer-rest-params": "off", "prefer-rest-params": "off",
@ -208,6 +213,7 @@
"quote-props": "off", "quote-props": "off",
"quotes": "off", "quotes": "off",
"radix": "off", "radix": "off",
"require-await": "off",
"require-jsdoc": "off", "require-jsdoc": "off",
"require-yield": "error", "require-yield": "error",
"rest-spread-spacing": "off", "rest-spread-spacing": "off",

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

@ -10,6 +10,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const esutils = require("esutils"); const esutils = require("esutils");
const lodash = require("lodash");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
@ -21,7 +22,7 @@ const arrayOrTypedArrayPattern = /Array$/;
const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/; const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/;
const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/; 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;
/** /**
* Checks reference if is non initializer and writable. * Checks reference if is non initializer and writable.
@ -83,6 +84,56 @@ function getUpperFunction(node) {
return null; return null;
} }
/**
* Checks whether a given node is a function node or not.
* The following types are function nodes:
*
* - ArrowFunctionExpression
* - FunctionDeclaration
* - FunctionExpression
*
* @param {ASTNode|null} node - A node to check.
* @returns {boolean} `true` if the node is a function node.
*/
function isFunction(node) {
return Boolean(node && anyFunctionPattern.test(node.type));
}
/**
* Checks whether a given node is a loop node or not.
* The following types are loop nodes:
*
* - DoWhileStatement
* - ForInStatement
* - ForOfStatement
* - ForStatement
* - WhileStatement
*
* @param {ASTNode|null} node - A node to check.
* @returns {boolean} `true` if the node is a loop node.
*/
function isLoop(node) {
return Boolean(node && anyLoopPattern.test(node.type));
}
/**
* Checks whether the given node is in a loop or not.
*
* @param {ASTNode} node - The node to check.
* @returns {boolean} `true` if the node is in a loop.
*/
function isInLoop(node) {
while (node && !isFunction(node)) {
if (isLoop(node)) {
return true;
}
node = node.parent;
}
return false;
}
/** /**
* Checks whether or not a node is `null` or `undefined`. * Checks whether or not a node is `null` or `undefined`.
* @param {ASTNode} node - A node to check. * @param {ASTNode} node - A node to check.
@ -176,9 +227,7 @@ function hasJSDocThisTag(node, sourceCode) {
// because callbacks don't have its JSDoc comment. // because callbacks don't have its JSDoc comment.
// e.g. // e.g.
// sinon.test(/* @this sinon.Sandbox */function() { this.spy(); }); // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); });
return sourceCode.getComments(node).leading.some(function(comment) { return sourceCode.getComments(node).leading.some(comment => thisTagPattern.test(comment.value));
return thisTagPattern.test(comment.value);
});
} }
/** /**
@ -197,6 +246,59 @@ function isParenthesised(sourceCode, node) {
nextToken.value === ")" && nextToken.range[0] >= node.range[1]; nextToken.value === ")" && nextToken.range[0] >= node.range[1];
} }
/**
* Gets the `=>` token of the given arrow function node.
*
* @param {ASTNode} node - The arrow function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {Token} `=>` token.
*/
function getArrowToken(node, sourceCode) {
let token = sourceCode.getTokenBefore(node.body);
while (token.value !== "=>") {
token = sourceCode.getTokenBefore(token);
}
return token;
}
/**
* 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) {
let token = node.id ? sourceCode.getTokenAfter(node.id) : sourceCode.getFirstToken(node);
while (token.value !== "(") {
token = sourceCode.getTokenAfter(token);
}
return token;
}
const lineIndexCache = new WeakMap();
/**
* Gets the range index for the first character in each of the lines of `sourceCode`.
* @param {SourceCode} sourceCode A sourceCode object
* @returns {number[]} The indices of the first characters in the each of the lines of the code
*/
function getLineIndices(sourceCode) {
if (!lineIndexCache.has(sourceCode)) {
const lineIndices = (sourceCode.text.match(/[^\r\n\u2028\u2029]*(\r\n|\r|\n|\u2028|\u2029)/g) || [])
.reduce((indices, line) => indices.concat(indices[indices.length - 1] + line.length), [0]);
// Store the sourceCode object in a WeakMap to avoid iterating over all of the lines every time a sourceCode object is passed in.
lineIndexCache.set(sourceCode, lineIndices);
}
return lineIndexCache.get(sourceCode);
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Public Interface // Public Interface
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -218,6 +320,9 @@ module.exports = {
isCallee, isCallee,
isES5Constructor, isES5Constructor,
getUpperFunction, getUpperFunction,
isFunction,
isLoop,
isInLoop,
isArrayFromMethod, isArrayFromMethod,
isParenthesised, isParenthesised,
@ -583,35 +688,23 @@ module.exports = {
}, },
/** /**
* Checks whether a given node is a loop node or not. * Checks whether the given node is an empty block node or not.
* The following types are loop nodes:
* *
* - DoWhileStatement * @param {ASTNode|null} node - The node to check.
* - ForInStatement * @returns {boolean} `true` if the node is an empty block.
* - ForOfStatement
* - ForStatement
* - WhileStatement
*
* @param {ASTNode|null} node - A node to check.
* @returns {boolean} `true` if the node is a loop node.
*/ */
isLoop(node) { isEmptyBlock(node) {
return Boolean(node && anyLoopPattern.test(node.type)); return Boolean(node && node.type === "BlockStatement" && node.body.length === 0);
}, },
/** /**
* Checks whether a given node is a function node or not. * Checks whether the given node is an empty function node or not.
* The following types are function nodes:
* *
* - ArrowFunctionExpression * @param {ASTNode|null} node - The node to check.
* - FunctionDeclaration * @returns {boolean} `true` if the node is an empty function.
* - FunctionExpression
*
* @param {ASTNode|null} node - A node to check.
* @returns {boolean} `true` if the node is a function node.
*/ */
isFunction(node) { isEmptyFunction(node) {
return Boolean(node && anyFunctionPattern.test(node.type)); return isFunction(node) && module.exports.isEmptyBlock(node.body);
}, },
/** /**
@ -738,5 +831,271 @@ module.exports = {
*/ */
isDecimalInteger(node) { isDecimalInteger(node) {
return node.type === "Literal" && typeof node.value === "number" && /^(0|[1-9]\d*)$/.test(node.raw); return node.type === "Literal" && typeof node.value === "number" && /^(0|[1-9]\d*)$/.test(node.raw);
},
/**
* Gets the name and kind of the given function node.
*
* - `function foo() {}` .................... `function 'foo'`
* - `(function foo() {})` .................. `function 'foo'`
* - `(function() {})` ...................... `function`
* - `function* foo() {}` ................... `generator function 'foo'`
* - `(function* foo() {})` ................. `generator function 'foo'`
* - `(function*() {})` ..................... `generator function`
* - `() => {}` ............................. `arrow function`
* - `async () => {}` ....................... `async arrow function`
* - `({ foo: function foo() {} })` ......... `method 'foo'`
* - `({ foo: function() {} })` ............. `method 'foo'`
* - `({ ['foo']: function() {} })` ......... `method 'foo'`
* - `({ [foo]: function() {} })` ........... `method`
* - `({ foo() {} })` ....................... `method 'foo'`
* - `({ foo: function* foo() {} })` ........ `generator method 'foo'`
* - `({ foo: function*() {} })` ............ `generator method 'foo'`
* - `({ ['foo']: function*() {} })` ........ `generator method 'foo'`
* - `({ [foo]: function*() {} })` .......... `generator method`
* - `({ *foo() {} })` ...................... `generator method 'foo'`
* - `({ foo: async function foo() {} })` ... `async method 'foo'`
* - `({ foo: async function() {} })` ....... `async method 'foo'`
* - `({ ['foo']: async function() {} })` ... `async method 'foo'`
* - `({ [foo]: async function() {} })` ..... `async method`
* - `({ async foo() {} })` ................. `async method 'foo'`
* - `({ get foo() {} })` ................... `getter 'foo'`
* - `({ set foo(a) {} })` .................. `setter 'foo'`
* - `class A { constructor() {} }` ......... `constructor`
* - `class A { foo() {} }` ................. `method 'foo'`
* - `class A { *foo() {} }` ................ `generator method 'foo'`
* - `class A { async foo() {} }` ........... `async method 'foo'`
* - `class A { ['foo']() {} }` ............. `method 'foo'`
* - `class A { *['foo']() {} }` ............ `generator method 'foo'`
* - `class A { async ['foo']() {} }` ....... `async method 'foo'`
* - `class A { [foo]() {} }` ............... `method`
* - `class A { *[foo]() {} }` .............. `generator method`
* - `class A { async [foo]() {} }` ......... `async method`
* - `class A { get foo() {} }` ............. `getter 'foo'`
* - `class A { set foo(a) {} }` ............ `setter 'foo'`
* - `class A { static foo() {} }` .......... `static method 'foo'`
* - `class A { static *foo() {} }` ......... `static generator method 'foo'`
* - `class A { static async foo() {} }` .... `static async method 'foo'`
* - `class A { static get foo() {} }` ...... `static getter 'foo'`
* - `class A { static set foo(a) {} }` ..... `static setter 'foo'`
*
* @param {ASTNode} node - The function node to get.
* @returns {string} The name and kind of the function node.
*/
getFunctionNameWithKind(node) {
const parent = node.parent;
const tokens = [];
if (parent.type === "MethodDefinition" && parent.static) {
tokens.push("static");
}
if (node.async) {
tokens.push("async");
}
if (node.generator) {
tokens.push("generator");
}
if (node.type === "ArrowFunctionExpression") {
tokens.push("arrow", "function");
} else if (parent.type === "Property" || parent.type === "MethodDefinition") {
if (parent.kind === "constructor") {
return "constructor";
} else if (parent.kind === "get") {
tokens.push("getter");
} else if (parent.kind === "set") {
tokens.push("setter");
} else {
tokens.push("method");
}
} else {
tokens.push("function");
}
if (node.id) {
tokens.push(`'${node.id.name}'`);
} else {
const name = module.exports.getStaticPropertyName(parent);
if (name) {
tokens.push(`'${name}'`);
}
}
return tokens.join(" ");
},
/**
* Gets the location of the given function node for reporting.
*
* - `function foo() {}`
* ^^^^^^^^^^^^
* - `(function foo() {})`
* ^^^^^^^^^^^^
* - `(function() {})`
* ^^^^^^^^
* - `function* foo() {}`
* ^^^^^^^^^^^^^
* - `(function* foo() {})`
* ^^^^^^^^^^^^^
* - `(function*() {})`
* ^^^^^^^^^
* - `() => {}`
* ^^
* - `async () => {}`
* ^^
* - `({ foo: function foo() {} })`
* ^^^^^^^^^^^^^^^^^
* - `({ foo: function() {} })`
* ^^^^^^^^^^^^^
* - `({ ['foo']: function() {} })`
* ^^^^^^^^^^^^^^^^^
* - `({ [foo]: function() {} })`
* ^^^^^^^^^^^^^^^
* - `({ foo() {} })`
* ^^^
* - `({ foo: function* foo() {} })`
* ^^^^^^^^^^^^^^^^^^
* - `({ foo: function*() {} })`
* ^^^^^^^^^^^^^^
* - `({ ['foo']: function*() {} })`
* ^^^^^^^^^^^^^^^^^^
* - `({ [foo]: function*() {} })`
* ^^^^^^^^^^^^^^^^
* - `({ *foo() {} })`
* ^^^^
* - `({ foo: async function foo() {} })`
* ^^^^^^^^^^^^^^^^^^^^^^^
* - `({ foo: async function() {} })`
* ^^^^^^^^^^^^^^^^^^^
* - `({ ['foo']: async function() {} })`
* ^^^^^^^^^^^^^^^^^^^^^^^
* - `({ [foo]: async function() {} })`
* ^^^^^^^^^^^^^^^^^^^^^
* - `({ async foo() {} })`
* ^^^^^^^^^
* - `({ get foo() {} })`
* ^^^^^^^
* - `({ set foo(a) {} })`
* ^^^^^^^
* - `class A { constructor() {} }`
* ^^^^^^^^^^^
* - `class A { foo() {} }`
* ^^^
* - `class A { *foo() {} }`
* ^^^^
* - `class A { async foo() {} }`
* ^^^^^^^^^
* - `class A { ['foo']() {} }`
* ^^^^^^^
* - `class A { *['foo']() {} }`
* ^^^^^^^^
* - `class A { async ['foo']() {} }`
* ^^^^^^^^^^^^^
* - `class A { [foo]() {} }`
* ^^^^^
* - `class A { *[foo]() {} }`
* ^^^^^^
* - `class A { async [foo]() {} }`
* ^^^^^^^^^^^
* - `class A { get foo() {} }`
* ^^^^^^^
* - `class A { set foo(a) {} }`
* ^^^^^^^
* - `class A { static foo() {} }`
* ^^^^^^^^^^
* - `class A { static *foo() {} }`
* ^^^^^^^^^^^
* - `class A { static async foo() {} }`
* ^^^^^^^^^^^^^^^^
* - `class A { static get foo() {} }`
* ^^^^^^^^^^^^^^
* - `class A { static set foo(a) {} }`
* ^^^^^^^^^^^^^^
*
* @param {ASTNode} node - The function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {string} The location of the function node for reporting.
*/
getFunctionHeadLoc(node, sourceCode) {
const parent = node.parent;
let start = null;
let end = null;
if (node.type === "ArrowFunctionExpression") {
const arrowToken = getArrowToken(node, sourceCode);
start = arrowToken.loc.start;
end = arrowToken.loc.end;
} else if (parent.type === "Property" || parent.type === "MethodDefinition") {
start = parent.loc.start;
end = getOpeningParenOfParams(node, sourceCode).loc.start;
} else {
start = node.loc.start;
end = getOpeningParenOfParams(node, sourceCode).loc.start;
}
return {
start: Object.assign({}, start),
end: Object.assign({}, end),
};
},
/*
* Converts a range index into a (line, column) pair.
* @param {SourceCode} sourceCode A SourceCode object
* @param {number} rangeIndex The range index of a character in a file
* @returns {Object} A {line, column} location object with a 0-indexed column
*/
getLocationFromRangeIndex(sourceCode, rangeIndex) {
const lineIndices = getLineIndices(sourceCode);
/*
* lineIndices is a sorted list of indices of the first character of each line.
* To figure out which line rangeIndex is on, determine the last index at which rangeIndex could
* be inserted into lineIndices to keep the list sorted.
*/
const lineNumber = lodash.sortedLastIndex(lineIndices, rangeIndex);
return { line: lineNumber, column: rangeIndex - lineIndices[lineNumber - 1] };
},
/**
* Converts a (line, column) pair into a range index.
* @param {SourceCode} sourceCode A SourceCode object
* @param {Object} loc A line/column location
* @param {number} loc.line The line number of the location (1-indexed)
* @param {number} loc.column The column number of the location (0-indexed)
* @returns {number} The range index of the location in the file.
*/
getRangeIndexFromLocation(sourceCode, loc) {
return getLineIndices(sourceCode)[loc.line - 1] + loc.column;
},
/**
* Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses
* surrounding the node.
* @param {SourceCode} sourceCode The source code object
* @param {ASTNode} node An expression node
* @returns {string} The text representing the node, with all surrounding parentheses included
*/
getParenthesisedText(sourceCode, node) {
let leftToken = sourceCode.getFirstToken(node);
let rightToken = sourceCode.getLastToken(node);
while (
sourceCode.getTokenBefore(leftToken) &&
sourceCode.getTokenBefore(leftToken).type === "Punctuator" &&
sourceCode.getTokenBefore(leftToken).value === "(" &&
sourceCode.getTokenAfter(rightToken) &&
sourceCode.getTokenAfter(rightToken).type === "Punctuator" &&
sourceCode.getTokenAfter(rightToken).value === ")"
) {
leftToken = sourceCode.getTokenBefore(leftToken);
rightToken = sourceCode.getTokenAfter(rightToken);
}
return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]);
} }
}; };

30
tools/eslint/lib/cli-engine.js

@ -90,7 +90,7 @@ const debug = require("debug")("eslint:cli-engine");
* @private * @private
*/ */
function calculateStatsPerFile(messages) { function calculateStatsPerFile(messages) {
return messages.reduce(function(stat, message) { return messages.reduce((stat, message) => {
if (message.fatal || message.severity === 2) { if (message.fatal || message.severity === 2) {
stat.errorCount++; stat.errorCount++;
} else { } else {
@ -110,7 +110,7 @@ function calculateStatsPerFile(messages) {
* @private * @private
*/ */
function calculateStatsPerRun(results) { function calculateStatsPerRun(results) {
return results.reduce(function(stat, result) { return results.reduce((stat, result) => {
stat.errorCount += result.errorCount; stat.errorCount += result.errorCount;
stat.warningCount += result.warningCount; stat.warningCount += result.warningCount;
return stat; return stat;
@ -241,7 +241,7 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
const parsedBlocks = processor.preprocess(text, filename); const parsedBlocks = processor.preprocess(text, filename);
const unprocessedMessages = []; const unprocessedMessages = [];
parsedBlocks.forEach(function(block) { parsedBlocks.forEach(block => {
unprocessedMessages.push(eslint.verify(block, config, { unprocessedMessages.push(eslint.verify(block, config, {
filename, filename,
allowInlineConfig allowInlineConfig
@ -320,11 +320,11 @@ function createIgnoreResult(filePath, baseDir) {
const isInBowerComponents = baseDir && /^bower_components/.test(path.relative(baseDir, filePath)); const isInBowerComponents = baseDir && /^bower_components/.test(path.relative(baseDir, filePath));
if (isHidden) { if (isHidden) {
message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern \'!<relative/path/to/filename>\'\") to override."; message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override.";
} else if (isInNodeModules) { } else if (isInNodeModules) {
message = "File ignored by default. Use \"--ignore-pattern \'!node_modules/*\'\" to override."; message = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override.";
} else if (isInBowerComponents) { } else if (isInBowerComponents) {
message = "File ignored by default. Use \"--ignore-pattern \'!bower_components/*\'\" to override."; message = "File ignored by default. Use \"--ignore-pattern '!bower_components/*'\" to override.";
} else { } else {
message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."; message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
} }
@ -442,7 +442,7 @@ function CLIEngine(options) {
options = Object.assign( options = Object.assign(
Object.create(null), Object.create(null),
defaultOptions, defaultOptions,
{cwd: process.cwd()}, { cwd: process.cwd() },
options options
); );
@ -466,15 +466,15 @@ function CLIEngine(options) {
if (this.options.rulePaths) { if (this.options.rulePaths) {
const cwd = this.options.cwd; const cwd = this.options.cwd;
this.options.rulePaths.forEach(function(rulesdir) { this.options.rulePaths.forEach(rulesdir => {
debug(`Loading rules from ${rulesdir}`); debug(`Loading rules from ${rulesdir}`);
rules.load(rulesdir, cwd); rules.load(rulesdir, cwd);
}); });
} }
Object.keys(this.options.rules || {}).forEach(function(name) { Object.keys(this.options.rules || {}).forEach(name => {
validator.validateRuleOptions(name, this.options.rules[name], "CLI"); validator.validateRuleOptions(name, this.options.rules[name], "CLI");
}.bind(this)); });
} }
/** /**
@ -526,7 +526,7 @@ CLIEngine.getFormatter = function(format) {
CLIEngine.getErrorResults = function(results) { CLIEngine.getErrorResults = function(results) {
const filtered = []; const filtered = [];
results.forEach(function(result) { results.forEach(result => {
const filteredMessages = result.messages.filter(isErrorMessage); const filteredMessages = result.messages.filter(isErrorMessage);
if (filteredMessages.length > 0) { if (filteredMessages.length > 0) {
@ -549,9 +549,7 @@ CLIEngine.getErrorResults = function(results) {
* @returns {void} * @returns {void}
*/ */
CLIEngine.outputFixes = function(report) { CLIEngine.outputFixes = function(report) {
report.results.filter(function(result) { report.results.filter(result => result.hasOwnProperty("output")).forEach(result => {
return result.hasOwnProperty("output");
}).forEach(function(result) {
fs.writeFileSync(result.filePath, result.output); fs.writeFileSync(result.filePath, result.output);
}); });
}; };
@ -708,7 +706,7 @@ CLIEngine.prototype = {
patterns = this.resolveFileGlobPatterns(patterns); patterns = this.resolveFileGlobPatterns(patterns);
const fileList = globUtil.listFilesToProcess(patterns, options); const fileList = globUtil.listFilesToProcess(patterns, options);
fileList.forEach(function(fileInfo) { fileList.forEach(fileInfo => {
executeOnFile(fileInfo.filename, fileInfo.ignored); executeOnFile(fileInfo.filename, fileInfo.ignored);
}); });
@ -794,4 +792,6 @@ CLIEngine.prototype = {
}; };
CLIEngine.version = pkg.version;
module.exports = CLIEngine; module.exports = CLIEngine;

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

@ -569,21 +569,20 @@ function postprocess(analyzer, node) {
/** /**
* The class to analyze code paths. * The class to analyze code paths.
* This class implements the EventGenerator interface. * This class implements the EventGenerator interface.
*
* @constructor
* @param {EventGenerator} eventGenerator - An event generator to wrap.
*/ */
function CodePathAnalyzer(eventGenerator) { class CodePathAnalyzer {
this.original = eventGenerator;
this.emitter = eventGenerator.emitter;
this.codePath = null;
this.idGenerator = new IdGenerator("s");
this.currentNode = null;
this.onLooped = this.onLooped.bind(this);
}
CodePathAnalyzer.prototype = { /**
constructor: CodePathAnalyzer, * @param {EventGenerator} eventGenerator - An event generator to wrap.
*/
constructor(eventGenerator) {
this.original = eventGenerator;
this.emitter = eventGenerator.emitter;
this.codePath = null;
this.idGenerator = new IdGenerator("s");
this.currentNode = null;
this.onLooped = this.onLooped.bind(this);
}
/** /**
* Does the process to enter a given AST node. * Does the process to enter a given AST node.
@ -608,7 +607,7 @@ CodePathAnalyzer.prototype = {
this.original.enterNode(node); this.original.enterNode(node);
this.currentNode = null; this.currentNode = null;
}, }
/** /**
* Does the process to leave a given AST node. * Does the process to leave a given AST node.
@ -631,7 +630,7 @@ CodePathAnalyzer.prototype = {
postprocess(this, node); postprocess(this, node);
this.currentNode = null; this.currentNode = null;
}, }
/** /**
* This is called on a code path looped. * This is called on a code path looped.
@ -652,6 +651,6 @@ CodePathAnalyzer.prototype = {
); );
} }
} }
}; }
module.exports = CodePathAnalyzer; module.exports = CodePathAnalyzer;

279
tools/eslint/lib/code-path-analysis/code-path-segment.js

@ -68,174 +68,175 @@ function isReachable(segment) {
/** /**
* A code path segment. * A code path segment.
*
* @constructor
* @param {string} id - An identifier.
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
* This array includes unreachable segments.
* @param {boolean} reachable - A flag which shows this is reachable.
*/ */
function CodePathSegment(id, allPrevSegments, reachable) { class CodePathSegment {
/** /**
* The identifier of this code path. * @param {string} id - An identifier.
* Rules use it to store additional information of each rule. * @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
* @type {string} * This array includes unreachable segments.
* @param {boolean} reachable - A flag which shows this is reachable.
*/ */
this.id = id; constructor(id, allPrevSegments, reachable) {
/**
* The identifier of this code path.
* Rules use it to store additional information of each rule.
* @type {string}
*/
this.id = id;
/**
* An array of the next segments.
* @type {CodePathSegment[]}
*/
this.nextSegments = [];
/**
* An array of the previous segments.
* @type {CodePathSegment[]}
*/
this.prevSegments = allPrevSegments.filter(isReachable);
/**
* An array of the next segments.
* This array includes unreachable segments.
* @type {CodePathSegment[]}
*/
this.allNextSegments = [];
/**
* An array of the previous segments.
* This array includes unreachable segments.
* @type {CodePathSegment[]}
*/
this.allPrevSegments = allPrevSegments;
/**
* A flag which shows this is reachable.
* @type {boolean}
*/
this.reachable = reachable;
// Internal data.
Object.defineProperty(this, "internal", {
value: {
used: false,
loopedPrevSegments: []
}
});
/** /* istanbul ignore if */
* An array of the next segments. if (debug.enabled) {
* @type {CodePathSegment[]} this.internal.nodes = [];
*/ this.internal.exitNodes = [];
this.nextSegments = []; }
}
/** /**
* An array of the previous segments. * Checks a given previous segment is coming from the end of a loop.
* @type {CodePathSegment[]} *
* @param {CodePathSegment} segment - A previous segment to check.
* @returns {boolean} `true` if the segment is coming from the end of a loop.
*/ */
this.prevSegments = allPrevSegments.filter(isReachable); isLoopedPrevSegment(segment) {
return this.internal.loopedPrevSegments.indexOf(segment) !== -1;
}
/** /**
* An array of the next segments. * Creates the root segment.
* This array includes unreachable segments. *
* @type {CodePathSegment[]} * @param {string} id - An identifier.
* @returns {CodePathSegment} The created segment.
*/ */
this.allNextSegments = []; static newRoot(id) {
return new CodePathSegment(id, [], true);
}
/** /**
* An array of the previous segments. * Creates a segment that follows given segments.
* This array includes unreachable segments. *
* @type {CodePathSegment[]} * @param {string} id - An identifier.
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
* @returns {CodePathSegment} The created segment.
*/ */
this.allPrevSegments = allPrevSegments; static newNext(id, allPrevSegments) {
return new CodePathSegment(
id,
flattenUnusedSegments(allPrevSegments),
allPrevSegments.some(isReachable));
}
/** /**
* A flag which shows this is reachable. * Creates an unreachable segment that follows given segments.
* @type {boolean} *
* @param {string} id - An identifier.
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
* @returns {CodePathSegment} The created segment.
*/ */
this.reachable = reachable; static newUnreachable(id, allPrevSegments) {
const segment = new CodePathSegment(id, flattenUnusedSegments(allPrevSegments), false);
// Internal data.
Object.defineProperty(this, "internal", {value: {
used: false,
loopedPrevSegments: []
}});
/* istanbul ignore if */
if (debug.enabled) {
this.internal.nodes = [];
this.internal.exitNodes = [];
}
}
CodePathSegment.prototype = { // In `if (a) return a; foo();` case, the unreachable segment preceded by
constructor: CodePathSegment, // the return statement is not used but must not be remove.
CodePathSegment.markUsed(segment);
return segment;
}
/** /**
* Checks a given previous segment is coming from the end of a loop. * Creates a segment that follows given segments.
* This factory method does not connect with `allPrevSegments`.
* But this inherits `reachable` flag.
* *
* @param {CodePathSegment} segment - A previous segment to check. * @param {string} id - An identifier.
* @returns {boolean} `true` if the segment is coming from the end of a loop. * @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
* @returns {CodePathSegment} The created segment.
*/ */
isLoopedPrevSegment(segment) { static newDisconnected(id, allPrevSegments) {
return this.internal.loopedPrevSegments.indexOf(segment) !== -1; return new CodePathSegment(id, [], allPrevSegments.some(isReachable));
} }
};
/**
* Creates the root segment.
*
* @param {string} id - An identifier.
* @returns {CodePathSegment} The created segment.
*/
CodePathSegment.newRoot = function(id) {
return new CodePathSegment(id, [], true);
};
/** /**
* Creates a segment that follows given segments. * Makes a given segment being used.
* *
* @param {string} id - An identifier. * And this function registers the segment into the previous segments as a next.
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments. *
* @returns {CodePathSegment} The created segment. * @param {CodePathSegment} segment - A segment to mark.
*/ * @returns {void}
CodePathSegment.newNext = function(id, allPrevSegments) { */
return new CodePathSegment( static markUsed(segment) {
id, if (segment.internal.used) {
flattenUnusedSegments(allPrevSegments), return;
allPrevSegments.some(isReachable)); }
}; segment.internal.used = true;
/**
* Creates an unreachable segment that follows given segments.
*
* @param {string} id - An identifier.
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
* @returns {CodePathSegment} The created segment.
*/
CodePathSegment.newUnreachable = function(id, allPrevSegments) {
const segment = new CodePathSegment(id, flattenUnusedSegments(allPrevSegments), false);
// In `if (a) return a; foo();` case, the unreachable segment preceded by
// the return statement is not used but must not be remove.
CodePathSegment.markUsed(segment);
return segment;
};
/**
* Creates a segment that follows given segments.
* This factory method does not connect with `allPrevSegments`.
* But this inherits `reachable` flag.
*
* @param {string} id - An identifier.
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
* @returns {CodePathSegment} The created segment.
*/
CodePathSegment.newDisconnected = function(id, allPrevSegments) {
return new CodePathSegment(id, [], allPrevSegments.some(isReachable));
};
/**
* Makes a given segment being used.
*
* And this function registers the segment into the previous segments as a next.
*
* @param {CodePathSegment} segment - A segment to mark.
* @returns {void}
*/
CodePathSegment.markUsed = function(segment) {
if (segment.internal.used) {
return;
}
segment.internal.used = true;
let i; let i;
if (segment.reachable) { if (segment.reachable) {
for (i = 0; i < segment.allPrevSegments.length; ++i) { for (i = 0; i < segment.allPrevSegments.length; ++i) {
const prevSegment = segment.allPrevSegments[i]; const prevSegment = segment.allPrevSegments[i];
prevSegment.allNextSegments.push(segment); prevSegment.allNextSegments.push(segment);
prevSegment.nextSegments.push(segment); prevSegment.nextSegments.push(segment);
} }
} else { } else {
for (i = 0; i < segment.allPrevSegments.length; ++i) { for (i = 0; i < segment.allPrevSegments.length; ++i) {
segment.allPrevSegments[i].allNextSegments.push(segment); segment.allPrevSegments[i].allNextSegments.push(segment);
}
} }
} }
};
/** /**
* Marks a previous segment as looped. * Marks a previous segment as looped.
* *
* @param {CodePathSegment} segment - A segment. * @param {CodePathSegment} segment - A segment.
* @param {CodePathSegment} prevSegment - A previous segment to mark. * @param {CodePathSegment} prevSegment - A previous segment to mark.
* @returns {void} * @returns {void}
*/ */
CodePathSegment.markPrevSegmentAsLooped = function(segment, prevSegment) { static markPrevSegmentAsLooped(segment, prevSegment) {
segment.internal.loopedPrevSegments.push(prevSegment); segment.internal.loopedPrevSegments.push(prevSegment);
}; }
}
module.exports = CodePathSegment; module.exports = CodePathSegment;

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

@ -221,36 +221,35 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) {
/** /**
* A class which manages state to analyze code paths. * A class which manages state to analyze code paths.
*
* @constructor
* @param {IdGenerator} idGenerator - An id generator to generate id for code
* path segments.
* @param {Function} onLooped - A callback function to notify looping.
*/ */
function CodePathState(idGenerator, onLooped) { class CodePathState {
this.idGenerator = idGenerator;
this.notifyLooped = onLooped;
this.forkContext = ForkContext.newRoot(idGenerator);
this.choiceContext = null;
this.switchContext = null;
this.tryContext = null;
this.loopContext = null;
this.breakContext = null;
this.currentSegments = [];
this.initialSegment = this.forkContext.head[0];
// returnedSegments and thrownSegments push elements into finalSegments also.
const final = this.finalSegments = [];
const returned = this.returnedForkContext = [];
const thrown = this.thrownForkContext = [];
returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
}
CodePathState.prototype = { /**
constructor: CodePathState, * @param {IdGenerator} idGenerator - An id generator to generate id for code
* path segments.
* @param {Function} onLooped - A callback function to notify looping.
*/
constructor(idGenerator, onLooped) {
this.idGenerator = idGenerator;
this.notifyLooped = onLooped;
this.forkContext = ForkContext.newRoot(idGenerator);
this.choiceContext = null;
this.switchContext = null;
this.tryContext = null;
this.loopContext = null;
this.breakContext = null;
this.currentSegments = [];
this.initialSegment = this.forkContext.head[ 0 ];
// returnedSegments and thrownSegments push elements into finalSegments also.
const final = this.finalSegments = [];
const returned = this.returnedForkContext = [];
const thrown = this.thrownForkContext = [];
returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
}
/** /**
* The head segments. * The head segments.
@ -258,7 +257,7 @@ CodePathState.prototype = {
*/ */
get headSegments() { get headSegments() {
return this.forkContext.head; return this.forkContext.head;
}, }
/** /**
* The parent forking context. * The parent forking context.
@ -269,7 +268,7 @@ CodePathState.prototype = {
const current = this.forkContext; const current = this.forkContext;
return current && current.upper; return current && current.upper;
}, }
/** /**
* Creates and stacks new forking context. * Creates and stacks new forking context.
@ -285,7 +284,7 @@ CodePathState.prototype = {
); );
return this.forkContext; return this.forkContext;
}, }
/** /**
* Pops and merges the last forking context. * Pops and merges the last forking context.
@ -298,7 +297,7 @@ CodePathState.prototype = {
this.forkContext.replaceHead(lastContext.makeNext(0, -1)); this.forkContext.replaceHead(lastContext.makeNext(0, -1));
return lastContext; return lastContext;
}, }
/** /**
* Creates a new path. * Creates a new path.
@ -306,7 +305,7 @@ CodePathState.prototype = {
*/ */
forkPath() { forkPath() {
this.forkContext.add(this.parentForkContext.makeNext(-1, -1)); this.forkContext.add(this.parentForkContext.makeNext(-1, -1));
}, }
/** /**
* Creates a bypass path. * Creates a bypass path.
@ -316,7 +315,7 @@ CodePathState.prototype = {
*/ */
forkBypassPath() { forkBypassPath() {
this.forkContext.add(this.parentForkContext.head); this.forkContext.add(this.parentForkContext.head);
}, }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// ConditionalExpression, LogicalExpression, IfStatement // ConditionalExpression, LogicalExpression, IfStatement
@ -362,7 +361,7 @@ CodePathState.prototype = {
falseForkContext: ForkContext.newEmpty(this.forkContext), falseForkContext: ForkContext.newEmpty(this.forkContext),
processed: false processed: false
}; };
}, }
/** /**
* Pops the last choice context and finalizes it. * Pops the last choice context and finalizes it.
@ -449,7 +448,7 @@ CodePathState.prototype = {
forkContext.replaceHead(prevForkContext.makeNext(0, -1)); forkContext.replaceHead(prevForkContext.makeNext(0, -1));
return context; return context;
}, }
/** /**
* Makes a code path segment of the right-hand operand of a logical * Makes a code path segment of the right-hand operand of a logical
@ -494,7 +493,7 @@ CodePathState.prototype = {
forkContext.replaceHead(forkContext.makeNext(-1, -1)); forkContext.replaceHead(forkContext.makeNext(-1, -1));
} }
}, }
/** /**
* Makes a code path segment of the `if` block. * Makes a code path segment of the `if` block.
@ -521,7 +520,7 @@ CodePathState.prototype = {
forkContext.replaceHead( forkContext.replaceHead(
context.trueForkContext.makeNext(0, -1) context.trueForkContext.makeNext(0, -1)
); );
}, }
/** /**
* Makes a code path segment of the `else` block. * Makes a code path segment of the `else` block.
@ -544,7 +543,7 @@ CodePathState.prototype = {
forkContext.replaceHead( forkContext.replaceHead(
context.falseForkContext.makeNext(0, -1) context.falseForkContext.makeNext(0, -1)
); );
}, }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// SwitchStatement // SwitchStatement
@ -570,7 +569,7 @@ CodePathState.prototype = {
}; };
this.pushBreakContext(true, label); this.pushBreakContext(true, label);
}, }
/** /**
* Pops the last context of SwitchStatement and finalizes it. * Pops the last context of SwitchStatement and finalizes it.
@ -649,7 +648,7 @@ CodePathState.prototype = {
* This is a path after switch statement. * This is a path after switch statement.
*/ */
this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
}, }
/** /**
* Makes a code path segment for a `SwitchCase` node. * Makes a code path segment for a `SwitchCase` node.
@ -696,7 +695,7 @@ CodePathState.prototype = {
context.lastIsDefault = isDefault; context.lastIsDefault = isDefault;
context.countForks += 1; context.countForks += 1;
}, }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// TryStatement // TryStatement
@ -723,7 +722,7 @@ CodePathState.prototype = {
lastOfTryIsReachable: false, lastOfTryIsReachable: false,
lastOfCatchIsReachable: false lastOfCatchIsReachable: false
}; };
}, }
/** /**
* Pops the last context of TryStatement and finalizes it. * Pops the last context of TryStatement and finalizes it.
@ -777,7 +776,7 @@ CodePathState.prototype = {
if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) { if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) {
this.forkContext.makeUnreachable(); this.forkContext.makeUnreachable();
} }
}, }
/** /**
* Makes a code path segment for a `catch` block. * Makes a code path segment for a `catch` block.
@ -802,7 +801,7 @@ CodePathState.prototype = {
this.pushForkContext(); this.pushForkContext();
this.forkBypassPath(); this.forkBypassPath();
this.forkContext.add(thrownSegments); this.forkContext.add(thrownSegments);
}, }
/** /**
* Makes a code path segment for a `finally` block. * Makes a code path segment for a `finally` block.
@ -863,7 +862,7 @@ CodePathState.prototype = {
this.pushForkContext(true); this.pushForkContext(true);
this.forkContext.add(segments); this.forkContext.add(segments);
}, }
/** /**
* Makes a code path segment from the first throwable node to the `catch` * Makes a code path segment from the first throwable node to the `catch`
@ -889,7 +888,7 @@ CodePathState.prototype = {
context.thrownForkContext.add(forkContext.head); context.thrownForkContext.add(forkContext.head);
forkContext.replaceHead(forkContext.makeNext(-1, -1)); forkContext.replaceHead(forkContext.makeNext(-1, -1));
}, }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Loop Statements // Loop Statements
@ -969,7 +968,7 @@ CodePathState.prototype = {
default: default:
throw new Error(`unknown type: "${type}"`); throw new Error(`unknown type: "${type}"`);
} }
}, }
/** /**
* Pops the last context of a loop statement and finalizes it. * Pops the last context of a loop statement and finalizes it.
@ -1039,7 +1038,7 @@ CodePathState.prototype = {
} else { } else {
forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
} }
}, }
/** /**
* Makes a code path segment for the test part of a WhileStatement. * Makes a code path segment for the test part of a WhileStatement.
@ -1056,7 +1055,7 @@ CodePathState.prototype = {
context.test = test; context.test = test;
context.continueDestSegments = testSegments; context.continueDestSegments = testSegments;
forkContext.replaceHead(testSegments); forkContext.replaceHead(testSegments);
}, }
/** /**
* Makes a code path segment for the body part of a WhileStatement. * Makes a code path segment for the body part of a WhileStatement.
@ -1078,7 +1077,7 @@ CodePathState.prototype = {
context.brokenForkContext.addAll(choiceContext.falseForkContext); context.brokenForkContext.addAll(choiceContext.falseForkContext);
} }
forkContext.replaceHead(choiceContext.trueForkContext.makeNext(0, -1)); forkContext.replaceHead(choiceContext.trueForkContext.makeNext(0, -1));
}, }
/** /**
* Makes a code path segment for the body part of a DoWhileStatement. * Makes a code path segment for the body part of a DoWhileStatement.
@ -1093,7 +1092,7 @@ CodePathState.prototype = {
// Update state. // Update state.
context.entrySegments = bodySegments; context.entrySegments = bodySegments;
forkContext.replaceHead(bodySegments); forkContext.replaceHead(bodySegments);
}, }
/** /**
* Makes a code path segment for the test part of a DoWhileStatement. * Makes a code path segment for the test part of a DoWhileStatement.
@ -1114,7 +1113,7 @@ CodePathState.prototype = {
forkContext.replaceHead(testSegments); forkContext.replaceHead(testSegments);
} }
}, }
/** /**
* Makes a code path segment for the test part of a ForStatement. * Makes a code path segment for the test part of a ForStatement.
@ -1133,7 +1132,7 @@ CodePathState.prototype = {
context.endOfInitSegments = endOfInitSegments; context.endOfInitSegments = endOfInitSegments;
context.continueDestSegments = context.testSegments = testSegments; context.continueDestSegments = context.testSegments = testSegments;
forkContext.replaceHead(testSegments); forkContext.replaceHead(testSegments);
}, }
/** /**
* Makes a code path segment for the update part of a ForStatement. * Makes a code path segment for the update part of a ForStatement.
@ -1160,7 +1159,7 @@ CodePathState.prototype = {
context.continueDestSegments = context.updateSegments = updateSegments; context.continueDestSegments = context.updateSegments = updateSegments;
forkContext.replaceHead(updateSegments); forkContext.replaceHead(updateSegments);
}, }
/** /**
* Makes a code path segment for the body part of a ForStatement. * Makes a code path segment for the body part of a ForStatement.
@ -1211,7 +1210,7 @@ CodePathState.prototype = {
} }
context.continueDestSegments = context.continueDestSegments || bodySegments; context.continueDestSegments = context.continueDestSegments || bodySegments;
forkContext.replaceHead(bodySegments); forkContext.replaceHead(bodySegments);
}, }
/** /**
* Makes a code path segment for the left part of a ForInStatement and a * Makes a code path segment for the left part of a ForInStatement and a
@ -1228,7 +1227,7 @@ CodePathState.prototype = {
context.prevSegments = forkContext.head; context.prevSegments = forkContext.head;
context.leftSegments = context.continueDestSegments = leftSegments; context.leftSegments = context.continueDestSegments = leftSegments;
forkContext.replaceHead(leftSegments); forkContext.replaceHead(leftSegments);
}, }
/** /**
* Makes a code path segment for the right part of a ForInStatement and a * Makes a code path segment for the right part of a ForInStatement and a
@ -1247,7 +1246,7 @@ CodePathState.prototype = {
// Update state. // Update state.
context.endOfLeftSegments = forkContext.head; context.endOfLeftSegments = forkContext.head;
forkContext.replaceHead(rightSegments); forkContext.replaceHead(rightSegments);
}, }
/** /**
* Makes a code path segment for the body part of a ForInStatement and a * Makes a code path segment for the body part of a ForInStatement and a
@ -1269,7 +1268,7 @@ CodePathState.prototype = {
// Update state. // Update state.
context.brokenForkContext.add(forkContext.head); context.brokenForkContext.add(forkContext.head);
forkContext.replaceHead(bodySegments); forkContext.replaceHead(bodySegments);
}, }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Control Statements // Control Statements
@ -1291,7 +1290,7 @@ CodePathState.prototype = {
brokenForkContext: ForkContext.newEmpty(this.forkContext) brokenForkContext: ForkContext.newEmpty(this.forkContext)
}; };
return this.breakContext; return this.breakContext;
}, }
/** /**
* Removes the top item of the break context stack. * Removes the top item of the break context stack.
@ -1315,7 +1314,7 @@ CodePathState.prototype = {
} }
return context; return context;
}, }
/** /**
* Makes a path for a `break` statement. * Makes a path for a `break` statement.
@ -1341,7 +1340,7 @@ CodePathState.prototype = {
} }
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
}, }
/** /**
* Makes a path for a `continue` statement. * Makes a path for a `continue` statement.
@ -1377,7 +1376,7 @@ CodePathState.prototype = {
} }
} }
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
}, }
/** /**
* Makes a path for a `return` statement. * Makes a path for a `return` statement.
@ -1394,7 +1393,7 @@ CodePathState.prototype = {
getReturnContext(this).returnedForkContext.add(forkContext.head); getReturnContext(this).returnedForkContext.add(forkContext.head);
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
} }
}, }
/** /**
* Makes a path for a `throw` statement. * Makes a path for a `throw` statement.
@ -1411,7 +1410,7 @@ CodePathState.prototype = {
getThrowContext(this).thrownForkContext.add(forkContext.head); getThrowContext(this).thrownForkContext.add(forkContext.head);
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
} }
}, }
/** /**
* Makes the final path. * Makes the final path.
@ -1424,6 +1423,6 @@ CodePathState.prototype = {
this.returnedForkContext.add(segments); this.returnedForkContext.add(segments);
} }
} }
}; }
module.exports = CodePathState; module.exports = CodePathState;

93
tools/eslint/lib/code-path-analysis/code-path.js

@ -18,47 +18,56 @@ const IdGenerator = require("./id-generator");
/** /**
* A code path. * A code path.
*
* @constructor
* @param {string} id - An identifier.
* @param {CodePath|null} upper - The code path of the upper function scope.
* @param {Function} onLooped - A callback function to notify looping.
*/ */
function CodePath(id, upper, onLooped) { class CodePath {
/** /**
* The identifier of this code path. * @param {string} id - An identifier.
* Rules use it to store additional information of each rule. * @param {CodePath|null} upper - The code path of the upper function scope.
* @type {string} * @param {Function} onLooped - A callback function to notify looping.
*/ */
this.id = id; constructor(id, upper, onLooped) {
/** /**
* The code path of the upper function scope. * The identifier of this code path.
* @type {CodePath|null} * Rules use it to store additional information of each rule.
*/ * @type {string}
this.upper = upper; */
this.id = id;
/** /**
* The code paths of nested function scopes. * The code path of the upper function scope.
* @type {CodePath[]} * @type {CodePath|null}
*/ */
this.childCodePaths = []; this.upper = upper;
/**
* The code paths of nested function scopes.
* @type {CodePath[]}
*/
this.childCodePaths = [];
// Initializes internal state. // Initializes internal state.
Object.defineProperty( Object.defineProperty(
this, this,
"internal", "internal",
{value: new CodePathState(new IdGenerator(`${id}_`), onLooped)}); { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) });
// Adds this into `childCodePaths` of `upper`. // Adds this into `childCodePaths` of `upper`.
if (upper) { if (upper) {
upper.childCodePaths.push(this); upper.childCodePaths.push(this);
}
} }
}
CodePath.prototype = { /**
constructor: CodePath, * Gets the state of a given code path.
*
* @param {CodePath} codePath - A code path to get.
* @returns {CodePathState} The state of the code path.
*/
static getState(codePath) {
return codePath.internal;
}
/** /**
* The initial code path segment. * The initial code path segment.
@ -66,7 +75,7 @@ CodePath.prototype = {
*/ */
get initialSegment() { get initialSegment() {
return this.internal.initialSegment; return this.internal.initialSegment;
}, }
/** /**
* Final code path segments. * Final code path segments.
@ -75,7 +84,7 @@ CodePath.prototype = {
*/ */
get finalSegments() { get finalSegments() {
return this.internal.finalSegments; return this.internal.finalSegments;
}, }
/** /**
* Final code path segments which is with `return` statements. * Final code path segments which is with `return` statements.
@ -85,7 +94,7 @@ CodePath.prototype = {
*/ */
get returnedSegments() { get returnedSegments() {
return this.internal.returnedForkContext; return this.internal.returnedForkContext;
}, }
/** /**
* Final code path segments which is with `throw` statements. * Final code path segments which is with `throw` statements.
@ -93,7 +102,7 @@ CodePath.prototype = {
*/ */
get thrownSegments() { get thrownSegments() {
return this.internal.thrownForkContext; return this.internal.thrownForkContext;
}, }
/** /**
* Current code path segments. * Current code path segments.
@ -101,7 +110,7 @@ CodePath.prototype = {
*/ */
get currentSegments() { get currentSegments() {
return this.internal.currentSegments; return this.internal.currentSegments;
}, }
/** /**
* Traverses all segments in this code path. * Traverses all segments in this code path.
@ -219,16 +228,6 @@ CodePath.prototype = {
} }
} }
} }
}; }
/**
* Gets the state of a given code path.
*
* @param {CodePath} codePath - A code path to get.
* @returns {CodePathState} The state of the code path.
*/
CodePath.getState = function getState(codePath) {
return codePath.internal;
};
module.exports = CodePath; module.exports = CodePath;

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

@ -108,7 +108,7 @@ module.exports = {
} }
if (segment.internal.nodes.length > 0) { if (segment.internal.nodes.length > 0) {
text += segment.internal.nodes.map(function(node) { text += segment.internal.nodes.map(node => {
switch (node.type) { switch (node.type) {
case "Identifier": return `${node.type} (${node.name})`; case "Identifier": return `${node.type} (${node.name})`;
case "Literal": return `${node.type} (${node.value})`; case "Literal": return `${node.type} (${node.value})`;
@ -116,7 +116,7 @@ module.exports = {
} }
}).join("\\n"); }).join("\\n");
} else if (segment.internal.exitNodes.length > 0) { } else if (segment.internal.exitNodes.length > 0) {
text += segment.internal.exitNodes.map(function(node) { text += 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})`;
@ -176,7 +176,7 @@ module.exports = {
stack.push([nextSegment, 0]); stack.push([nextSegment, 0]);
} }
codePath.returnedSegments.forEach(function(finalSegment) { codePath.returnedSegments.forEach(finalSegment => {
if (lastId === finalSegment.id) { if (lastId === finalSegment.id) {
text += "->final"; text += "->final";
} else { } else {
@ -185,7 +185,7 @@ module.exports = {
lastId = null; lastId = null;
}); });
codePath.thrownSegments.forEach(function(finalSegment) { codePath.thrownSegments.forEach(finalSegment => {
if (lastId === finalSegment.id) { if (lastId === finalSegment.id) {
text += "->thrown"; text += "->thrown";
} else { } else {

93
tools/eslint/lib/code-path-analysis/fork-context.js

@ -99,21 +99,20 @@ function mergeExtraSegments(context, segments) {
/** /**
* A class to manage forking. * A class to manage forking.
*
* @constructor
* @param {IdGenerator} idGenerator - An identifier generator for segments.
* @param {ForkContext|null} upper - An upper fork context.
* @param {number} count - A number of parallel segments.
*/ */
function ForkContext(idGenerator, upper, count) { class ForkContext {
this.idGenerator = idGenerator;
this.upper = upper;
this.count = count;
this.segmentsList = [];
}
ForkContext.prototype = { /**
constructor: ForkContext, * @param {IdGenerator} idGenerator - An identifier generator for segments.
* @param {ForkContext|null} upper - An upper fork context.
* @param {number} count - A number of parallel segments.
*/
constructor(idGenerator, upper, count) {
this.idGenerator = idGenerator;
this.upper = upper;
this.count = count;
this.segmentsList = [];
}
/** /**
* The head segments. * The head segments.
@ -123,7 +122,7 @@ ForkContext.prototype = {
const list = this.segmentsList; const list = this.segmentsList;
return list.length === 0 ? [] : list[list.length - 1]; return list.length === 0 ? [] : list[list.length - 1];
}, }
/** /**
* A flag which shows empty. * A flag which shows empty.
@ -131,7 +130,7 @@ ForkContext.prototype = {
*/ */
get empty() { get empty() {
return this.segmentsList.length === 0; return this.segmentsList.length === 0;
}, }
/** /**
* A flag which shows reachable. * A flag which shows reachable.
@ -141,7 +140,7 @@ ForkContext.prototype = {
const segments = this.head; const segments = this.head;
return segments.length > 0 && segments.some(isReachable); return segments.length > 0 && segments.some(isReachable);
}, }
/** /**
* Creates new segments from this context. * Creates new segments from this context.
@ -152,7 +151,7 @@ ForkContext.prototype = {
*/ */
makeNext(begin, end) { makeNext(begin, end) {
return makeSegments(this, begin, end, CodePathSegment.newNext); return makeSegments(this, begin, end, CodePathSegment.newNext);
}, }
/** /**
* Creates new segments from this context. * Creates new segments from this context.
@ -164,7 +163,7 @@ ForkContext.prototype = {
*/ */
makeUnreachable(begin, end) { makeUnreachable(begin, end) {
return makeSegments(this, begin, end, CodePathSegment.newUnreachable); return makeSegments(this, begin, end, CodePathSegment.newUnreachable);
}, }
/** /**
* Creates new segments from this context. * Creates new segments from this context.
@ -177,7 +176,7 @@ ForkContext.prototype = {
*/ */
makeDisconnected(begin, end) { makeDisconnected(begin, end) {
return makeSegments(this, begin, end, CodePathSegment.newDisconnected); return makeSegments(this, begin, end, CodePathSegment.newDisconnected);
}, }
/** /**
* Adds segments into this context. * Adds segments into this context.
@ -190,7 +189,7 @@ ForkContext.prototype = {
assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
this.segmentsList.push(mergeExtraSegments(this, segments)); this.segmentsList.push(mergeExtraSegments(this, segments));
}, }
/** /**
* Replaces the head segments with given segments. * Replaces the head segments with given segments.
@ -203,7 +202,7 @@ ForkContext.prototype = {
assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments)); this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments));
}, }
/** /**
* Adds all segments of a given fork context into this context. * Adds all segments of a given fork context into this context.
@ -219,7 +218,7 @@ ForkContext.prototype = {
for (let i = 0; i < source.length; ++i) { for (let i = 0; i < source.length; ++i) {
this.segmentsList.push(source[i]); this.segmentsList.push(source[i]);
} }
}, }
/** /**
* Clears all secments in this context. * Clears all secments in this context.
@ -229,34 +228,34 @@ ForkContext.prototype = {
clear() { clear() {
this.segmentsList = []; this.segmentsList = [];
} }
};
/** /**
* Creates the root fork context. * Creates the root fork context.
* *
* @param {IdGenerator} idGenerator - An identifier generator for segments. * @param {IdGenerator} idGenerator - An identifier generator for segments.
* @returns {ForkContext} New fork context. * @returns {ForkContext} New fork context.
*/ */
ForkContext.newRoot = function(idGenerator) { static newRoot(idGenerator) {
const context = new ForkContext(idGenerator, null, 1); const context = new ForkContext(idGenerator, null, 1);
context.add([CodePathSegment.newRoot(idGenerator.next())]); context.add([CodePathSegment.newRoot(idGenerator.next())]);
return context; return context;
}; }
/** /**
* Creates an empty fork context preceded by a given context. * Creates an empty fork context preceded by a given context.
* *
* @param {ForkContext} parentContext - The parent fork context. * @param {ForkContext} parentContext - The parent fork context.
* @param {boolean} forkLeavingPath - A flag which shows inside of `finally` block. * @param {boolean} forkLeavingPath - A flag which shows inside of `finally` block.
* @returns {ForkContext} New fork context. * @returns {ForkContext} New fork context.
*/ */
ForkContext.newEmpty = function(parentContext, forkLeavingPath) { static newEmpty(parentContext, forkLeavingPath) {
return new ForkContext( return new ForkContext(
parentContext.idGenerator, parentContext.idGenerator,
parentContext, parentContext,
(forkLeavingPath ? 2 : 1) * parentContext.count); (forkLeavingPath ? 2 : 1) * parentContext.count);
}; }
}
module.exports = ForkContext; module.exports = ForkContext;

43
tools/eslint/lib/code-path-analysis/id-generator.js

@ -15,29 +15,32 @@
/** /**
* A generator for unique ids. * A generator for unique ids.
*
* @constructor
* @param {string} prefix - Optional. A prefix of generated ids.
*/
function IdGenerator(prefix) {
this.prefix = String(prefix);
this.n = 0;
}
/**
* Generates id.
*
* @returns {string} A generated id.
*/ */
IdGenerator.prototype.next = function() { class IdGenerator {
this.n = 1 + this.n | 0;
/* istanbul ignore if */ /**
if (this.n < 0) { * @param {string} prefix - Optional. A prefix of generated ids.
this.n = 1; */
constructor(prefix) {
this.prefix = String(prefix);
this.n = 0;
} }
return this.prefix + this.n; /**
}; * Generates id.
*
* @returns {string} A generated id.
*/
next() {
this.n = 1 + this.n | 0;
/* istanbul ignore if */
if (this.n < 0) {
this.n = 1;
}
return this.prefix + this.n;
}
}
module.exports = IdGenerator; module.exports = IdGenerator;

236
tools/eslint/lib/config.js

@ -179,155 +179,159 @@ function getLocalConfig(thisConfig, directory) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** /**
* Config * Configuration class
* @constructor
* @class Config
* @param {Object} options Options to be passed in
*/ */
function Config(options) { class Config {
options = options || {};
this.ignore = options.ignore; /**
this.ignorePath = options.ignorePath; * Config options
this.cache = {}; * @param {Object} options Options to be passed in
this.parser = options.parser; */
this.parserOptions = options.parserOptions || {}; constructor(options) {
options = options || {};
this.baseConfig = options.baseConfig ? loadConfig(options.baseConfig) : { rules: {} }; this.ignore = options.ignore;
this.ignorePath = options.ignorePath;
this.cache = {};
this.parser = options.parser;
this.parserOptions = options.parserOptions || {};
this.useEslintrc = (options.useEslintrc !== false); this.baseConfig = options.baseConfig ? loadConfig(options.baseConfig) : { rules: {} };
this.env = (options.envs || []).reduce(function(envs, name) { this.useEslintrc = (options.useEslintrc !== false);
envs[name] = true;
return envs;
}, {});
/* this.env = (options.envs || []).reduce((envs, name) => {
* Handle declared globals. envs[ name ] = true;
* For global variable foo, handle "foo:false" and "foo:true" to set return envs;
* whether global is writable. }, {});
* If user declares "foo", convert to "foo:false".
*/
this.globals = (options.globals || []).reduce(function(globals, def) {
const parts = def.split(":");
globals[parts[0]] = (parts.length > 1 && parts[1] === "true"); /*
* Handle declared globals.
* For global variable foo, handle "foo:false" and "foo:true" to set
* whether global is writable.
* If user declares "foo", convert to "foo:false".
*/
this.globals = (options.globals || []).reduce((globals, def) => {
const parts = def.split(":");
return globals; globals[parts[0]] = (parts.length > 1 && parts[1] === "true");
}, {});
const useConfig = options.configFile; return globals;
}, {});
this.options = options; const useConfig = options.configFile;
if (useConfig) { this.options = options;
debug(`Using command line config ${useConfig}`);
if (isResolvable(useConfig) || isResolvable(`eslint-config-${useConfig}`) || useConfig.charAt(0) === "@") { if (useConfig) {
this.useSpecificConfig = loadConfig(useConfig); debug(`Using command line config ${useConfig}`);
} else { if (isResolvable(useConfig) || isResolvable(`eslint-config-${useConfig}`) || useConfig.charAt(0) === "@") {
this.useSpecificConfig = loadConfig(path.resolve(this.options.cwd, useConfig)); this.useSpecificConfig = loadConfig(useConfig);
} else {
this.useSpecificConfig = loadConfig(path.resolve(this.options.cwd, useConfig));
}
} }
} }
}
/** /**
* Build a config object merging the base config (conf/eslint.json), the * Build a config object merging the base config (conf/eslint.json), the
* environments config (conf/environments.js) and eventually the user config. * 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
*/ */
Config.prototype.getConfig = function(filePath) { getConfig(filePath) {
const directory = filePath ? path.dirname(filePath) : this.options.cwd; const directory = filePath ? path.dirname(filePath) : this.options.cwd;
let config, let config,
userConfig; userConfig;
debug(`Constructing config for ${filePath ? filePath : "text"}`); debug(`Constructing config for ${filePath ? filePath : "text"}`);
config = this.cache[directory]; config = this.cache[directory];
if (config) { if (config) {
debug("Using config from cache"); debug("Using config from cache");
return config; return config;
} }
// Step 1: Determine user-specified config from .eslintrc.* and package.json files // Step 1: Determine user-specified config from .eslintrc.* and package.json files
if (this.useEslintrc) { if (this.useEslintrc) {
debug("Using .eslintrc and package.json files"); debug("Using .eslintrc and package.json files");
userConfig = getLocalConfig(this, directory); userConfig = getLocalConfig(this, directory);
} else { } else {
debug("Not using .eslintrc or package.json files"); debug("Not using .eslintrc or package.json files");
userConfig = {}; userConfig = {};
} }
// Step 2: Create a copy of the baseConfig // Step 2: Create a copy of the baseConfig
config = ConfigOps.merge({}, this.baseConfig); config = ConfigOps.merge({}, this.baseConfig);
// Step 3: Merge in the user-specified configuration from .eslintrc and package.json // Step 3: Merge in the user-specified configuration from .eslintrc and package.json
config = ConfigOps.merge(config, userConfig); config = ConfigOps.merge(config, userConfig);
// Step 4: Merge in command line config file // Step 4: Merge in command line config file
if (this.useSpecificConfig) { if (this.useSpecificConfig) {
debug("Merging command line config file"); debug("Merging command line config file");
config = ConfigOps.merge(config, this.useSpecificConfig); config = ConfigOps.merge(config, this.useSpecificConfig);
} }
// Step 5: Merge in command line environments // Step 5: Merge in command line environments
debug("Merging command line environment settings"); debug("Merging command line environment settings");
config = ConfigOps.merge(config, { env: this.env }); config = ConfigOps.merge(config, { env: this.env });
// Step 6: Merge in command line rules // Step 6: Merge in command line rules
if (this.options.rules) { if (this.options.rules) {
debug("Merging command line rules"); debug("Merging command line rules");
config = ConfigOps.merge(config, { rules: this.options.rules }); config = ConfigOps.merge(config, { rules: this.options.rules });
} }
// Step 7: Merge in command line globals // Step 7: Merge in command line globals
config = ConfigOps.merge(config, { globals: this.globals }); config = ConfigOps.merge(config, { globals: this.globals });
// Only override parser if it is passed explicitly through the command line or if it's not // Only override parser if it is passed explicitly through the command line or if it's not
// defined yet (because the final object will at least have the parser key) // defined yet (because the final object will at least have the parser key)
if (this.parser || !config.parser) { if (this.parser || !config.parser) {
config = ConfigOps.merge(config, { config = ConfigOps.merge(config, {
parser: this.parser parser: this.parser
}); });
} }
if (this.parserOptions) { if (this.parserOptions) {
config = ConfigOps.merge(config, { config = ConfigOps.merge(config, {
parserOptions: this.parserOptions parserOptions: this.parserOptions
}); });
} }
// Step 8: Merge in command line plugins // Step 8: Merge in command line plugins
if (this.options.plugins) { if (this.options.plugins) {
debug("Merging command line plugins"); debug("Merging command line plugins");
Plugins.loadAll(this.options.plugins); Plugins.loadAll(this.options.plugins);
config = ConfigOps.merge(config, { plugins: this.options.plugins }); config = ConfigOps.merge(config, { plugins: this.options.plugins });
} }
// Step 9: Apply environments to the config if present // Step 9: Apply environments to the config if present
if (config.env) { if (config.env) {
config = ConfigOps.applyEnvironments(config); config = ConfigOps.applyEnvironments(config);
} }
this.cache[directory] = config; this.cache[directory] = config;
return config; return config;
}; }
/** /**
* Find local config files from directory and parent directories. * Find local config files from directory and parent directories.
* @param {string} directory The directory to start searching from. * @param {string} directory The directory to start searching from.
* @returns {string[]} The paths of local config files found. * @returns {string[]} The paths of local config files found.
*/ */
Config.prototype.findLocalConfigFiles = function(directory) { findLocalConfigFiles(directory) {
if (!this.localConfigFinder) { if (!this.localConfigFinder) {
this.localConfigFinder = new FileFinder(ConfigFile.CONFIG_FILES, this.options.cwd); this.localConfigFinder = new FileFinder(ConfigFile.CONFIG_FILES, this.options.cwd);
} }
return this.localConfigFinder.findAllInDirectoryAndParents(directory); return this.localConfigFinder.findAllInDirectoryAndParents(directory);
}; }
}
module.exports = Config; module.exports = Config;

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

@ -49,14 +49,12 @@ const MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only
* @returns {Object} registryItems for each rule in provided rulesConfig * @returns {Object} registryItems for each rule in provided rulesConfig
*/ */
function makeRegistryItems(rulesConfig) { function makeRegistryItems(rulesConfig) {
return Object.keys(rulesConfig).reduce(function(accumulator, ruleId) { return Object.keys(rulesConfig).reduce((accumulator, ruleId) => {
accumulator[ruleId] = rulesConfig[ruleId].map(function(config) { accumulator[ruleId] = rulesConfig[ruleId].map(config => ({
return { config,
config, specificity: config.length || 1,
specificity: config.length || 1, errorCount: void 0
errorCount: void 0 }));
};
});
return accumulator; return accumulator;
}, {}); }, {});
} }
@ -173,10 +171,8 @@ Registry.prototype = {
newRegistry = new Registry(); newRegistry = new Registry();
newRegistry.rules = Object.assign({}, this.rules); newRegistry.rules = Object.assign({}, this.rules);
ruleIds.forEach(function(ruleId) { ruleIds.forEach(ruleId => {
const errorFreeItems = newRegistry.rules[ruleId].filter(function(registryItem) { const errorFreeItems = newRegistry.rules[ruleId].filter(registryItem => (registryItem.errorCount === 0));
return (registryItem.errorCount === 0);
});
if (errorFreeItems.length > 0) { if (errorFreeItems.length > 0) {
newRegistry.rules[ruleId] = errorFreeItems; newRegistry.rules[ruleId] = errorFreeItems;
@ -198,10 +194,8 @@ Registry.prototype = {
newRegistry = new Registry(); newRegistry = new Registry();
newRegistry.rules = Object.assign({}, this.rules); newRegistry.rules = Object.assign({}, this.rules);
ruleIds.forEach(function(ruleId) { ruleIds.forEach(ruleId => {
newRegistry.rules[ruleId] = newRegistry.rules[ruleId].filter(function(registryItem) { newRegistry.rules[ruleId] = newRegistry.rules[ruleId].filter(registryItem => (typeof registryItem.errorCount !== "undefined"));
return (typeof registryItem.errorCount !== "undefined");
});
}); });
return newRegistry; return newRegistry;
@ -218,15 +212,13 @@ Registry.prototype = {
const ruleIds = Object.keys(this.rules), const ruleIds = Object.keys(this.rules),
failingRegistry = new Registry(); failingRegistry = new Registry();
ruleIds.forEach(function(ruleId) { ruleIds.forEach(ruleId => {
const failingConfigs = this.rules[ruleId].filter(function(registryItem) { const failingConfigs = this.rules[ruleId].filter(registryItem => (registryItem.errorCount > 0));
return (registryItem.errorCount > 0);
});
if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) { if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) {
failingRegistry.rules[ruleId] = failingConfigs; failingRegistry.rules[ruleId] = failingConfigs;
} }
}.bind(this)); });
return failingRegistry; return failingRegistry;
}, },
@ -239,13 +231,13 @@ Registry.prototype = {
*/ */
createConfig() { createConfig() {
const ruleIds = Object.keys(this.rules), const ruleIds = Object.keys(this.rules),
config = {rules: {}}; config = { rules: {} };
ruleIds.forEach(function(ruleId) { ruleIds.forEach(ruleId => {
if (this.rules[ruleId].length === 1) { if (this.rules[ruleId].length === 1) {
config.rules[ruleId] = this.rules[ruleId][0].config; config.rules[ruleId] = this.rules[ruleId][0].config;
} }
}.bind(this)); });
return config; return config;
}, },
@ -261,11 +253,9 @@ Registry.prototype = {
newRegistry = new Registry(); newRegistry = new Registry();
newRegistry.rules = Object.assign({}, this.rules); newRegistry.rules = Object.assign({}, this.rules);
ruleIds.forEach(function(ruleId) { ruleIds.forEach(ruleId => {
newRegistry.rules[ruleId] = this.rules[ruleId].filter(function(registryItem) { newRegistry.rules[ruleId] = this.rules[ruleId].filter(registryItem => (registryItem.specificity === specificity));
return (registryItem.specificity === specificity); });
});
}.bind(this));
return newRegistry; return newRegistry;
}, },
@ -294,16 +284,16 @@ Registry.prototype = {
const filenames = Object.keys(sourceCodes); const filenames = Object.keys(sourceCodes);
const totalFilesLinting = filenames.length * ruleSets.length; const totalFilesLinting = filenames.length * ruleSets.length;
filenames.forEach(function(filename) { filenames.forEach(filename => {
debug(`Linting file: ${filename}`); debug(`Linting file: ${filename}`);
ruleSetIdx = 0; ruleSetIdx = 0;
ruleSets.forEach(function(ruleSet) { ruleSets.forEach(ruleSet => {
const lintConfig = Object.assign({}, config, {rules: ruleSet}); const lintConfig = Object.assign({}, config, { rules: ruleSet });
const lintResults = eslint.verify(sourceCodes[filename], lintConfig); const lintResults = eslint.verify(sourceCodes[filename], lintConfig);
lintResults.forEach(function(result) { lintResults.forEach(result => {
// 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
@ -342,11 +332,9 @@ function extendFromRecommended(config) {
ConfigOps.normalizeToStrings(newConfig); ConfigOps.normalizeToStrings(newConfig);
const recRules = Object.keys(recConfig.rules).filter(function(ruleId) { const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId]));
return ConfigOps.isErrorSeverity(recConfig.rules[ruleId]);
});
recRules.forEach(function(ruleId) { recRules.forEach(ruleId => {
if (lodash.isEqual(recConfig.rules[ruleId], newConfig.rules[ruleId])) { if (lodash.isEqual(recConfig.rules[ruleId], newConfig.rules[ruleId])) {
delete newConfig.rules[ruleId]; delete newConfig.rules[ruleId];
} }

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

@ -235,7 +235,7 @@ function loadConfigFile(file) {
function writeJSONConfigFile(config, filePath) { function writeJSONConfigFile(config, filePath) {
debug(`Writing JSON config file: ${filePath}`); debug(`Writing JSON config file: ${filePath}`);
const content = stringify(config, {cmp: sortByKey, space: 4}); const content = stringify(config, { cmp: sortByKey, space: 4 });
fs.writeFileSync(filePath, content, "utf8"); fs.writeFileSync(filePath, content, "utf8");
} }
@ -253,7 +253,7 @@ function writeYAMLConfigFile(config, filePath) {
// lazy load YAML to improve performance when not used // lazy load YAML to improve performance when not used
const yaml = require("js-yaml"); const yaml = require("js-yaml");
const content = yaml.safeDump(config, {sortKeys: true}); const content = yaml.safeDump(config, { sortKeys: true });
fs.writeFileSync(filePath, content, "utf8"); fs.writeFileSync(filePath, content, "utf8");
} }
@ -268,7 +268,7 @@ function writeYAMLConfigFile(config, filePath) {
function writeJSConfigFile(config, filePath) { function writeJSConfigFile(config, filePath) {
debug(`Writing JS config file: ${filePath}`); debug(`Writing JS config file: ${filePath}`);
const content = `module.exports = ${stringify(config, {cmp: sortByKey, space: 4})};`; const content = `module.exports = ${stringify(config, { cmp: sortByKey, space: 4 })};`;
fs.writeFileSync(filePath, content, "utf8"); fs.writeFileSync(filePath, content, "utf8");
} }
@ -359,7 +359,7 @@ 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(function(previousValue, parentPath) { config = configExtends.reduceRight((previousValue, parentPath) => {
if (parentPath === "eslint:recommended") { if (parentPath === "eslint:recommended") {
@ -430,7 +430,7 @@ function normalizePackageName(name, prefix) {
* it's a scoped package * it's a scoped package
* package name is "eslint-config", or just a username * package name is "eslint-config", or just a username
*/ */
const scopedPackageShortcutRegex = new RegExp(`^(@[^\/]+)(?:\/(?:${prefix})?)?$`), const scopedPackageShortcutRegex = new RegExp(`^(@[^/]+)(?:/(?:${prefix})?)?$`),
scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`); scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`);
if (scopedPackageShortcutRegex.test(name)) { if (scopedPackageShortcutRegex.test(name)) {
@ -441,7 +441,7 @@ function normalizePackageName(name, prefix) {
* for scoped packages, insert the eslint-config after the first / unless * for scoped packages, insert the eslint-config after the first / unless
* the path is already @scope/eslint or @scope/eslint-config-xxx * the path is already @scope/eslint or @scope/eslint-config-xxx
*/ */
name = name.replace(/^@([^\/]+)\/(.*)$/, `@$1/${prefix}-$2`); name = name.replace(/^@([^/]+)\/(.*)$/, `@$1/${prefix}-$2`);
} }
} else if (name.indexOf(`${prefix}-`) !== 0) { } else if (name.indexOf(`${prefix}-`) !== 0) {
name = `${prefix}-${name}`; name = `${prefix}-${name}`;

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

@ -44,10 +44,14 @@ function writeFile(config, format) {
extname = ".json"; extname = ".json";
} }
const installedESLint = config.installedESLint;
delete config.installedESLint;
ConfigFile.write(config, `./.eslintrc${extname}`); ConfigFile.write(config, `./.eslintrc${extname}`);
log.info(`Successfully created .eslintrc${extname} file in ${process.cwd()}`); log.info(`Successfully created .eslintrc${extname} file in ${process.cwd()}`);
if (config.installedESLint) { if (installedESLint) {
log.info("ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy."); log.info("ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy.");
} }
} }
@ -62,9 +66,7 @@ function installModules(config) {
// Create a list of modules which should be installed based on config // Create a list of modules which should be installed based on config
if (config.plugins) { if (config.plugins) {
modules = modules.concat(config.plugins.map(function(name) { modules = modules.concat(config.plugins.map(name => `eslint-plugin-${name}`));
return `eslint-plugin-${name}`;
}));
} }
if (config.extends && config.extends.indexOf("eslint:") === -1) { if (config.extends && config.extends.indexOf("eslint:") === -1) {
modules.push(`eslint-config-${config.extends}`); modules.push(`eslint-config-${config.extends}`);
@ -81,7 +83,7 @@ function installModules(config) {
const installStatus = npmUtil.checkDevDeps(modules); const installStatus = npmUtil.checkDevDeps(modules);
// Install packages which aren't already installed // Install packages which aren't already installed
const modulesToInstall = Object.keys(installStatus).filter(function(module) { const modulesToInstall = Object.keys(installStatus).filter(module => {
const notInstalled = installStatus[module] === false; const notInstalled = installStatus[module] === false;
if (module === "eslint" && notInstalled) { if (module === "eslint" && notInstalled) {
@ -128,7 +130,7 @@ function configureRules(answers, config) {
const patterns = answers.patterns.split(/[\s]+/); const patterns = answers.patterns.split(/[\s]+/);
try { try {
sourceCodes = getSourceCodeOfFiles(patterns, { baseConfig: newConfig, useEslintrc: false }, function(total) { sourceCodes = getSourceCodeOfFiles(patterns, { baseConfig: newConfig, useEslintrc: false }, total => {
bar.tick((BAR_SOURCE_CODE_TOTAL / total)); bar.tick((BAR_SOURCE_CODE_TOTAL / total));
}); });
} catch (e) { } catch (e) {
@ -147,20 +149,18 @@ function configureRules(answers, config) {
registry.populateFromCoreRules(); registry.populateFromCoreRules();
// Lint all files with each rule config in the registry // Lint all files with each rule config in the registry
registry = registry.lintSourceCode(sourceCodes, newConfig, function(total) { registry = registry.lintSourceCode(sourceCodes, newConfig, total => {
bar.tick((BAR_TOTAL - BAR_SOURCE_CODE_TOTAL) / total); // Subtract out ticks used at beginning bar.tick((BAR_TOTAL - BAR_SOURCE_CODE_TOTAL) / total); // Subtract out ticks used at beginning
}); });
debug(`\nRegistry: ${util.inspect(registry.rules, {depth: null})}`); debug(`\nRegistry: ${util.inspect(registry.rules, { depth: null })}`);
// Create a list of recommended rules, because we don't want to disable them // Create a list of recommended rules, because we don't want to disable them
const recRules = Object.keys(recConfig.rules).filter(function(ruleId) { const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId]));
return ConfigOps.isErrorSeverity(recConfig.rules[ruleId]);
});
// Find and disable rules which had no error-free configuration // Find and disable rules which had no error-free configuration
const failingRegistry = registry.getFailingRulesRegistry(); const failingRegistry = registry.getFailingRulesRegistry();
Object.keys(failingRegistry.rules).forEach(function(ruleId) { Object.keys(failingRegistry.rules).forEach(ruleId => {
// If the rule is recommended, set it to error, otherwise disable it // If the rule is recommended, set it to error, otherwise disable it
disabledConfigs[ruleId] = (recRules.indexOf(ruleId) !== -1) ? 2 : 0; disabledConfigs[ruleId] = (recRules.indexOf(ruleId) !== -1) ? 2 : 0;
@ -194,9 +194,7 @@ function configureRules(answers, config) {
// Log out some stats to let the user know what happened // Log out some stats to let the user know what happened
const finalRuleIds = Object.keys(newConfig.rules); const finalRuleIds = Object.keys(newConfig.rules);
const totalRules = finalRuleIds.length; const totalRules = finalRuleIds.length;
const enabledRules = finalRuleIds.filter(function(ruleId) { const enabledRules = finalRuleIds.filter(ruleId => (newConfig.rules[ruleId] !== 0)).length;
return (newConfig.rules[ruleId] !== 0);
}).length;
const resultMessage = [ const resultMessage = [
`\nEnabled ${enabledRules} out of ${totalRules}`, `\nEnabled ${enabledRules} out of ${totalRules}`,
`rules based on ${fileQty}`, `rules based on ${fileQty}`,
@ -215,7 +213,7 @@ function configureRules(answers, config) {
* @returns {Object} config object * @returns {Object} config object
*/ */
function processAnswers(answers) { function processAnswers(answers) {
let config = {rules: {}, env: {}}; let config = { rules: {}, env: {} };
if (answers.es6) { if (answers.es6) {
config.env.es6 = true; config.env.es6 = true;
@ -227,7 +225,7 @@ function processAnswers(answers) {
if (answers.commonjs) { if (answers.commonjs) {
config.env.commonjs = true; config.env.commonjs = true;
} }
answers.env.forEach(function(env) { answers.env.forEach(env => {
config.env[env] = true; config.env[env] = true;
}); });
if (answers.jsx) { if (answers.jsx) {
@ -266,9 +264,10 @@ function processAnswers(answers) {
*/ */
function getConfigForStyleGuide(guide) { function getConfigForStyleGuide(guide) {
const guides = { const guides = {
google: {extends: "google"}, google: { extends: "google" },
airbnb: {extends: "airbnb", plugins: ["react", "jsx-a11y", "import"]}, airbnb: { extends: "airbnb", plugins: ["react", "jsx-a11y", "import"] },
standard: {extends: "standard", plugins: ["standard", "promise"]} "airbnb-base": { extends: "airbnb-base", plugins: ["import"] },
standard: { extends: "standard", plugins: ["standard", "promise"] }
}; };
if (!guides[guide]) { if (!guides[guide]) {
@ -296,21 +295,30 @@ function promptUser(callback) {
message: "How would you like to configure ESLint?", message: "How would you like to configure ESLint?",
default: "prompt", default: "prompt",
choices: [ choices: [
{name: "Answer questions about your style", value: "prompt"}, { name: "Answer questions about your style", value: "prompt" },
{name: "Use a popular style guide", value: "guide"}, { name: "Use a popular style guide", value: "guide" },
{name: "Inspect your JavaScript file(s)", value: "auto"} { name: "Inspect your JavaScript file(s)", value: "auto" }
] ]
}, },
{ {
type: "list", type: "list",
name: "styleguide", name: "styleguide",
message: "Which style guide do you want to follow?", message: "Which style guide do you want to follow?",
choices: [{name: "Google", value: "google"}, {name: "Airbnb", value: "airbnb"}, {name: "Standard", value: "standard"}], choices: [{ name: "Google", value: "google" }, { name: "Airbnb", value: "airbnb" }, { name: "Standard", value: "standard" }],
when(answers) { when(answers) {
answers.packageJsonExists = npmUtil.checkPackageJson(); answers.packageJsonExists = npmUtil.checkPackageJson();
return answers.source === "guide" && answers.packageJsonExists; return answers.source === "guide" && answers.packageJsonExists;
} }
}, },
{
type: "confirm",
name: "airbnbReact",
message: "Do you use React?",
default: false,
when(answers) {
return answers.styleguide === "airbnb";
},
},
{ {
type: "input", type: "input",
name: "patterns", name: "patterns",
@ -335,7 +343,7 @@ function promptUser(callback) {
return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto"); return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto");
} }
} }
], function(earlyAnswers) { ], earlyAnswers => {
// early exit if you are using a style guide // early exit if you are using a style guide
if (earlyAnswers.source === "guide") { if (earlyAnswers.source === "guide") {
@ -343,7 +351,9 @@ function promptUser(callback) {
log.info("A package.json is necessary to install plugins such as style guides. Run `npm init` to create a package.json file and try again."); log.info("A package.json is necessary to install plugins such as style guides. Run `npm init` to create a package.json file and try again.");
return; return;
} }
if (earlyAnswers.styleguide === "airbnb" && !earlyAnswers.airbnbReact) {
earlyAnswers.styleguide = "airbnb-base";
}
try { try {
config = getConfigForStyleGuide(earlyAnswers.styleguide); config = getConfigForStyleGuide(earlyAnswers.styleguide);
writeFile(config, earlyAnswers.format); writeFile(config, earlyAnswers.format);
@ -376,7 +386,7 @@ function promptUser(callback) {
name: "env", name: "env",
message: "Where will your code run?", message: "Where will your code run?",
default: ["browser"], default: ["browser"],
choices: [{name: "Browser", value: "browser"}, {name: "Node", value: "node"}] choices: [{ name: "Browser", value: "browser" }, { name: "Node", value: "node" }]
}, },
{ {
type: "confirm", type: "confirm",
@ -384,9 +394,7 @@ function promptUser(callback) {
message: "Do you use CommonJS?", message: "Do you use CommonJS?",
default: false, default: false,
when(answers) { when(answers) {
return answers.env.some(function(env) { return answers.env.some(env => env === "browser");
return env === "browser";
});
} }
}, },
{ {
@ -398,13 +406,13 @@ function promptUser(callback) {
{ {
type: "confirm", type: "confirm",
name: "react", name: "react",
message: "Do you use React", message: "Do you use React?",
default: false, default: false,
when(answers) { when(answers) {
return answers.jsx; return answers.jsx;
} }
} }
], function(secondAnswers) { ], secondAnswers => {
// early exit if you are using automatic style generation // early exit if you are using automatic style generation
if (earlyAnswers.source === "auto") { if (earlyAnswers.source === "auto") {
@ -428,21 +436,21 @@ function promptUser(callback) {
name: "indent", name: "indent",
message: "What style of indentation do you use?", message: "What style of indentation do you use?",
default: "tab", default: "tab",
choices: [{name: "Tabs", value: "tab"}, {name: "Spaces", value: 4}] choices: [{ name: "Tabs", value: "tab" }, { name: "Spaces", value: 4 }]
}, },
{ {
type: "list", type: "list",
name: "quotes", name: "quotes",
message: "What quotes do you use for strings?", message: "What quotes do you use for strings?",
default: "double", default: "double",
choices: [{name: "Double", value: "double"}, {name: "Single", value: "single"}] choices: [{ name: "Double", value: "double" }, { name: "Single", value: "single" }]
}, },
{ {
type: "list", type: "list",
name: "linebreak", name: "linebreak",
message: "What line endings do you use?", message: "What line endings do you use?",
default: "unix", default: "unix",
choices: [{name: "Unix", value: "unix"}, {name: "Windows", value: "windows"}] choices: [{ name: "Unix", value: "unix" }, { name: "Windows", value: "windows" }]
}, },
{ {
type: "confirm", type: "confirm",
@ -457,7 +465,7 @@ function promptUser(callback) {
default: "JavaScript", default: "JavaScript",
choices: ["JavaScript", "YAML", "JSON"] choices: ["JavaScript", "YAML", "JSON"]
} }
], function(answers) { ], answers => {
try { try {
const totalAnswers = Object.assign({}, earlyAnswers, secondAnswers, answers); const totalAnswers = Object.assign({}, earlyAnswers, secondAnswers, answers);
@ -465,10 +473,8 @@ function promptUser(callback) {
installModules(config); installModules(config);
writeFile(config, answers.format); writeFile(config, answers.format);
} catch (err) { } catch (err) {
callback(err); callback(err); // eslint-disable-line callback-return
return;
} }
return;
}); });
}); });
}); });

20
tools/eslint/lib/config/config-ops.js

@ -18,7 +18,7 @@ const debug = require("debug")("eslint:config-ops");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const RULE_SEVERITY_STRINGS = ["off", "warn", "error"], const RULE_SEVERITY_STRINGS = ["off", "warn", "error"],
RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce(function(map, value, index) { RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce((map, value, index) => {
map[value] = index; map[value] = index;
return map; return map;
}, {}), }, {}),
@ -57,9 +57,7 @@ module.exports = {
envConfig.env = env; envConfig.env = env;
Object.keys(env).filter(function(name) { Object.keys(env).filter(name => env[name]).forEach(name => {
return env[name];
}).forEach(function(name) {
const environment = Environments.get(name); const environment = Environments.get(name);
if (environment) { if (environment) {
@ -149,7 +147,7 @@ module.exports = {
if (typeof src !== "object" && !Array.isArray(src)) { if (typeof src !== "object" && !Array.isArray(src)) {
src = [src]; src = [src];
} }
Object.keys(src).forEach(function(e, i) { Object.keys(src).forEach((e, i) => {
e = src[i]; e = src[i];
if (typeof dst[i] === "undefined") { if (typeof dst[i] === "undefined") {
dst[i] = e; dst[i] = e;
@ -171,11 +169,11 @@ module.exports = {
}); });
} else { } else {
if (target && typeof target === "object") { if (target && typeof target === "object") {
Object.keys(target).forEach(function(key) { Object.keys(target).forEach(key => {
dst[key] = target[key]; dst[key] = target[key];
}); });
} }
Object.keys(src).forEach(function(key) { Object.keys(src).forEach(key => {
if (Array.isArray(src[key]) || Array.isArray(target[key])) { if (Array.isArray(src[key]) || Array.isArray(target[key])) {
dst[key] = deepmerge(target[key], src[key], key === "plugins", isRule); dst[key] = deepmerge(target[key], src[key], key === "plugins", isRule);
} else if (typeof src[key] !== "object" || !src[key] || key === "exported" || key === "astGlobals") { } else if (typeof src[key] !== "object" || !src[key] || key === "exported" || key === "astGlobals") {
@ -199,7 +197,7 @@ module.exports = {
normalize(config) { normalize(config) {
if (config.rules) { if (config.rules) {
Object.keys(config.rules).forEach(function(ruleId) { Object.keys(config.rules).forEach(ruleId => {
const ruleConfig = config.rules[ruleId]; const ruleConfig = config.rules[ruleId];
if (typeof ruleConfig === "string") { if (typeof ruleConfig === "string") {
@ -221,7 +219,7 @@ module.exports = {
normalizeToStrings(config) { normalizeToStrings(config) {
if (config.rules) { if (config.rules) {
Object.keys(config.rules).forEach(function(ruleId) { Object.keys(config.rules).forEach(ruleId => {
const ruleConfig = config.rules[ruleId]; const ruleConfig = config.rules[ruleId];
if (typeof ruleConfig === "number") { if (typeof ruleConfig === "number") {
@ -269,8 +267,6 @@ module.exports = {
* @returns {boolean} `true` if the configuration has valid severity. * @returns {boolean} `true` if the configuration has valid severity.
*/ */
isEverySeverityValid(config) { isEverySeverityValid(config) {
return Object.keys(config).every(function(ruleId) { return Object.keys(config).every(ruleId => this.isValidSeverity(config[ruleId]));
return this.isValidSeverity(config[ruleId]);
}, this);
} }
}; };

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

@ -23,7 +23,7 @@ const rules = require("../rules"),
* @returns {Array[]} An array of arrays. * @returns {Array[]} An array of arrays.
*/ */
function explodeArray(xs) { function explodeArray(xs) {
return xs.reduce(function(accumulator, x) { return xs.reduce((accumulator, x) => {
accumulator.push([x]); accumulator.push([x]);
return accumulator; return accumulator;
}, []); }, []);
@ -49,8 +49,8 @@ function combineArrays(arr1, arr2) {
if (arr2.length === 0) { if (arr2.length === 0) {
return explodeArray(arr1); return explodeArray(arr1);
} }
arr1.forEach(function(x1) { arr1.forEach(x1 => {
arr2.forEach(function(x2) { arr2.forEach(x2 => {
res.push([].concat(x1, x2)); res.push([].concat(x1, x2));
}); });
}); });
@ -78,16 +78,14 @@ function combineArrays(arr1, arr2) {
* @returns {Array[]} Array of arrays of objects grouped by property * @returns {Array[]} Array of arrays of objects grouped by property
*/ */
function groupByProperty(objects) { function groupByProperty(objects) {
const groupedObj = objects.reduce(function(accumulator, obj) { const groupedObj = objects.reduce((accumulator, obj) => {
const prop = Object.keys(obj)[0]; const prop = Object.keys(obj)[0];
accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj]; accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj];
return accumulator; return accumulator;
}, {}); }, {});
return Object.keys(groupedObj).map(function(prop) { return Object.keys(groupedObj).map(prop => groupedObj[prop]);
return groupedObj[prop];
});
} }
@ -152,16 +150,16 @@ function combinePropertyObjects(objArr1, objArr2) {
if (objArr2.length === 0) { if (objArr2.length === 0) {
return objArr1; return objArr1;
} }
objArr1.forEach(function(obj1) { objArr1.forEach(obj1 => {
objArr2.forEach(function(obj2) { objArr2.forEach(obj2 => {
const combinedObj = {}; const combinedObj = {};
const obj1Props = Object.keys(obj1); const obj1Props = Object.keys(obj1);
const obj2Props = Object.keys(obj2); const obj2Props = Object.keys(obj2);
obj1Props.forEach(function(prop1) { obj1Props.forEach(prop1 => {
combinedObj[prop1] = obj1[prop1]; combinedObj[prop1] = obj1[prop1];
}); });
obj2Props.forEach(function(prop2) { obj2Props.forEach(prop2 => {
combinedObj[prop2] = obj2[prop2]; combinedObj[prop2] = obj2[prop2];
}); });
res.push(combinedObj); res.push(combinedObj);
@ -205,7 +203,7 @@ RuleConfigSet.prototype = {
addErrorSeverity(severity) { addErrorSeverity(severity) {
severity = severity || 2; severity = severity || 2;
this.ruleConfigs = this.ruleConfigs.map(function(config) { this.ruleConfigs = this.ruleConfigs.map(config => {
config.unshift(severity); config.unshift(severity);
return config; return config;
}); });
@ -241,9 +239,7 @@ RuleConfigSet.prototype = {
}, },
combine() { combine() {
this.objectConfigs = groupByProperty(this.objectConfigs).reduce(function(accumulator, objArr) { this.objectConfigs = groupByProperty(this.objectConfigs).reduce((accumulator, objArr) => combinePropertyObjects(accumulator, objArr), []);
return combinePropertyObjects(accumulator, objArr);
}, []);
} }
}; };
@ -251,7 +247,7 @@ RuleConfigSet.prototype = {
* The object schema could have multiple independent properties. * The object schema could have multiple independent properties.
* If any contain enums or booleans, they can be added and then combined * If any contain enums or booleans, they can be added and then combined
*/ */
Object.keys(obj.properties).forEach(function(prop) { Object.keys(obj.properties).forEach(prop => {
if (obj.properties[prop].enum) { if (obj.properties[prop].enum) {
objectConfigSet.add(prop, obj.properties[prop].enum); objectConfigSet.add(prop, obj.properties[prop].enum);
} }
@ -276,7 +272,7 @@ function generateConfigsFromSchema(schema) {
const configSet = new RuleConfigSet(); const configSet = new RuleConfigSet();
if (Array.isArray(schema)) { if (Array.isArray(schema)) {
schema.forEach(function(opt) { schema.forEach(opt => {
if (opt.enum) { if (opt.enum) {
configSet.addEnums(opt.enum); configSet.addEnums(opt.enum);
} }
@ -302,7 +298,7 @@ function generateConfigsFromSchema(schema) {
function createCoreRuleConfigs() { function createCoreRuleConfigs() {
const ruleList = loadRules(); const ruleList = loadRules();
return Object.keys(ruleList).reduce(function(accumulator, id) { return Object.keys(ruleList).reduce((accumulator, id) => {
const rule = rules.get(id); const rule = rules.get(id);
const schema = (typeof rule === "function") ? rule.schema : rule.meta.schema; const schema = (typeof rule === "function") ? rule.schema : rule.meta.schema;

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

@ -54,65 +54,59 @@ function getRuleOptionsSchema(id) {
} }
/** /**
* Validates a rule's options against its schema. * Validates a rule's severity and returns the severity value. Throws an error if the severity is invalid.
* @param {string} id The rule's unique name. * @param {options} options The given options for the rule.
* @param {array|number} options The given options for the rule. * @returns {number|string} The rule's severity value
* @param {string} source The name of the configuration source. */
* @returns {void} function validateRuleSeverity(options) {
*/ const severity = Array.isArray(options) ? options[0] : options;
function validateRuleOptions(id, options, source) {
const schema = getRuleOptionsSchema(id); if (severity !== 0 && severity !== 1 && severity !== 2 && !(typeof severity === "string" && /^(?:off|warn|error)$/i.test(severity))) {
let validateRule = validators.rules[id], throw new Error(`\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '${util.inspect(severity).replace(/'/g, "\"").replace(/\n/g, "")}').\n`);
severity,
localOptions,
validSeverity = true;
if (!validateRule && schema) {
validateRule = schemaValidator(schema, { verbose: true });
validators.rules[id] = validateRule;
} }
// if it's not an array, it should be just a severity return severity;
if (Array.isArray(options)) { }
localOptions = options.concat(); // clone
severity = localOptions.shift(); /**
} else { * Validates the non-severity options passed to a rule, based on its schema.
severity = options; * @param {string} id The rule's unique name
localOptions = []; * @param {array} localOptions The options for the rule, excluding severity
* @returns {void}
*/
function validateRuleSchema(id, localOptions) {
const schema = getRuleOptionsSchema(id);
if (!validators.rules[id] && schema) {
validators.rules[id] = schemaValidator(schema, { verbose: true });
} }
validSeverity = ( const validateRule = validators.rules[id];
severity === 0 || severity === 1 || severity === 2 ||
(typeof severity === "string" && /^(?:off|warn|error)$/i.test(severity))
);
if (validateRule) { if (validateRule) {
validateRule(localOptions); validateRule(localOptions);
if (validateRule.errors) {
throw new Error(validateRule.errors.map(error => `\tValue "${error.value}" ${error.message}.\n`).join(""));
}
} }
}
if ((validateRule && validateRule.errors) || !validSeverity) { /**
const message = [ * Validates a rule's options against its schema.
source, ":\n", * @param {string} id The rule's unique name.
"\tConfiguration for rule \"", id, "\" is invalid:\n" * @param {array|number} options The given options for the rule.
]; * @param {string} source The name of the configuration source.
* @returns {void}
if (!validSeverity) { */
message.push( function validateRuleOptions(id, options, source) {
"\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '", try {
util.inspect(severity).replace(/'/g, "\"").replace(/\n/g, ""), const severity = validateRuleSeverity(options);
"').\n"
);
}
if (validateRule && validateRule.errors) { if (severity !== 0 && !(typeof severity === "string" && severity.toLowerCase() === "off")) {
validateRule.errors.forEach(function(error) { validateRuleSchema(id, Array.isArray(options) ? options.slice(1) : []);
message.push(
"\tValue \"", error.value, "\" ", error.message, ".\n"
);
});
} }
} catch (err) {
throw new Error(message.join("")); throw new Error(`${source}:\n\tConfiguration for rule "${id}" is invalid:\n${err.message}`);
} }
} }
@ -134,7 +128,7 @@ function validateEnvironment(environment, source) {
} }
if (typeof environment === "object") { if (typeof environment === "object") {
Object.keys(environment).forEach(function(env) { Object.keys(environment).forEach(env => {
if (!Environments.get(env)) { if (!Environments.get(env)) {
const message = [ const message = [
source, ":\n", source, ":\n",
@ -158,7 +152,7 @@ function validateEnvironment(environment, source) {
function validate(config, source) { function validate(config, source) {
if (typeof config.rules === "object") { if (typeof config.rules === "object") {
Object.keys(config.rules).forEach(function(id) { Object.keys(config.rules).forEach(id => {
validateRuleOptions(id, config.rules[id], source); validateRuleOptions(id, config.rules[id], source);
}); });
} }

6
tools/eslint/lib/config/environments.js

@ -22,7 +22,7 @@ let environments = new Map();
* @private * @private
*/ */
function load() { function load() {
Object.keys(envs).forEach(function(envName) { Object.keys(envs).forEach(envName => {
environments.set(envName, envs[envName]); environments.set(envName, envs[envName]);
}); });
} }
@ -65,9 +65,9 @@ module.exports = {
*/ */
importPlugin(plugin, pluginName) { importPlugin(plugin, pluginName) {
if (plugin.environments) { if (plugin.environments) {
Object.keys(plugin.environments).forEach(function(envName) { Object.keys(plugin.environments).forEach(envName => {
this.define(`${pluginName}/${envName}`, plugin.environments[envName]); this.define(`${pluginName}/${envName}`, plugin.environments[envName]);
}, this); });
} }
}, },

102
tools/eslint/lib/eslint.js

@ -26,7 +26,22 @@ const assert = require("assert"),
Traverser = require("./util/traverser"), Traverser = require("./util/traverser"),
RuleContext = require("./rule-context"), RuleContext = require("./rule-context"),
rules = require("./rules"), rules = require("./rules"),
timing = require("./timing"); timing = require("./timing"),
pkg = require("../package.json");
//------------------------------------------------------------------------------
// Typedefs
//------------------------------------------------------------------------------
/**
* The result of a parsing operation from parseForESLint()
* @typedef {Object} CustomParseResult
* @property {ASTNode} ast The ESTree AST Program node.
* @property {Object} services An object containing additional services related
* to the parser.
*/
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
@ -45,7 +60,7 @@ function parseBooleanConfig(string, comment) {
// Collapse whitespace around `:` and `,` to make parsing easier // Collapse whitespace around `:` and `,` to make parsing easier
string = string.replace(/\s*([:,])\s*/g, "$1"); string = string.replace(/\s*([:,])\s*/g, "$1");
string.split(/\s|,+/).forEach(function(name) { string.split(/\s|,+/).forEach(name => {
if (!name) { if (!name) {
return; return;
} }
@ -95,7 +110,7 @@ function parseJsonConfig(string, location, messages) {
// Optionator cannot parse commaless notations. // Optionator cannot parse commaless notations.
// But we are supporting that. So this is a fallback for that. // But we are supporting that. So this is a fallback for that.
items = {}; items = {};
string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,"); string = string.replace(/([a-zA-Z0-9\-/]+):/g, "\"$1\":").replace(/(]|[0-9])\s+(?=")/, "$1,");
try { try {
items = JSON.parse(`{${string}}`); items = JSON.parse(`{${string}}`);
} catch (ex) { } catch (ex) {
@ -126,7 +141,7 @@ function parseListConfig(string) {
// Collapse whitespace around , // Collapse whitespace around ,
string = string.replace(/\s*,\s*/g, ","); string = string.replace(/\s*,\s*/g, ",");
string.split(/,+/).forEach(function(name) { string.split(/,+/).forEach(name => {
name = name.trim(); name = name.trim();
if (!name) { if (!name) {
return; return;
@ -153,7 +168,7 @@ function addDeclaredGlobals(program, globalScope, config) {
Object.assign(declaredGlobals, builtin); Object.assign(declaredGlobals, builtin);
Object.keys(config.env).forEach(function(name) { Object.keys(config.env).forEach(name => {
if (config.env[name]) { if (config.env[name]) {
const env = Environments.get(name), const env = Environments.get(name),
environmentGlobals = env && env.globals; environmentGlobals = env && env.globals;
@ -168,7 +183,7 @@ function addDeclaredGlobals(program, globalScope, config) {
Object.assign(declaredGlobals, config.globals); Object.assign(declaredGlobals, config.globals);
Object.assign(explicitGlobals, config.astGlobals); Object.assign(explicitGlobals, config.astGlobals);
Object.keys(declaredGlobals).forEach(function(name) { Object.keys(declaredGlobals).forEach(name => {
let variable = globalScope.set.get(name); let variable = globalScope.set.get(name);
if (!variable) { if (!variable) {
@ -180,7 +195,7 @@ function addDeclaredGlobals(program, globalScope, config) {
variable.writeable = declaredGlobals[name]; variable.writeable = declaredGlobals[name];
}); });
Object.keys(explicitGlobals).forEach(function(name) { Object.keys(explicitGlobals).forEach(name => {
let variable = globalScope.set.get(name); let variable = globalScope.set.get(name);
if (!variable) { if (!variable) {
@ -194,7 +209,7 @@ function addDeclaredGlobals(program, globalScope, config) {
}); });
// mark all exported variables as such // mark all exported variables as such
Object.keys(exportedGlobals).forEach(function(name) { Object.keys(exportedGlobals).forEach(name => {
const variable = globalScope.set.get(name); const variable = globalScope.set.get(name);
if (variable) { if (variable) {
@ -207,7 +222,7 @@ function addDeclaredGlobals(program, globalScope, config) {
* Since we augment the global scope using configuration, we need to update * Since we augment the global scope using configuration, we need to update
* references and remove the ones that were added by configuration. * references and remove the ones that were added by configuration.
*/ */
globalScope.through = globalScope.through.filter(function(reference) { globalScope.through = globalScope.through.filter(reference => {
const name = reference.identifier.name; const name = reference.identifier.name;
const variable = globalScope.set.get(name); const variable = globalScope.set.get(name);
@ -238,7 +253,7 @@ function addDeclaredGlobals(program, globalScope, config) {
function disableReporting(reportingConfig, start, rulesToDisable) { function disableReporting(reportingConfig, start, rulesToDisable) {
if (rulesToDisable.length) { if (rulesToDisable.length) {
rulesToDisable.forEach(function(rule) { rulesToDisable.forEach(rule => {
reportingConfig.push({ reportingConfig.push({
start, start,
end: null, end: null,
@ -266,7 +281,7 @@ function enableReporting(reportingConfig, start, rulesToEnable) {
let i; let i;
if (rulesToEnable.length) { if (rulesToEnable.length) {
rulesToEnable.forEach(function(rule) { rulesToEnable.forEach(rule => {
for (i = reportingConfig.length - 1; i >= 0; i--) { for (i = reportingConfig.length - 1; i >= 0; i--) {
if (!reportingConfig[i].end && reportingConfig[i].rule === rule) { if (!reportingConfig[i].end && reportingConfig[i].rule === rule) {
reportingConfig[i].end = start; reportingConfig[i].end = start;
@ -313,7 +328,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
}; };
const commentRules = {}; const commentRules = {};
ast.comments.forEach(function(comment) { ast.comments.forEach(comment => {
let value = comment.value.trim(); let value = comment.value.trim();
const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value); const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value);
@ -347,7 +362,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
case "eslint": { case "eslint": {
const items = parseJsonConfig(value, comment.loc, messages); const items = parseJsonConfig(value, comment.loc, messages);
Object.keys(items).forEach(function(name) { Object.keys(items).forEach(name => {
const ruleValue = items[name]; const ruleValue = items[name];
validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`); validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`);
@ -371,7 +386,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
}); });
// apply environment configs // apply environment configs
Object.keys(commentConfig.env).forEach(function(name) { Object.keys(commentConfig.env).forEach(name => {
const env = Environments.get(name); const env = Environments.get(name);
if (env) { if (env) {
@ -442,11 +457,11 @@ function prepareConfig(config) {
let parserOptions = {}; let parserOptions = {};
if (typeof config.rules === "object") { if (typeof config.rules === "object") {
Object.keys(config.rules).forEach(function(k) { Object.keys(config.rules).forEach(k => {
const rule = config.rules[k]; const rule = config.rules[k];
if (rule === null) { if (rule === null) {
throw new Error(`Invalid config for rule '${k}'\.`); throw new Error(`Invalid config for rule '${k}'.`);
} }
if (Array.isArray(rule)) { if (Array.isArray(rule)) {
copiedRules[k] = rule.slice(); copiedRules[k] = rule.slice();
@ -458,7 +473,7 @@ function prepareConfig(config) {
// merge in environment parserOptions // merge in environment parserOptions
if (typeof config.env === "object") { if (typeof config.env === "object") {
Object.keys(config.env).forEach(function(envName) { Object.keys(config.env).forEach(envName => {
const env = Environments.get(envName); const env = Environments.get(envName);
if (config.env[envName] && env && env.parserOptions) { if (config.env[envName] && env && env.parserOptions) {
@ -598,7 +613,8 @@ module.exports = (function() {
* @param {string} text The text to parse. * @param {string} text The text to parse.
* @param {Object} config The ESLint configuration object. * @param {Object} config The ESLint configuration object.
* @param {string} filePath The path to the file being parsed. * @param {string} filePath The path to the file being parsed.
* @returns {ASTNode} The AST if successful or null if not. * @returns {ASTNode|CustomParseResult} The AST or parse result if successful,
* or null if not.
* @private * @private
*/ */
function parse(text, config, filePath) { function parse(text, config, filePath) {
@ -642,7 +658,11 @@ module.exports = (function() {
* problem that ESLint identified just like any other. * problem that ESLint identified just like any other.
*/ */
try { try {
return parser.parse(text, parserOptions); if (typeof parser.parseForESLint === "function") {
return parser.parseForESLint(text, parserOptions);
} else {
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:
@ -738,6 +758,7 @@ module.exports = (function() {
api.verify = function(textOrSourceCode, config, filenameOrOptions, saveState) { api.verify = function(textOrSourceCode, config, filenameOrOptions, saveState) {
const text = (typeof textOrSourceCode === "string") ? textOrSourceCode : null; const text = (typeof textOrSourceCode === "string") ? textOrSourceCode : null;
let ast, let ast,
parseResult,
shebang, shebang,
allowInlineConfig; allowInlineConfig;
@ -759,7 +780,7 @@ module.exports = (function() {
if (envInFile) { if (envInFile) {
if (!config || !config.env) { if (!config || !config.env) {
config = Object.assign({}, config || {}, {env: envInFile}); config = Object.assign({}, config || {}, { env: envInFile });
} else { } else {
config = Object.assign({}, config); config = Object.assign({}, config);
config.env = Object.assign({}, config.env, envInFile); config.env = Object.assign({}, config.env, envInFile);
@ -778,8 +799,8 @@ module.exports = (function() {
return messages; return messages;
} }
ast = parse( parseResult = parse(
stripUnicodeBOM(text).replace(/^#!([^\r\n]+)/, function(match, captured) { stripUnicodeBOM(text).replace(/^#!([^\r\n]+)/, (match, captured) => {
shebang = captured; shebang = captured;
return `//${captured}`; return `//${captured}`;
}), }),
@ -787,6 +808,14 @@ module.exports = (function() {
currentFilename currentFilename
); );
// if this result is from a parseForESLint() method, normalize
if (parseResult && parseResult.ast) {
ast = parseResult.ast;
} else {
ast = parseResult;
parseResult = null;
}
if (ast) { if (ast) {
sourceCode = new SourceCode(text, ast); sourceCode = new SourceCode(text, ast);
} }
@ -808,9 +837,7 @@ module.exports = (function() {
ConfigOps.normalize(config); ConfigOps.normalize(config);
// enable appropriate rules // enable appropriate rules
Object.keys(config.rules).filter(function(key) { Object.keys(config.rules).filter(key => getRuleSeverity(config.rules[key]) > 0).forEach(key => {
return getRuleSeverity(config.rules[key]) > 0;
}).forEach(function(key) {
let ruleCreator; let ruleCreator;
ruleCreator = rules.get(key); ruleCreator = rules.get(key);
@ -832,13 +859,16 @@ module.exports = (function() {
try { try {
const ruleContext = new RuleContext( const ruleContext = new RuleContext(
key, api, severity, options, key, api, severity, options,
config.settings, config.parserOptions, config.parser, ruleCreator.meta); config.settings, config.parserOptions, config.parser,
ruleCreator.meta,
(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 node types as listeners
Object.keys(rule).forEach(function(nodeType) { Object.keys(rule).forEach(nodeType => {
api.on(nodeType, timing.enabled api.on(nodeType, timing.enabled
? timing.time(key, rule[nodeType]) ? timing.time(key, rule[nodeType])
: rule[nodeType] : rule[nodeType]
@ -904,7 +934,7 @@ module.exports = (function() {
} }
// sort by line and column // sort by line and column
messages.sort(function(a, b) { messages.sort((a, b) => {
const lineDiff = a.line - b.line; const lineDiff = a.line - b.line;
if (lineDiff === 0) { if (lineDiff === 0) {
@ -957,7 +987,7 @@ module.exports = (function() {
} }
if (opts) { if (opts) {
message = message.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, function(fullMatch, term) { message = message.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (fullMatch, term) => {
if (term in opts) { if (term in opts) {
return opts[term]; return opts[term];
} }
@ -1027,7 +1057,7 @@ module.exports = (function() {
}; };
// copy over methods // copy over methods
Object.keys(externalMethods).forEach(function(methodName) { Object.keys(externalMethods).forEach(methodName => {
const exMethodName = externalMethods[methodName]; const exMethodName = externalMethods[methodName];
// All functions expected to have less arguments than 5. // All functions expected to have less arguments than 5.
@ -1152,7 +1182,7 @@ module.exports = (function() {
* @returns {void} * @returns {void}
*/ */
api.defineRules = function(rulesToDefine) { api.defineRules = function(rulesToDefine) {
Object.getOwnPropertyNames(rulesToDefine).forEach(function(ruleId) { Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => {
defineRule(ruleId, rulesToDefine[ruleId]); defineRule(ruleId, rulesToDefine[ruleId]);
}); });
}; };
@ -1165,6 +1195,16 @@ module.exports = (function() {
return require("../conf/eslint.json"); return require("../conf/eslint.json");
}; };
/**
* Gets an object with all loaded rules.
* @returns {Map} All loaded rules
*/
api.getRules = function() {
return rules.getAllLoadedRules();
};
api.version = pkg.version;
/** /**
* Gets variables that are declared by a specified node. * Gets variables that are declared by a specified node.
* *

130
tools/eslint/lib/file-finder.js

@ -31,22 +31,6 @@ function getDirectoryEntries(directory) {
} }
} }
//------------------------------------------------------------------------------
// API
//------------------------------------------------------------------------------
/**
* FileFinder
* @constructor
* @param {string[]} files The basename(s) of the file(s) to find.
* @param {stirng} cwd Current working directory
*/
function FileFinder(files, cwd) {
this.fileNames = Array.isArray(files) ? files : [files];
this.cwd = cwd || process.cwd();
this.cache = {};
}
/** /**
* Create a hash of filenames from a directory listing * Create a hash of filenames from a directory listing
* @param {string[]} entries Array of directory entries. * @param {string[]} entries Array of directory entries.
@ -57,7 +41,7 @@ function FileFinder(files, cwd) {
function normalizeDirectoryEntries(entries, directory, supportedConfigs) { function normalizeDirectoryEntries(entries, directory, supportedConfigs) {
const fileHash = {}; const fileHash = {};
entries.forEach(function(entry) { entries.forEach(entry => {
if (supportedConfigs.indexOf(entry) >= 0) { if (supportedConfigs.indexOf(entry) >= 0) {
const resolvedEntry = path.resolve(directory, entry); const resolvedEntry = path.resolve(directory, entry);
@ -69,69 +53,89 @@ function normalizeDirectoryEntries(entries, directory, supportedConfigs) {
return fileHash; return fileHash;
} }
//------------------------------------------------------------------------------
// API
//------------------------------------------------------------------------------
/** /**
* Find all instances of files with the specified file names, in directory and * FileFinder class
* parent directories. Cache the results.
* Does not check if a matching directory entry is a file.
* Searches for all the file names in this.fileNames.
* Is currently used by lib/config.js to find .eslintrc and package.json files.
* @param {string} directory The directory to start the search from.
* @returns {string[]} The file paths found.
*/ */
FileFinder.prototype.findAllInDirectoryAndParents = function(directory) { class FileFinder {
const cache = this.cache;
/**
if (directory) { * @param {string[]} files The basename(s) of the file(s) to find.
directory = path.resolve(this.cwd, directory); * @param {stirng} cwd Current working directory
} else { */
directory = this.cwd; constructor(files, cwd) {
this.fileNames = Array.isArray(files) ? files : [files];
this.cwd = cwd || process.cwd();
this.cache = {};
} }
if (cache.hasOwnProperty(directory)) { /**
return cache[directory]; * Find all instances of files with the specified file names, in directory and
} * parent directories. Cache the results.
* Does not check if a matching directory entry is a file.
* Searches for all the file names in this.fileNames.
* Is currently used by lib/config.js to find .eslintrc and package.json files.
* @param {string} directory The directory to start the search from.
* @returns {string[]} The file paths found.
*/
findAllInDirectoryAndParents(directory) {
const cache = this.cache;
if (directory) {
directory = path.resolve(this.cwd, directory);
} else {
directory = this.cwd;
}
const dirs = []; if (cache.hasOwnProperty(directory)) {
const fileNames = this.fileNames; return cache[directory];
let searched = 0; }
do { const dirs = [];
dirs[searched++] = directory; const fileNames = this.fileNames;
cache[directory] = []; let searched = 0;
const filesMap = normalizeDirectoryEntries(getDirectoryEntries(directory), directory, fileNames); do {
dirs[searched++] = directory;
cache[directory] = [];
if (Object.keys(filesMap).length) { const filesMap = normalizeDirectoryEntries(getDirectoryEntries(directory), directory, fileNames);
for (let k = 0; k < fileNames.length; k++) {
if (filesMap[fileNames[k]]) { if (Object.keys(filesMap).length) {
const filePath = filesMap[fileNames[k]]; for (let k = 0; k < fileNames.length; k++) {
// Add the file path to the cache of each directory searched. if (filesMap[fileNames[k]]) {
for (let j = 0; j < searched; j++) { const filePath = filesMap[fileNames[k]];
cache[dirs[j]].push(filePath);
} // Add the file path to the cache of each directory searched.
for (let j = 0; j < searched; j++) {
cache[dirs[j]].push(filePath);
}
break; break;
}
} }
} }
} const child = directory;
const child = directory;
// Assign parent directory to directory.
directory = path.dirname(directory);
// Assign parent directory to directory. if (directory === child) {
directory = path.dirname(directory); return cache[dirs[0]];
}
} while (!cache.hasOwnProperty(directory));
if (directory === child) { // Add what has been cached previously to the cache of each directory searched.
return cache[dirs[0]]; for (let i = 0; i < searched; i++) {
dirs.push.apply(cache[dirs[i]], cache[directory]);
} }
} while (!cache.hasOwnProperty(directory));
// Add what has been cached previously to the cache of each directory searched. return cache[dirs[0]];
for (let i = 0; i < searched; i++) {
dirs.push.apply(cache[dirs[i]], cache[directory]);
} }
}
return cache[dirs[0]];
};
module.exports = FileFinder; module.exports = FileFinder;

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

@ -35,12 +35,12 @@ module.exports = function(results) {
output += "<?xml version=\"1.0\" encoding=\"utf-8\"?>"; output += "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
output += "<checkstyle version=\"4.3\">"; output += "<checkstyle version=\"4.3\">";
results.forEach(function(result) { results.forEach(result => {
const messages = result.messages; const messages = result.messages;
output += `<file name="${xmlEscape(result.filePath)}">`; output += `<file name="${xmlEscape(result.filePath)}">`;
messages.forEach(function(message) { messages.forEach(message => {
output += [ output += [
`<error line="${xmlEscape(message.line)}"`, `<error line="${xmlEscape(message.line)}"`,
`column="${xmlEscape(message.column)}"`, `column="${xmlEscape(message.column)}"`,

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

@ -0,0 +1,121 @@
/**
* @fileoverview Codeframe reporter
* @author Vitor Balocco
*/
"use strict";
const chalk = require("chalk");
const codeFrame = require("babel-code-frame");
const path = require("path");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Given a word and a count, append an s if count is not one.
* @param {string} word A word in its singular form.
* @param {number} count A number controlling whether word should be pluralized.
* @returns {string} The original word with an s on the end if count is not one.
*/
function pluralize(word, count) {
return (count === 1 ? word : `${word}s`);
}
/**
* Gets a formatted relative file path from an absolute path and a line/column in the file.
* @param {string} filePath The absolute file path to format.
* @param {number} line The line from the file to use for formatting.
* @param {number} column The column from the file to use for formatting.
* @returns {string} The formatted file path.
*/
function formatFilePath(filePath, line, column) {
let relPath = path.relative(process.cwd(), filePath);
if (line && column) {
relPath += `:${line}:${column}`;
}
return chalk.green(relPath);
}
/**
* Gets the formatted output for a given message.
* @param {Object} message The object that represents this message.
* @param {Object} parentResult The result object that this message belongs to.
* @returns {string} The formatted output.
*/
function formatMessage(message, parentResult) {
const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning");
const msg = `${chalk.bold(message.message.replace(/\.$/, ""))}`;
const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`);
const filePath = formatFilePath(parentResult.filePath, message.line, message.column);
const sourceCode = parentResult.output ? parentResult.output : parentResult.source;
const firstLine = [
`${type}:`,
`${msg}`,
ruleId ? `${ruleId}` : "",
sourceCode ? `at ${filePath}:` : `at ${filePath}`,
].filter(String).join(" ");
const result = [firstLine];
if (sourceCode) {
result.push(
codeFrame(sourceCode, message.line, message.column, { highlightCode: false })
);
}
return result.join("\n");
}
/**
* Gets the formatted output summary for a given number of errors and warnings.
* @param {number} errors The number of errors.
* @param {number} warnings The number of warnings.
* @returns {string} The formatted output summary.
*/
function formatSummary(errors, warnings) {
const summaryColor = errors > 0 ? "red" : "yellow";
const summary = [];
if (errors > 0) {
summary.push(`${errors} ${pluralize("error", errors)}`);
}
if (warnings > 0) {
summary.push(`${warnings} ${pluralize("warning", warnings)}`);
}
return chalk[summaryColor].bold(`${summary.join(" and ")} found.`);
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = function(results) {
let errors = 0;
let warnings = 0;
const resultsWithMessages = results.filter(result => result.messages.length > 0);
let output = resultsWithMessages.reduce((resultsOutput, result) => {
const messages = result.messages.map(message => {
if (message.fatal || message.severity === 2) {
errors++;
} else {
warnings++;
}
return `${formatMessage(message, result)}\n\n`;
});
return resultsOutput.concat(messages);
}, []).join("\n");
output += "\n";
output += formatSummary(errors, warnings);
return (errors + warnings) > 0 ? output : "";
};

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

@ -32,13 +32,13 @@ module.exports = function(results) {
let output = "", let output = "",
total = 0; total = 0;
results.forEach(function(result) { results.forEach(result => {
const messages = result.messages; const messages = result.messages;
total += messages.length; total += messages.length;
messages.forEach(function(message) { messages.forEach(message => {
output += `${result.filePath}: `; output += `${result.filePath}: `;
output += `line ${message.line || 0}`; output += `line ${message.line || 0}`;

20
tools/eslint/lib/formatters/html.js

@ -70,7 +70,7 @@ function renderMessages(messages, parentIndex) {
* @param {Object} message Message. * @param {Object} message Message.
* @returns {string} HTML (table row) describing a message. * @returns {string} HTML (table row) describing a message.
*/ */
return lodash.map(messages, function(message) { return lodash.map(messages, message => {
const lineNumber = message.line || 0; const lineNumber = message.line || 0;
const columnNumber = message.column || 0; const columnNumber = message.column || 0;
@ -91,15 +91,13 @@ function renderMessages(messages, parentIndex) {
* @returns {string} HTML string describing the results. * @returns {string} HTML string describing the results.
*/ */
function renderResults(results) { function renderResults(results) {
return lodash.map(results, function(result, index) { return lodash.map(results, (result, index) => resultTemplate({
return resultTemplate({ index,
index, color: renderColor(result.errorCount, result.warningCount),
color: renderColor(result.errorCount, result.warningCount), filePath: result.filePath,
filePath: result.filePath, summary: renderSummary(result.errorCount, result.warningCount)
summary: renderSummary(result.errorCount, result.warningCount)
}) + renderMessages(result.messages, index)).join("\n");
}) + renderMessages(result.messages, index);
}).join("\n");
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -114,7 +112,7 @@ module.exports = function(results) {
totalWarnings = 0; totalWarnings = 0;
// Iterate over results to get totals // Iterate over results to get totals
results.forEach(function(result) { results.forEach(result => {
totalErrors += result.errorCount; totalErrors += result.errorCount;
totalWarnings += result.warningCount; totalWarnings += result.warningCount;
}); });

4
tools/eslint/lib/formatters/jslint-xml.js

@ -17,12 +17,12 @@ module.exports = function(results) {
output += "<?xml version=\"1.0\" encoding=\"utf-8\"?>"; output += "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
output += "<jslint>"; output += "<jslint>";
results.forEach(function(result) { results.forEach(result => {
const messages = result.messages; const messages = result.messages;
output += `<file name="${result.filePath}">`; output += `<file name="${result.filePath}">`;
messages.forEach(function(message) { messages.forEach(message => {
output += [ output += [
`<issue line="${message.line}"`, `<issue line="${message.line}"`,
`char="${message.column}"`, `char="${message.column}"`,

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

@ -35,7 +35,7 @@ module.exports = function(results) {
output += "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; output += "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
output += "<testsuites>\n"; output += "<testsuites>\n";
results.forEach(function(result) { results.forEach(result => {
const messages = result.messages; const messages = result.messages;
@ -43,7 +43,7 @@ module.exports = function(results) {
output += `<testsuite package="org.eslint" time="0" tests="${messages.length}" errors="${messages.length}" name="${result.filePath}">\n`; output += `<testsuite package="org.eslint" time="0" tests="${messages.length}" errors="${messages.length}" name="${result.filePath}">\n`;
} }
messages.forEach(function(message) { messages.forEach(message => {
const type = message.fatal ? "error" : "failure"; const type = message.fatal ? "error" : "failure";
output += `<testcase time="0" name="org.eslint.${message.ruleId || "unknown"}">`; output += `<testcase time="0" name="org.eslint.${message.ruleId || "unknown"}">`;

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

@ -33,7 +33,7 @@ module.exports = function(results) {
warnings = 0, warnings = 0,
summaryColor = "yellow"; summaryColor = "yellow";
results.forEach(function(result) { results.forEach(result => {
const messages = result.messages; const messages = result.messages;
if (messages.length === 0) { if (messages.length === 0) {
@ -44,7 +44,7 @@ module.exports = function(results) {
output += `${chalk.underline(result.filePath)}\n`; output += `${chalk.underline(result.filePath)}\n`;
output += `${table( output += `${table(
messages.map(function(message) { messages.map(message => {
let messageType; let messageType;
if (message.fatal || message.severity === 2) { if (message.fatal || message.severity === 2) {
@ -71,11 +71,7 @@ module.exports = function(results) {
return chalk.stripColor(str).length; return chalk.stripColor(str).length;
} }
} }
).split("\n").map(function(el) { ).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
return el.replace(/(\d+)\s+(\d+)/, function(m, p1, p2) {
return chalk.dim(`${p1}:${p2}`);
});
}).join("\n")}\n\n`;
}); });
if (total > 0) { if (total > 0) {

10
tools/eslint/lib/formatters/table.js

@ -36,7 +36,7 @@ function drawTable(messages) {
chalk.bold("Rule ID") chalk.bold("Rule ID")
]); ]);
messages.forEach(function(message) { messages.forEach(message => {
let messageType; let messageType;
if (message.fatal || message.severity === 2) { if (message.fatal || message.severity === 2) {
@ -92,7 +92,7 @@ function drawTable(messages) {
function drawReport(results) { function drawReport(results) {
let files; let files;
files = results.map(function(result) { files = results.map(result => {
if (!result.messages.length) { if (!result.messages.length) {
return ""; return "";
} }
@ -100,9 +100,7 @@ function drawReport(results) {
return `\n${result.filePath}\n\n${drawTable(result.messages)}`; return `\n${result.filePath}\n\n${drawTable(result.messages)}`;
}); });
files = files.filter(function(content) { files = files.filter(content => content.trim());
return content.trim();
});
return files.join(""); return files.join("");
} }
@ -120,7 +118,7 @@ module.exports = function(report) {
errorCount = 0; errorCount = 0;
warningCount = 0; warningCount = 0;
report.forEach(function(fileReport) { report.forEach(fileReport => {
errorCount += fileReport.errorCount; errorCount += fileReport.errorCount;
warningCount += fileReport.warningCount; warningCount += fileReport.warningCount;
}); });

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

@ -44,7 +44,7 @@ function outputDiagnostics(diagnostic) {
module.exports = function(results) { module.exports = function(results) {
let output = `TAP version 13\n1..${results.length}\n`; let output = `TAP version 13\n1..${results.length}\n`;
results.forEach(function(result, id) { results.forEach((result, id) => {
const messages = result.messages; const messages = result.messages;
let testResult = "ok"; let testResult = "ok";
let diagnostics = {}; let diagnostics = {};
@ -52,7 +52,7 @@ module.exports = function(results) {
if (messages.length > 0) { if (messages.length > 0) {
testResult = "not ok"; testResult = "not ok";
messages.forEach(function(message) { messages.forEach(message => {
const diagnostic = { const diagnostic = {
message: message.message, message: message.message,
severity: getMessageType(message), severity: getMessageType(message),

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

@ -31,13 +31,13 @@ module.exports = function(results) {
let output = "", let output = "",
total = 0; total = 0;
results.forEach(function(result) { results.forEach(result => {
const messages = result.messages; const messages = result.messages;
total += messages.length; total += messages.length;
messages.forEach(function(message) { messages.forEach(message => {
output += `${result.filePath}:`; output += `${result.filePath}:`;
output += `${message.line || 0}:`; output += `${message.line || 0}:`;

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

@ -33,13 +33,13 @@ module.exports = function(results) {
let output = "", let output = "",
total = 0; total = 0;
results.forEach(function(result) { results.forEach(result => {
const messages = result.messages; const messages = result.messages;
total += messages.length; total += messages.length;
messages.forEach(function(message) { messages.forEach(message => {
output += result.filePath; output += result.filePath;
output += `(${message.line || 0}`; output += `(${message.line || 0}`;

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

@ -72,160 +72,161 @@ function mergeDefaultOptions(options) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** /**
* IgnoredPaths * IgnoredPaths class
* @constructor
* @class IgnoredPaths
* @param {Object} options object containing 'ignore', 'ignorePath' and 'patterns' properties
*/ */
function IgnoredPaths(options) { class IgnoredPaths {
options = mergeDefaultOptions(options);
/**
* add pattern to node-ignore instance
* @param {Object} ig, instance of node-ignore
* @param {string} pattern, pattern do add to ig
* @returns {array} raw ignore rules
*/
function addPattern(ig, pattern) {
return ig.addPattern(pattern);
}
/** /**
* add ignore file to node-ignore instance * @param {Object} options object containing 'ignore', 'ignorePath' and 'patterns' properties
* @param {Object} ig, instance of node-ignore
* @param {string} filepath, file to add to ig
* @returns {array} raw ignore rules
*/ */
function addIgnoreFile(ig, filepath) { constructor(options) {
ig.ignoreFiles.push(filepath); options = mergeDefaultOptions(options);
return ig.add(fs.readFileSync(filepath, "utf8"));
} /**
* add pattern to node-ignore instance
this.defaultPatterns = [].concat(DEFAULT_IGNORE_DIRS, options.patterns || []); * @param {Object} ig, instance of node-ignore
this.baseDir = options.cwd; * @param {string} pattern, pattern do add to ig
* @returns {array} raw ignore rules
this.ig = { */
custom: ignore(), function addPattern(ig, pattern) {
default: ignore() return ig.addPattern(pattern);
}; }
// Add a way to keep track of ignored files. This was present in node-ignore /**
// 2.x, but dropped for now as of 3.0.10. * add ignore file to node-ignore instance
this.ig.custom.ignoreFiles = []; * @param {Object} ig, instance of node-ignore
this.ig.default.ignoreFiles = []; * @param {string} filepath, file to add to ig
* @returns {array} raw ignore rules
*/
function addIgnoreFile(ig, filepath) {
ig.ignoreFiles.push(filepath);
return ig.add(fs.readFileSync(filepath, "utf8"));
}
if (options.dotfiles !== true) { this.defaultPatterns = [].concat(DEFAULT_IGNORE_DIRS, options.patterns || []);
this.baseDir = options.cwd;
/* this.ig = {
* ignore files beginning with a dot, but not files in a parent or custom: ignore(),
* ancestor directory (which in relative format will begin with `../`). default: ignore()
*/ };
addPattern(this.ig.default, [".*", "!../"]);
}
addPattern(this.ig.default, this.defaultPatterns); // Add a way to keep track of ignored files. This was present in node-ignore
// 2.x, but dropped for now as of 3.0.10.
this.ig.custom.ignoreFiles = [];
this.ig.default.ignoreFiles = [];
if (options.ignore !== false) { if (options.dotfiles !== true) {
let ignorePath;
if (options.ignorePath) { /*
debug("Using specific ignore file"); * ignore files beginning with a dot, but not files in a parent or
* ancestor directory (which in relative format will begin with `../`).
*/
addPattern(this.ig.default, [".*", "!../"]);
}
try { addPattern(this.ig.default, this.defaultPatterns);
fs.statSync(options.ignorePath);
ignorePath = options.ignorePath; if (options.ignore !== false) {
} catch (e) { let ignorePath;
e.message = `Cannot read ignore file: ${options.ignorePath}\nError: ${e.message}`;
throw e; if (options.ignorePath) {
debug("Using specific ignore file");
try {
fs.statSync(options.ignorePath);
ignorePath = options.ignorePath;
} catch (e) {
e.message = `Cannot read ignore file: ${options.ignorePath}\nError: ${e.message}`;
throw e;
}
} else {
debug(`Looking for ignore file in ${options.cwd}`);
ignorePath = findIgnoreFile(options.cwd);
try {
fs.statSync(ignorePath);
debug(`Loaded ignore file ${ignorePath}`);
} catch (e) {
debug("Could not find ignore file in cwd");
this.options = options;
}
} }
} else {
debug(`Looking for ignore file in ${options.cwd}`); if (ignorePath) {
ignorePath = findIgnoreFile(options.cwd); debug(`Adding ${ignorePath}`);
this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath));
try { addIgnoreFile(this.ig.custom, ignorePath);
fs.statSync(ignorePath); addIgnoreFile(this.ig.default, ignorePath);
debug(`Loaded ignore file ${ignorePath}`);
} catch (e) {
debug("Could not find ignore file in cwd");
this.options = options;
} }
}
if (ignorePath) { if (options.ignorePattern) {
debug(`Adding ${ignorePath}`); addPattern(this.ig.custom, options.ignorePattern);
this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath)); addPattern(this.ig.default, options.ignorePattern);
addIgnoreFile(this.ig.custom, ignorePath); }
addIgnoreFile(this.ig.default, ignorePath);
} }
if (options.ignorePattern) { this.options = options;
addPattern(this.ig.custom, options.ignorePattern);
addPattern(this.ig.default, options.ignorePattern);
}
} }
this.options = options; /**
* Determine whether a file path is included in the default or custom ignore patterns
* @param {string} filepath Path to check
* @param {string} [category=null] check 'default', 'custom' or both (null)
* @returns {boolean} true if the file path matches one or more patterns, false otherwise
*/
contains(filepath, category) {
} let result = false;
const absolutePath = path.resolve(this.options.cwd, filepath);
const relativePath = pathUtil.getRelativePath(absolutePath, this.options.cwd);
/** if ((typeof category === "undefined") || (category === "default")) {
* Determine whether a file path is included in the default or custom ignore patterns result = result || (this.ig.default.filter([relativePath]).length === 0);
* @param {string} filepath Path to check }
* @param {string} [category=null] check 'default', 'custom' or both (null)
* @returns {boolean} true if the file path matches one or more patterns, false otherwise
*/
IgnoredPaths.prototype.contains = function(filepath, category) {
let result = false; if ((typeof category === "undefined") || (category === "custom")) {
const absolutePath = path.resolve(this.options.cwd, filepath); result = result || (this.ig.custom.filter([relativePath]).length === 0);
const relativePath = pathUtil.getRelativePath(absolutePath, this.options.cwd); }
if ((typeof category === "undefined") || (category === "default")) { return result;
result = result || (this.ig.default.filter([relativePath]).length === 0);
}
if ((typeof category === "undefined") || (category === "custom")) {
result = result || (this.ig.custom.filter([relativePath]).length === 0);
} }
return result; /**
* Returns a list of dir patterns for glob to ignore
}; * @returns {function()} method to check whether a folder should be ignored by glob.
*/
/** getIgnoredFoldersGlobChecker() {
* Returns a list of dir patterns for glob to ignore
* @returns {function()} method to check whether a folder should be ignored by glob.
*/
IgnoredPaths.prototype.getIgnoredFoldersGlobChecker = function() {
const ig = ignore().add(DEFAULT_IGNORE_DIRS); const ig = ignore().add(DEFAULT_IGNORE_DIRS);
if (this.options.ignore) { if (this.options.ignore) {
ig.add(this.ig.custom); ig.add(this.ig.custom);
} }
const filter = ig.createFilter(); const filter = ig.createFilter();
/** /**
* TODO * TODO
* 1. * 1.
* Actually, it should be `this.options.baseDir`, which is the base dir of `ignore-path`, * Actually, it should be `this.options.baseDir`, which is the base dir of `ignore-path`,
* as well as Line 177. * as well as Line 177.
* But doing this leads to a breaking change and fails tests. * But doing this leads to a breaking change and fails tests.
* Related to #6759 * Related to #6759
*/ */
const base = this.options.cwd; const base = this.options.cwd;
return function(absolutePath) { return function(absolutePath) {
const relative = pathUtil.getRelativePath(absolutePath, base); const relative = pathUtil.getRelativePath(absolutePath, base);
if (!relative) { if (!relative) {
return false; return false;
} }
return !filter(relative); return !filter(relative);
}; };
}; }
}
module.exports = IgnoredPaths; module.exports = IgnoredPaths;

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

@ -95,7 +95,6 @@ function checkMetaDocsDescription(context, exportsNode) {
firstWord firstWord
} }
}); });
return;
} }
} }

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

@ -147,7 +147,6 @@ function checkMetaValidity(context, exportsNode, ruleIsFixable) {
if (ruleIsFixable && !hasMetaFixable(metaProperty)) { if (ruleIsFixable && !hasMetaFixable(metaProperty)) {
context.report(metaProperty, "Rule is fixable, but is missing a meta.fixable property."); context.report(metaProperty, "Rule is fixable, but is missing a meta.fixable property.");
return;
} }
} }
@ -216,7 +215,7 @@ module.exports = {
"Program:exit"() { "Program:exit"() {
if (!isCorrectExportsFormat(exportsNode)) { if (!isCorrectExportsFormat(exportsNode)) {
context.report(exportsNode, "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;
} }

2
tools/eslint/lib/load-rules.js

@ -31,7 +31,7 @@ module.exports = function(rulesDir, cwd) {
const rules = Object.create(null); const rules = Object.create(null);
fs.readdirSync(rulesDir).forEach(function(file) { fs.readdirSync(rulesDir).forEach(file => {
if (path.extname(file) !== ".js") { if (path.extname(file) !== ".js") {
return; return;
} }

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

@ -61,36 +61,41 @@ const PASSTHROUGHS = [
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** /**
* Rule context class
* Acts as an abstraction layer between rules and the main eslint object. * Acts as an abstraction layer between rules and the main eslint object.
* @constructor
* @param {string} ruleId The ID of the rule using this object.
* @param {eslint} eslint The eslint object.
* @param {number} severity The configured severity level of the rule.
* @param {Array} options The configuration information to be added to the rule.
* @param {Object} settings The configuration settings passed from the config file.
* @param {Object} parserOptions The parserOptions settings passed from the config file.
* @param {Object} parserPath The parser setting passed from the config file.
* @param {Object} meta The metadata of the rule
*/ */
function RuleContext(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta) { class RuleContext {
// public. /**
this.id = ruleId; * @param {string} ruleId The ID of the rule using this object.
this.options = options; * @param {eslint} eslint The eslint object.
this.settings = settings; * @param {number} severity The configured severity level of the rule.
this.parserOptions = parserOptions; * @param {Array} options The configuration information to be added to the rule.
this.parserPath = parserPath; * @param {Object} settings The configuration settings passed from the config file.
this.meta = meta; * @param {Object} parserOptions The parserOptions settings passed from the config file.
* @param {Object} parserPath The parser setting passed from the config file.
* @param {Object} meta The metadata of the rule
* @param {Object} parserServices The parser services for the rule.
*/
constructor(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta, parserServices) {
// private. // public.
this.eslint = eslint; this.id = ruleId;
this.severity = severity; this.options = options;
this.settings = settings;
this.parserOptions = parserOptions;
this.parserPath = parserPath;
this.meta = meta;
Object.freeze(this); // create a separate copy and freeze it (it's not nice to freeze other people's objects)
} this.parserServices = Object.freeze(Object.assign({}, parserServices));
RuleContext.prototype = { // private.
constructor: RuleContext, this.eslint = eslint;
this.severity = severity;
Object.freeze(this);
}
/** /**
* Passthrough to eslint.getSourceCode(). * Passthrough to eslint.getSourceCode().
@ -98,7 +103,7 @@ RuleContext.prototype = {
*/ */
getSourceCode() { getSourceCode() {
return this.eslint.getSourceCode(); return this.eslint.getSourceCode();
}, }
/** /**
* Passthrough to eslint.report() that automatically assigns the rule ID and severity. * Passthrough to eslint.report() that automatically assigns the rule ID and severity.
@ -147,7 +152,7 @@ RuleContext.prototype = {
this.meta this.meta
); );
} }
}; }
// Copy over passthrough methods. All functions will have 5 or fewer parameters. // Copy over passthrough methods. All functions will have 5 or fewer parameters.
PASSTHROUGHS.forEach(function(name) { PASSTHROUGHS.forEach(function(name) {

20
tools/eslint/lib/rules.js

@ -40,7 +40,7 @@ function define(ruleId, ruleModule) {
function load(rulesDir, cwd) { function load(rulesDir, cwd) {
const newRules = loadRules(rulesDir, cwd); const newRules = loadRules(rulesDir, cwd);
Object.keys(newRules).forEach(function(ruleId) { Object.keys(newRules).forEach(ruleId => {
define(ruleId, newRules[ruleId]); define(ruleId, newRules[ruleId]);
}); });
} }
@ -53,7 +53,7 @@ function load(rulesDir, cwd) {
*/ */
function importPlugin(plugin, pluginName) { function importPlugin(plugin, pluginName) {
if (plugin.rules) { if (plugin.rules) {
Object.keys(plugin.rules).forEach(function(ruleId) { Object.keys(plugin.rules).forEach(ruleId => {
const qualifiedRuleId = `${pluginName}/${ruleId}`, const qualifiedRuleId = `${pluginName}/${ruleId}`,
rule = plugin.rules[ruleId]; rule = plugin.rules[ruleId];
@ -75,6 +75,21 @@ function getHandler(ruleId) {
} }
} }
/**
* Get an object with all currently loaded rules
* @returns {Map} All loaded rules
*/
function getAllLoadedRules() {
const allRules = new Map();
Object.keys(rules).forEach(name => {
const rule = getHandler(name);
allRules.set(name, rule);
});
return allRules;
}
/** /**
* Reset rules storage. * Reset rules storage.
* Should be used only in tests. * Should be used only in tests.
@ -89,6 +104,7 @@ module.exports = {
load, load,
importPlugin, importPlugin,
get: getHandler, get: getHandler,
getAllLoadedRules,
testClear, testClear,
/** /**

4
tools/eslint/lib/rules/accessor-pairs.js

@ -139,9 +139,9 @@ module.exports = {
} }
if (checkSetWithoutGet && isSetPresent && !isGetPresent) { if (checkSetWithoutGet && isSetPresent && !isGetPresent) {
context.report(node, "Getter is not present."); context.report({ node, message: "Getter is not present." });
} else if (checkGetWithoutSet && isGetPresent && !isSetPresent) { } else if (checkGetWithoutSet && isGetPresent && !isSetPresent) {
context.report(node, "Setter is not present."); context.report({ node, message: "Setter is not present." });
} }
} }

6
tools/eslint/lib/rules/array-bracket-spacing.js

@ -179,8 +179,10 @@ module.exports = {
const first = sourceCode.getFirstToken(node), const first = sourceCode.getFirstToken(node),
second = sourceCode.getFirstToken(node, 1), second = sourceCode.getFirstToken(node, 1),
penultimate = sourceCode.getLastToken(node, 1), last = node.typeAnnotation
last = sourceCode.getLastToken(node), ? sourceCode.getTokenBefore(node.typeAnnotation)
: sourceCode.getLastToken(node),
penultimate = sourceCode.getTokenBefore(last),
firstElement = node.elements[0], firstElement = node.elements[0],
lastElement = node.elements[node.elements.length - 1]; lastElement = node.elements[node.elements.length - 1];

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

@ -37,7 +37,7 @@ module.exports = {
{ {
type: "object", type: "object",
properties: { properties: {
requireReturnForObjectLiteral: {type: "boolean"} requireReturnForObjectLiteral: { type: "boolean" }
}, },
additionalProperties: false additionalProperties: false
} }
@ -46,7 +46,9 @@ module.exports = {
maxItems: 2 maxItems: 2
} }
] ]
} },
fixable: "code"
}, },
create(context) { create(context) {
@ -55,6 +57,7 @@ module.exports = {
const asNeeded = !options[0] || options[0] === "as-needed"; const asNeeded = !options[0] || options[0] === "as-needed";
const never = options[0] === "never"; const never = options[0] === "never";
const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral; const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
const sourceCode = context.getSourceCode();
/** /**
* Determines whether a arrow function body needs braces * Determines whether a arrow function body needs braces
@ -65,38 +68,85 @@ module.exports = {
const arrowBody = node.body; const arrowBody = node.body;
if (arrowBody.type === "BlockStatement") { if (arrowBody.type === "BlockStatement") {
if (never) { const blockBody = arrowBody.body;
if (blockBody.length !== 1 && !never) {
return;
}
if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" &&
blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") {
return;
}
if (never || asNeeded && blockBody[0].type === "ReturnStatement") {
context.report({ context.report({
node, node,
loc: arrowBody.loc.start, loc: arrowBody.loc.start,
message: "Unexpected block statement surrounding arrow body." message: "Unexpected block statement surrounding arrow body.",
fix(fixer) {
if (blockBody.length !== 1 || blockBody[0].type !== "ReturnStatement" || !blockBody[0].argument) {
return null;
}
const sourceText = sourceCode.getText();
const returnKeyword = sourceCode.getFirstToken(blockBody[0]);
const firstValueToken = sourceCode.getTokenAfter(returnKeyword);
let lastValueToken = sourceCode.getLastToken(blockBody[0]);
if (lastValueToken.type === "Punctuator" && lastValueToken.value === ";") {
/* 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
* has a semicolon).
*/
lastValueToken = sourceCode.getTokenBefore(lastValueToken);
}
const tokenAfterArrowBody = sourceCode.getTokenAfter(arrowBody);
if (tokenAfterArrowBody && tokenAfterArrowBody.type === "Punctuator" && /^[([/`+-]/.test(tokenAfterArrowBody.value)) {
// Don't do a fix if the next token would cause ASI issues when preceded by the returned value.
return null;
}
const textBeforeReturn = sourceText.slice(arrowBody.range[0] + 1, returnKeyword.range[0]);
const textBetweenReturnAndValue = sourceText.slice(returnKeyword.range[1], firstValueToken.range[0]);
const rawReturnValueText = sourceText.slice(firstValueToken.range[0], lastValueToken.range[1]);
const returnValueText = firstValueToken.value === "{" ? `(${rawReturnValueText})` : rawReturnValueText;
const textAfterValue = sourceText.slice(lastValueToken.range[1], blockBody[0].range[1] - 1);
const textAfterReturnStatement = sourceText.slice(blockBody[0].range[1], arrowBody.range[1] - 1);
/*
* For fixes that only contain spaces around the return value, remove the extra spaces.
* This avoids ugly fixes that end up with extra spaces after the arrow, e.g. `() => 0 ;`
*/
return fixer.replaceText(
arrowBody,
(textBeforeReturn + textBetweenReturnAndValue).replace(/^\s*$/, "") + returnValueText + (textAfterValue + textAfterReturnStatement).replace(/^\s*$/, "")
);
}
}); });
} else {
const blockBody = arrowBody.body;
if (blockBody.length !== 1) {
return;
}
if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" &&
blockBody[0].argument.type === "ObjectExpression") {
return;
}
if (asNeeded && blockBody[0].type === "ReturnStatement") {
context.report({
node,
loc: arrowBody.loc.start,
message: "Unexpected block statement surrounding arrow body."
});
}
} }
} else { } else {
if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) { if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
context.report({ context.report({
node, node,
loc: arrowBody.loc.start, loc: arrowBody.loc.start,
message: "Expected block statement surrounding arrow body." message: "Expected block statement surrounding arrow body.",
fix(fixer) {
const lastTokenBeforeBody = sourceCode.getTokensBetween(sourceCode.getFirstToken(node), arrowBody)
.reverse()
.find(token => token.value !== "(");
const firstBodyToken = sourceCode.getTokenAfter(lastTokenBeforeBody);
return fixer.replaceTextRange(
[firstBodyToken.range[0], node.range[1]],
`{return ${sourceCode.getText().slice(firstBodyToken.range[0], node.range[1])}}`
);
}
}); });
} }
} }

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

@ -58,7 +58,9 @@ module.exports = {
requireForBlockBody && requireForBlockBody &&
node.params.length === 1 && node.params.length === 1 &&
node.params[0].type === "Identifier" && node.params[0].type === "Identifier" &&
node.body.type !== "BlockStatement" !node.params[0].typeAnnotation &&
node.body.type !== "BlockStatement" &&
!node.returnType
) { ) {
if (token.type === "Punctuator" && token.value === "(") { if (token.type === "Punctuator" && token.value === "(") {
context.report({ context.report({
@ -95,7 +97,12 @@ module.exports = {
} }
// "as-needed": x => x // "as-needed": x => x
if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") { if (asNeeded &&
node.params.length === 1 &&
node.params[0].type === "Identifier" &&
!node.params[0].typeAnnotation &&
!node.returnType
) {
if (token.type === "Punctuator" && token.value === "(") { if (token.type === "Punctuator" && token.value === "(") {
context.report({ context.report({
node, node,

5
tools/eslint/lib/rules/block-scoped-var.js

@ -47,10 +47,7 @@ module.exports = {
function report(reference) { function report(reference) {
const identifier = reference.identifier; const identifier = reference.identifier;
context.report( context.report({ node: identifier, message: "'{{name}}' used outside of binding context.", data: { name: identifier.name } });
identifier,
"'{{name}}' used outside of binding context.",
{name: identifier.name});
} }
/** /**

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

@ -22,7 +22,7 @@ module.exports = {
fixable: "whitespace", fixable: "whitespace",
schema: [ schema: [
{enum: ["always", "never"]} { enum: ["always", "never"] }
] ]
}, },

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

@ -30,7 +30,9 @@ module.exports = {
}, },
additionalProperties: false additionalProperties: false
} }
] ],
fixable: "whitespace"
}, },
create(context) { create(context) {
@ -69,6 +71,28 @@ module.exports = {
return token.value === "{" || token.value === "}"; 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
* @returns {void}
*/
function reportExtraNewline(node, message, firstToken) {
context.report({
node,
message,
fix(fixer) {
const secondToken = sourceCode.getTokenAfter(firstToken);
const textBetween = sourceCode.getText().slice(firstToken.range[1], secondToken.range[0]);
const NEWLINE_REGEX = /\r\n|\r|\n|\u2028|\u2029/g;
// 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, ""));
}
});
}
/** /**
* Binds a list of properties to a function that verifies that the opening * Binds a list of properties to a function that verifies that the opening
* curly brace is on the same line as its controlling statement of a given * curly brace is on the same line as its controlling statement of a given
@ -81,7 +105,7 @@ module.exports = {
const blockProperties = arguments; const blockProperties = arguments;
return function(node) { return function(node) {
Array.prototype.forEach.call(blockProperties, function(blockProp) { Array.prototype.forEach.call(blockProperties, blockProp => {
const block = node[blockProp]; const block = node[blockProp];
if (!isBlock(block)) { if (!isBlock(block)) {
@ -98,9 +122,13 @@ module.exports = {
} }
if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) { if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) {
context.report(node, OPEN_MESSAGE); reportExtraNewline(node, OPEN_MESSAGE, previousToken);
} else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) { } else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) {
context.report(node, OPEN_MESSAGE_ALLMAN); context.report({
node,
message: OPEN_MESSAGE_ALLMAN,
fix: fixer => fixer.insertTextBefore(curlyToken, "\n")
});
} }
if (!block.body.length) { if (!block.body.length) {
@ -108,11 +136,19 @@ module.exports = {
} }
if (curlyToken.loc.start.line === block.body[0].loc.start.line) { if (curlyToken.loc.start.line === block.body[0].loc.start.line) {
context.report(block.body[0], BODY_MESSAGE); 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.start.line) { if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.end.line) {
context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE); context.report({
node: block.body[block.body.length - 1],
message: CLOSE_MESSAGE_SINGLE,
fix: fixer => fixer.insertTextBefore(curlyTokenEnd, "\n")
});
} }
}); });
}; };
@ -135,10 +171,14 @@ module.exports = {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line && if (tokens[0].loc.start.line !== tokens[1].loc.start.line &&
node.consequent.type === "BlockStatement" && node.consequent.type === "BlockStatement" &&
isCurlyPunctuator(tokens[0])) { isCurlyPunctuator(tokens[0])) {
context.report(node.alternate, CLOSE_MESSAGE); reportExtraNewline(node.alternate, CLOSE_MESSAGE, tokens[0]);
} }
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) { } else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node.alternate, CLOSE_MESSAGE_STROUSTRUP_ALLMAN); context.report({
node: node.alternate,
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
fix: fixer => fixer.insertTextAfter(tokens[0], "\n")
});
} }
} }
@ -158,10 +198,14 @@ module.exports = {
if (style === "1tbs") { if (style === "1tbs") {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) { if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node.finalizer, CLOSE_MESSAGE); reportExtraNewline(node.finalizer, CLOSE_MESSAGE, tokens[0]);
} }
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) { } else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node.finalizer, CLOSE_MESSAGE_STROUSTRUP_ALLMAN); context.report({
node: node.finalizer,
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
fix: fixer => fixer.insertTextAfter(tokens[0], "\n")
});
} }
} }
} }
@ -181,11 +225,15 @@ module.exports = {
if (isBlock(node.body)) { if (isBlock(node.body)) {
if (style === "1tbs") { if (style === "1tbs") {
if (previousToken.loc.start.line !== firstToken.loc.start.line) { if (previousToken.loc.start.line !== firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE); reportExtraNewline(node, CLOSE_MESSAGE, previousToken);
} }
} else { } else {
if (previousToken.loc.start.line === firstToken.loc.start.line) { if (previousToken.loc.start.line === firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE_STROUSTRUP_ALLMAN); context.report({
node,
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
fix: fixer => fixer.insertTextAfter(previousToken, "\n")
});
} }
} }
} }
@ -207,9 +255,13 @@ module.exports = {
} }
if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) { if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE); reportExtraNewline(node, OPEN_MESSAGE, tokens[0]);
} else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) { } else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE_ALLMAN); context.report({
node,
message: OPEN_MESSAGE_ALLMAN,
fix: fixer => fixer.insertTextBefore(tokens[1], "\n")
});
} }
} }

2
tools/eslint/lib/rules/callback-return.js

@ -164,7 +164,7 @@ module.exports = {
// as long as you're the child of a function at this point you should be asked to return // as long as you're the child of a function at this point you should be asked to return
if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) { if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) {
context.report(node, "Expected return with your callback function."); context.report({ node, message: "Expected return with your callback function." });
} }
} }

7
tools/eslint/lib/rules/camelcase.js

@ -38,6 +38,7 @@ module.exports = {
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
const reported = []; const reported = [];
const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
/** /**
* Checks if a string contains an underscore and isn't all upper-case * Checks if a string contains an underscore and isn't all upper-case
@ -60,7 +61,7 @@ module.exports = {
function report(node) { function report(node) {
if (reported.indexOf(node) < 0) { if (reported.indexOf(node) < 0) {
reported.push(node); reported.push(node);
context.report(node, "Identifier '{{name}}' is not in camel case.", { name: node.name }); context.report({ node, message: "Identifier '{{name}}' is not in camel case.", data: { name: node.name } });
} }
} }
@ -118,7 +119,7 @@ module.exports = {
return; return;
} }
if (isUnderscored(name) && effectiveParent.type !== "CallExpression") { if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
report(node); report(node);
} }
@ -131,7 +132,7 @@ module.exports = {
} }
// Report anything that is underscored that isn't a CallExpression // Report anything that is underscored that isn't a CallExpression
} else if (isUnderscored(name) && effectiveParent.type !== "CallExpression") { } else if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
report(node); report(node);
} }
} }

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

@ -0,0 +1,301 @@
/**
* @fileoverview enforce or disallow capitalization of the first letter of a comment
* @author Kevin Partington
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const LETTER_PATTERN = require("../util/patterns/letters");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const ALWAYS_MESSAGE = "Comments should not begin with a lowercase character",
NEVER_MESSAGE = "Comments should not begin with an uppercase character",
DEFAULT_IGNORE_PATTERN = /^\s*(?:eslint|istanbul|jscs|jshint|globals?|exported)\b/,
WHITESPACE = /\s/g,
MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern?
DEFAULTS = {
ignorePattern: null,
ignoreInlineComments: false,
ignoreConsecutiveComments: false
};
/*
* Base schema body for defining the basic capitalization rule, ignorePattern,
* and ignoreInlineComments values.
* This can be used in a few different ways in the actual schema.
*/
const SCHEMA_BODY = {
type: "object",
properties: {
ignorePattern: {
type: "string"
},
ignoreInlineComments: {
type: "boolean"
},
ignoreConsecutiveComments: {
type: "boolean"
}
},
additionalProperties: false
};
/**
* Get normalized options for either block or line comments from the given
* user-provided options.
* - If the user-provided options is just a string, returns a normalized
* set of options using default values for all other options.
* - If the user-provided options is an object, then a normalized option
* set is returned. Options specified in overrides will take priority
* over options specified in the main options object, which will in
* turn take priority over the rule's defaults.
*
* @param {Object|string} rawOptions The user-provided options.
* @param {string} which Either "line" or "block".
* @returns {Object} The normalized options.
*/
function getNormalizedOptions(rawOptions, which) {
if (!rawOptions) {
return Object.assign({}, DEFAULTS);
}
return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions);
}
/**
* Get normalized options for block and line comments.
*
* @param {Object|string} rawOptions The user-provided options.
* @returns {Object} An object with "Line" and "Block" keys and corresponding
* normalized options objects.
*/
function getAllNormalizedOptions(rawOptions) {
return {
Line: getNormalizedOptions(rawOptions, "line"),
Block: getNormalizedOptions(rawOptions, "block")
};
}
/**
* Creates a regular expression for each ignorePattern defined in the rule
* options.
*
* This is done in order to avoid invoking the RegExp constructor repeatedly.
*
* @param {Object} normalizedOptions The normalized rule options.
* @returns {void}
*/
function createRegExpForIgnorePatterns(normalizedOptions) {
Object.keys(normalizedOptions).forEach(key => {
const ignorePatternStr = normalizedOptions[key].ignorePattern;
if (ignorePatternStr) {
const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`);
normalizedOptions[key].ignorePatternRegExp = regExp;
}
});
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "enforce or disallow capitalization of the first letter of a comment",
category: "Stylistic Issues",
recommended: false
},
fixable: "code",
schema: [
{ enum: ["always", "never"] },
{
oneOf: [
SCHEMA_BODY,
{
type: "object",
properties: {
line: SCHEMA_BODY,
block: SCHEMA_BODY
},
additionalProperties: false
}
]
}
]
},
create(context) {
const capitalize = context.options[0] || "always",
normalizedOptions = getAllNormalizedOptions(context.options[1]),
sourceCode = context.getSourceCode();
createRegExpForIgnorePatterns(normalizedOptions);
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
/**
* Checks whether a comment is an inline comment.
*
* For the purpose of this rule, a comment is inline if:
* 1. The comment is preceded by a token on the same line; and
* 2. The command is followed by a token on the same line.
*
* Note that the comment itself need not be single-line!
*
* Also, it follows from this definition that only block comments can
* be considered as possibly inline. This is because line comments
* would consume any following tokens on the same line as the comment.
*
* @param {ASTNode} comment The comment node to check.
* @returns {boolean} True if the comment is an inline comment, false
* otherwise.
*/
function isInlineComment(comment) {
const previousToken = sourceCode.getTokenOrCommentBefore(comment),
nextToken = sourceCode.getTokenOrCommentAfter(comment);
return Boolean(
previousToken &&
nextToken &&
comment.loc.start.line === previousToken.loc.end.line &&
comment.loc.end.line === nextToken.loc.start.line
);
}
/**
* Determine if a comment follows another comment.
*
* @param {ASTNode} comment The comment to check.
* @returns {boolean} True if the comment follows a valid comment.
*/
function isConsecutiveComment(comment) {
const previousTokenOrComment = sourceCode.getTokenOrCommentBefore(comment);
return Boolean(
previousTokenOrComment &&
["Block", "Line"].indexOf(previousTokenOrComment.type) !== -1
);
}
/**
* Check a comment to determine if it is valid for this rule.
*
* @param {ASTNode} comment The comment node to process.
* @param {Object} options The options for checking this comment.
* @returns {boolean} True if the comment is valid, false otherwise.
*/
function isCommentValid(comment, options) {
// 1. Check for default ignore pattern.
if (DEFAULT_IGNORE_PATTERN.test(comment.value)) {
return true;
}
// 2. Check for custom ignore pattern.
const commentWithoutAsterisks = comment.value
.replace(/\*/g, "");
if (options.ignorePatternRegExp && options.ignorePatternRegExp.test(commentWithoutAsterisks)) {
return true;
}
// 3. Check for inline comments.
if (options.ignoreInlineComments && isInlineComment(comment)) {
return true;
}
// 4. Is this a consecutive comment (and are we tolerating those)?
if (options.ignoreConsecutiveComments && isConsecutiveComment(comment)) {
return true;
}
// 5. Does the comment start with a possible URL?
if (MAYBE_URL.test(commentWithoutAsterisks)) {
return true;
}
// 6. Is the initial word character a letter?
const commentWordCharsOnly = commentWithoutAsterisks
.replace(WHITESPACE, "");
if (commentWordCharsOnly.length === 0) {
return true;
}
const firstWordChar = commentWordCharsOnly[0];
if (!LETTER_PATTERN.test(firstWordChar)) {
return true;
}
// 7. Check the case of the initial word character.
const isUppercase = firstWordChar !== firstWordChar.toLocaleLowerCase(),
isLowercase = firstWordChar !== firstWordChar.toLocaleUpperCase();
if (capitalize === "always" && isLowercase) {
return false;
} else if (capitalize === "never" && isUppercase) {
return false;
}
return true;
}
/**
* Process a comment to determine if it needs to be reported.
*
* @param {ASTNode} comment The comment node to process.
* @returns {void}
*/
function processComment(comment) {
const options = normalizedOptions[comment.type],
commentValid = isCommentValid(comment, options);
if (!commentValid) {
const message = capitalize === "always" ?
ALWAYS_MESSAGE :
NEVER_MESSAGE;
context.report({
node: null, // Intentionally using loc instead
loc: comment.loc,
message,
fix(fixer) {
const match = comment.value.match(LETTER_PATTERN);
return fixer.replaceTextRange(
// Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*)
[comment.range[0] + match.index + 2, comment.range[0] + match.index + 3],
capitalize === "always" ? match[0].toLocaleUpperCase() : match[0].toLocaleLowerCase()
);
}
});
}
}
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
Program() {
const comments = sourceCode.getAllComments();
comments.forEach(processComment);
}
};
}
};

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

@ -112,11 +112,11 @@ module.exports = {
{ {
type: "object", type: "object",
properties: { properties: {
arrays: {$refs: "#/defs/valueWithIgnore"}, arrays: { $refs: "#/defs/valueWithIgnore" },
objects: {$refs: "#/defs/valueWithIgnore"}, objects: { $refs: "#/defs/valueWithIgnore" },
imports: {$refs: "#/defs/valueWithIgnore"}, imports: { $refs: "#/defs/valueWithIgnore" },
exports: {$refs: "#/defs/valueWithIgnore"}, exports: { $refs: "#/defs/valueWithIgnore" },
functions: {$refs: "#/defs/valueWithIgnore"} functions: { $refs: "#/defs/valueWithIgnore" }
}, },
additionalProperties: false additionalProperties: false
} }
@ -171,15 +171,10 @@ module.exports = {
function getTrailingToken(node, lastItem) { function getTrailingToken(node, lastItem) {
switch (node.type) { switch (node.type) {
case "ObjectExpression": case "ObjectExpression":
case "ObjectPattern":
case "ArrayExpression": case "ArrayExpression":
case "ArrayPattern":
case "CallExpression": case "CallExpression":
case "NewExpression": case "NewExpression":
return sourceCode.getLastToken(node, 1); return sourceCode.getLastToken(node, 1);
case "FunctionDeclaration":
case "FunctionExpression":
return sourceCode.getTokenBefore(node.body, 1);
default: { default: {
const nextToken = sourceCode.getTokenAfter(lastItem); const nextToken = sourceCode.getTokenAfter(lastItem);

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

@ -141,7 +141,7 @@ module.exports = {
function addNullElementsToIgnoreList(node) { function addNullElementsToIgnoreList(node) {
let previousToken = sourceCode.getFirstToken(node); let previousToken = sourceCode.getFirstToken(node);
node.elements.forEach(function(element) { node.elements.forEach(element => {
let token; let token;
if (element === null) { if (element === null) {
@ -164,7 +164,7 @@ module.exports = {
return { return {
"Program:exit"() { "Program:exit"() {
tokensAndComments.forEach(function(token, i) { tokensAndComments.forEach((token, i) => {
if (!isComma(token)) { if (!isComma(token)) {
return; return;

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

@ -41,10 +41,22 @@ module.exports = {
create(context) { create(context) {
const style = context.options[0] || "last", const style = context.options[0] || "last",
sourceCode = context.getSourceCode(); sourceCode = context.getSourceCode();
let exceptions = {}; const exceptions = {
ArrayPattern: true,
ArrowFunctionExpression: true,
CallExpression: true,
FunctionDeclaration: true,
FunctionExpression: true,
ImportDeclaration: true,
ObjectPattern: true,
};
if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) { if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
exceptions = context.options[1].exceptions; const keys = Object.keys(context.options[1].exceptions);
for (let i = 0; i < keys.length; i++) {
exceptions[keys[i]] = context.options[1].exceptions[keys[i]];
}
} }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -119,7 +131,7 @@ module.exports = {
if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) && if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
return; // do nothing.
} else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) && } else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
!astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { !astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
@ -166,14 +178,14 @@ module.exports = {
*/ */
function validateComma(node, property) { function validateComma(node, property) {
const items = node[property], const items = node[property],
arrayLiteral = (node.type === "ArrayExpression"); arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern");
if (items.length > 1 || arrayLiteral) { if (items.length > 1 || arrayLiteral) {
// seed as opening [ // seed as opening [
let previousItemToken = sourceCode.getFirstToken(node); let previousItemToken = sourceCode.getFirstToken(node);
items.forEach(function(item) { items.forEach(item => {
const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken, const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken,
currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken), currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken),
reportItem = item || currentItemToken, reportItem = item || currentItemToken,
@ -245,11 +257,46 @@ module.exports = {
validateComma(node, "properties"); validateComma(node, "properties");
}; };
} }
if (!exceptions.ObjectPattern) {
nodes.ObjectPattern = function(node) {
validateComma(node, "properties");
};
}
if (!exceptions.ArrayExpression) { if (!exceptions.ArrayExpression) {
nodes.ArrayExpression = function(node) { nodes.ArrayExpression = function(node) {
validateComma(node, "elements"); validateComma(node, "elements");
}; };
} }
if (!exceptions.ArrayPattern) {
nodes.ArrayPattern = function(node) {
validateComma(node, "elements");
};
}
if (!exceptions.FunctionDeclaration) {
nodes.FunctionDeclaration = function(node) {
validateComma(node, "params");
};
}
if (!exceptions.FunctionExpression) {
nodes.FunctionExpression = function(node) {
validateComma(node, "params");
};
}
if (!exceptions.ArrowFunctionExpression) {
nodes.ArrowFunctionExpression = function(node) {
validateComma(node, "params");
};
}
if (!exceptions.CallExpression) {
nodes.CallExpression = function(node) {
validateComma(node, "arguments");
};
}
if (!exceptions.ImportDeclaration) {
nodes.ImportDeclaration = function(node) {
validateComma(node, "specifiers");
};
}
return nodes; return nodes;
} }

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

@ -91,7 +91,7 @@ module.exports = {
} }
if (complexity > THRESHOLD) { if (complexity > THRESHOLD) {
context.report(node, "Function '{{name}}' has a complexity of {{complexity}}.", { name, complexity }); context.report({ node, message: "Function '{{name}}' has a complexity of {{complexity}}.", data: { name, complexity } });
} }
} }

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

@ -33,6 +33,18 @@ function isUnreachable(segment) {
return !segment.reachable; return !segment.reachable;
} }
/**
* Checks whether a given node is a `constructor` method in an ES6 class
* @param {ASTNode} node A node to check
* @returns {boolean} `true` if the node is a `constructor` method
*/
function isClassConstructor(node) {
return node.type === "FunctionExpression" &&
node.parent &&
node.parent.type === "MethodDefinition" &&
node.parent.kind === "constructor";
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -77,7 +89,8 @@ module.exports = {
*/ */
if (!funcInfo.hasReturnValue || if (!funcInfo.hasReturnValue ||
funcInfo.codePath.currentSegments.every(isUnreachable) || funcInfo.codePath.currentSegments.every(isUnreachable) ||
astUtils.isES5Constructor(node) astUtils.isES5Constructor(node) ||
isClassConstructor(node)
) { ) {
return; return;
} }
@ -86,7 +99,7 @@ module.exports = {
if (node.type === "Program") { if (node.type === "Program") {
// The head of program. // The head of program.
loc = {line: 1, column: 0}; loc = { line: 1, column: 0 };
type = "program"; type = "program";
} else if (node.type === "ArrowFunctionExpression") { } else if (node.type === "ArrowFunctionExpression") {
@ -113,7 +126,7 @@ module.exports = {
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 this {{type}}.",
data: {type} data: { type }
}); });
} }

21
tools/eslint/lib/rules/consistent-this.js

@ -43,9 +43,7 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function reportBadAssignment(node, alias) { function reportBadAssignment(node, alias) {
context.report(node, context.report({ node, message: "Designated alias '{{alias}}' is not assigned to 'this'.", data: { alias } });
"Designated alias '{{alias}}' is not assigned to 'this'.",
{ alias });
} }
/** /**
@ -64,8 +62,7 @@ module.exports = {
reportBadAssignment(node, name); reportBadAssignment(node, name);
} }
} else if (isThis) { } else if (isThis) {
context.report(node, context.report({ node, message: "Unexpected alias '{{name}}' for 'this'.", data: { name } });
"Unexpected alias '{{name}}' for 'this'.", { name });
} }
} }
@ -84,16 +81,14 @@ module.exports = {
return; return;
} }
if (variable.defs.some(function(def) { if (variable.defs.some(def => def.node.type === "VariableDeclarator" &&
return def.node.type === "VariableDeclarator" && def.node.init !== null)) {
def.node.init !== null;
})) {
return; return;
} }
// The alias has been declared and not assigned: check it was // The alias has been declared and not assigned: check it was
// assigned later in the same scope. // assigned later in the same scope.
if (!variable.references.some(function(reference) { if (!variable.references.some(reference => {
const write = reference.writeExpr; const write = reference.writeExpr;
return ( return (
@ -102,9 +97,7 @@ module.exports = {
write.parent.operator === "=" write.parent.operator === "="
); );
})) { })) {
variable.defs.map(function(def) { variable.defs.map(def => def.node).forEach(node => {
return def.node;
}).forEach(function(node) {
reportBadAssignment(node, alias); reportBadAssignment(node, alias);
}); });
} }
@ -117,7 +110,7 @@ module.exports = {
function ensureWasAssigned() { function ensureWasAssigned() {
const scope = context.getScope(); const scope = context.getScope();
aliases.forEach(function(alias) { aliases.forEach(alias => {
checkWasAssigned(alias, scope); checkWasAssigned(alias, scope);
}); });
} }

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

@ -261,8 +261,8 @@ module.exports = {
const isRealLoop = toSegment.prevSegments.length >= 2; const isRealLoop = toSegment.prevSegments.length >= 2;
funcInfo.codePath.traverseSegments( funcInfo.codePath.traverseSegments(
{first: toSegment, last: fromSegment}, { first: toSegment, last: fromSegment },
function(segment) { segment => {
const info = segInfoMap[segment.id]; const info = segInfoMap[segment.id];
const prevSegments = segment.prevSegments; const prevSegments = segment.prevSegments;

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

@ -74,10 +74,11 @@ module.exports = {
* @private * @private
*/ */
function isCollapsedOneLiner(node) { function isCollapsedOneLiner(node) {
const before = sourceCode.getTokenBefore(node), const before = sourceCode.getTokenBefore(node);
last = sourceCode.getLastToken(node); const last = sourceCode.getLastToken(node);
const lastExcludingSemicolon = last.type === "Punctuator" && last.value === ";" ? sourceCode.getTokenBefore(last) : last;
return before.loc.start.line === last.loc.end.line; return before.loc.start.line === lastExcludingSemicolon.loc.end.line;
} }
/** /**
@ -195,7 +196,7 @@ module.exports = {
return true; return true;
} }
if (/^[(\[\/`+-]/.test(tokenAfter.value)) { if (/^[([/`+-]/.test(tokenAfter.value)) {
// If the next token starts with a character that would disrupt ASI, insert a semicolon. // If the next token starts with a character that would disrupt ASI, insert a semicolon.
return true; return true;
@ -289,7 +290,9 @@ module.exports = {
} }
} else if (multiOrNest) { } else if (multiOrNest) {
if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) { if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) {
expected = false; const leadingComments = sourceCode.getComments(body.body[0]).leading;
expected = leadingComments.length > 0;
} else if (!isOneLiner(body)) { } else if (!isOneLiner(body)) {
expected = true; expected = true;
} }
@ -337,14 +340,14 @@ module.exports = {
* all have braces. * all have braces.
* If all nodes shouldn't have braces, make sure they don't. * If all nodes shouldn't have braces, make sure they don't.
*/ */
const expected = preparedChecks.some(function(preparedCheck) { const expected = preparedChecks.some(preparedCheck => {
if (preparedCheck.expected !== null) { if (preparedCheck.expected !== null) {
return preparedCheck.expected; return preparedCheck.expected;
} }
return preparedCheck.actual; return preparedCheck.actual;
}); });
preparedChecks.forEach(function(preparedCheck) { preparedChecks.forEach(preparedCheck => {
preparedCheck.expected = expected; preparedCheck.expected = expected;
}); });
} }
@ -359,7 +362,7 @@ module.exports = {
return { return {
IfStatement(node) { IfStatement(node) {
if (node.parent.type !== "IfStatement") { if (node.parent.type !== "IfStatement") {
prepareIfChecks(node).forEach(function(preparedCheck) { prepareIfChecks(node).forEach(preparedCheck => {
preparedCheck.check(); preparedCheck.check();
}); });
} }

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

@ -4,7 +4,7 @@
*/ */
"use strict"; "use strict";
const DEFAULT_COMMENT_PATTERN = /^no default$/; const DEFAULT_COMMENT_PATTERN = /^no default$/i;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
@ -67,9 +67,7 @@ module.exports = {
return; return;
} }
const hasDefault = node.cases.some(function(v) { const hasDefault = node.cases.some(v => v.test === null);
return v.test === null;
});
if (!hasDefault) { if (!hasDefault) {
@ -83,7 +81,7 @@ module.exports = {
} }
if (!comment || !commentPattern.test(comment.value.trim())) { if (!comment || !commentPattern.test(comment.value.trim())) {
context.report(node, "Expected a default case."); context.report({ node, message: "Expected a default case." });
} }
} }
} }

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

@ -47,7 +47,9 @@ module.exports = {
additionalItems: false additionalItems: false
} }
] ]
} },
fixable: "code"
}, },
create(context) { create(context) {
@ -112,22 +114,32 @@ module.exports = {
function getOperatorLocation(node) { function getOperatorLocation(node) {
const opToken = sourceCode.getTokenAfter(node.left); const opToken = sourceCode.getTokenAfter(node.left);
return {line: opToken.loc.start.line, column: opToken.loc.start.column}; return { line: opToken.loc.start.line, column: opToken.loc.start.column };
} }
/** /**
* Reports a message for this rule. * Reports a message for this rule.
* @param {ASTNode} node The binary expression node that was checked * @param {ASTNode} node The binary expression node that was checked
* @param {string} message The message to report * @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==')
* @returns {void} * @returns {void}
* @private * @private
*/ */
function report(node, message) { function report(node, expectedOperator) {
context.report({ context.report({
node, node,
loc: getOperatorLocation(node), loc: getOperatorLocation(node),
message, message: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'.",
data: { op: node.operator.charAt(0) } data: { expectedOperator, actualOperator: node.operator },
fix(fixer) {
// If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix.
if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {
const operatorToken = sourceCode.getTokensBetween(node.left, node.right).find(token => token.value === node.operator);
return fixer.replaceText(operatorToken, expectedOperator);
}
return null;
}
}); });
} }
@ -137,7 +149,7 @@ module.exports = {
if (node.operator !== "==" && node.operator !== "!=") { if (node.operator !== "==" && node.operator !== "!=") {
if (enforceInverseRuleForNull && isNull) { if (enforceInverseRuleForNull && isNull) {
report(node, "Expected '{{op}}=' and instead saw '{{op}}=='."); report(node, node.operator.slice(0, -1));
} }
return; return;
} }
@ -151,7 +163,7 @@ module.exports = {
return; return;
} }
report(node, "Expected '{{op}}==' and instead saw '{{op}}='."); report(node, `${node.operator}=`);
} }
}; };

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

@ -120,7 +120,14 @@ module.exports = {
loc: lastCalleeToken.loc.start, loc: lastCalleeToken.loc.start,
message: "Unexpected space between function name and paren.", message: "Unexpected space between function name and paren.",
fix(fixer) { fix(fixer) {
return fixer.removeRange([prevToken.range[1], parenToken.range[0]]);
// Only autofix if there is no newline
// https://github.com/eslint/eslint/issues/7787
if (!hasNewline) {
return fixer.removeRange([prevToken.range[1], parenToken.range[0]]);
}
return null;
} }
}); });
} else if (!never && !hasWhitespace) { } else if (!never && !hasWhitespace) {

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

@ -54,6 +54,17 @@ function isIdentifier(name, ecmaVersion) {
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const alwaysOrNever = { enum: ["always", "never"] };
const optionsObject = {
type: "object",
properties: {
includeCommonJSModuleExports: {
type: "boolean"
}
},
additionalProperties: false
};
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
@ -62,24 +73,35 @@ module.exports = {
recommended: false recommended: false
}, },
schema: [ schema: {
{ anyOf: [{
type: "object", type: "array",
properties: { additionalItems: false,
includeCommonJSModuleExports: { items: [alwaysOrNever, optionsObject]
type: "boolean" }, {
} type: "array",
}, additionalItems: false,
additionalProperties: false items: [optionsObject]
} }]
] }
}, },
create(context) { create(context) {
const options = (typeof context.options[0] === "object" ? context.options[0] : context.options[1]) || {};
const includeModuleExports = context.options[0] && context.options[0].includeCommonJSModuleExports; const nameMatches = typeof context.options[0] === "string" ? context.options[0] : "always";
const includeModuleExports = options.includeCommonJSModuleExports;
const ecmaVersion = context.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5; const ecmaVersion = context.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5;
/**
* Compares identifiers based on the nameMatches option
* @param {string} x the first identifier
* @param {string} y the second identifier
* @returns {boolean} whether the two identifiers should warn.
*/
function shouldWarn(x, y) {
return (nameMatches === "always" && x !== y) || (nameMatches === "never" && x === y);
}
/** /**
* Reports * Reports
* @param {ASTNode} node The node to report * @param {ASTNode} node The node to report
@ -89,10 +111,20 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function report(node, name, funcName, isProp) { function report(node, name, funcName, isProp) {
let message;
if (nameMatches === "always" && isProp) {
message = "Function name `{{funcName}}` should match property name `{{name}}`";
} else if (nameMatches === "always") {
message = "Function name `{{funcName}}` should match variable name `{{name}}`";
} else if (isProp) {
message = "Function name `{{funcName}}` should not match property name `{{name}}`";
} else {
message = "Function name `{{funcName}}` should not match variable name `{{name}}`";
}
context.report({ context.report({
node, node,
message: isProp ? "Function name `{{funcName}}` should match property name `{{name}}`" message,
: "Function name `{{funcName}}` should match variable name `{{name}}`",
data: { data: {
name, name,
funcName funcName
@ -110,7 +142,7 @@ module.exports = {
if (!node.init || node.init.type !== "FunctionExpression") { if (!node.init || node.init.type !== "FunctionExpression") {
return; return;
} }
if (node.init.id && node.id.name !== node.init.id.name) { if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) {
report(node, node.id.name, node.init.id.name, false); report(node, node.id.name, node.init.id.name, false);
} }
}, },
@ -126,7 +158,7 @@ module.exports = {
const isProp = node.left.type === "MemberExpression" ? true : false; const isProp = node.left.type === "MemberExpression" ? true : false;
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) && name !== node.right.id.name) { if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
report(node, name, node.right.id.name, isProp); report(node, name, node.right.id.name, isProp);
} }
}, },
@ -135,12 +167,13 @@ module.exports = {
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && node.key.type !== "Literal") { if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && node.key.type !== "Literal") {
return; return;
} }
if (node.key.type === "Identifier" && 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 (node.key.type === "Literal" && } else if (
isIdentifier(node.key.value, ecmaVersion) && node.key.type === "Literal" &&
node.key.value !== node.value.id.name isIdentifier(node.key.value, ecmaVersion) &&
) { shouldWarn(node.key.value, node.value.id.name)
) {
report(node, node.key.value, node.value.id.name, true); report(node, node.key.value, node.value.id.name, true);
} }
} }

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

@ -28,21 +28,23 @@ module.exports = {
schema: [ schema: [
{ {
enum: ["always", "never"] enum: ["always", "as-needed", "never"]
} }
] ]
}, },
create(context) { create(context) {
const never = context.options[0] === "never"; const never = context.options[0] === "never";
const asNeeded = context.options[0] === "as-needed";
/** /**
* Determines whether the current FunctionExpression node is a get, set, or * Determines whether the current FunctionExpression node is a get, set, or
* shorthand method in an object literal or a class. * shorthand method in an object literal or a class.
* @param {ASTNode} node - A node to check.
* @returns {boolean} True if the node is a get, set, or shorthand method. * @returns {boolean} True if the node is a get, set, or shorthand method.
*/ */
function isObjectOrClassMethod() { function isObjectOrClassMethod(node) {
const parent = context.getAncestors().pop(); const parent = node.parent;
return (parent.type === "MethodDefinition" || ( return (parent.type === "MethodDefinition" || (
parent.type === "Property" && ( parent.type === "Property" && (
@ -53,6 +55,23 @@ module.exports = {
)); ));
} }
/**
* Determines whether the current FunctionExpression node has a name that would be
* inferred from context in a conforming ES6 environment.
* @param {ASTNode} node - A node to check.
* @returns {boolean} True if the node would have a name assigned automatically.
*/
function hasInferredName(node) {
const parent = node.parent;
return isObjectOrClassMethod(node) ||
(parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) ||
(parent.type === "Property" && parent.value === node) ||
(parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) ||
(parent.type === "ExportDefaultDeclaration" && parent.declaration === node) ||
(parent.type === "AssignmentPattern" && parent.right === node);
}
return { return {
"FunctionExpression:exit"(node) { "FunctionExpression:exit"(node) {
@ -67,11 +86,11 @@ module.exports = {
if (never) { if (never) {
if (name) { if (name) {
context.report(node, "Unexpected function expression name."); context.report({ node, message: "Unexpected function expression name." });
} }
} else { } else {
if (!name && !isObjectOrClassMethod()) { if (!name && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
context.report(node, "Missing function expression name."); context.report({ node, message: "Missing function expression name." });
} }
} }
} }

6
tools/eslint/lib/rules/func-style.js

@ -44,7 +44,7 @@ module.exports = {
stack.push(false); stack.push(false);
if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") { if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") {
context.report(node, "Expected a function expression."); context.report({ node, message: "Expected a function expression." });
} }
}, },
"FunctionDeclaration:exit"() { "FunctionDeclaration:exit"() {
@ -55,7 +55,7 @@ module.exports = {
stack.push(false); stack.push(false);
if (enforceDeclarations && node.parent.type === "VariableDeclarator") { if (enforceDeclarations && node.parent.type === "VariableDeclarator") {
context.report(node.parent, "Expected a function declaration."); context.report({ node: node.parent, message: "Expected a function declaration." });
} }
}, },
"FunctionExpression:exit"() { "FunctionExpression:exit"() {
@ -78,7 +78,7 @@ module.exports = {
const hasThisExpr = stack.pop(); const hasThisExpr = stack.pop();
if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") { if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") {
context.report(node.parent, "Expected a function declaration."); context.report({ node: node.parent, message: "Expected a function declaration." });
} }
}; };
} }

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

@ -28,8 +28,8 @@ module.exports = {
{ {
type: "object", type: "object",
properties: { properties: {
before: {type: "boolean"}, before: { type: "boolean" },
after: {type: "boolean"} after: { type: "boolean" }
}, },
additionalProperties: false additionalProperties: false
} }

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

@ -23,10 +23,8 @@ const ACCEPTABLE_PARENTS = [
* @returns {Reference|null} Returns the found reference or null if none were found. * @returns {Reference|null} Returns the found reference or null if none were found.
*/ */
function findReference(scope, node) { function findReference(scope, node) {
const references = scope.references.filter(function(reference) { const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] &&
return reference.identifier.range[0] === node.range[0] && reference.identifier.range[1] === node.range[1]);
reference.identifier.range[1] === node.range[1];
});
/* istanbul ignore else: correctly returns null */ /* istanbul ignore else: correctly returns null */
if (references.length === 1) { if (references.length === 1) {
@ -65,12 +63,10 @@ module.exports = {
const currentScope = context.getScope(); const currentScope = context.getScope();
if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) {
const isGoodRequire = context.getAncestors().every(function(parent) { const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.indexOf(parent.type) > -1);
return ACCEPTABLE_PARENTS.indexOf(parent.type) > -1;
});
if (!isGoodRequire) { if (!isGoodRequire) {
context.report(node, "Unexpected require()."); context.report({ node, message: "Unexpected require()." });
} }
} }
} }

2
tools/eslint/lib/rules/guard-for-in.js

@ -33,7 +33,7 @@ module.exports = {
const body = node.body.type === "BlockStatement" ? node.body.body[0] : node.body; const body = node.body.type === "BlockStatement" ? node.body.body[0] : node.body;
if (body && body.type !== "IfStatement") { if (body && body.type !== "IfStatement") {
context.report(node, "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype."); context.report({ node, message: "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype." });
} }
} }
}; };

6
tools/eslint/lib/rules/handle-callback-err.js

@ -59,9 +59,7 @@ module.exports = {
* @returns {array} All parameters of the given scope. * @returns {array} All parameters of the given scope.
*/ */
function getParameters(scope) { function getParameters(scope) {
return scope.variables.filter(function(variable) { return scope.variables.filter(variable => variable.defs[0] && variable.defs[0].type === "Parameter");
return variable.defs[0] && variable.defs[0].type === "Parameter";
});
} }
/** /**
@ -76,7 +74,7 @@ module.exports = {
if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) { if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) {
if (firstParameter.references.length === 0) { if (firstParameter.references.length === 0) {
context.report(node, "Expected error to be handled."); context.report({ node, message: "Expected error to be handled." });
} }
} }
} }

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

@ -67,9 +67,9 @@ module.exports = {
* @private * @private
*/ */
function report(node) { function report(node) {
context.report(node, "Identifier '{{name}}' is blacklisted.", { context.report({ node, message: "Identifier '{{name}}' is blacklisted.", data: {
name: node.name name: node.name
}); } });
} }
return { return {

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

@ -50,7 +50,7 @@ module.exports = {
const maxLength = typeof options.max !== "undefined" ? options.max : Infinity; const maxLength = typeof options.max !== "undefined" ? options.max : Infinity;
const properties = options.properties !== "never"; const properties = options.properties !== "never";
const exceptions = (options.exceptions ? options.exceptions : []) const exceptions = (options.exceptions ? options.exceptions : [])
.reduce(function(obj, item) { .reduce((obj, item) => {
obj[item] = true; obj[item] = true;
return obj; return obj;
@ -102,13 +102,13 @@ module.exports = {
const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type]; const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) { if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) {
context.report( context.report({
node, node,
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}}).",
{ name, min: minLength, max: maxLength } data: { name, min: minLength, max: maxLength }
); });
} }
} }
}; };

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

@ -75,10 +75,10 @@ module.exports = {
* @private * @private
*/ */
function report(node) { function report(node) {
context.report(node, "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", { context.report({ node, message: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", data: {
name: node.name, name: node.name,
pattern pattern
}); } });
} }
return { return {

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

@ -113,6 +113,44 @@ module.exports = {
minimum: 0 minimum: 0
} }
} }
},
CallExpression: {
type: "object",
properties: {
parameters: {
oneOf: [
{
type: "integer",
minimum: 0
},
{
enum: ["first"]
}
]
}
}
},
ArrayExpression: {
oneOf: [
{
type: "integer",
minimum: 0
},
{
enum: ["first"]
}
]
},
ObjectExpression: {
oneOf: [
{
type: "integer",
minimum: 0
},
{
enum: ["first"]
}
]
} }
}, },
additionalProperties: false additionalProperties: false
@ -142,7 +180,12 @@ module.exports = {
FunctionExpression: { FunctionExpression: {
parameters: DEFAULT_PARAMETER_INDENT, parameters: DEFAULT_PARAMETER_INDENT,
body: DEFAULT_FUNCTION_BODY_INDENT body: DEFAULT_FUNCTION_BODY_INDENT
} },
CallExpression: {
arguments: DEFAULT_PARAMETER_INDENT
},
ArrayExpression: 1,
ObjectExpression: 1
}; };
const sourceCode = context.getSourceCode(); const sourceCode = context.getSourceCode();
@ -187,6 +230,18 @@ module.exports = {
if (typeof opts.FunctionExpression === "object") { if (typeof opts.FunctionExpression === "object") {
Object.assign(options.FunctionExpression, opts.FunctionExpression); Object.assign(options.FunctionExpression, opts.FunctionExpression);
} }
if (typeof opts.CallExpression === "object") {
Object.assign(options.CallExpression, opts.CallExpression);
}
if (typeof opts.ArrayExpression === "number" || typeof opts.ArrayExpression === "string") {
options.ArrayExpression = opts.ArrayExpression;
}
if (typeof opts.ObjectExpression === "number" || typeof opts.ObjectExpression === "string") {
options.ObjectExpression = opts.ObjectExpression;
}
} }
} }
@ -229,10 +284,10 @@ module.exports = {
* @param {int} gottenTabs Indentation tab count in the actual node/code * @param {int} gottenTabs Indentation tab count in the actual node/code
* @param {Object=} loc Error line and column location * @param {Object=} loc Error line and column location
* @param {boolean} isLastNodeCheck Is the error for last node check * @param {boolean} isLastNodeCheck Is the error for last node check
* @param {int} lastNodeCheckEndOffset Number of charecters to skip from the end
* @returns {void} * @returns {void}
*/ */
function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) { function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) {
if (gottenSpaces && gottenTabs) { if (gottenSpaces && gottenTabs) {
// To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs. // To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs.
@ -242,8 +297,8 @@ module.exports = {
const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed); const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed);
const textRange = isLastNodeCheck const textRange = isLastNodeCheck
? [node.range[1] - gottenSpaces - gottenTabs - 1, node.range[1] - 1] ? [node.range[1] - node.loc.end.column, node.range[1] - node.loc.end.column + gottenSpaces + gottenTabs]
: [node.range[0] - gottenSpaces - gottenTabs, node.range[0]]; : [node.range[0] - node.loc.start.column, node.range[0] - node.loc.start.column + gottenSpaces + gottenTabs];
context.report({ context.report({
node, node,
@ -319,6 +374,24 @@ module.exports = {
checkNodeIndent(node.alternate, neededIndent); checkNodeIndent(node.alternate, neededIndent);
} }
} }
if (node.type === "TryStatement" && node.handler) {
const catchToken = sourceCode.getFirstToken(node.handler);
checkNodeIndent(catchToken, neededIndent);
}
if (node.type === "TryStatement" && node.finalizer) {
const finallyToken = sourceCode.getTokenBefore(node.finalizer);
checkNodeIndent(finallyToken, neededIndent);
}
if (node.type === "DoWhileStatement") {
const whileToken = sourceCode.getTokenAfter(node.body);
checkNodeIndent(whileToken, neededIndent);
}
} }
/** /**
@ -354,6 +427,45 @@ module.exports = {
} }
} }
/**
* Check last node line indent this detects, that block closed correctly
* This function for more complicated return statement case, where closing parenthesis may be followed by ';'
* @param {ASTNode} node Node to examine
* @param {int} firstLineIndent first line needed indent
* @returns {void}
*/
function checkLastReturnStatementLineIndent(node, firstLineIndent) {
const nodeLastToken = sourceCode.getLastToken(node);
let lastToken = nodeLastToken;
// in case if return statement ends with ');' we have traverse back to ')'
// otherwise we'll measure indent for ';' and replace ')'
while (lastToken.value !== ")") {
lastToken = sourceCode.getTokenBefore(lastToken);
}
const textBeforeClosingParenthesis = sourceCode.getText(lastToken, lastToken.loc.start.column).slice(0, -1);
if (textBeforeClosingParenthesis.trim()) {
// There are tokens before the closing paren, don't report this case
return;
}
const endIndent = getNodeIndent(lastToken, true);
if (endIndent.goodChar !== firstLineIndent) {
report(
node,
firstLineIndent,
endIndent.space,
endIndent.tab,
{ line: lastToken.loc.start.line, column: lastToken.loc.start.column },
true
);
}
}
/** /**
* Check first node line indent is correct * Check first node line indent is correct
* @param {ASTNode} node Node to examine * @param {ASTNode} node Node to examine
@ -379,12 +491,17 @@ module.exports = {
* if not present then return null * if not present then return null
* @param {ASTNode} node node to examine * @param {ASTNode} node node to examine
* @param {string} type type that is being looked for * @param {string} type type that is being looked for
* @param {string} stopAtList end points for the evaluating code
* @returns {ASTNode|void} if found then node otherwise null * @returns {ASTNode|void} if found then node otherwise null
*/ */
function getParentNodeByType(node, type) { function getParentNodeByType(node, type, stopAtList) {
let parent = node.parent; let parent = node.parent;
while (parent.type !== type && parent.type !== "Program") { if (!stopAtList) {
stopAtList = ["Program"];
}
while (parent.type !== type && stopAtList.indexOf(parent.type) === -1 && parent.type !== "Program") {
parent = parent.parent; parent = parent.parent;
} }
@ -401,16 +518,6 @@ module.exports = {
return getParentNodeByType(node, "VariableDeclarator"); return getParentNodeByType(node, "VariableDeclarator");
} }
/**
* Returns the ExpressionStatement based on the current node
* if not present then return null
* @param {ASTNode} node node to examine
* @returns {ASTNode|void} if found then node otherwise null
*/
function getAssignmentExpressionNode(node) {
return getParentNodeByType(node, "AssignmentExpression");
}
/** /**
* Check to see if the node is part of the multi-line variable declaration. * Check to see if the node is part of the multi-line variable declaration.
* Also if its on the same line as the varNode * Also if its on the same line as the varNode
@ -604,14 +711,7 @@ module.exports = {
let elements = (node.type === "ArrayExpression") ? node.elements : node.properties; let elements = (node.type === "ArrayExpression") ? node.elements : node.properties;
// filter out empty elements example would be [ , 2] so remove first element as espree considers it as null // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null
elements = elements.filter(function(elem) { elements = elements.filter(elem => elem !== null);
return elem !== null;
});
// Skip if first element is in same line with this node
if (elements.length > 0 && elements[0].loc.start.line === node.loc.start.line) {
return;
}
let nodeIndent; let nodeIndent;
let elementsIndent; let elementsIndent;
@ -620,41 +720,59 @@ module.exports = {
// TODO - come up with a better strategy in future // TODO - come up with a better strategy in future
if (isNodeFirstInLine(node)) { if (isNodeFirstInLine(node)) {
const parent = node.parent; const parent = node.parent;
let effectiveParent = parent;
if (parent.type === "MemberExpression") { nodeIndent = getNodeIndent(parent).goodChar;
if (isNodeFirstInLine(parent)) { if (!parentVarNode || parentVarNode.loc.start.line !== node.loc.start.line) {
effectiveParent = parent.parent.parent;
} else {
effectiveParent = parent.parent;
}
}
nodeIndent = getNodeIndent(effectiveParent).goodChar;
if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) {
if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) { if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === effectiveParent.loc.start.line) { if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === parent.loc.start.line) {
nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]); nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
} else if ( } else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
parent.type === "ObjectExpression" || const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
parent.type === "ArrayExpression" ||
parent.type === "CallExpression" || if (parentElements[0].loc.start.line === parent.loc.start.line && parentElements[0].loc.end.line !== parent.loc.start.line) {
parent.type === "ArrowFunctionExpression" ||
parent.type === "NewExpression" || /*
parent.type === "LogicalExpression" * If the first element of the array spans multiple lines, don't increase the expected indentation of the rest.
) { * e.g. [{
nodeIndent = nodeIndent + indentSize; * foo: 1
* },
* {
* bar: 1
* }]
* the second object is not indented.
*/
} else if (typeof options[parent.type] === "number") {
nodeIndent += options[parent.type] * indentSize;
} else {
nodeIndent = parentElements[0].loc.start.column;
}
} else if (parent.type === "CallExpression" || parent.type === "NewExpression") {
if (typeof options.CallExpression.arguments === "number") {
nodeIndent += options.CallExpression.arguments * indentSize;
} else if (options.CallExpression.arguments === "first") {
if (parent.arguments.indexOf(node) !== -1) {
nodeIndent = parent.arguments[0].loc.start.column;
}
} else {
nodeIndent += indentSize;
}
} else if (parent.type === "LogicalExpression" || parent.type === "ArrowFunctionExpression") {
nodeIndent += indentSize;
} }
} }
} else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && effectiveParent.type !== "MemberExpression" && effectiveParent.type !== "ExpressionStatement" && effectiveParent.type !== "AssignmentExpression" && effectiveParent.type !== "Property") { } else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && parent.type !== "MemberExpression" && parent.type !== "ExpressionStatement" && parent.type !== "AssignmentExpression" && parent.type !== "Property") {
nodeIndent = nodeIndent + indentSize; nodeIndent = nodeIndent + indentSize;
} }
elementsIndent = nodeIndent + indentSize;
checkFirstNodeLineIndent(node, nodeIndent); checkFirstNodeLineIndent(node, nodeIndent);
} else { } else {
nodeIndent = getNodeIndent(node).goodChar; nodeIndent = getNodeIndent(node).goodChar;
elementsIndent = nodeIndent + indentSize; }
if (options[node.type] === "first") {
elementsIndent = elements.length ? elements[0].loc.start.column : 0; // If there are no elements, elementsIndent doesn't matter.
} else {
elementsIndent = nodeIndent + indentSize * options[node.type];
} }
/* /*
@ -675,7 +793,7 @@ module.exports = {
} }
} }
checkLastNodeLineIndent(node, elementsIndent - indentSize); checkLastNodeLineIndent(node, nodeIndent + (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
} }
/** /**
@ -717,11 +835,13 @@ module.exports = {
* not from the beginning of the block. * not from the beginning of the block.
*/ */
const statementsWithProperties = [ const statementsWithProperties = [
"IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration" "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration", "TryStatement"
]; ];
if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) { if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) {
indent = getNodeIndent(node.parent).goodChar; indent = getNodeIndent(node.parent).goodChar;
} else if (node.parent && node.parent.type === "CatchClause") {
indent = getNodeIndent(node.parent.parent).goodChar;
} else { } else {
indent = getNodeIndent(node).goodChar; indent = getNodeIndent(node).goodChar;
} }
@ -750,7 +870,7 @@ module.exports = {
* @returns {ASTNode[]} Filtered elements * @returns {ASTNode[]} Filtered elements
*/ */
function filterOutSameLineVars(node) { function filterOutSameLineVars(node) {
return node.declarations.reduce(function(finalCollection, elem) { return node.declarations.reduce((finalCollection, elem) => {
const lastElem = finalCollection[finalCollection.length - 1]; const lastElem = finalCollection[finalCollection.length - 1];
if ((elem.loc.start.line !== node.loc.start.line && !lastElem) || if ((elem.loc.start.line !== node.loc.start.line && !lastElem) ||
@ -832,6 +952,20 @@ module.exports = {
} }
} }
/**
* Checks wether a return statement is wrapped in ()
* @param {ASTNode} node node to examine
* @returns {boolean} the result
*/
function isWrappedInParenthesis(node) {
const regex = /^return\s*?\(\s*?\);*?/;
const statementWithoutArgument = sourceCode.getText(node).replace(
sourceCode.getText(node.argument), "");
return regex.test(statementWithoutArgument);
}
return { return {
Program(node) { Program(node) {
if (node.body.length > 0) { if (node.body.length > 0) {
@ -876,6 +1010,7 @@ module.exports = {
}, },
MemberExpression(node) { MemberExpression(node) {
if (typeof options.MemberExpression === "undefined") { if (typeof options.MemberExpression === "undefined") {
return; return;
} }
@ -888,11 +1023,11 @@ module.exports = {
// alter the expectation of correct indentation. Skip them. // alter the expectation of correct indentation. Skip them.
// TODO: Add appropriate configuration options for variable // TODO: Add appropriate configuration options for variable
// declarations and assignments. // declarations and assignments.
if (getVariableDeclaratorNode(node)) { if (getParentNodeByType(node, "VariableDeclarator", ["FunctionExpression", "ArrowFunctionExpression"])) {
return; return;
} }
if (getAssignmentExpressionNode(node)) { if (getParentNodeByType(node, "AssignmentExpression", ["FunctionExpression"])) {
return; return;
} }
@ -952,7 +1087,34 @@ module.exports = {
} else if (options.FunctionExpression.parameters !== null) { } else if (options.FunctionExpression.parameters !== null) {
checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionExpression.parameters); checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionExpression.parameters);
} }
},
ReturnStatement(node) {
if (isSingleLineNode(node)) {
return;
}
const firstLineIndent = getNodeIndent(node).goodChar;
// in case if return statement is wrapped in parenthesis
if (isWrappedInParenthesis(node)) {
checkLastReturnStatementLineIndent(node, firstLineIndent);
} else {
checkNodeIndent(node, firstLineIndent);
}
},
CallExpression(node) {
if (isSingleLineNode(node)) {
return;
}
if (options.CallExpression.arguments === "first" && node.arguments.length) {
checkNodesIndent(node.arguments.slice(1), node.arguments[0].loc.start.column);
} else if (options.CallExpression.arguments !== null) {
checkNodesIndent(node.arguments, getNodeIndent(node).goodChar + indentSize * options.CallExpression.arguments);
}
} }
}; };
} }

2
tools/eslint/lib/rules/jsx-quotes.js

@ -48,7 +48,7 @@ module.exports = {
schema: [ schema: [
{ {
enum: [ "prefer-single", "prefer-double" ] enum: ["prefer-single", "prefer-double"]
} }
] ]
}, },

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

@ -417,8 +417,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.getTokenBefore(nextColon), tokenBeforeColon = sourceCode.getTokenOrCommentBefore(nextColon),
tokenAfterColon = sourceCode.getTokenAfter(nextColon), tokenAfterColon = sourceCode.getTokenOrCommentAfter(nextColon),
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,
@ -514,7 +514,7 @@ module.exports = {
return [node.properties]; return [node.properties];
} }
return node.properties.reduce(function(groups, property) { return node.properties.reduce((groups, property) => {
const currentGroup = last(groups), const currentGroup = last(groups),
prev = last(currentGroup); prev = last(currentGroup);
@ -579,7 +579,7 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function verifyAlignment(node) { function verifyAlignment(node) {
createGroups(node).forEach(function(group) { createGroups(node).forEach(group => {
verifyGroupAlignment(group.filter(isKeyValueProperty)); verifyGroupAlignment(group.filter(isKeyValueProperty));
}); });
} }

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

@ -16,10 +16,10 @@ const astUtils = require("../ast-utils"),
// Constants // Constants
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const PREV_TOKEN = /^[\)\]\}>]$/; const PREV_TOKEN = /^[)\]}>]$/;
const NEXT_TOKEN = /^(?:[\(\[\{<~!]|\+\+?|--?)$/; const NEXT_TOKEN = /^(?:[([{<~!]|\+\+?|--?)$/;
const PREV_TOKEN_M = /^[\)\]\}>*]$/; const PREV_TOKEN_M = /^[)\]}>*]$/;
const NEXT_TOKEN_M = /^[\{*]$/; const NEXT_TOKEN_M = /^[{*]$/;
const TEMPLATE_OPEN_PAREN = /\$\{$/; const TEMPLATE_OPEN_PAREN = /\$\{$/;
const TEMPLATE_CLOSE_PAREN = /^\}/; const TEMPLATE_CLOSE_PAREN = /^\}/;
const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/; const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/;
@ -77,16 +77,16 @@ module.exports = {
{ {
type: "object", type: "object",
properties: { properties: {
before: {type: "boolean"}, before: { type: "boolean" },
after: {type: "boolean"}, after: { type: "boolean" },
overrides: { overrides: {
type: "object", type: "object",
properties: KEYS.reduce(function(retv, key) { properties: KEYS.reduce((retv, key) => {
retv[key] = { retv[key] = {
type: "object", type: "object",
properties: { properties: {
before: {type: "boolean"}, before: { type: "boolean" },
after: {type: "boolean"} after: { type: "boolean" }
}, },
additionalProperties: false additionalProperties: false
}; };

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

@ -21,16 +21,10 @@ const lodash = require("lodash"),
* @returns {Array} An array of line numbers. * @returns {Array} An array of line numbers.
*/ */
function getEmptyLineNums(lines) { function getEmptyLineNums(lines) {
const emptyLines = lines.map(function(line, i) { const emptyLines = lines.map((line, i) => ({
return { code: line.trim(),
code: line.trim(), num: i + 1
num: i + 1 })).filter(line => !line.code).map(line => line.num);
};
}).filter(function(line) {
return !line.code;
}).map(function(line) {
return line.num;
});
return emptyLines; return emptyLines;
} }
@ -43,7 +37,7 @@ function getEmptyLineNums(lines) {
function getCommentLineNums(comments) { function getCommentLineNums(comments) {
const lines = []; const lines = [];
comments.forEach(function(token) { comments.forEach(token => {
const start = token.loc.start.line; const start = token.loc.start.line;
const end = token.loc.end.line; const end = token.loc.end.line;

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

@ -63,15 +63,32 @@ module.exports = {
return node.loc.start.line - tokenLineBefore >= 2; return node.loc.start.line - tokenLineBefore >= 2;
} }
/**
* Gets the last token of a node that is on the same line as the rest of the node.
* This will usually be the last token of the node, but it will be the second-to-last token if the node has a trailing
* semicolon on a different line.
* @param {ASTNode} node A directive node
* @returns {Token} The last token of the node on the line
*/
function getLastTokenOnLine(node) {
const lastToken = sourceCode.getLastToken(node);
const secondToLastToken = sourceCode.getTokenBefore(lastToken);
return lastToken.type === "Punctuator" && lastToken.value === ";" && lastToken.loc.start.line > secondToLastToken.loc.end.line
? secondToLastToken
: lastToken;
}
/** /**
* Check if node is followed by a blank newline. * Check if node is followed by a blank newline.
* @param {ASTNode} node Node to check. * @param {ASTNode} node Node to check.
* @returns {boolean} Whether or not the passed in node is followed by a blank newline. * @returns {boolean} Whether or not the passed in node is followed by a blank newline.
*/ */
function hasNewlineAfter(node) { function hasNewlineAfter(node) {
const tokenAfter = sourceCode.getTokenOrCommentAfter(node); const lastToken = getLastTokenOnLine(node);
const tokenAfter = sourceCode.getTokenOrCommentAfter(lastToken);
return tokenAfter.loc.start.line - node.loc.end.line >= 2; return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2;
} }
/** /**
@ -91,10 +108,12 @@ module.exports = {
location location
}, },
fix(fixer) { fix(fixer) {
const lastToken = getLastTokenOnLine(node);
if (expected) { if (expected) {
return location === "before" ? fixer.insertTextBefore(node, "\n") : fixer.insertTextAfter(node, "\n"); return location === "before" ? fixer.insertTextBefore(node, "\n") : fixer.insertTextAfter(lastToken, "\n");
} }
return fixer.removeRange(location === "before" ? [node.range[0] - 1, node.range[0]] : [node.range[1], node.range[1] + 1]); return fixer.removeRange(location === "before" ? [node.range[0] - 1, node.range[0]] : [lastToken.range[1], lastToken.range[1] + 1]);
} }
}); });
} }

3
tools/eslint/lib/rules/max-depth.js

@ -91,8 +91,7 @@ module.exports = {
const len = ++functionStack[functionStack.length - 1]; const len = ++functionStack[functionStack.length - 1];
if (len > maxDepth) { if (len > maxDepth) {
context.report(node, "Blocks are nested too deeply ({{depth}}).", context.report({ node, message: "Blocks are nested too deeply ({{depth}}).", data: { depth: len } });
{ depth: len });
} }
} }

32
tools/eslint/lib/rules/max-len.js

@ -39,6 +39,9 @@ const OPTIONS_SCHEMA = {
ignoreTemplateLiterals: { ignoreTemplateLiterals: {
type: "boolean" type: "boolean"
}, },
ignoreRegExpLiterals: {
type: "boolean"
},
ignoreTrailingComments: { ignoreTrailingComments: {
type: "boolean" type: "boolean"
} }
@ -100,7 +103,7 @@ module.exports = {
function computeLineLength(line, tabWidth) { function computeLineLength(line, tabWidth) {
let extraCharacterCount = 0; let extraCharacterCount = 0;
line.replace(/\t/g, function(match, offset) { line.replace(/\t/g, (match, offset) => {
const totalOffset = offset + extraCharacterCount, const totalOffset = offset + extraCharacterCount,
previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0, previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0,
spaceCount = tabWidth - previousTabStopOffset; spaceCount = tabWidth - previousTabStopOffset;
@ -129,6 +132,7 @@ module.exports = {
ignoreComments = options.ignoreComments || false, ignoreComments = options.ignoreComments || false,
ignoreStrings = options.ignoreStrings || false, ignoreStrings = options.ignoreStrings || false,
ignoreTemplateLiterals = options.ignoreTemplateLiterals || false, ignoreTemplateLiterals = options.ignoreTemplateLiterals || false,
ignoreRegExpLiterals = options.ignoreRegExpLiterals || false,
ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false, ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false,
ignoreUrls = options.ignoreUrls || false, ignoreUrls = options.ignoreUrls || false,
maxCommentLength = options.comments; maxCommentLength = options.comments;
@ -209,9 +213,7 @@ module.exports = {
* @returns {ASTNode[]} An array of string nodes. * @returns {ASTNode[]} An array of string nodes.
*/ */
function getAllStrings() { function getAllStrings() {
return sourceCode.ast.tokens.filter(function(token) { return sourceCode.ast.tokens.filter(token => token.type === "String");
return token.type === "String";
});
} }
/** /**
@ -220,9 +222,17 @@ module.exports = {
* @returns {ASTNode[]} An array of template literal nodes. * @returns {ASTNode[]} An array of template literal nodes.
*/ */
function getAllTemplateLiterals() { function getAllTemplateLiterals() {
return sourceCode.ast.tokens.filter(function(token) { return sourceCode.ast.tokens.filter(token => token.type === "Template");
return token.type === "Template"; }
});
/**
* Retrieves an array containing all RegExp literals in the source code.
*
* @returns {ASTNode[]} An array of RegExp literal nodes.
*/
function getAllRegExpLiterals() {
return sourceCode.ast.tokens.filter(token => token.type === "RegularExpression");
} }
@ -264,7 +274,10 @@ module.exports = {
const templateLiterals = getAllTemplateLiterals(sourceCode); const templateLiterals = getAllTemplateLiterals(sourceCode);
const templateLiteralsByLine = templateLiterals.reduce(groupByLineNumber, {}); const templateLiteralsByLine = templateLiterals.reduce(groupByLineNumber, {});
lines.forEach(function(line, i) { const regExpLiterals = getAllRegExpLiterals(sourceCode);
const regExpLiteralsByLine = regExpLiterals.reduce(groupByLineNumber, {});
lines.forEach((line, i) => {
// i is zero-indexed, line numbers are one-indexed // i is zero-indexed, line numbers are one-indexed
const lineNumber = i + 1; const lineNumber = i + 1;
@ -299,7 +312,8 @@ module.exports = {
if (ignorePattern && ignorePattern.test(line) || if (ignorePattern && ignorePattern.test(line) ||
ignoreUrls && URL_REGEXP.test(line) || ignoreUrls && URL_REGEXP.test(line) ||
ignoreStrings && stringsByLine[lineNumber] || ignoreStrings && stringsByLine[lineNumber] ||
ignoreTemplateLiterals && templateLiteralsByLine[lineNumber] ignoreTemplateLiterals && templateLiteralsByLine[lineNumber] ||
ignoreRegExpLiterals && regExpLiteralsByLine[lineNumber]
) { ) {
// ignore this line // ignore this line

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

@ -114,26 +114,18 @@ module.exports = {
return { return {
"Program:exit"() { "Program:exit"() {
let lines = sourceCode.lines.map(function(text, i) { let lines = sourceCode.lines.map((text, i) => ({ lineNumber: i + 1, text }));
return { lineNumber: i + 1, text };
});
if (skipBlankLines) { if (skipBlankLines) {
lines = lines.filter(function(l) { lines = lines.filter(l => l.text.trim() !== "");
return l.text.trim() !== "";
});
} }
if (skipComments) { if (skipComments) {
const comments = sourceCode.getAllComments(); const comments = sourceCode.getAllComments();
const commentLines = lodash.flatten(comments.map(function(comment) { const commentLines = lodash.flatten(comments.map(comment => getLinesWithoutCode(comment)));
return getLinesWithoutCode(comment);
}));
lines = lines.filter(function(l) { lines = lines.filter(l => !lodash.includes(commentLines, l.lineNumber));
return !lodash.includes(commentLines, l.lineNumber);
});
} }
if (lines.length > max) { if (lines.length > max) {

4
tools/eslint/lib/rules/max-nested-callbacks.js

@ -81,9 +81,9 @@ module.exports = {
} }
if (callbackStack.length > THRESHOLD) { if (callbackStack.length > THRESHOLD) {
const opts = {num: callbackStack.length, max: THRESHOLD}; const opts = { num: callbackStack.length, max: THRESHOLD };
context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts); context.report({ node, message: "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", data: opts });
} }
} }

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

@ -66,10 +66,10 @@ module.exports = {
*/ */
function checkFunction(node) { function checkFunction(node) {
if (node.params.length > numParams) { if (node.params.length > numParams) {
context.report(node, "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", { context.report({ node, message: "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", data: {
count: node.params.length, count: node.params.length,
max: numParams max: numParams
}); } });
} }
} }

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

@ -84,10 +84,20 @@ module.exports = {
*/ */
function reportIfTooManyStatements(node, count, max) { function reportIfTooManyStatements(node, count, max) {
if (count > max) { if (count > max) {
context.report( const messageEnd = " has too many statements ({{count}}). Maximum allowed is {{max}}.";
let name = "This function";
if (node.id) {
name = `Function '${node.id.name}'`;
} else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") {
name = `Function '${context.getSource(node.parent.key)}'`;
}
context.report({
node, node,
"This function has too many statements ({{count}}). Maximum allowed is {{max}}.", message: name + messageEnd,
{ count, max }); data: { count, max }
});
} }
} }
@ -110,7 +120,7 @@ module.exports = {
const count = functionStack.pop(); const count = functionStack.pop();
if (ignoreTopLevelFunctions && functionStack.length === 0) { if (ignoreTopLevelFunctions && functionStack.length === 0) {
topLevelFunctions.push({ node, count}); topLevelFunctions.push({ node, count });
} else { } else {
reportIfTooManyStatements(node, count, maxStatements); reportIfTooManyStatements(node, count, maxStatements);
} }
@ -146,7 +156,7 @@ module.exports = {
return; return;
} }
topLevelFunctions.forEach(function(element) { topLevelFunctions.forEach(element => {
const count = element.count; const count = element.count;
const node = element.node; const node = element.node;

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

@ -227,7 +227,7 @@ module.exports = {
callee = callee.property; callee = callee.property;
} }
context.report(node, callee.loc.start, message); context.report({ node, loc: callee.loc.start, message });
} }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------

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

@ -29,20 +29,6 @@ function isClosingParen(token) {
return token.type === "Punctuator" && token.value === ")"; return token.type === "Punctuator" && token.value === ")";
} }
/**
* Checks whether the given node is inside of another given node.
*
* @param {ASTNode|Token} inner - The inner node to check.
* @param {ASTNode|Token} outer - The outer node to check.
* @returns {boolean} `true` if the `inner` is in `outer`.
*/
function isInRange(inner, outer) {
const ir = inner.range;
const or = outer.range;
return or[0] <= ir[0] && ir[1] <= or[1];
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -65,14 +51,15 @@ module.exports = {
return { return {
NewExpression(node) { NewExpression(node) {
let token = sourceCode.getTokenAfter(node.callee); if (node.arguments.length !== 0) {
return; // shortcut: if there are arguments, there have to be parens
// Skip ')'
while (token && isClosingParen(token)) {
token = sourceCode.getTokenAfter(token);
} }
if (!(token && isOpeningParen(token) && isInRange(token, node))) { const lastToken = sourceCode.getLastToken(node);
const hasLastParen = lastToken && isClosingParen(lastToken);
const hasParens = hasLastParen && isOpeningParen(sourceCode.getTokenBefore(lastToken));
if (!hasParens) {
context.report({ context.report({
node, node,
message: "Missing '()' invoking a constructor.", message: "Missing '()' invoking a constructor.",

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

@ -21,7 +21,9 @@ module.exports = {
{ {
enum: ["never", "always"] enum: ["never", "always"]
} }
] ],
fixable: "whitespace"
}, },
create(context) { create(context) {
@ -35,7 +37,7 @@ module.exports = {
const mode = context.options[0] === "never" ? "never" : "always"; const mode = context.options[0] === "never" ? "never" : "always";
// Cache starting and ending line numbers of comments for faster lookup // Cache starting and ending line numbers of comments for faster lookup
const commentEndLine = sourceCode.getAllComments().reduce(function(result, token) { const commentEndLine = sourceCode.getAllComments().reduce((result, token) => {
result[token.loc.start.line] = token.loc.end.line; result[token.loc.start.line] = token.loc.end.line;
return result; return result;
}, {}); }, {});
@ -119,6 +121,17 @@ module.exports = {
return !token || (token.type === "Punctuator" && token.value === "}"); return !token || (token.type === "Punctuator" && token.value === "}");
} }
/**
* Gets the last line of a group of consecutive comments
* @param {number} commentStartLine The starting line of the group
* @returns {number} The number of the last comment line of the group
*/
function getLastCommentLineOfBlock(commentStartLine) {
const currentCommentEnd = commentEndLine[commentStartLine];
return commentEndLine[currentCommentEnd + 1] ? getLastCommentLineOfBlock(currentCommentEnd + 1) : currentCommentEnd;
}
/** /**
* Determine if a token starts more than one line after a comment ends * Determine if a token starts more than one line after a comment ends
* @param {token} token The token being checked * @param {token} token The token being checked
@ -126,14 +139,7 @@ module.exports = {
* @returns {boolean} True if `token` does not start immediately after a comment * @returns {boolean} True if `token` does not start immediately after a comment
*/ */
function hasBlankLineAfterComment(token, commentStartLine) { function hasBlankLineAfterComment(token, commentStartLine) {
const commentEnd = commentEndLine[commentStartLine]; return token.loc.start.line > getLastCommentLineOfBlock(commentStartLine) + 1;
// If there's another comment, repeat check for blank line
if (commentEndLine[commentEnd + 1]) {
return hasBlankLineAfterComment(token, commentEnd + 1);
}
return (token.loc.start.line > commentEndLine[commentStartLine] + 1);
} }
/** /**
@ -145,8 +151,18 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function checkForBlankLine(node) { function checkForBlankLine(node) {
/*
* lastToken is the last token on the node's line. It will usually also be the last token of the node, but it will
* sometimes be second-last if there is a semicolon on a different line.
*/
const lastToken = getLastToken(node), const lastToken = getLastToken(node),
nextToken = sourceCode.getTokenAfter(node),
/*
* If lastToken is the last token of the node, nextToken should be the token after the node. Otherwise, nextToken
* is the last token of the node.
*/
nextToken = lastToken === sourceCode.getLastToken(node) ? sourceCode.getTokenAfter(node) : sourceCode.getLastToken(node),
nextLineNum = lastToken.loc.end.line + 1; nextLineNum = lastToken.loc.end.line + 1;
// Ignore if there is no following statement // Ignore if there is no following statement
@ -180,7 +196,17 @@ module.exports = {
const hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined"); const hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined");
if (mode === "never" && noNextLineToken && !hasNextLineComment) { if (mode === "never" && noNextLineToken && !hasNextLineComment) {
context.report(node, NEVER_MESSAGE, { identifier: node.name }); context.report({
node,
message: NEVER_MESSAGE,
data: { identifier: node.name },
fix(fixer) {
const NEWLINE_REGEX = /\r\n|\r|\n|\u2028|\u2029/;
const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(NEWLINE_REGEX);
return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween[linesBetween.length - 1]}`);
}
});
} }
// Token on the next line, or comment without blank line // Token on the next line, or comment without blank line
@ -190,7 +216,18 @@ module.exports = {
hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum) hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum)
) )
) { ) {
context.report(node, ALWAYS_MESSAGE, { identifier: node.name }); context.report({
node,
message: ALWAYS_MESSAGE,
data: { identifier: node.name },
fix(fixer) {
if ((noNextLineToken ? getLastCommentLineOfBlock(nextLineNum) : lastToken.loc.end.line) === nextToken.loc.start.line) {
return fixer.insertTextBefore(nextToken, "\n\n");
}
return fixer.insertTextBeforeRange([nextToken.range[0] - nextToken.loc.start.column, nextToken.range[1]], "\n");
}
});
} }
} }

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

@ -36,9 +36,7 @@ module.exports = {
function isPrecededByTokens(node, testTokens) { function isPrecededByTokens(node, testTokens) {
const tokenBefore = sourceCode.getTokenBefore(node); const tokenBefore = sourceCode.getTokenBefore(node);
return testTokens.some(function(token) { return testTokens.some(token => tokenBefore.value === token);
return tokenBefore.value === token;
});
} }
/** /**
@ -82,7 +80,7 @@ module.exports = {
return numLinesComments; return numLinesComments;
} }
comments.forEach(function(comment) { comments.forEach(comment => {
numLinesComments++; numLinesComments++;
if (comment.type === "Block") { if (comment.type === "Block") {

6
tools/eslint/lib/rules/no-alert.js

@ -41,10 +41,8 @@ function report(context, node, identifierName) {
* @returns {Reference|null} Returns the found reference or null if none were found. * @returns {Reference|null} Returns the found reference or null if none were found.
*/ */
function findReference(scope, node) { function findReference(scope, node) {
const references = scope.references.filter(function(reference) { const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] &&
return reference.identifier.range[0] === node.range[0] && reference.identifier.range[1] === node.range[1]);
reference.identifier.range[1] === node.range[1];
});
if (references.length === 1) { if (references.length === 1) {
return references[0]; return references[0];

2
tools/eslint/lib/rules/no-array-constructor.js

@ -34,7 +34,7 @@ module.exports = {
node.callee.type === "Identifier" && node.callee.type === "Identifier" &&
node.callee.name === "Array" node.callee.name === "Array"
) { ) {
context.report(node, "The array literal notation [] is preferrable."); context.report({ node, message: "The array literal notation [] is preferrable." });
} }
} }

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

@ -0,0 +1,75 @@
/**
* @fileoverview Rule to disallow uses of await inside of loops.
* @author Nat Mote (nmote)
*/
"use strict";
// Node types which are considered loops.
const loopTypes = new Set([
"ForStatement",
"ForOfStatement",
"ForInStatement",
"WhileStatement",
"DoWhileStatement",
]);
// Node types at which we should stop looking for loops. For example, it is fine to declare an async
// function within a loop, and use await inside of that.
const boundaryTypes = new Set([
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
]);
module.exports = {
meta: {
docs: {
description: "disallow `await` inside of loops",
category: "Possible Errors",
recommended: false,
},
schema: [],
},
create(context) {
return {
AwaitExpression(node) {
const ancestors = context.getAncestors();
// Reverse so that we can traverse from the deepest node upwards.
ancestors.reverse();
// Create a set of all the ancestors plus this node so that we can check
// if this use of await appears in the body of the loop as opposed to
// the right-hand side of a for...of, for example.
const ancestorSet = new Set(ancestors).add(node);
for (let i = 0; i < ancestors.length; i++) {
const ancestor = ancestors[i];
if (boundaryTypes.has(ancestor.type)) {
// Short-circuit out if we encounter a boundary type. Loops above
// this do not matter.
return;
}
if (loopTypes.has(ancestor.type)) {
// Only report if we are actually in the body or another part that gets executed on
// every iteration.
if (
ancestorSet.has(ancestor.body) ||
ancestorSet.has(ancestor.test) ||
ancestorSet.has(ancestor.update)
) {
context.report({
node,
message: "Unexpected `await` inside a loop."
});
return;
}
}
}
},
};
}
};

2
tools/eslint/lib/rules/no-bitwise.js

@ -57,7 +57,7 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function report(node) { function report(node) {
context.report(node, "Unexpected use of '{{operator}}'.", { operator: node.operator }); context.report({ node, message: "Unexpected use of '{{operator}}'.", data: { operator: node.operator } });
} }
/** /**

2
tools/eslint/lib/rules/no-caller.js

@ -29,7 +29,7 @@ module.exports = {
propertyName = node.property.name; propertyName = node.property.name;
if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) { if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) {
context.report(node, "Avoid arguments.{{property}}.", { property: propertyName }); context.report({ node, message: "Avoid arguments.{{property}}.", data: { property: propertyName } });
} }
} }

3
tools/eslint/lib/rules/no-catch-shadow.js

@ -58,8 +58,7 @@ module.exports = {
} }
if (paramIsShadowing(scope, node.param.name)) { if (paramIsShadowing(scope, node.param.name)) {
context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.", context.report({ node, message: "Value of '{{name}}' may be overwritten in IE 8 and earlier.", data: { name: node.param.name } });
{ name: node.param.name });
} }
} }
}; };

7
tools/eslint/lib/rules/no-class-assign.js

@ -30,11 +30,8 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function checkVariable(variable) { function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) { astUtils.getModifyingReferences(variable.references).forEach(reference => {
context.report( context.report({ node: reference.identifier, message: "'{{name}}' is a class.", data: { name: reference.identifier.name } });
reference.identifier,
"'{{name}}' is a class.",
{name: reference.identifier.name});
}); });
} }

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

@ -125,9 +125,9 @@ module.exports = {
const ancestor = findConditionalAncestor(node); const ancestor = findConditionalAncestor(node);
if (ancestor) { if (ancestor) {
context.report(ancestor, "Unexpected assignment within {{type}}.", { context.report({ node: ancestor, message: "Unexpected assignment within {{type}}.", data: {
type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
}); } });
} }
} }

4
tools/eslint/lib/rules/no-confusing-arrow.js

@ -36,7 +36,7 @@ module.exports = {
schema: [{ schema: [{
type: "object", type: "object",
properties: { properties: {
allowParens: {type: "boolean"} allowParens: { type: "boolean" }
}, },
additionalProperties: false additionalProperties: false
}] }]
@ -55,7 +55,7 @@ module.exports = {
const body = node.body; const body = node.body;
if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(sourceCode, body))) { if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(sourceCode, body))) {
context.report(node, "Arrow function used ambiguously with a conditional expression."); context.report({ node, message: "Arrow function used ambiguously with a conditional expression." });
} }
} }

7
tools/eslint/lib/rules/no-const-assign.js

@ -30,11 +30,8 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function checkVariable(variable) { function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) { astUtils.getModifyingReferences(variable.references).forEach(reference => {
context.report( context.report({ node: reference.identifier, message: "'{{name}}' is constant.", data: { name: reference.identifier.name } });
reference.identifier,
"'{{name}}' is constant.",
{name: reference.identifier.name});
}); });
} }

2
tools/eslint/lib/rules/no-constant-condition.js

@ -122,7 +122,7 @@ module.exports = {
*/ */
function checkConstantCondition(node) { function checkConstantCondition(node) {
if (node.test && isConstant(node.test, true)) { if (node.test && isConstant(node.test, true)) {
context.report(node, "Unexpected constant condition."); context.report({ node, message: "Unexpected constant condition." });
} }
} }

2
tools/eslint/lib/rules/no-continue.js

@ -24,7 +24,7 @@ module.exports = {
return { return {
ContinueStatement(node) { ContinueStatement(node) {
context.report(node, "Unexpected use of continue statement."); context.report({ node, message: "Unexpected use of continue statement." });
} }
}; };

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

Loading…
Cancel
Save