Browse Source

tools: update ESLint to current version

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

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

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

6
.eslintrc

@ -84,7 +84,11 @@ rules:
eol-last: 2
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:
* Checks whether the given node is an empty block node or not.
*
* - DoWhileStatement
* - ForInStatement
* - ForOfStatement
* - ForStatement
* - WhileStatement
*
* @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]);
}
};

30
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.";
}
@ -442,7 +442,7 @@ function CLIEngine(options) {
options = Object.assign(
Object.create(null),
defaultOptions,
{cwd: process.cwd()},
{ cwd: process.cwd() },
options
);
@ -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;

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

@ -569,21 +569,20 @@ function postprocess(analyzer, node) {
/**
* The class to analyze code paths.
* This class implements the EventGenerator interface.
*
* @constructor
* @param {EventGenerator} eventGenerator - An event generator to wrap.
*/
function CodePathAnalyzer(eventGenerator) {
this.original = eventGenerator;
this.emitter = eventGenerator.emitter;
this.codePath = null;
this.idGenerator = new IdGenerator("s");
this.currentNode = null;
this.onLooped = this.onLooped.bind(this);
}
class CodePathAnalyzer {
CodePathAnalyzer.prototype = {
constructor: CodePathAnalyzer,
/**
* @param {EventGenerator} eventGenerator - An event generator to wrap.
*/
constructor(eventGenerator) {
this.original = eventGenerator;
this.emitter = eventGenerator.emitter;
this.codePath = null;
this.idGenerator = new IdGenerator("s");
this.currentNode = null;
this.onLooped = this.onLooped.bind(this);
}
/**
* Does the process to enter a given AST node.
@ -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;

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

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

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

@ -221,36 +221,35 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) {
/**
* A class which manages state to analyze code paths.
*
* @constructor
* @param {IdGenerator} idGenerator - An id generator to generate id for code
* path segments.
* @param {Function} onLooped - A callback function to notify looping.
*/
function CodePathState(idGenerator, onLooped) {
this.idGenerator = idGenerator;
this.notifyLooped = onLooped;
this.forkContext = ForkContext.newRoot(idGenerator);
this.choiceContext = null;
this.switchContext = null;
this.tryContext = null;
this.loopContext = null;
this.breakContext = null;
this.currentSegments = [];
this.initialSegment = this.forkContext.head[0];
// returnedSegments and thrownSegments push elements into finalSegments also.
const final = this.finalSegments = [];
const returned = this.returnedForkContext = [];
const thrown = this.thrownForkContext = [];
returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
}
class CodePathState {
CodePathState.prototype = {
constructor: CodePathState,
/**
* @param {IdGenerator} idGenerator - An id generator to generate id for code
* path segments.
* @param {Function} onLooped - A callback function to notify looping.
*/
constructor(idGenerator, onLooped) {
this.idGenerator = idGenerator;
this.notifyLooped = onLooped;
this.forkContext = ForkContext.newRoot(idGenerator);
this.choiceContext = null;
this.switchContext = null;
this.tryContext = null;
this.loopContext = null;
this.breakContext = null;
this.currentSegments = [];
this.initialSegment = this.forkContext.head[ 0 ];
// returnedSegments and thrownSegments push elements into finalSegments also.
const final = this.finalSegments = [];
const returned = this.returnedForkContext = [];
const thrown = this.thrownForkContext = [];
returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
}
/**
* The head segments.
@ -258,7 +257,7 @@ CodePathState.prototype = {
*/
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;

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

@ -18,47 +18,56 @@ const IdGenerator = require("./id-generator");
/**
* A code path.
*
* @constructor
* @param {string} id - An identifier.
* @param {CodePath|null} upper - The code path of the upper function scope.
* @param {Function} onLooped - A callback function to notify looping.
*/
function CodePath(id, upper, onLooped) {
class CodePath {
/**
* The identifier of this code path.
* Rules use it to store additional information of each rule.
* @type {string}
* @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.
*/
this.id = id;
constructor(id, upper, onLooped) {
/**
* The code path of the upper function scope.
* @type {CodePath|null}
*/
this.upper = upper;
/**
* The identifier of this code path.
* Rules use it to store additional information of each rule.
* @type {string}
*/
this.id = id;
/**
* The code paths of nested function scopes.
* @type {CodePath[]}
*/
this.childCodePaths = [];
/**
* The code path of the upper function scope.
* @type {CodePath|null}
*/
this.upper = upper;
/**
* The code paths of nested function scopes.
* @type {CodePath[]}
*/
this.childCodePaths = [];
// Initializes internal state.
Object.defineProperty(
this,
"internal",
{value: new CodePathState(new IdGenerator(`${id}_`), onLooped)});
// Initializes internal state.
Object.defineProperty(
this,
"internal",
{ value: new CodePathState(new IdGenerator(`${id}_`), onLooped) });
// Adds this into `childCodePaths` of `upper`.
if (upper) {
upper.childCodePaths.push(this);
// Adds this into `childCodePaths` of `upper`.
if (upper) {
upper.childCodePaths.push(this);
}
}
}
CodePath.prototype = {
constructor: CodePath,
/**
* Gets the state of a given code path.
*
* @param {CodePath} codePath - A code path to get.
* @returns {CodePathState} The state of the code path.
*/
static getState(codePath) {
return codePath.internal;
}
/**
* The initial code path segment.
@ -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 {

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

@ -99,21 +99,20 @@ function mergeExtraSegments(context, segments) {
/**
* A class to manage forking.
*
* @constructor
* @param {IdGenerator} idGenerator - An identifier generator for segments.
* @param {ForkContext|null} upper - An upper fork context.
* @param {number} count - A number of parallel segments.
*/
function ForkContext(idGenerator, upper, count) {
this.idGenerator = idGenerator;
this.upper = upper;
this.count = count;
this.segmentsList = [];
}
class ForkContext {
ForkContext.prototype = {
constructor: ForkContext,
/**
* @param {IdGenerator} idGenerator - An identifier generator for segments.
* @param {ForkContext|null} upper - An upper fork context.
* @param {number} count - A number of parallel segments.
*/
constructor(idGenerator, upper, count) {
this.idGenerator = idGenerator;
this.upper = upper;
this.count = count;
this.segmentsList = [];
}
/**
* The head segments.
@ -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,34 +228,34 @@ ForkContext.prototype = {
clear() {
this.segmentsList = [];
}
};
/**
* Creates the root fork context.
*
* @param {IdGenerator} idGenerator - An identifier generator for segments.
* @returns {ForkContext} New fork context.
*/
ForkContext.newRoot = function(idGenerator) {
const context = new ForkContext(idGenerator, null, 1);
/**
* Creates the root fork context.
*
* @param {IdGenerator} idGenerator - An identifier generator for segments.
* @returns {ForkContext} New fork context.
*/
static newRoot(idGenerator) {
const context = new ForkContext(idGenerator, null, 1);
context.add([CodePathSegment.newRoot(idGenerator.next())]);
context.add([CodePathSegment.newRoot(idGenerator.next())]);
return context;
};
return context;
}
/**
* Creates an empty fork context preceded by a given context.
*
* @param {ForkContext} parentContext - The parent fork context.
* @param {boolean} forkLeavingPath - A flag which shows inside of `finally` block.
* @returns {ForkContext} New fork context.
*/
ForkContext.newEmpty = function(parentContext, forkLeavingPath) {
return new ForkContext(
parentContext.idGenerator,
parentContext,
(forkLeavingPath ? 2 : 1) * parentContext.count);
};
/**
* Creates an empty fork context preceded by a given context.
*
* @param {ForkContext} parentContext - The parent fork context.
* @param {boolean} forkLeavingPath - A flag which shows inside of `finally` block.
* @returns {ForkContext} New fork context.
*/
static newEmpty(parentContext, forkLeavingPath) {
return new ForkContext(
parentContext.idGenerator,
parentContext,
(forkLeavingPath ? 2 : 1) * parentContext.count);
}
}
module.exports = ForkContext;

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

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

236
tools/eslint/lib/config.js

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

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

@ -49,14 +49,12 @@ const MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only
* @returns {Object} registryItems for each rule in provided rulesConfig
*/
function makeRegistryItems(rulesConfig) {
return Object.keys(rulesConfig).reduce(function(accumulator, ruleId) {
accumulator[ruleId] = rulesConfig[ruleId].map(function(config) {
return {
config,
specificity: config.length || 1,
errorCount: void 0
};
});
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;
},
@ -239,13 +231,13 @@ Registry.prototype = {
*/
createConfig() {
const ruleIds = Object.keys(this.rules),
config = {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);
});
}.bind(this));
ruleIds.forEach(ruleId => {
newRegistry.rules[ruleId] = this.rules[ruleId].filter(registryItem => (registryItem.specificity === specificity));
});
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) {
const lintConfig = Object.assign({}, config, {rules: 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];
}

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

@ -235,7 +235,7 @@ function loadConfigFile(file) {
function writeJSONConfigFile(config, filePath) {
debug(`Writing JSON config file: ${filePath}`);
const content = stringify(config, {cmp: sortByKey, space: 4});
const content = stringify(config, { cmp: sortByKey, space: 4 });
fs.writeFileSync(filePath, content, "utf8");
}
@ -253,7 +253,7 @@ function writeYAMLConfigFile(config, filePath) {
// lazy load YAML to improve performance when not used
const yaml = require("js-yaml");
const content = yaml.safeDump(config, {sortKeys: true});
const content = yaml.safeDump(config, { sortKeys: true });
fs.writeFileSync(filePath, content, "utf8");
}
@ -268,7 +268,7 @@ function writeYAMLConfigFile(config, filePath) {
function writeJSConfigFile(config, filePath) {
debug(`Writing JS config file: ${filePath}`);
const content = `module.exports = ${stringify(config, {cmp: sortByKey, space: 4})};`;
const content = `module.exports = ${stringify(config, { cmp: sortByKey, space: 4 })};`;
fs.writeFileSync(filePath, content, "utf8");
}
@ -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}`;

84
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})}`);
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}`,
@ -215,7 +213,7 @@ function configureRules(answers, config) {
* @returns {Object} config object
*/
function processAnswers(answers) {
let config = {rules: {}, env: {}};
let config = { rules: {}, env: {} };
if (answers.es6) {
config.env.es6 = true;
@ -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) {
@ -266,9 +264,10 @@ function processAnswers(answers) {
*/
function getConfigForStyleGuide(guide) {
const guides = {
google: {extends: "google"},
airbnb: {extends: "airbnb", plugins: ["react", "jsx-a11y", "import"]},
standard: {extends: "standard", plugins: ["standard", "promise"]}
google: { extends: "google" },
airbnb: { extends: "airbnb", plugins: ["react", "jsx-a11y", "import"] },
"airbnb-base": { extends: "airbnb-base", plugins: ["import"] },
standard: { extends: "standard", plugins: ["standard", "promise"] }
};
if (!guides[guide]) {
@ -296,21 +295,30 @@ function promptUser(callback) {
message: "How would you like to configure ESLint?",
default: "prompt",
choices: [
{name: "Answer questions about your style", value: "prompt"},
{name: "Use a popular style guide", value: "guide"},
{name: "Inspect your JavaScript file(s)", value: "auto"}
{ name: "Answer questions about your style", value: "prompt" },
{ name: "Use a popular style guide", value: "guide" },
{ name: "Inspect your JavaScript file(s)", value: "auto" }
]
},
{
type: "list",
name: "styleguide",
message: "Which style guide do you want to follow?",
choices: [{name: "Google", value: "google"}, {name: "Airbnb", value: "airbnb"}, {name: "Standard", value: "standard"}],
choices: [{ name: "Google", value: "google" }, { name: "Airbnb", value: "airbnb" }, { name: "Standard", value: "standard" }],
when(answers) {
answers.packageJsonExists = npmUtil.checkPackageJson();
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);
@ -376,7 +386,7 @@ function promptUser(callback) {
name: "env",
message: "Where will your code run?",
default: ["browser"],
choices: [{name: "Browser", value: "browser"}, {name: "Node", value: "node"}]
choices: [{ name: "Browser", value: "browser" }, { name: "Node", value: "node" }]
},
{
type: "confirm",
@ -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") {
@ -428,21 +436,21 @@ function promptUser(callback) {
name: "indent",
message: "What style of indentation do you use?",
default: "tab",
choices: [{name: "Tabs", value: "tab"}, {name: "Spaces", value: 4}]
choices: [{ name: "Tabs", value: "tab" }, { name: "Spaces", value: 4 }]
},
{
type: "list",
name: "quotes",
message: "What quotes do you use for strings?",
default: "double",
choices: [{name: "Double", value: "double"}, {name: "Single", value: "single"}]
choices: [{ name: "Double", value: "double" }, { name: "Single", value: "single" }]
},
{
type: "list",
name: "linebreak",
message: "What line endings do you use?",
default: "unix",
choices: [{name: "Unix", value: "unix"}, {name: "Windows", value: "windows"}]
choices: [{ name: "Unix", value: "unix" }, { name: "Windows", value: "windows" }]
},
{
type: "confirm",
@ -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;

94
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}
*/
function validateRuleOptions(id, options, source) {
const schema = getRuleOptionsSchema(id);
let validateRule = validators.rules[id],
severity,
localOptions,
validSeverity = true;
if (!validateRule && schema) {
validateRule = schemaValidator(schema, { verbose: true });
validators.rules[id] = validateRule;
* 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 validateRuleSeverity(options) {
const severity = Array.isArray(options) ? options[0] : options;
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;
}
/**
* 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 (!validators.rules[id] && schema) {
validators.rules[id] = schemaValidator(schema, { verbose: true });
}
validSeverity = (
severity === 0 || severity === 1 || severity === 2 ||
(typeof severity === "string" && /^(?:off|warn|error)$/i.test(severity))
);
const validateRule = validators.rules[id];
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) || !validSeverity) {
const message = [
source, ":\n",
"\tConfiguration for rule \"", id, "\" is invalid:\n"
];
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"
);
}
/**
* 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 (validateRule && validateRule.errors) {
validateRule.errors.forEach(function(error) {
message.push(
"\tValue \"", error.value, "\" ", error.message, ".\n"
);
});
if (severity !== 0 && !(typeof severity === "string" && severity.toLowerCase() === "off")) {
validateRuleSchema(id, Array.isArray(options) ? options.slice(1) : []);
}
throw new Error(message.join(""));
} 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);
});
}
},

102
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 {
return parser.parse(text, parserOptions);
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;
@ -759,7 +780,7 @@ module.exports = (function() {
if (envInFile) {
if (!config || !config.env) {
config = Object.assign({}, config || {}, {env: envInFile});
config = Object.assign({}, config || {}, { env: envInFile });
} else {
config = Object.assign({}, config);
config.env = Object.assign({}, config.env, envInFile);
@ -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.
*

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

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

20
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({
index,
color: renderColor(result.errorCount, result.warningCount),
filePath: result.filePath,
summary: renderSummary(result.errorCount, result.warningCount)
}) + renderMessages(result.messages, index);
}).join("\n");
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");
}
//------------------------------------------------------------------------------
@ -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}`;

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

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

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

@ -61,36 +61,41 @@ const PASSTHROUGHS = [
//------------------------------------------------------------------------------
/**
* Rule context class
* Acts as an abstraction layer between rules and the main eslint object.
* @constructor
* @param {string} ruleId The ID of the rule using this object.
* @param {eslint} eslint The eslint object.
* @param {number} severity The configured severity level of the rule.
* @param {Array} options The configuration information to be added to the rule.
* @param {Object} settings The configuration settings passed from the config file.
* @param {Object} parserOptions The parserOptions settings passed from the config file.
* @param {Object} parserPath The parser setting passed from the config file.
* @param {Object} meta The metadata of the rule
*/
function RuleContext(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta) {
class RuleContext {
// public.
this.id = ruleId;
this.options = options;
this.settings = settings;
this.parserOptions = parserOptions;
this.parserPath = parserPath;
this.meta = meta;
/**
* @param {string} ruleId The ID of the rule using this object.
* @param {eslint} eslint The eslint object.
* @param {number} severity The configured severity level of the rule.
* @param {Array} options The configuration information to be added to the rule.
* @param {Object} settings The configuration settings passed from the config file.
* @param {Object} parserOptions The parserOptions settings passed from the config file.
* @param {Object} parserPath The parser setting passed from the config file.
* @param {Object} meta The metadata of the rule
* @param {Object} parserServices The parser services for the rule.
*/
constructor(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta, parserServices) {
// private.
this.eslint = eslint;
this.severity = severity;
// public.
this.id = ruleId;
this.options = options;
this.settings = settings;
this.parserOptions = parserOptions;
this.parserPath = parserPath;
this.meta = meta;
Object.freeze(this);
}
// create a separate copy and freeze it (it's not nice to freeze other people's objects)
this.parserServices = Object.freeze(Object.assign({}, parserServices));
RuleContext.prototype = {
constructor: RuleContext,
// private.
this.eslint = eslint;
this.severity = severity;
Object.freeze(this);
}
/**
* Passthrough to eslint.getSourceCode().
@ -98,7 +103,7 @@ RuleContext.prototype = {
*/
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];

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

@ -37,7 +37,7 @@ module.exports = {
{
type: "object",
properties: {
requireReturnForObjectLiteral: {type: "boolean"}
requireReturnForObjectLiteral: { type: "boolean" }
},
additionalProperties: false
}
@ -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) {
const blockBody = arrowBody.body;
if (blockBody.length !== 1 && !never) {
return;
}
if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" &&
blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") {
return;
}
if (never || asNeeded && blockBody[0].type === "ReturnStatement") {
context.report({
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 {
const blockBody = arrowBody.body;
if (blockBody.length !== 1) {
return;
}
if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" &&
blockBody[0].argument.type === "ObjectExpression") {
return;
}
if (asNeeded && blockBody[0].type === "ReturnStatement") {
context.report({
node,
loc: arrowBody.loc.start,
message: "Unexpected block statement surrounding arrow body."
});
}
}
} else {
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 } });
}
/**

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

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

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

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

@ -112,11 +112,11 @@ module.exports = {
{
type: "object",
properties: {
arrays: {$refs: "#/defs/valueWithIgnore"},
objects: {$refs: "#/defs/valueWithIgnore"},
imports: {$refs: "#/defs/valueWithIgnore"},
exports: {$refs: "#/defs/valueWithIgnore"},
functions: {$refs: "#/defs/valueWithIgnore"}
arrays: { $refs: "#/defs/valueWithIgnore" },
objects: { $refs: "#/defs/valueWithIgnore" },
imports: { $refs: "#/defs/valueWithIgnore" },
exports: { $refs: "#/defs/valueWithIgnore" },
functions: { $refs: "#/defs/valueWithIgnore" }
},
additionalProperties: false
}
@ -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 } });
}
}

19
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;
}
@ -86,7 +99,7 @@ module.exports = {
if (node.type === "Program") {
// The head of program.
loc = {line: 1, column: 0};
loc = { line: 1, column: 0 };
type = "program";
} else if (node.type === "ArrowFunctionExpression") {
@ -113,7 +126,7 @@ module.exports = {
node,
loc,
message: "Expected to return a value at the end of this {{type}}.",
data: {type}
data: { type }
});
}

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

@ -43,9 +43,7 @@ module.exports = {
* @returns {void}
*/
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);
});
}

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

@ -261,8 +261,8 @@ module.exports = {
const isRealLoop = toSegment.prevSegments.length >= 2;
funcInfo.codePath.traverseSegments(
{first: toSegment, last: fromSegment},
function(segment) {
{ first: toSegment, last: fromSegment },
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." });
}
}
}

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

@ -47,7 +47,9 @@ module.exports = {
additionalItems: false
}
]
}
},
fixable: "code"
},
create(context) {
@ -112,22 +114,32 @@ module.exports = {
function getOperatorLocation(node) {
const opToken = sourceCode.getTokenAfter(node.left);
return {line: opToken.loc.start.line, column: opToken.loc.start.column};
return { line: opToken.loc.start.line, column: opToken.loc.start.column };
}
/**
* Reports a message for this rule.
* @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}=`);
}
};

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

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

77
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,12 +167,13 @@ 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" &&
isIdentifier(node.key.value, ecmaVersion) &&
node.key.value !== node.value.id.name
) {
} else if (
node.key.type === "Literal" &&
isIdentifier(node.key.value, ecmaVersion) &&
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." });
}
};
}

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

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

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

@ -23,10 +23,8 @@ const ACCEPTABLE_PARENTS = [
* @returns {Reference|null} Returns the found reference or null if none were found.
*/
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 {

268
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;
} else {
effectiveParent = parent.parent;
}
}
nodeIndent = getNodeIndent(effectiveParent).goodChar;
if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) {
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 === effectiveParent.loc.start.line) {
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" ||
parent.type === "CallExpression" ||
parent.type === "ArrowFunctionExpression" ||
parent.type === "NewExpression" ||
parent.type === "LogicalExpression"
) {
nodeIndent = nodeIndent + indentSize;
} 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 {
nodeIndent = parentElements[0].loc.start.column;
}
} else if (parent.type === "CallExpression" || parent.type === "NewExpression") {
if (typeof options.CallExpression.arguments === "number") {
nodeIndent += options.CallExpression.arguments * indentSize;
} else if (options.CallExpression.arguments === "first") {
if (parent.arguments.indexOf(node) !== -1) {
nodeIndent = parent.arguments[0].loc.start.column;
}
} else {
nodeIndent += indentSize;
}
} else if (parent.type === "LogicalExpression" || parent.type === "ArrowFunctionExpression") {
nodeIndent += indentSize;
}
}
} else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && effectiveParent.type !== "MemberExpression" && effectiveParent.type !== "ExpressionStatement" && effectiveParent.type !== "AssignmentExpression" && effectiveParent.type !== "Property") {
} else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && parent.type !== "MemberExpression" && parent.type !== "ExpressionStatement" && parent.type !== "AssignmentExpression" && parent.type !== "Property") {
nodeIndent = nodeIndent + indentSize;
}
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);
}
}
};
}

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

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

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

