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. 28
      tools/eslint/lib/cli-engine.js
  8. 17
      tools/eslint/lib/code-path-analysis/code-path-analyzer.js
  9. 43
      tools/eslint/lib/code-path-analysis/code-path-segment.js
  10. 87
      tools/eslint/lib/code-path-analysis/code-path-state.js
  11. 41
      tools/eslint/lib/code-path-analysis/code-path.js
  12. 8
      tools/eslint/lib/code-path-analysis/debug-helpers.js
  13. 39
      tools/eslint/lib/code-path-analysis/fork-context.js
  14. 13
      tools/eslint/lib/code-path-analysis/id-generator.js
  15. 24
      tools/eslint/lib/config.js
  16. 50
      tools/eslint/lib/config/autoconfig.js
  17. 6
      tools/eslint/lib/config/config-file.js
  18. 58
      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. 90
      tools/eslint/lib/config/config-validator.js
  22. 6
      tools/eslint/lib/config/environments.js
  23. 98
      tools/eslint/lib/eslint.js
  24. 42
      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. 10
      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. 21
      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. 19
      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. 78
      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. 82
      tools/eslint/lib/rules/brace-style.js
  48. 2
      tools/eslint/lib/rules/callback-return.js
  49. 7
      tools/eslint/lib/rules/camelcase.js
  50. 301
      tools/eslint/lib/rules/capitalized-comments.js
  51. 5
      tools/eslint/lib/rules/comma-dangle.js
  52. 4
      tools/eslint/lib/rules/comma-spacing.js
  53. 57
      tools/eslint/lib/rules/comma-style.js
  54. 2
      tools/eslint/lib/rules/complexity.js
  55. 15
      tools/eslint/lib/rules/consistent-return.js
  56. 21
      tools/eslint/lib/rules/consistent-this.js
  57. 2
      tools/eslint/lib/rules/constructor-super.js
  58. 19
      tools/eslint/lib/rules/curly.js
  59. 8
      tools/eslint/lib/rules/default-case.js
  60. 26
      tools/eslint/lib/rules/eqeqeq.js
  61. 7
      tools/eslint/lib/rules/func-call-spacing.js
  62. 71
      tools/eslint/lib/rules/func-name-matching.js
  63. 31
      tools/eslint/lib/rules/func-names.js
  64. 6
      tools/eslint/lib/rules/func-style.js
  65. 12
      tools/eslint/lib/rules/global-require.js
  66. 2
      tools/eslint/lib/rules/guard-for-in.js
  67. 6
      tools/eslint/lib/rules/handle-callback-err.js
  68. 4
      tools/eslint/lib/rules/id-blacklist.js
  69. 10
      tools/eslint/lib/rules/id-length.js
  70. 4
      tools/eslint/lib/rules/id-match.js
  71. 266
      tools/eslint/lib/rules/indent.js
  72. 8
      tools/eslint/lib/rules/key-spacing.js
  73. 10
      tools/eslint/lib/rules/keyword-spacing.js
  74. 12
      tools/eslint/lib/rules/lines-around-comment.js
  75. 27
      tools/eslint/lib/rules/lines-around-directive.js
  76. 3
      tools/eslint/lib/rules/max-depth.js
  77. 32
      tools/eslint/lib/rules/max-len.js
  78. 16
      tools/eslint/lib/rules/max-lines.js
  79. 2
      tools/eslint/lib/rules/max-nested-callbacks.js
  80. 4
      tools/eslint/lib/rules/max-params.js
  81. 18
      tools/eslint/lib/rules/max-statements.js
  82. 2
      tools/eslint/lib/rules/new-cap.js
  83. 27
      tools/eslint/lib/rules/new-parens.js
  84. 63
      tools/eslint/lib/rules/newline-after-var.js
  85. 6
      tools/eslint/lib/rules/newline-before-return.js
  86. 6
      tools/eslint/lib/rules/no-alert.js
  87. 2
      tools/eslint/lib/rules/no-array-constructor.js
  88. 75
      tools/eslint/lib/rules/no-await-in-loop.js
  89. 2
      tools/eslint/lib/rules/no-bitwise.js
  90. 2
      tools/eslint/lib/rules/no-caller.js
  91. 3
      tools/eslint/lib/rules/no-catch-shadow.js
  92. 7
      tools/eslint/lib/rules/no-class-assign.js
  93. 4
      tools/eslint/lib/rules/no-cond-assign.js
  94. 2
      tools/eslint/lib/rules/no-confusing-arrow.js
  95. 7
      tools/eslint/lib/rules/no-const-assign.js
  96. 2
      tools/eslint/lib/rules/no-constant-condition.js
  97. 2
      tools/eslint/lib/rules/no-continue.js
  98. 4
      tools/eslint/lib/rules/no-control-regex.js
  99. 2
      tools/eslint/lib/rules/no-debugger.js
  100. 2
      tools/eslint/lib/rules/no-delete-var.js

6
.eslintrc

@ -84,7 +84,11 @@ rules:
eol-last: 2
func-call-spacing: 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}]
keyword-spacing: 2
linebreak-style: [2, unix]

2
tools/eslint/LICENSE

@ -1,5 +1,5 @@
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
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/) |
[Contributing](http://eslint.org/docs/developer-guide/contributing) |
[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) |
[Mailing List](https://groups.google.com/group/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.
Once a language feature has been adopted into the ECMAScript standard, 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.
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.
### Where to ask for help?

10
tools/eslint/bin/eslint.js

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

14
tools/eslint/conf/eslint.json

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

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

@ -10,6 +10,7 @@
//------------------------------------------------------------------------------
const esutils = require("esutils");
const lodash = require("lodash");
//------------------------------------------------------------------------------
// Helpers
@ -21,7 +22,7 @@ const arrayOrTypedArrayPattern = /Array$/;
const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/;
const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/;
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.
@ -83,6 +84,56 @@ function getUpperFunction(node) {
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`.
* @param {ASTNode} node - A node to check.
@ -176,9 +227,7 @@ function hasJSDocThisTag(node, sourceCode) {
// because callbacks don't have its JSDoc comment.
// e.g.
// sinon.test(/* @this sinon.Sandbox */function() { this.spy(); });
return sourceCode.getComments(node).leading.some(function(comment) {
return thisTagPattern.test(comment.value);
});
return sourceCode.getComments(node).leading.some(comment => thisTagPattern.test(comment.value));
}
/**
@ -197,6 +246,59 @@ function isParenthesised(sourceCode, node) {
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
//------------------------------------------------------------------------------
@ -218,6 +320,9 @@ module.exports = {
isCallee,
isES5Constructor,
getUpperFunction,
isFunction,
isLoop,
isInLoop,
isArrayFromMethod,
isParenthesised,
@ -583,35 +688,23 @@ module.exports = {
},
/**
* Checks whether a given node is a loop node or not.
* The following types are loop nodes:
*
* - DoWhileStatement
* - ForInStatement
* - ForOfStatement
* - ForStatement
* - WhileStatement
* Checks whether the given node is an empty block node or not.
*
* @param {ASTNode|null} node - A node to check.
* @returns {boolean} `true` if the node is a loop node.
* @param {ASTNode|null} node - The node to check.
* @returns {boolean} `true` if the node is an empty block.
*/
isLoop(node) {
return Boolean(node && anyLoopPattern.test(node.type));
isEmptyBlock(node) {
return Boolean(node && node.type === "BlockStatement" && node.body.length === 0);
},
/**
* Checks whether a given node is a function node or not.
* The following types are function nodes:
* Checks whether the given node is an empty function node or not.
*
* - ArrowFunctionExpression
* - FunctionDeclaration
* - FunctionExpression
*
* @param {ASTNode|null} node - A node to check.
* @returns {boolean} `true` if the node is a function node.
* @param {ASTNode|null} node - The node to check.
* @returns {boolean} `true` if the node is an empty function.
*/
isFunction(node) {
return Boolean(node && anyFunctionPattern.test(node.type));
isEmptyFunction(node) {
return isFunction(node) && module.exports.isEmptyBlock(node.body);
},
/**
@ -738,5 +831,271 @@ module.exports = {
*/
isDecimalInteger(node) {
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]);
}
};

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

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

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

@ -569,11 +569,13 @@ function postprocess(analyzer, node) {
/**
* The class to analyze code paths.
* This class implements the EventGenerator interface.
*
* @constructor
*/
class CodePathAnalyzer {
/**
* @param {EventGenerator} eventGenerator - An event generator to wrap.
*/
function CodePathAnalyzer(eventGenerator) {
constructor(eventGenerator) {
this.original = eventGenerator;
this.emitter = eventGenerator.emitter;
this.codePath = null;
@ -582,9 +584,6 @@ function CodePathAnalyzer(eventGenerator) {
this.onLooped = this.onLooped.bind(this);
}
CodePathAnalyzer.prototype = {
constructor: CodePathAnalyzer,
/**
* Does the process to enter a given AST node.
* This updates state of analysis and calls `enterNode` of the wrapped.
@ -608,7 +607,7 @@ CodePathAnalyzer.prototype = {
this.original.enterNode(node);
this.currentNode = null;
},
}
/**
* Does the process to leave a given AST node.
@ -631,7 +630,7 @@ CodePathAnalyzer.prototype = {
postprocess(this, node);
this.currentNode = null;
},
}
/**
* This is called on a code path looped.
@ -652,6 +651,6 @@ CodePathAnalyzer.prototype = {
);
}
}
};
}
module.exports = CodePathAnalyzer;

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

@ -68,14 +68,16 @@ function isReachable(segment) {
/**
* A code path segment.
*
* @constructor
*/
class CodePathSegment {
/**
* @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) {
constructor(id, allPrevSegments, reachable) {
/**
* The identifier of this code path.
@ -117,10 +119,12 @@ function CodePathSegment(id, allPrevSegments, reachable) {
this.reachable = reachable;
// Internal data.
Object.defineProperty(this, "internal", {value: {
Object.defineProperty(this, "internal", {
value: {
used: false,
loopedPrevSegments: []
}});
}
});
/* istanbul ignore if */
if (debug.enabled) {
@ -129,9 +133,6 @@ function CodePathSegment(id, allPrevSegments, reachable) {
}
}
CodePathSegment.prototype = {
constructor: CodePathSegment,
/**
* Checks a given previous segment is coming from the end of a loop.
*
@ -141,7 +142,6 @@ CodePathSegment.prototype = {
isLoopedPrevSegment(segment) {
return this.internal.loopedPrevSegments.indexOf(segment) !== -1;
}
};
/**
* Creates the root segment.
@ -149,9 +149,9 @@ CodePathSegment.prototype = {
* @param {string} id - An identifier.
* @returns {CodePathSegment} The created segment.
*/
CodePathSegment.newRoot = function(id) {
static newRoot(id) {
return new CodePathSegment(id, [], true);
};
}
/**
* Creates a segment that follows given segments.
@ -160,12 +160,12 @@ CodePathSegment.newRoot = function(id) {
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
* @returns {CodePathSegment} The created segment.
*/
CodePathSegment.newNext = function(id, allPrevSegments) {
static newNext(id, allPrevSegments) {
return new CodePathSegment(
id,
flattenUnusedSegments(allPrevSegments),
allPrevSegments.some(isReachable));
};
}
/**
* Creates an unreachable segment that follows given segments.
@ -174,7 +174,7 @@ CodePathSegment.newNext = function(id, allPrevSegments) {
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
* @returns {CodePathSegment} The created segment.
*/
CodePathSegment.newUnreachable = function(id, allPrevSegments) {
static newUnreachable(id, allPrevSegments) {
const segment = new CodePathSegment(id, flattenUnusedSegments(allPrevSegments), false);
// In `if (a) return a; foo();` case, the unreachable segment preceded by
@ -182,7 +182,7 @@ CodePathSegment.newUnreachable = function(id, allPrevSegments) {
CodePathSegment.markUsed(segment);
return segment;
};
}
/**
* Creates a segment that follows given segments.
@ -193,9 +193,9 @@ CodePathSegment.newUnreachable = function(id, allPrevSegments) {
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
* @returns {CodePathSegment} The created segment.
*/
CodePathSegment.newDisconnected = function(id, allPrevSegments) {
static newDisconnected(id, allPrevSegments) {
return new CodePathSegment(id, [], allPrevSegments.some(isReachable));
};
}
/**
* Makes a given segment being used.
@ -205,7 +205,7 @@ CodePathSegment.newDisconnected = function(id, allPrevSegments) {
* @param {CodePathSegment} segment - A segment to mark.
* @returns {void}
*/
CodePathSegment.markUsed = function(segment) {
static markUsed(segment) {
if (segment.internal.used) {
return;
}
@ -225,7 +225,7 @@ CodePathSegment.markUsed = function(segment) {
segment.allPrevSegments[i].allNextSegments.push(segment);
}
}
};
}
/**
* Marks a previous segment as looped.
@ -234,8 +234,9 @@ CodePathSegment.markUsed = function(segment) {
* @param {CodePathSegment} prevSegment - A previous segment to mark.
* @returns {void}
*/
CodePathSegment.markPrevSegmentAsLooped = function(segment, prevSegment) {
static markPrevSegmentAsLooped(segment, prevSegment) {
segment.internal.loopedPrevSegments.push(prevSegment);
};
}
}
module.exports = CodePathSegment;

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

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

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

@ -18,13 +18,15 @@ const IdGenerator = require("./id-generator");
/**
* A code path.
*
* @constructor
*/
class CodePath {
/**
* @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) {
constructor(id, upper, onLooped) {
/**
* The identifier of this code path.
@ -57,8 +59,15 @@ function CodePath(id, upper, onLooped) {
}
}
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.
@ -66,7 +75,7 @@ CodePath.prototype = {
*/
get initialSegment() {
return this.internal.initialSegment;
},
}
/**
* Final code path segments.
@ -75,7 +84,7 @@ CodePath.prototype = {
*/
get finalSegments() {
return this.internal.finalSegments;
},
}
/**
* Final code path segments which is with `return` statements.
@ -85,7 +94,7 @@ CodePath.prototype = {
*/
get returnedSegments() {
return this.internal.returnedForkContext;
},
}
/**
* Final code path segments which is with `throw` statements.
@ -93,7 +102,7 @@ CodePath.prototype = {
*/
get thrownSegments() {
return this.internal.thrownForkContext;
},
}
/**
* Current code path segments.
@ -101,7 +110,7 @@ CodePath.prototype = {
*/
get currentSegments() {
return this.internal.currentSegments;
},
}
/**
* 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;

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

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

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

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

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

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

24
tools/eslint/lib/config.js

@ -179,12 +179,15 @@ function getLocalConfig(thisConfig, directory) {
//------------------------------------------------------------------------------
/**
* Config
* @constructor
* @class Config
* Configuration class
*/
class Config {
/**
* Config options
* @param {Object} options Options to be passed in
*/
function Config(options) {
constructor(options) {
options = options || {};
this.ignore = options.ignore;
@ -197,7 +200,7 @@ function Config(options) {
this.useEslintrc = (options.useEslintrc !== false);
this.env = (options.envs || []).reduce(function(envs, name) {
this.env = (options.envs || []).reduce((envs, name) => {
envs[ name ] = true;
return envs;
}, {});
@ -208,7 +211,7 @@ function Config(options) {
* whether global is writable.
* If user declares "foo", convert to "foo:false".
*/
this.globals = (options.globals || []).reduce(function(globals, def) {
this.globals = (options.globals || []).reduce((globals, def) => {
const parts = def.split(":");
globals[parts[0]] = (parts.length > 1 && parts[1] === "true");
@ -236,7 +239,7 @@ function Config(options) {
* @param {string} filePath a file in whose directory we start looking for a local config
* @returns {Object} config object
*/
Config.prototype.getConfig = function(filePath) {
getConfig(filePath) {
const directory = filePath ? path.dirname(filePath) : this.options.cwd;
let config,
userConfig;
@ -314,20 +317,21 @@ Config.prototype.getConfig = function(filePath) {
this.cache[directory] = config;
return config;
};
}
/**
* Find local config files from directory and parent directories.
* @param {string} directory The directory to start searching from.
* @returns {string[]} The paths of local config files found.
*/
Config.prototype.findLocalConfigFiles = function(directory) {
findLocalConfigFiles(directory) {
if (!this.localConfigFinder) {
this.localConfigFinder = new FileFinder(ConfigFile.CONFIG_FILES, this.options.cwd);
}
return this.localConfigFinder.findAllInDirectoryAndParents(directory);
};
}
}
module.exports = Config;

50
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
*/
function makeRegistryItems(rulesConfig) {
return Object.keys(rulesConfig).reduce(function(accumulator, ruleId) {
accumulator[ruleId] = rulesConfig[ruleId].map(function(config) {
return {
return Object.keys(rulesConfig).reduce((accumulator, ruleId) => {
accumulator[ruleId] = rulesConfig[ruleId].map(config => ({
config,
specificity: config.length || 1,
errorCount: void 0
};
});
}));
return accumulator;
}, {});
}
@ -173,10 +171,8 @@ Registry.prototype = {
newRegistry = new Registry();
newRegistry.rules = Object.assign({}, this.rules);
ruleIds.forEach(function(ruleId) {
const errorFreeItems = newRegistry.rules[ruleId].filter(function(registryItem) {
return (registryItem.errorCount === 0);
});
ruleIds.forEach(ruleId => {
const errorFreeItems = newRegistry.rules[ruleId].filter(registryItem => (registryItem.errorCount === 0));
if (errorFreeItems.length > 0) {
newRegistry.rules[ruleId] = errorFreeItems;
@ -198,10 +194,8 @@ Registry.prototype = {
newRegistry = new Registry();
newRegistry.rules = Object.assign({}, this.rules);
ruleIds.forEach(function(ruleId) {
newRegistry.rules[ruleId] = newRegistry.rules[ruleId].filter(function(registryItem) {
return (typeof registryItem.errorCount !== "undefined");
});
ruleIds.forEach(ruleId => {
newRegistry.rules[ruleId] = newRegistry.rules[ruleId].filter(registryItem => (typeof registryItem.errorCount !== "undefined"));
});
return newRegistry;
@ -218,15 +212,13 @@ Registry.prototype = {
const ruleIds = Object.keys(this.rules),
failingRegistry = new Registry();
ruleIds.forEach(function(ruleId) {
const failingConfigs = this.rules[ruleId].filter(function(registryItem) {
return (registryItem.errorCount > 0);
});
ruleIds.forEach(ruleId => {
const failingConfigs = this.rules[ruleId].filter(registryItem => (registryItem.errorCount > 0));
if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) {
failingRegistry.rules[ruleId] = failingConfigs;
}
}.bind(this));
});
return failingRegistry;
},
@ -241,11 +233,11 @@ Registry.prototype = {
const ruleIds = Object.keys(this.rules),
config = { rules: {} };
ruleIds.forEach(function(ruleId) {
ruleIds.forEach(ruleId => {
if (this.rules[ruleId].length === 1) {
config.rules[ruleId] = this.rules[ruleId][0].config;
}
}.bind(this));
});
return config;
},
@ -261,11 +253,9 @@ Registry.prototype = {
newRegistry = new Registry();
newRegistry.rules = Object.assign({}, this.rules);
ruleIds.forEach(function(ruleId) {
newRegistry.rules[ruleId] = this.rules[ruleId].filter(function(registryItem) {
return (registryItem.specificity === specificity);
ruleIds.forEach(ruleId => {
newRegistry.rules[ruleId] = this.rules[ruleId].filter(registryItem => (registryItem.specificity === specificity));
});
}.bind(this));
return newRegistry;
},
@ -294,16 +284,16 @@ Registry.prototype = {
const filenames = Object.keys(sourceCodes);
const totalFilesLinting = filenames.length * ruleSets.length;
filenames.forEach(function(filename) {
filenames.forEach(filename => {
debug(`Linting file: ${filename}`);
ruleSetIdx = 0;
ruleSets.forEach(function(ruleSet) {
ruleSets.forEach(ruleSet => {
const lintConfig = Object.assign({}, config, { rules: ruleSet });
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
// in a linted file, in which case there may not be a config
@ -342,11 +332,9 @@ function extendFromRecommended(config) {
ConfigOps.normalizeToStrings(newConfig);
const recRules = Object.keys(recConfig.rules).filter(function(ruleId) {
return ConfigOps.isErrorSeverity(recConfig.rules[ruleId]);
});
const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId]));
recRules.forEach(function(ruleId) {
recRules.forEach(ruleId => {
if (lodash.isEqual(recConfig.rules[ruleId], newConfig.rules[ruleId])) {
delete newConfig.rules[ruleId];
}

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

@ -359,7 +359,7 @@ function applyExtends(config, filePath, relativeTo) {
}
// 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") {
@ -430,7 +430,7 @@ function normalizePackageName(name, prefix) {
* it's a scoped package
* package name is "eslint-config", or just a username
*/
const scopedPackageShortcutRegex = new RegExp(`^(@[^\/]+)(?:\/(?:${prefix})?)?$`),
const scopedPackageShortcutRegex = new RegExp(`^(@[^/]+)(?:/(?:${prefix})?)?$`),
scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`);
if (scopedPackageShortcutRegex.test(name)) {
@ -441,7 +441,7 @@ function normalizePackageName(name, prefix) {
* for scoped packages, insert the eslint-config after the first / unless
* 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) {
name = `${prefix}-${name}`;

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

@ -44,10 +44,14 @@ function writeFile(config, format) {
extname = ".json";
}
const installedESLint = config.installedESLint;
delete config.installedESLint;
ConfigFile.write(config, `./.eslintrc${extname}`);
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.");
}
}
@ -62,9 +66,7 @@ function installModules(config) {
// Create a list of modules which should be installed based on config
if (config.plugins) {
modules = modules.concat(config.plugins.map(function(name) {
return `eslint-plugin-${name}`;
}));
modules = modules.concat(config.plugins.map(name => `eslint-plugin-${name}`));
}
if (config.extends && config.extends.indexOf("eslint:") === -1) {
modules.push(`eslint-config-${config.extends}`);
@ -81,7 +83,7 @@ function installModules(config) {
const installStatus = npmUtil.checkDevDeps(modules);
// 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;
if (module === "eslint" && notInstalled) {
@ -128,7 +130,7 @@ function configureRules(answers, config) {
const patterns = answers.patterns.split(/[\s]+/);
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));
});
} catch (e) {
@ -147,20 +149,18 @@ function configureRules(answers, config) {
registry.populateFromCoreRules();
// 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
});
debug(`\nRegistry: ${util.inspect(registry.rules, { depth: null })}`);
// Create a list of recommended rules, because we don't want to disable them
const recRules = Object.keys(recConfig.rules).filter(function(ruleId) {
return ConfigOps.isErrorSeverity(recConfig.rules[ruleId]);
});
const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId]));
// Find and disable rules which had no error-free configuration
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
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
const finalRuleIds = Object.keys(newConfig.rules);
const totalRules = finalRuleIds.length;
const enabledRules = finalRuleIds.filter(function(ruleId) {
return (newConfig.rules[ruleId] !== 0);
}).length;
const enabledRules = finalRuleIds.filter(ruleId => (newConfig.rules[ruleId] !== 0)).length;
const resultMessage = [
`\nEnabled ${enabledRules} out of ${totalRules}`,
`rules based on ${fileQty}`,
@ -227,7 +225,7 @@ function processAnswers(answers) {
if (answers.commonjs) {
config.env.commonjs = true;
}
answers.env.forEach(function(env) {
answers.env.forEach(env => {
config.env[env] = true;
});
if (answers.jsx) {
@ -268,6 +266,7 @@ function getConfigForStyleGuide(guide) {
const guides = {
google: { extends: "google" },
airbnb: { extends: "airbnb", plugins: ["react", "jsx-a11y", "import"] },
"airbnb-base": { extends: "airbnb-base", plugins: ["import"] },
standard: { extends: "standard", plugins: ["standard", "promise"] }
};
@ -311,6 +310,15 @@ function promptUser(callback) {
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",
name: "patterns",
@ -335,7 +343,7 @@ function promptUser(callback) {
return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto");
}
}
], function(earlyAnswers) {
], earlyAnswers => {
// early exit if you are using a style 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.");
return;
}
if (earlyAnswers.styleguide === "airbnb" && !earlyAnswers.airbnbReact) {
earlyAnswers.styleguide = "airbnb-base";
}
try {
config = getConfigForStyleGuide(earlyAnswers.styleguide);
writeFile(config, earlyAnswers.format);
@ -384,9 +394,7 @@ function promptUser(callback) {
message: "Do you use CommonJS?",
default: false,
when(answers) {
return answers.env.some(function(env) {
return env === "browser";
});
return answers.env.some(env => env === "browser");
}
},
{
@ -398,13 +406,13 @@ function promptUser(callback) {
{
type: "confirm",
name: "react",
message: "Do you use React",
message: "Do you use React?",
default: false,
when(answers) {
return answers.jsx;
}
}
], function(secondAnswers) {
], secondAnswers => {
// early exit if you are using automatic style generation
if (earlyAnswers.source === "auto") {
@ -457,7 +465,7 @@ function promptUser(callback) {
default: "JavaScript",
choices: ["JavaScript", "YAML", "JSON"]
}
], function(answers) {
], answers => {
try {
const totalAnswers = Object.assign({}, earlyAnswers, secondAnswers, answers);
@ -465,10 +473,8 @@ function promptUser(callback) {
installModules(config);
writeFile(config, answers.format);
} catch (err) {
callback(err);
return;
callback(err); // eslint-disable-line callback-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"],
RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce(function(map, value, index) {
RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce((map, value, index) => {
map[value] = index;
return map;
}, {}),
@ -57,9 +57,7 @@ module.exports = {
envConfig.env = env;
Object.keys(env).filter(function(name) {
return env[name];
}).forEach(function(name) {
Object.keys(env).filter(name => env[name]).forEach(name => {
const environment = Environments.get(name);
if (environment) {
@ -149,7 +147,7 @@ module.exports = {
if (typeof src !== "object" && !Array.isArray(src)) {
src = [src];
}
Object.keys(src).forEach(function(e, i) {
Object.keys(src).forEach((e, i) => {
e = src[i];
if (typeof dst[i] === "undefined") {
dst[i] = e;
@ -171,11 +169,11 @@ module.exports = {
});
} else {
if (target && typeof target === "object") {
Object.keys(target).forEach(function(key) {
Object.keys(target).forEach(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])) {
dst[key] = deepmerge(target[key], src[key], key === "plugins", isRule);
} else if (typeof src[key] !== "object" || !src[key] || key === "exported" || key === "astGlobals") {
@ -199,7 +197,7 @@ module.exports = {
normalize(config) {
if (config.rules) {
Object.keys(config.rules).forEach(function(ruleId) {
Object.keys(config.rules).forEach(ruleId => {
const ruleConfig = config.rules[ruleId];
if (typeof ruleConfig === "string") {
@ -221,7 +219,7 @@ module.exports = {
normalizeToStrings(config) {
if (config.rules) {
Object.keys(config.rules).forEach(function(ruleId) {
Object.keys(config.rules).forEach(ruleId => {
const ruleConfig = config.rules[ruleId];
if (typeof ruleConfig === "number") {
@ -269,8 +267,6 @@ module.exports = {
* @returns {boolean} `true` if the configuration has valid severity.
*/
isEverySeverityValid(config) {
return Object.keys(config).every(function(ruleId) {
return this.isValidSeverity(config[ruleId]);
}, this);
return Object.keys(config).every(ruleId => this.isValidSeverity(config[ruleId]));
}
};

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

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

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

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

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

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

98
tools/eslint/lib/eslint.js

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

42
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
* @param {string[]} entries Array of directory entries.
@ -57,7 +41,7 @@ function FileFinder(files, cwd) {
function normalizeDirectoryEntries(entries, directory, supportedConfigs) {
const fileHash = {};
entries.forEach(function(entry) {
entries.forEach(entry => {
if (supportedConfigs.indexOf(entry) >= 0) {
const resolvedEntry = path.resolve(directory, entry);
@ -69,6 +53,25 @@ function normalizeDirectoryEntries(entries, directory, supportedConfigs) {
return fileHash;
}
//------------------------------------------------------------------------------
// API
//------------------------------------------------------------------------------
/**
* FileFinder class
*/
class FileFinder {
/**
* @param {string[]} files The basename(s) of the file(s) to find.
* @param {stirng} cwd Current working directory
*/
constructor(files, cwd) {
this.fileNames = Array.isArray(files) ? files : [files];
this.cwd = cwd || process.cwd();
this.cache = {};
}
/**
* Find all instances of files with the specified file names, in directory and
* parent directories. Cache the results.
@ -78,7 +81,7 @@ function normalizeDirectoryEntries(entries, directory, supportedConfigs) {
* @param {string} directory The directory to start the search from.
* @returns {string[]} The file paths found.
*/
FileFinder.prototype.findAllInDirectoryAndParents = function(directory) {
findAllInDirectoryAndParents(directory) {
const cache = this.cache;
if (directory) {
@ -132,6 +135,7 @@ FileFinder.prototype.findAllInDirectoryAndParents = function(directory) {
}
return cache[dirs[0]];
};
}
}
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 += "<checkstyle version=\"4.3\">";
results.forEach(function(result) {
results.forEach(result => {
const messages = result.messages;
output += `<file name="${xmlEscape(result.filePath)}">`;
messages.forEach(function(message) {
messages.forEach(message => {
output += [
`<error line="${xmlEscape(message.line)}"`,
`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 = "",
total = 0;
results.forEach(function(result) {
results.forEach(result => {
const messages = result.messages;
total += messages.length;
messages.forEach(function(message) {
messages.forEach(message => {
output += `${result.filePath}: `;
output += `line ${message.line || 0}`;

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

@ -70,7 +70,7 @@ function renderMessages(messages, parentIndex) {
* @param {Object} message 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 columnNumber = message.column || 0;
@ -91,15 +91,13 @@ function renderMessages(messages, parentIndex) {
* @returns {string} HTML string describing the results.
*/
function renderResults(results) {
return lodash.map(results, function(result, index) {
return resultTemplate({
return lodash.map(results, (result, index) => resultTemplate({
index,
color: renderColor(result.errorCount, result.warningCount),
filePath: result.filePath,
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;
// Iterate over results to get totals
results.forEach(function(result) {
results.forEach(result => {
totalErrors += result.errorCount;
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 += "<jslint>";
results.forEach(function(result) {
results.forEach(result => {
const messages = result.messages;
output += `<file name="${result.filePath}">`;
messages.forEach(function(message) {
messages.forEach(message => {
output += [
`<issue line="${message.line}"`,
`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 += "<testsuites>\n";
results.forEach(function(result) {
results.forEach(result => {
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`;
}
messages.forEach(function(message) {
messages.forEach(message => {
const type = message.fatal ? "error" : "failure";
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,
summaryColor = "yellow";
results.forEach(function(result) {
results.forEach(result => {
const messages = result.messages;
if (messages.length === 0) {
@ -44,7 +44,7 @@ module.exports = function(results) {
output += `${chalk.underline(result.filePath)}\n`;
output += `${table(
messages.map(function(message) {
messages.map(message => {
let messageType;
if (message.fatal || message.severity === 2) {
@ -71,11 +71,7 @@ module.exports = function(results) {
return chalk.stripColor(str).length;
}
}
).split("\n").map(function(el) {
return el.replace(/(\d+)\s+(\d+)/, function(m, p1, p2) {
return chalk.dim(`${p1}:${p2}`);
});
}).join("\n")}\n\n`;
).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
});
if (total > 0) {

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

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

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

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

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

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

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

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

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

@ -72,13 +72,14 @@ function mergeDefaultOptions(options) {
//------------------------------------------------------------------------------
/**
* IgnoredPaths
* @constructor
* @class IgnoredPaths
* @param {Object} options object containing 'ignore', 'ignorePath' and 'patterns' properties
* IgnoredPaths class
*/
function IgnoredPaths(options) {
class IgnoredPaths {
/**
* @param {Object} options object containing 'ignore', 'ignorePath' and 'patterns' properties
*/
constructor(options) {
options = mergeDefaultOptions(options);
/**
@ -166,7 +167,6 @@ function IgnoredPaths(options) {
}
this.options = options;
}
/**
@ -175,7 +175,7 @@ function IgnoredPaths(options) {
* @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) {
contains(filepath, category) {
let result = false;
const absolutePath = path.resolve(this.options.cwd, filepath);
@ -191,13 +191,13 @@ IgnoredPaths.prototype.contains = function(filepath, category) {
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.
*/
IgnoredPaths.prototype.getIgnoredFoldersGlobChecker = function() {
getIgnoredFoldersGlobChecker() {
const ig = ignore().add(DEFAULT_IGNORE_DIRS);
@ -226,6 +226,7 @@ IgnoredPaths.prototype.getIgnoredFoldersGlobChecker = function() {
return !filter(relative);
};
};
}
}
module.exports = IgnoredPaths;

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

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

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

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

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

@ -61,8 +61,12 @@ const PASSTHROUGHS = [
//------------------------------------------------------------------------------
/**
* Rule context class
* Acts as an abstraction layer between rules and the main eslint object.
* @constructor
*/
class RuleContext {
/**
* @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.
@ -71,8 +75,9 @@ const PASSTHROUGHS = [
* @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.
*/
function RuleContext(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta) {
constructor(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta, parserServices) {
// public.
this.id = ruleId;
@ -82,6 +87,9 @@ function RuleContext(ruleId, eslint, severity, options, settings, parserOptions,
this.parserPath = parserPath;
this.meta = meta;
// create a separate copy and freeze it (it's not nice to freeze other people's objects)
this.parserServices = Object.freeze(Object.assign({}, parserServices));
// private.
this.eslint = eslint;
this.severity = severity;
@ -89,16 +97,13 @@ function RuleContext(ruleId, eslint, severity, options, settings, parserOptions,
Object.freeze(this);
}
RuleContext.prototype = {
constructor: RuleContext,
/**
* Passthrough to eslint.getSourceCode().
* @returns {SourceCode} The SourceCode object for the code.
*/
getSourceCode() {
return this.eslint.getSourceCode();
},
}
/**
* Passthrough to eslint.report() that automatically assigns the rule ID and severity.
@ -147,7 +152,7 @@ RuleContext.prototype = {
this.meta
);
}
};
}
// Copy over passthrough methods. All functions will have 5 or fewer parameters.
PASSTHROUGHS.forEach(function(name) {

20
tools/eslint/lib/rules.js

@ -40,7 +40,7 @@ function define(ruleId, ruleModule) {
function load(rulesDir, cwd) {
const newRules = loadRules(rulesDir, cwd);
Object.keys(newRules).forEach(function(ruleId) {
Object.keys(newRules).forEach(ruleId => {
define(ruleId, newRules[ruleId]);
});
}
@ -53,7 +53,7 @@ function load(rulesDir, cwd) {
*/
function importPlugin(plugin, pluginName) {
if (plugin.rules) {
Object.keys(plugin.rules).forEach(function(ruleId) {
Object.keys(plugin.rules).forEach(ruleId => {
const qualifiedRuleId = `${pluginName}/${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.
* Should be used only in tests.
@ -89,6 +104,7 @@ module.exports = {
load,
importPlugin,
get: getHandler,
getAllLoadedRules,
testClear,
/**

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

@ -139,9 +139,9 @@ module.exports = {
}
if (checkSetWithoutGet && isSetPresent && !isGetPresent) {
context.report(node, "Getter is not present.");
context.report({ node, message: "Getter is not present." });
} 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),
second = sourceCode.getFirstToken(node, 1),
penultimate = sourceCode.getLastToken(node, 1),
last = sourceCode.getLastToken(node),
last = node.typeAnnotation
? sourceCode.getTokenBefore(node.typeAnnotation)
: sourceCode.getLastToken(node),
penultimate = sourceCode.getTokenBefore(last),
firstElement = node.elements[0],
lastElement = node.elements[node.elements.length - 1];

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

@ -46,7 +46,9 @@ module.exports = {
maxItems: 2
}
]
}
},
fixable: "code"
},
create(context) {
@ -55,6 +57,7 @@ module.exports = {
const asNeeded = !options[0] || options[0] === "as-needed";
const never = options[0] === "never";
const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
const sourceCode = context.getSourceCode();
/**
* Determines whether a arrow function body needs braces
@ -65,38 +68,85 @@ module.exports = {
const arrowBody = node.body;
if (arrowBody.type === "BlockStatement") {
if (never) {
context.report({
node,
loc: arrowBody.loc.start,
message: "Unexpected block statement surrounding arrow body."
});
} else {
const blockBody = arrowBody.body;
if (blockBody.length !== 1) {
if (blockBody.length !== 1 && !never) {
return;
}
if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" &&
blockBody[0].argument.type === "ObjectExpression") {
blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") {
return;
}
if (asNeeded && blockBody[0].type === "ReturnStatement") {
if (never || asNeeded && blockBody[0].type === "ReturnStatement") {
context.report({
node,
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 {
if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
context.report({
node,
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 &&
node.params.length === 1 &&
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 === "(") {
context.report({
@ -95,7 +97,12 @@ module.exports = {
}
// "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 === "(") {
context.report({
node,

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

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

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

@ -30,7 +30,9 @@ module.exports = {
},
additionalProperties: false
}
]
],
fixable: "whitespace"
},
create(context) {
@ -69,6 +71,28 @@ module.exports = {
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
* curly brace is on the same line as its controlling statement of a given
@ -81,7 +105,7 @@ module.exports = {
const blockProperties = arguments;
return function(node) {
Array.prototype.forEach.call(blockProperties, function(blockProp) {
Array.prototype.forEach.call(blockProperties, blockProp => {
const block = node[blockProp];
if (!isBlock(block)) {
@ -98,9 +122,13 @@ module.exports = {
}
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) {
context.report(node, OPEN_MESSAGE_ALLMAN);
context.report({
node,
message: OPEN_MESSAGE_ALLMAN,
fix: fixer => fixer.insertTextBefore(curlyToken, "\n")
});
}
if (!block.body.length) {
@ -108,11 +136,19 @@ module.exports = {
}
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) {
context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE);
if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.end.line) {
context.report({
node: block.body[block.body.length - 1],
message: CLOSE_MESSAGE_SINGLE,
fix: fixer => fixer.insertTextBefore(curlyTokenEnd, "\n")
});
}
});
};
@ -135,10 +171,14 @@ module.exports = {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line &&
node.consequent.type === "BlockStatement" &&
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) {
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 (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) {
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 (style === "1tbs") {
if (previousToken.loc.start.line !== firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE);
reportExtraNewline(node, CLOSE_MESSAGE, previousToken);
}
} else {
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) {
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) {
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
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
const reported = [];
const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
/**
* Checks if a string contains an underscore and isn't all upper-case
@ -60,7 +61,7 @@ module.exports = {
function report(node) {
if (reported.indexOf(node) < 0) {
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;
}
if (isUnderscored(name) && effectiveParent.type !== "CallExpression") {
if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
report(node);
}
@ -131,7 +132,7 @@ module.exports = {
}
// 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);
}
}

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);
}
};
}
};

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

@ -171,15 +171,10 @@ module.exports = {
function getTrailingToken(node, lastItem) {
switch (node.type) {
case "ObjectExpression":
case "ObjectPattern":
case "ArrayExpression":
case "ArrayPattern":
case "CallExpression":
case "NewExpression":
return sourceCode.getLastToken(node, 1);
case "FunctionDeclaration":
case "FunctionExpression":
return sourceCode.getTokenBefore(node.body, 1);
default: {
const nextToken = sourceCode.getTokenAfter(lastItem);

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

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

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

@ -41,10 +41,22 @@ module.exports = {
create(context) {
const style = context.options[0] || "last",
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")) {
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) &&
astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
return;
// do nothing.
} else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
!astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
@ -166,14 +178,14 @@ module.exports = {
*/
function validateComma(node, property) {
const items = node[property],
arrayLiteral = (node.type === "ArrayExpression");
arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern");
if (items.length > 1 || arrayLiteral) {
// seed as opening [
let previousItemToken = sourceCode.getFirstToken(node);
items.forEach(function(item) {
items.forEach(item => {
const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken,
currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken),
reportItem = item || currentItemToken,
@ -245,11 +257,46 @@ module.exports = {
validateComma(node, "properties");
};
}
if (!exceptions.ObjectPattern) {
nodes.ObjectPattern = function(node) {
validateComma(node, "properties");
};
}
if (!exceptions.ArrayExpression) {
nodes.ArrayExpression = function(node) {
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;
}

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

@ -91,7 +91,7 @@ module.exports = {
}
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 } });
}
}

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

@ -33,6 +33,18 @@ function isUnreachable(segment) {
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
//------------------------------------------------------------------------------
@ -77,7 +89,8 @@ module.exports = {
*/
if (!funcInfo.hasReturnValue ||
funcInfo.codePath.currentSegments.every(isUnreachable) ||
astUtils.isES5Constructor(node)
astUtils.isES5Constructor(node) ||
isClassConstructor(node)
) {
return;
}

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

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

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

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

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

@ -74,10 +74,11 @@ module.exports = {
* @private
*/
function isCollapsedOneLiner(node) {
const before = sourceCode.getTokenBefore(node),
last = sourceCode.getLastToken(node);
const before = sourceCode.getTokenBefore(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;
}
if (/^[(\[\/`+-]/.test(tokenAfter.value)) {
if (/^[([/`+-]/.test(tokenAfter.value)) {
// If the next token starts with a character that would disrupt ASI, insert a semicolon.
return true;
@ -289,7 +290,9 @@ module.exports = {
}
} else if (multiOrNest) {
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)) {
expected = true;
}
@ -337,14 +340,14 @@ module.exports = {
* all have braces.
* 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) {
return preparedCheck.expected;
}
return preparedCheck.actual;
});
preparedChecks.forEach(function(preparedCheck) {
preparedChecks.forEach(preparedCheck => {
preparedCheck.expected = expected;
});
}
@ -359,7 +362,7 @@ module.exports = {
return {
IfStatement(node) {
if (node.parent.type !== "IfStatement") {
prepareIfChecks(node).forEach(function(preparedCheck) {
prepareIfChecks(node).forEach(preparedCheck => {
preparedCheck.check();
});
}

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

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

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

@ -47,7 +47,9 @@ module.exports = {
additionalItems: false
}
]
}
},
fixable: "code"
},
create(context) {
@ -118,16 +120,26 @@ module.exports = {
/**
* Reports a message for this rule.
* @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}
* @private
*/
function report(node, message) {
function report(node, expectedOperator) {
context.report({
node,
loc: getOperatorLocation(node),
message,
data: { op: node.operator.charAt(0) }
message: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'.",
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 (enforceInverseRuleForNull && isNull) {
report(node, "Expected '{{op}}=' and instead saw '{{op}}=='.");
report(node, node.operator.slice(0, -1));
}
return;
}
@ -151,7 +163,7 @@ module.exports = {
return;
}
report(node, "Expected '{{op}}==' and instead saw '{{op}}='.");
report(node, `${node.operator}=`);
}
};

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

@ -120,8 +120,15 @@ module.exports = {
loc: lastCalleeToken.loc.start,
message: "Unexpected space between function name and paren.",
fix(fixer) {
// 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) {
context.report({

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

@ -54,6 +54,17 @@ function isIdentifier(name, ecmaVersion) {
// Rule Definition
//------------------------------------------------------------------------------
const alwaysOrNever = { enum: ["always", "never"] };
const optionsObject = {
type: "object",
properties: {
includeCommonJSModuleExports: {
type: "boolean"
}
},
additionalProperties: false
};
module.exports = {
meta: {
docs: {
@ -62,24 +73,35 @@ module.exports = {
recommended: false
},
schema: [
{
type: "object",
properties: {
includeCommonJSModuleExports: {
type: "boolean"
}
},
additionalProperties: false
schema: {
anyOf: [{
type: "array",
additionalItems: false,
items: [alwaysOrNever, optionsObject]
}, {
type: "array",
additionalItems: false,
items: [optionsObject]
}]
}
]
},
create(context) {
const includeModuleExports = context.options[0] && context.options[0].includeCommonJSModuleExports;
const options = (typeof context.options[0] === "object" ? context.options[0] : context.options[1]) || {};
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;
/**
* 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
* @param {ASTNode} node The node to report
@ -89,10 +111,20 @@ module.exports = {
* @returns {void}
*/
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({
node,
message: isProp ? "Function name `{{funcName}}` should match property name `{{name}}`"
: "Function name `{{funcName}}` should match variable name `{{name}}`",
message,
data: {
name,
funcName
@ -110,7 +142,7 @@ module.exports = {
if (!node.init || node.init.type !== "FunctionExpression") {
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);
}
},
@ -126,7 +158,7 @@ module.exports = {
const isProp = node.left.type === "MemberExpression" ? true : false;
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);
}
},
@ -135,11 +167,12 @@ module.exports = {
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && node.key.type !== "Literal") {
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);
} else if (node.key.type === "Literal" &&
} else if (
node.key.type === "Literal" &&
isIdentifier(node.key.value, ecmaVersion) &&
node.key.value !== node.value.id.name
shouldWarn(node.key.value, node.value.id.name)
) {
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: [
{
enum: ["always", "never"]
enum: ["always", "as-needed", "never"]
}
]
},
create(context) {
const never = context.options[0] === "never";
const asNeeded = context.options[0] === "as-needed";
/**
* Determines whether the current FunctionExpression node is a get, set, or
* 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.
*/
function isObjectOrClassMethod() {
const parent = context.getAncestors().pop();
function isObjectOrClassMethod(node) {
const parent = node.parent;
return (parent.type === "MethodDefinition" || (
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 {
"FunctionExpression:exit"(node) {
@ -67,11 +86,11 @@ module.exports = {
if (never) {
if (name) {
context.report(node, "Unexpected function expression name.");
context.report({ node, message: "Unexpected function expression name." });
}
} else {
if (!name && !isObjectOrClassMethod()) {
context.report(node, "Missing function expression name.");
if (!name && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
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);
if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") {
context.report(node, "Expected a function expression.");
context.report({ node, message: "Expected a function expression." });
}
},
"FunctionDeclaration:exit"() {
@ -55,7 +55,7 @@ module.exports = {
stack.push(false);
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"() {
@ -78,7 +78,7 @@ module.exports = {
const hasThisExpr = stack.pop();
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." });
}
};
}

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.
*/
function findReference(scope, node) {
const references = scope.references.filter(function(reference) {
return reference.identifier.range[0] === node.range[0] &&
reference.identifier.range[1] === node.range[1];
});
const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] &&
reference.identifier.range[1] === node.range[1]);
/* istanbul ignore else: correctly returns null */
if (references.length === 1) {
@ -65,12 +63,10 @@ module.exports = {
const currentScope = context.getScope();
if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) {
const isGoodRequire = context.getAncestors().every(function(parent) {
return ACCEPTABLE_PARENTS.indexOf(parent.type) > -1;
});
const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.indexOf(parent.type) > -1);
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;
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.
*/
function getParameters(scope) {
return scope.variables.filter(function(variable) {
return variable.defs[0] && variable.defs[0].type === "Parameter";
});
return scope.variables.filter(variable => variable.defs[0] && variable.defs[0].type === "Parameter");
}
/**
@ -76,7 +74,7 @@ module.exports = {
if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) {
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
*/
function report(node) {
context.report(node, "Identifier '{{name}}' is blacklisted.", {
context.report({ node, message: "Identifier '{{name}}' is blacklisted.", data: {
name: node.name
});
} });
}
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 properties = options.properties !== "never";
const exceptions = (options.exceptions ? options.exceptions : [])
.reduce(function(obj, item) {
.reduce((obj, item) => {
obj[item] = true;
return obj;
@ -102,13 +102,13 @@ module.exports = {
const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) {
context.report(
context.report({
node,
isShort ?
message: isShort ?
"Identifier name '{{name}}' is too short (< {{min}})." :
"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
*/
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,
pattern
});
} });
}
return {

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

@ -113,6 +113,44 @@ module.exports = {
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
@ -142,7 +180,12 @@ module.exports = {
FunctionExpression: {
parameters: DEFAULT_PARAMETER_INDENT,
body: DEFAULT_FUNCTION_BODY_INDENT
}
},
CallExpression: {
arguments: DEFAULT_PARAMETER_INDENT
},
ArrayExpression: 1,
ObjectExpression: 1
};
const sourceCode = context.getSourceCode();
@ -187,6 +230,18 @@ module.exports = {
if (typeof opts.FunctionExpression === "object") {
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 {Object=} loc Error line and column location
* @param {boolean} isLastNodeCheck Is the error for last node check
* @param {int} lastNodeCheckEndOffset Number of charecters to skip from the end
* @returns {void}
*/
function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) {
if (gottenSpaces && gottenTabs) {
// 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 textRange = isLastNodeCheck
? [node.range[1] - gottenSpaces - gottenTabs - 1, node.range[1] - 1]
: [node.range[0] - gottenSpaces - gottenTabs, node.range[0]];
? [node.range[1] - node.loc.end.column, node.range[1] - node.loc.end.column + gottenSpaces + gottenTabs]
: [node.range[0] - node.loc.start.column, node.range[0] - node.loc.start.column + gottenSpaces + gottenTabs];
context.report({
node,
@ -319,6 +374,24 @@ module.exports = {
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
* @param {ASTNode} node Node to examine
@ -379,12 +491,17 @@ module.exports = {
* if not present then return null
* @param {ASTNode} node node to examine
* @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
*/
function getParentNodeByType(node, type) {
function getParentNodeByType(node, type, stopAtList) {
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;
}
@ -401,16 +518,6 @@ module.exports = {
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.
* 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;
// filter out empty elements example would be [ , 2] so remove first element as espree considers it as null
elements = elements.filter(function(elem) {
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;
}
elements = elements.filter(elem => elem !== null);
let nodeIndent;
let elementsIndent;
@ -620,41 +720,59 @@ module.exports = {
// TODO - come up with a better strategy in future
if (isNodeFirstInLine(node)) {
const parent = node.parent;
let effectiveParent = parent;
if (parent.type === "MemberExpression") {
if (isNodeFirstInLine(parent)) {
effectiveParent = parent.parent.parent;
nodeIndent = getNodeIndent(parent).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.loc.start.line === parent.loc.start.line) {
nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
} else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
if (parentElements[0].loc.start.line === parent.loc.start.line && parentElements[0].loc.end.line !== parent.loc.start.line) {
/*
* If the first element of the array spans multiple lines, don't increase the expected indentation of the rest.
* e.g. [{
* foo: 1
* },
* {
* bar: 1
* }]
* the second object is not indented.
*/
} else if (typeof options[parent.type] === "number") {
nodeIndent += options[parent.type] * indentSize;
} else {
effectiveParent = parent.parent;
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;
}
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.loc.start.line === effectiveParent.loc.start.line) {
nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
} else if (
parent.type === "ObjectExpression" ||
parent.type === "ArrayExpression" ||
parent.type === "CallExpression" ||
parent.type === "ArrowFunctionExpression" ||
parent.type === "NewExpression" ||
parent.type === "LogicalExpression"
) {
nodeIndent = nodeIndent + indentSize;
} 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;
}
elementsIndent = nodeIndent + indentSize;
checkFirstNodeLineIndent(node, nodeIndent);
} else {
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.
*/
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)) {
indent = getNodeIndent(node.parent).goodChar;
} else if (node.parent && node.parent.type === "CatchClause") {
indent = getNodeIndent(node.parent.parent).goodChar;
} else {
indent = getNodeIndent(node).goodChar;
}
@ -750,7 +870,7 @@ module.exports = {
* @returns {ASTNode[]} Filtered elements
*/
function filterOutSameLineVars(node) {
return node.declarations.reduce(function(finalCollection, elem) {
return node.declarations.reduce((finalCollection, elem) => {
const lastElem = finalCollection[finalCollection.length - 1];
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 {
Program(node) {
if (node.body.length > 0) {
@ -876,6 +1010,7 @@ module.exports = {
},
MemberExpression(node) {
if (typeof options.MemberExpression === "undefined") {
return;
}
@ -888,11 +1023,11 @@ module.exports = {
// alter the expectation of correct indentation. Skip them.
// TODO: Add appropriate configuration options for variable
// declarations and assignments.
if (getVariableDeclaratorNode(node)) {
if (getParentNodeByType(node, "VariableDeclarator", ["FunctionExpression", "ArrowFunctionExpression"])) {
return;
}
if (getAssignmentExpressionNode(node)) {
if (getParentNodeByType(node, "AssignmentExpression", ["FunctionExpression"])) {
return;
}
@ -952,7 +1087,34 @@ module.exports = {
} else if (options.FunctionExpression.parameters !== null) {
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);
}
}
};
}

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

@ -417,8 +417,8 @@ module.exports = {
function report(property, side, whitespace, expected, mode) {
const diff = whitespace.length - expected,
nextColon = getNextColon(property.key),
tokenBeforeColon = sourceCode.getTokenBefore(nextColon),
tokenAfterColon = sourceCode.getTokenAfter(nextColon),
tokenBeforeColon = sourceCode.getTokenOrCommentBefore(nextColon),
tokenAfterColon = sourceCode.getTokenOrCommentAfter(nextColon),
isKeySide = side === "key",
locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
isExtra = diff > 0,
@ -514,7 +514,7 @@ module.exports = {
return [node.properties];
}
return node.properties.reduce(function(groups, property) {
return node.properties.reduce((groups, property) => {
const currentGroup = last(groups),
prev = last(currentGroup);
@ -579,7 +579,7 @@ module.exports = {
* @returns {void}
*/
function verifyAlignment(node) {
createGroups(node).forEach(function(group) {
createGroups(node).forEach(group => {
verifyGroupAlignment(group.filter(isKeyValueProperty));
});
}

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

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

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

@ -21,16 +21,10 @@ const lodash = require("lodash"),
* @returns {Array} An array of line numbers.
*/
function getEmptyLineNums(lines) {
const emptyLines = lines.map(function(line, i) {
return {
const emptyLines = lines.map((line, i) => ({
code: line.trim(),
num: i + 1
};
}).filter(function(line) {
return !line.code;
}).map(function(line) {
return line.num;
});
})).filter(line => !line.code).map(line => line.num);
return emptyLines;
}
@ -43,7 +37,7 @@ function getEmptyLineNums(lines) {
function getCommentLineNums(comments) {
const lines = [];
comments.forEach(function(token) {
comments.forEach(token => {
const start = token.loc.start.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;
}
/**
* 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.
* @param {ASTNode} node Node to check.
* @returns {boolean} Whether or not the passed in node is followed by a blank newline.
*/
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
},
fix(fixer) {
const lastToken = getLastTokenOnLine(node);
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];
if (len > maxDepth) {
context.report(node, "Blocks are nested too deeply ({{depth}}).",
{ depth: len });
context.report({ node, message: "Blocks are nested too deeply ({{depth}}).", data: { depth: len } });
}
}

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

@ -39,6 +39,9 @@ const OPTIONS_SCHEMA = {
ignoreTemplateLiterals: {
type: "boolean"
},
ignoreRegExpLiterals: {
type: "boolean"
},
ignoreTrailingComments: {
type: "boolean"
}
@ -100,7 +103,7 @@ module.exports = {
function computeLineLength(line, tabWidth) {
let extraCharacterCount = 0;
line.replace(/\t/g, function(match, offset) {
line.replace(/\t/g, (match, offset) => {
const totalOffset = offset + extraCharacterCount,
previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0,
spaceCount = tabWidth - previousTabStopOffset;
@ -129,6 +132,7 @@ module.exports = {
ignoreComments = options.ignoreComments || false,
ignoreStrings = options.ignoreStrings || false,
ignoreTemplateLiterals = options.ignoreTemplateLiterals || false,
ignoreRegExpLiterals = options.ignoreRegExpLiterals || false,
ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false,
ignoreUrls = options.ignoreUrls || false,
maxCommentLength = options.comments;
@ -209,9 +213,7 @@ module.exports = {
* @returns {ASTNode[]} An array of string nodes.
*/
function getAllStrings() {
return sourceCode.ast.tokens.filter(function(token) {
return token.type === "String";
});
return sourceCode.ast.tokens.filter(token => token.type === "String");
}
/**
@ -220,9 +222,17 @@ module.exports = {
* @returns {ASTNode[]} An array of template literal nodes.
*/
function getAllTemplateLiterals() {
return sourceCode.ast.tokens.filter(function(token) {
return token.type === "Template";
});
return sourceCode.ast.tokens.filter(token => 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 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
const lineNumber = i + 1;
@ -299,7 +312,8 @@ module.exports = {
if (ignorePattern && ignorePattern.test(line) ||
ignoreUrls && URL_REGEXP.test(line) ||
ignoreStrings && stringsByLine[lineNumber] ||
ignoreTemplateLiterals && templateLiteralsByLine[lineNumber]
ignoreTemplateLiterals && templateLiteralsByLine[lineNumber] ||
ignoreRegExpLiterals && regExpLiteralsByLine[lineNumber]
) {
// ignore this line

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

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

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

@ -83,7 +83,7 @@ module.exports = {
if (callbackStack.length > 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) {
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,
max: numParams
});
} });
}
}

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

@ -84,10 +84,20 @@ module.exports = {
*/
function reportIfTooManyStatements(node, 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,
"This function has too many statements ({{count}}). Maximum allowed is {{max}}.",
{ count, max });
message: name + messageEnd,
data: { count, max }
});
}
}
@ -146,7 +156,7 @@ module.exports = {
return;
}
topLevelFunctions.forEach(function(element) {
topLevelFunctions.forEach(element => {
const count = element.count;
const node = element.node;

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

@ -227,7 +227,7 @@ module.exports = {
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 === ")";
}
/**
* 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
//------------------------------------------------------------------------------
@ -65,14 +51,15 @@ module.exports = {
return {
NewExpression(node) {
let token = sourceCode.getTokenAfter(node.callee);
// Skip ')'
while (token && isClosingParen(token)) {
token = sourceCode.getTokenAfter(token);
if (node.arguments.length !== 0) {
return; // shortcut: if there are arguments, there have to be parens
}
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({
node,
message: "Missing '()' invoking a constructor.",

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

@ -21,7 +21,9 @@ module.exports = {
{
enum: ["never", "always"]
}
]
],
fixable: "whitespace"
},
create(context) {
@ -35,7 +37,7 @@ module.exports = {
const mode = context.options[0] === "never" ? "never" : "always";
// 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;
return result;
}, {});
@ -119,6 +121,17 @@ module.exports = {
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
* @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
*/
function hasBlankLineAfterComment(token, commentStartLine) {
const commentEnd = commentEndLine[commentStartLine];
// 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);
return token.loc.start.line > getLastCommentLineOfBlock(commentStartLine) + 1;
}
/**
@ -145,8 +151,18 @@ module.exports = {
* @returns {void}
*/
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),
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;
// Ignore if there is no following statement
@ -180,7 +196,17 @@ module.exports = {
const hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined");
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
@ -190,7 +216,18 @@ module.exports = {
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) {
const tokenBefore = sourceCode.getTokenBefore(node);
return testTokens.some(function(token) {
return tokenBefore.value === token;
});
return testTokens.some(token => tokenBefore.value === token);
}
/**
@ -82,7 +80,7 @@ module.exports = {
return numLinesComments;
}
comments.forEach(function(comment) {
comments.forEach(comment => {
numLinesComments++;
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.
*/
function findReference(scope, node) {
const references = scope.references.filter(function(reference) {
return reference.identifier.range[0] === node.range[0] &&
reference.identifier.range[1] === node.range[1];
});
const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] &&
reference.identifier.range[1] === node.range[1]);
if (references.length === 1) {
return references[0];

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

@ -34,7 +34,7 @@ module.exports = {
node.callee.type === "Identifier" &&
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}
*/
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;
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)) {
context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.",
{ name: node.param.name });
context.report({ node, message: "Value of '{{name}}' may be overwritten in IE 8 and earlier.", data: { name: node.param.name } });
}
}
};

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

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

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

@ -125,9 +125,9 @@ module.exports = {
const ancestor = findConditionalAncestor(node);
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
});
} });
}
}

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

@ -55,7 +55,7 @@ module.exports = {
const body = node.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}
*/
function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
context.report(
reference.identifier,
"'{{name}}' is constant.",
{name: reference.identifier.name});
astUtils.getModifyingReferences(variable.references).forEach(reference => {
context.report({ node: reference.identifier, message: "'{{name}}' is constant.", data: { name: reference.identifier.name } });
});
}

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

@ -122,7 +122,7 @@ module.exports = {
*/
function checkConstantCondition(node) {
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 {
ContinueStatement(node) {
context.report(node, "Unexpected use of continue statement.");
context.report({ node, message: "Unexpected use of continue statement." });
}
};

4
tools/eslint/lib/rules/no-control-regex.js

@ -85,7 +85,7 @@ module.exports = {
stringControlChars = regexStr.slice(subStrIndex, -1)
.split(consecutiveSlashes)
.filter(Boolean)
.map(function(x) {
.map(x => {
const match = x.match(stringControlCharWithoutSlash) || [x];
return `\\${match[0]}`;
@ -93,7 +93,7 @@ module.exports = {
}
}
return controlChars.map(function(x) {
return controlChars.map(x => {
const hexCode = `0${x.charCodeAt(0).toString(16)}`.slice(-2);
return `\\x${hexCode}`;

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

@ -24,7 +24,7 @@ module.exports = {
return {
DebuggerStatement(node) {
context.report(node, "Unexpected 'debugger' statement.");
context.report({ node, message: "Unexpected 'debugger' statement." });
}
};

2
tools/eslint/lib/rules/no-delete-var.js

@ -26,7 +26,7 @@ module.exports = {
UnaryExpression(node) {
if (node.operator === "delete" && node.argument.type === "Identifier") {
context.report(node, "Variables should not be deleted.");
context.report({ node, message: "Variables should not be deleted." });
}
}
};

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

Loading…
Cancel
Save