@ -417,8 +417,8 @@ module.exports = {
function report(property, side, whitespace, expected, mode) {
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));
});
}

18
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)$/;
@ -77,16 +77,16 @@ module.exports = {
{
type: "object",
properties: {
before: {type: "boolean"},
after: {type: "boolean"},
before: { type: "boolean" },
after: { type: "boolean" },
overrides: {
type: "object",
properties: KEYS.reduce(function(retv, key) {
properties: KEYS.reduce((retv, key) => {
retv[key] = {
type: "object",
properties: {
before: {type: "boolean"},
after: {type: "boolean"}
before: { type: "boolean" },
after: { type: "boolean" }
},
additionalProperties: false
};

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

@ -21,16 +21,10 @@ const lodash = require("lodash"),
* @returns {Array} An array of line numbers.
*/
function getEmptyLineNums(lines) {
const emptyLines = lines.map(function(line, i) {
return {
code: line.trim(),
num: i + 1
};
}).filter(function(line) {
return !line.code;
}).map(function(line) {
return line.num;
});
const emptyLines = lines.map((line, i) => ({
code: line.trim(),
num: i + 1
})).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) {

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

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

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

@ -66,10 +66,10 @@ module.exports = {
*/
function checkFunction(node) {
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
});
} });
}
}

20
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 }
});
}
}
@ -110,7 +120,7 @@ module.exports = {
const count = functionStack.pop();
if (ignoreTopLevelFunctions && functionStack.length === 0) {
topLevelFunctions.push({ node, count});
topLevelFunctions.push({ node, count });
} else {
reportIfTooManyStatements(node, count, maxStatements);
}
@ -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
});
} });
}
}

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

@ -36,7 +36,7 @@ module.exports = {
schema: [{
type: "object",
properties: {
allowParens: {type: "boolean"}
allowParens: { type: "boolean" }
},
additionalProperties: false
}]
@ -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." });
}
};

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

Loading…
Cancel
Save