Browse Source

tools: update ESLint, fix unused vars bug

Update ESLint to 3.0.0. This includes an enhancement to `no-unused-vars`
such that it finds a few instances in our code base that it did not find
previously (fixed in previous commits readying this for landing).

PR-URL: https://github.com/nodejs/node/pull/7601
Reviewed-By: Michaël Zasso <mic.besace@gmail.com>
Reviewed-By: Roman Reiss <me@silverwind.io>
v4.x
Rich Trott 9 years ago
committed by Myles Borins
parent
commit
d7ce99214d
  1. 2984
      tools/eslint/CHANGELOG.md
  2. 133
      tools/eslint/README.md
  3. 14
      tools/eslint/bin/eslint.js
  4. 5
      tools/eslint/conf/cli-options.js
  5. 29
      tools/eslint/conf/eslint-all.js
  6. 30
      tools/eslint/conf/eslint.json
  7. 101
      tools/eslint/lib/ast-utils.js
  8. 85
      tools/eslint/lib/cli-engine.js
  9. 9
      tools/eslint/lib/cli.js
  10. 62
      tools/eslint/lib/config.js
  11. 21
      tools/eslint/lib/config/config-file.js
  12. 11
      tools/eslint/lib/config/config-initializer.js
  13. 29
      tools/eslint/lib/config/config-ops.js
  14. 5
      tools/eslint/lib/config/environments.js
  15. 59
      tools/eslint/lib/eslint.js
  16. 62
      tools/eslint/lib/file-finder.js
  17. 51
      tools/eslint/lib/ignored-paths.js
  18. 3
      tools/eslint/lib/options.js
  19. 12
      tools/eslint/lib/rule-context.js
  20. 2
      tools/eslint/lib/rules/accessor-pairs.js
  21. 14
      tools/eslint/lib/rules/array-bracket-spacing.js
  22. 72
      tools/eslint/lib/rules/arrow-body-style.js
  23. 30
      tools/eslint/lib/rules/arrow-parens.js
  24. 8
      tools/eslint/lib/rules/arrow-spacing.js
  25. 8
      tools/eslint/lib/rules/block-spacing.js
  26. 28
      tools/eslint/lib/rules/callback-return.js
  27. 18
      tools/eslint/lib/rules/comma-dangle.js
  28. 6
      tools/eslint/lib/rules/comma-spacing.js
  29. 26
      tools/eslint/lib/rules/comma-style.js
  30. 8
      tools/eslint/lib/rules/computed-property-spacing.js
  31. 29
      tools/eslint/lib/rules/consistent-return.js
  32. 11
      tools/eslint/lib/rules/curly.js
  33. 6
      tools/eslint/lib/rules/default-case.js
  34. 9
      tools/eslint/lib/rules/dot-location.js
  35. 4
      tools/eslint/lib/rules/eol-last.js
  36. 5
      tools/eslint/lib/rules/eqeqeq.js
  37. 19
      tools/eslint/lib/rules/func-names.js
  38. 30
      tools/eslint/lib/rules/generator-star-spacing.js
  39. 61
      tools/eslint/lib/rules/indent.js
  40. 66
      tools/eslint/lib/rules/key-spacing.js
  41. 6
      tools/eslint/lib/rules/linebreak-style.js
  42. 75
      tools/eslint/lib/rules/lines-around-comment.js
  43. 13
      tools/eslint/lib/rules/max-len.js
  44. 148
      tools/eslint/lib/rules/max-lines.js
  45. 153
      tools/eslint/lib/rules/max-statements-per-line.js
  46. 4
      tools/eslint/lib/rules/new-cap.js
  47. 3
      tools/eslint/lib/rules/new-parens.js
  48. 2
      tools/eslint/lib/rules/newline-after-var.js
  49. 25
      tools/eslint/lib/rules/newline-before-return.js
  50. 19
      tools/eslint/lib/rules/newline-per-chained-call.js
  51. 10
      tools/eslint/lib/rules/no-cond-assign.js
  52. 3
      tools/eslint/lib/rules/no-confusing-arrow.js
  53. 33
      tools/eslint/lib/rules/no-constant-condition.js
  54. 3
      tools/eslint/lib/rules/no-div-regex.js
  55. 3
      tools/eslint/lib/rules/no-duplicate-case.js
  56. 15
      tools/eslint/lib/rules/no-else-return.js
  57. 3
      tools/eslint/lib/rules/no-empty-character-class.js
  58. 4
      tools/eslint/lib/rules/no-empty-function.js
  59. 4
      tools/eslint/lib/rules/no-empty.js
  60. 188
      tools/eslint/lib/rules/no-extra-parens.js
  61. 18
      tools/eslint/lib/rules/no-extra-semi.js
  62. 14
      tools/eslint/lib/rules/no-implicit-coercion.js
  63. 5
      tools/eslint/lib/rules/no-inline-comments.js
  64. 88
      tools/eslint/lib/rules/no-irregular-whitespace.js
  65. 2
      tools/eslint/lib/rules/no-loop-func.js
  66. 212
      tools/eslint/lib/rules/no-mixed-operators.js
  67. 5
      tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js
  68. 11
      tools/eslint/lib/rules/no-multi-spaces.js
  69. 58
      tools/eslint/lib/rules/no-multiple-empty-lines.js
  70. 10
      tools/eslint/lib/rules/no-native-reassign.js
  71. 52
      tools/eslint/lib/rules/no-prototype-builtins.js
  72. 70
      tools/eslint/lib/rules/no-regex-spaces.js
  73. 64
      tools/eslint/lib/rules/no-return-assign.js
  74. 2
      tools/eslint/lib/rules/no-script-url.js
  75. 14
      tools/eslint/lib/rules/no-sequences.js
  76. 6
      tools/eslint/lib/rules/no-unexpected-multiline.js
  77. 33
      tools/eslint/lib/rules/no-unsafe-finally.js
  78. 219
      tools/eslint/lib/rules/no-unused-vars.js
  79. 18
      tools/eslint/lib/rules/no-useless-call.js
  80. 4
      tools/eslint/lib/rules/no-useless-computed-key.js
  81. 6
      tools/eslint/lib/rules/no-useless-concat.js
  82. 150
      tools/eslint/lib/rules/no-useless-rename.js
  83. 209
      tools/eslint/lib/rules/object-curly-newline.js
  84. 2
      tools/eslint/lib/rules/object-curly-spacing.js
  85. 73
      tools/eslint/lib/rules/object-property-newline.js
  86. 146
      tools/eslint/lib/rules/object-shorthand.js
  87. 3
      tools/eslint/lib/rules/one-var.js
  88. 10
      tools/eslint/lib/rules/operator-linebreak.js
  89. 20
      tools/eslint/lib/rules/padded-blocks.js
  90. 196
      tools/eslint/lib/rules/prefer-const.js
  91. 12
      tools/eslint/lib/rules/prefer-spread.js
  92. 2
      tools/eslint/lib/rules/require-yield.js
  93. 107
      tools/eslint/lib/rules/rest-spread-spacing.js
  94. 20
      tools/eslint/lib/rules/semi-spacing.js
  95. 6
      tools/eslint/lib/rules/semi.js
  96. 6
      tools/eslint/lib/rules/space-before-blocks.js
  97. 2
      tools/eslint/lib/rules/space-before-function-paren.js
  98. 10
      tools/eslint/lib/rules/space-infix-ops.js
  99. 6
      tools/eslint/lib/rules/space-unary-ops.js
  100. 43
      tools/eslint/lib/rules/strict.js

2984
tools/eslint/CHANGELOG.md

File diff suppressed because it is too large

133
tools/eslint/README.md

@ -24,21 +24,53 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
* ESLint uses an AST to evaluate patterns in code.
* ESLint is completely pluggable, every single rule is a plugin and you can add more at runtime.
## Installation
## Installation and Usage
You can install ESLint using npm:
There are two ways to install ESLint: globally and locally.
npm install -g eslint
### Local Installation and Usage
## Usage
If you want to include ESLint as part of your project's build system, we recommend installing it locally. You can do so using npm:
If it's your first time using ESLint, you should set up a config file using `--init`:
```
$ npm install eslint --save-dev
```
You should then setup a configuration file:
```
$ ./node_modules/.bin/eslint --init
```
After that, you can run ESLint on any file or directory like this:
```
$ ./node_modules/.bin/eslint yourfile.js
```
Any plugins or shareable configs that you use must also be installed locally to work with a locally-installed ESLint.
### Global Installation and Usage
eslint --init
If you want to make ESLint available to tools that run across all of your projects, we recommend installing ESLint globally. You can do so using npm:
After that, you can run ESLint on any JavaScript file:
```
$ npm install -g eslint
```
eslint test.js test2.js
You should then setup a configuration file:
```
$ eslint --init
```
After that, you can run ESLint on any file or directory like this:
```
$ eslint yourfile.js
```
Any plugins or shareable configs that you use must also be installed globally to work with a globally-installed ESLint.
**Note:** `eslint --init` is intended for setting up and configuring ESLint on a per-project basis and will perform a local installation of ESLint and its plugins in the directory in which it is run. If you prefer using a global installation of ESLint, any plugins used in your configuration must also be installed globally.
@ -55,7 +87,7 @@ After running `eslint --init`, you'll have a `.eslintrc` file in your directory.
}
```
The names `"semi"` and `"quotes"` are the names of [rules](http://eslint.org/docs/rules) in ESLint. The number is the error level of the rule and can be one of the three values:
The names `"semi"` and `"quotes"` are the names of [rules](http://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values:
* `"off"` or `0` - turn the rule off
* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
@ -65,31 +97,40 @@ The three error levels allow you fine-grained control over how ESLint applies ru
## Sponsors
* Development is sponsored by [Box](https://box.com)
* Site search ([eslint.org](http://eslint.org)) is sponsored by [Algolia](https://www.algolia.com)
## Team
These folks keep the project moving and are resources for help:
* Nicholas C. Zakas ([@nzakas](https://github.com/nzakas)) - project lead
* Ilya Volodin ([@ilyavolodin](https://github.com/ilyavolodin)) - reviewer
* Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - reviewer
* Alberto Rodríguez ([@alberto](https://github.com/alberto)) - reviewer
* Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - committer
* Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - committer
* Ian VanSchooten ([@ianvs](https://github.com/ianvs)) - committer
* Burak Yiğit Kaya ([@byk](https://github.com/byk)) - committer
* Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo)) - committer
* Michael Ficarra ([@michaelficarra](https://github.com/michaelficarra)) - committer
* Mark Pedrotti ([@pedrottimark](https://github.com/pedrottimark)) - committer
* Oleg Gaidarenko ([@markelog](https://github.com/markelog)) - committer
* Mike Sherov [@mikesherov](https://github.com/mikesherov)) - committer
* Henry Zhu ([@hzoo](https://github.com/hzoo)) - committer
* Marat Dulin ([@mdevils](https://github.com/mdevils)) - committer
* Alexej Yaroshevich ([@zxqfox](https://github.com/zxqfox)) - committer
These folks keep the project moving and are resources for help.
### Technical Steering Committee (TSC)
* Nicholas C. Zakas ([@nzakas](https://github.com/nzakas))
* Ilya Volodin ([@ilyavolodin](https://github.com/ilyavolodin))
* Brandon Mills ([@btmills](https://github.com/btmills))
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps))
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea))
* Alberto Rodríguez ([@alberto](https://github.com/alberto))
### Development Team
* Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma))
* Jamund Ferguson ([@xjamundx](https://github.com/xjamundx))
* Ian VanSchooten ([@ianvs](https://github.com/ianvs))
* Burak Yiğit Kaya ([@byk](https://github.com/byk))
* Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo))
* Michael Ficarra ([@michaelficarra](https://github.com/michaelficarra))
* Mark Pedrotti ([@pedrottimark](https://github.com/pedrottimark))
* Oleg Gaidarenko ([@markelog](https://github.com/markelog))
* Mike Sherov [@mikesherov](https://github.com/mikesherov))
* Henry Zhu ([@hzoo](https://github.com/hzoo))
* Marat Dulin ([@mdevils](https://github.com/mdevils))
* Alexej Yaroshevich ([@zxqfox](https://github.com/zxqfox))
### Issues Team
* Kevin Partington ([@platinumazure](https://github.com/platinumazure))
* Vitor Balocco ([@vitorbal](https://github.com/vitorbal))
## Releases
@ -104,15 +145,35 @@ Before filing an issue, please be sure to read the guidelines for what you're re
* [Proposing a Rule Change](http://eslint.org/docs/developer-guide/contributing/rule-changes)
* [Request a Change](http://eslint.org/docs/developer-guide/contributing/changes)
## Frequently Asked Questions
### Why don't you like JSHint???
## Semantic Versioning Policy
ESLint follows [semantic versioning](http://semver.org). However, due to the nature of ESLint as a code quality tool, it's not always clear when a minor or major version bump occurs. To help clarify this for everyone, we've defined the following semantic versioning policy for ESLint:
* Patch release (intended to not break your lint build)
* A bug fix in a rule that results in ESLint reporting fewer errors.
* A bug fix to the CLI or core (including formatters).
* Improvements to documentation.
* Non-user-facing changes such as refactoring code, adding, deleting, or modifying tests, and increasing test coverage.
* Re-releasing after a failed release (i.e., publishing a release that doesn't work for anyone).
* Minor release (might break your lint build)
* A bug fix in a rule that results in ESLint reporting more errors.
* A new rule is created.
* A new option to an existing rule is created.
* An existing rule is deprecated.
* A new CLI capability is created.
* New capabilities to the public API are added (new classes, new methods, new arguments to existing methods, etc.).
* A new formatter is created.
* Major release (likely to break your lint build)
* `eslint:recommended` is updated.
* An existing rule is removed.
* An existing formatter is removed.
* Part of the public API is removed or changed in an incompatible way.
I do like JSHint. And I like Anton and Rick. Neither of those were deciding factors in creating this tool. The fact is that I've had a dire need for a JavaScript tool with pluggable linting rules. I had hoped JSHint would be able to do this, however after chatting with Anton, I found that the planned plugin infrastructure wasn't going to suit my purpose.
## Frequently Asked Questions
### I'm not giving up JSHint for this!
### How is ESLint different from JSHint?
That's not really a question, but I got it. I'm not trying to convince you that ESLint is better than JSHint. The only thing I know is that ESLint is better than JSHint for what I'm doing. In the off chance you're doing something similar, it might be better for you. Otherwise, keep using JSHint, I'm certainly not going to tell you to stop using it.
The most significant difference is that ESlint has pluggable linting rules. That means you can use the rules it comes with, or you can extend it with rules created by others or by yourself!
### How does ESLint performance compare to JSHint?

14
tools/eslint/bin/eslint.js

@ -77,16 +77,4 @@ if (useStdIn) {
exitCode = cli.execute(process.argv);
}
// https://github.com/eslint/eslint/issues/4691
// In Node.js >= 0.12, you can use a cleaner way
if ("exitCode" in process) {
process.exitCode = exitCode;
} else {
/*
* Wait for the stdout buffer to drain.
* See https://github.com/eslint/eslint/issues/317
*/
process.on("exit", function() {
process.exit(exitCode);
});
}
process.exitCode = exitCode;

5
tools/eslint/conf/cli-options.js

@ -5,8 +5,6 @@
"use strict";
var DEFAULT_PARSER = require("../conf/eslint.json").parser;
module.exports = {
configFile: null,
baseConfig: false,
@ -18,8 +16,9 @@ module.exports = {
extensions: [".js"],
ignore: true,
ignorePath: null,
parser: DEFAULT_PARSER,
parser: "", // must be empty
cache: false,
// in order to honor the cacheFile option if specified
// this option should not have a default value otherwise
// it will always be used

29
tools/eslint/conf/eslint-all.js

@ -0,0 +1,29 @@
/**
* @fileoverview Config to enable all rules.
* @author Robert Fletcher
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var fs = require("fs"),
path = require("path");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
var ruleFiles = fs.readdirSync(path.resolve(__dirname, "../lib/rules"));
var enabledRules = ruleFiles.reduce(function(result, filename) {
result[path.basename(filename, ".js")] = "error";
return result;
}, {});
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = { rules: enabledRules };

30
tools/eslint/conf/eslint.json

@ -19,9 +19,9 @@
"no-debugger": "error",
"no-delete-var": "error",
"no-div-regex": "off",
"no-dupe-args": "error",
"no-dupe-class-members": "error",
"no-dupe-keys": "error",
"no-dupe-args": "error",
"no-duplicate-case": "error",
"no-duplicate-imports": "off",
"no-else-return": "off",
@ -55,13 +55,14 @@
"no-lone-blocks": "off",
"no-lonely-if": "off",
"no-loop-func": "off",
"no-magic-numbers": "off",
"no-mixed-operators": "off",
"no-mixed-requires": "off",
"no-mixed-spaces-and-tabs": "error",
"linebreak-style": "off",
"no-multi-spaces": "off",
"no-multi-str": "off",
"no-multiple-empty-lines": "off",
"no-native-reassign": "off",
"no-native-reassign": "error",
"no-negated-condition": "off",
"no-negated-in-lhs": "error",
"no-nested-ternary": "off",
@ -80,6 +81,7 @@
"no-process-env": "off",
"no-process-exit": "off",
"no-proto": "off",
"no-prototype-builtins": "off",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-restricted-globals": "off",
@ -109,7 +111,7 @@
"no-unmodified-loop-condition": "off",
"no-unneeded-ternary": "off",
"no-unreachable": "error",
"no-unsafe-finally": "off",
"no-unsafe-finally": "error",
"no-unused-expressions": "off",
"no-unused-labels": "error",
"no-unused-vars": "error",
@ -119,11 +121,11 @@
"no-useless-concat": "off",
"no-useless-constructor": "off",
"no-useless-escape": "off",
"no-useless-rename": "off",
"no-void": "off",
"no-var": "off",
"no-warning-comments": "off",
"no-with": "off",
"no-magic-numbers": "off",
"array-bracket-spacing": "off",
"array-callback-return": "off",
"arrow-body-style": "off",
@ -135,10 +137,10 @@
"brace-style": "off",
"callback-return": "off",
"camelcase": "off",
"comma-dangle": "error",
"comma-dangle": "off",
"comma-spacing": "off",
"comma-style": "off",
"complexity": ["off", 11],
"complexity": "off",
"computed-property-spacing": "off",
"consistent-return": "off",
"consistent-this": "off",
@ -155,15 +157,19 @@
"global-require": "off",
"guard-for-in": "off",
"handle-callback-err": "off",
"id-blacklist": "off",
"id-length": "off",
"id-match": "off",
"indent": "off",
"init-declarations": "off",
"jsx-quotes": "off",
"key-spacing": "off",
"keyword-spacing": "off",
"linebreak-style": "off",
"lines-around-comment": "off",
"max-depth": "off",
"max-len": "off",
"max-lines": "off",
"max-nested-callbacks": "off",
"max-params": "off",
"max-statements": "off",
@ -173,7 +179,9 @@
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"object-curly-newline": "off",
"object-curly-spacing": ["off", "never"],
"object-property-newline": "off",
"object-shorthand": "off",
"one-var": "off",
"one-var-declaration-per-line": "off",
@ -189,14 +197,13 @@
"quote-props": "off",
"quotes": "off",
"radix": "off",
"id-match": "off",
"id-blacklist": "off",
"require-jsdoc": "off",
"require-yield": "off",
"require-yield": "error",
"rest-spread-spacing": "off",
"semi": "off",
"semi-spacing": "off",
"sort-vars": "off",
"sort-imports": "off",
"sort-vars": "off",
"space-before-blocks": "off",
"space-before-function-paren": "off",
"space-in-parens": "off",
@ -205,6 +212,7 @@
"spaced-comment": "off",
"strict": "off",
"template-curly-spacing": "off",
"unicode-bom": "off",
"use-isnan": "error",
"valid-jsdoc": "off",
"valid-typeof": "error",

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

@ -57,7 +57,7 @@ function isModifyingReference(reference, index, references) {
function isES5Constructor(node) {
return (
node.id &&
node.id.name[0] === node.id.name[0].toLocaleUpperCase()
node.id.name[0] !== node.id.name[0].toLocaleLowerCase()
);
}
@ -176,14 +176,14 @@ function hasJSDocThisTag(node, sourceCode) {
/**
* Determines if a node is surrounded by parentheses.
* @param {RuleContext} context The context object passed to the rule
* @param {SourceCode} sourceCode The ESLint source code object
* @param {ASTNode} node The node to be checked.
* @returns {boolean} True if the node is parenthesised.
* @private
*/
function isParenthesised(context, node) {
var previousToken = context.getTokenBefore(node),
nextToken = context.getTokenAfter(node);
function isParenthesised(sourceCode, node) {
var previousToken = sourceCode.getTokenBefore(node),
nextToken = sourceCode.getTokenAfter(node);
return Boolean(previousToken && nextToken) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
@ -460,5 +460,96 @@ module.exports = {
/* istanbul ignore next */
return true;
},
/**
* Get the precedence level based on the node type
* @param {ASTNode} node node to evaluate
* @returns {int} precedence level
* @private
*/
getPrecedence: function(node) {
switch (node.type) {
case "SequenceExpression":
return 0;
case "AssignmentExpression":
case "ArrowFunctionExpression":
case "YieldExpression":
return 1;
case "ConditionalExpression":
return 3;
case "LogicalExpression":
switch (node.operator) {
case "||":
return 4;
case "&&":
return 5;
// no default
}
/* falls through */
case "BinaryExpression":
switch (node.operator) {
case "|":
return 6;
case "^":
return 7;
case "&":
return 8;
case "==":
case "!=":
case "===":
case "!==":
return 9;
case "<":
case "<=":
case ">":
case ">=":
case "in":
case "instanceof":
return 10;
case "<<":
case ">>":
case ">>>":
return 11;
case "+":
case "-":
return 12;
case "*":
case "/":
case "%":
return 13;
// no default
}
/* falls through */
case "UnaryExpression":
return 14;
case "UpdateExpression":
return 15;
case "CallExpression":
// IIFE is allowed to have parens in any position (#655)
if (node.callee.type === "FunctionExpression") {
return -1;
}
return 16;
case "NewExpression":
return 17;
// no default
}
return 18;
}
};

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

@ -20,7 +20,6 @@ var fs = require("fs"),
lodash = require("lodash"),
debug = require("debug"),
isAbsolute = require("path-is-absolute"),
rules = require("./rules"),
eslint = require("./eslint"),
@ -68,6 +67,8 @@ var fs = require("fs"),
* @typedef {Object} LintResult
* @property {string} filePath The path to the file that was linted.
* @property {LintMessage[]} messages All of the messages for the result.
* @property {number} errorCount Number or errors for the result.
* @property {number} warningCount Number or warnings for the result.
*/
//------------------------------------------------------------------------------
@ -132,23 +133,19 @@ function multipassFix(text, config, options) {
fixedResult,
fixed = false,
passNumber = 0,
lastMessageCount,
MAX_PASSES = 10;
/**
* This loop continues until one of the following is true:
*
* 1. No more fixes have been applied.
* 2. There are no more linting errors reported.
* 3. The number of linting errors is no different between two passes.
* 4. Ten passes have been made.
* 2. Ten passes have been made.
*
* That means anytime a fix is successfully applied, there will be another pass.
* Essentially, guaranteeing a minimum of two passes.
*/
do {
passNumber++;
lastMessageCount = messages.length;
debug("Linting code for " + options.filename + " (pass " + passNumber + ")");
messages = eslint.verify(text, config, options);
@ -156,6 +153,12 @@ function multipassFix(text, config, options) {
debug("Generating fixed text for " + options.filename + " (pass " + passNumber + ")");
fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages);
// stop if there are any syntax errors.
// 'fixedResult.output' is a empty string.
if (messages.length === 1 && messages[0].fatal) {
break;
}
// keep track if any fixes were ever applied - important for return value
fixed = fixed || fixedResult.fixed;
@ -163,8 +166,7 @@ function multipassFix(text, config, options) {
text = fixedResult.output;
} while (
fixedResult.fixed && fixedResult.messages.length > 0 &&
fixedResult.messages.length !== lastMessageCount &&
fixedResult.fixed &&
passNumber < MAX_PASSES
);
@ -300,18 +302,34 @@ function processFile(filename, configHelper, options) {
/**
* Returns result with warning by ignore settings
* @param {string} filePath File path of checked code
* @returns {Result} Result with single warning
* @param {string} filePath - File path of checked code
* @param {string} baseDir - Absolute path of base directory
* @returns {Result} Result with single warning
* @private
*/
function createIgnoreResult(filePath) {
function createIgnoreResult(filePath, baseDir) {
var message;
var isHidden = /^\./.test(path.basename(filePath));
var isInNodeModules = baseDir && /^node_modules/.test(path.relative(baseDir, filePath));
var 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.";
} else if (isInNodeModules) {
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.";
} else {
message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
}
return {
filePath: path.resolve(filePath),
messages: [
{
fatal: false,
severity: 1,
message: "File ignored because of a matching ignore pattern. Use --no-ignore to override."
message: message
}
],
errorCount: 0,
@ -438,10 +456,6 @@ function CLIEngine(options) {
*/
this._fileCache = fileEntryCache.create(cacheFile);
if (!this.options.cache) {
this._fileCache.destroy();
}
// load in additional rules
if (this.options.rulePaths) {
var cwd = this.options.cwd;
@ -489,7 +503,8 @@ CLIEngine.getFormatter = function(format) {
try {
return require(formatterPath);
} catch (ex) {
return null;
ex.message = "There was a problem loading formatter: " + formatterPath + "\nError: " + ex.message;
throw ex;
}
} else {
@ -511,7 +526,9 @@ CLIEngine.getErrorResults = function(results) {
if (filteredMessages.length > 0) {
filtered.push({
filePath: result.filePath,
messages: filteredMessages
messages: filteredMessages,
errorCount: filteredMessages.length,
warningCount: 0
});
}
});
@ -563,7 +580,6 @@ CLIEngine.prototype = {
*/
executeOnFiles: function(patterns) {
var results = [],
processed = {},
options = this.options,
fileCache = this._fileCache,
configHelper = new Config(options),
@ -612,7 +628,7 @@ CLIEngine.prototype = {
var hashOfConfig;
if (warnIgnored) {
results.push(createIgnoreResult(filename));
results.push(createIgnoreResult(filename, options.cwd));
return;
}
@ -633,13 +649,6 @@ CLIEngine.prototype = {
if (!changed) {
debug("Skipping file since hasn't changed: " + filename);
/*
* Adding the filename to the processed hashmap
* so the reporting is not affected (showing a warning about .eslintignore being used
* when it is not really used)
*/
processed[filename] = true;
/*
* Add the the cached results (always will be 0 error and
* 0 warnings). We should not cache results for files that
@ -651,12 +660,12 @@ CLIEngine.prototype = {
// move to the next file
return;
}
} else {
fileCache.destroy();
}
debug("Processing " + filename);
processed[filename] = true;
var res = processFile(filename, configHelper, options);
if (options.cache) {
@ -718,9 +727,10 @@ CLIEngine.prototype = {
* Executes the current configuration on text.
* @param {string} text A string of JavaScript code to lint.
* @param {string} filename An optional string representing the texts filename.
* @param {boolean} warnIgnored Always warn when a file is ignored
* @returns {Object} The results for the linting.
*/
executeOnText: function(text, filename) {
executeOnText: function(text, filename, warnIgnored) {
var results = [],
stats,
@ -729,12 +739,11 @@ CLIEngine.prototype = {
ignoredPaths = new IgnoredPaths(options);
// resolve filename based on options.cwd (for reporting, ignoredPaths also resolves)
if (filename && !isAbsolute(filename)) {
if (filename && !path.isAbsolute(filename)) {
filename = path.resolve(options.cwd, filename);
}
if (filename && (options.ignore !== false) && ignoredPaths.contains(filename)) {
results.push(createIgnoreResult(filename));
if (filename && warnIgnored && ignoredPaths.contains(filename)) {
results.push(createIgnoreResult(filename, options.cwd));
} else {
results.push(processText(text, configHelper, filename, options.fix, options.allowInlineConfig));
}
@ -770,12 +779,8 @@ CLIEngine.prototype = {
var ignoredPaths;
var resolvedPath = path.resolve(this.options.cwd, filePath);
if (this.options.ignore) {
ignoredPaths = new IgnoredPaths(this.options);
return ignoredPaths.contains(resolvedPath);
}
return false;
ignoredPaths = new IgnoredPaths(this.options);
return ignoredPaths.contains(resolvedPath);
},
getFormatter: CLIEngine.getFormatter

9
tools/eslint/lib/cli.js

@ -74,9 +74,10 @@ function printResults(engine, results, format, outputFile) {
output,
filePath;
formatter = engine.getFormatter(format);
if (!formatter) {
log.error("Could not find formatter '%s'.", format);
try {
formatter = engine.getFormatter(format);
} catch (e) {
log.error(e.message);
return false;
}
@ -177,7 +178,7 @@ var cli = {
return 0;
}
report = text ? engine.executeOnText(text, currentOptions.stdinFilename) : engine.executeOnFiles(files);
report = text ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files);
if (currentOptions.fix) {
debug("Fix mode enabled - applying fixes");
CLIEngine.outputFixes(report);

62
tools/eslint/lib/config.js

@ -71,7 +71,7 @@ function loadConfig(configToLoad) {
/**
* Get personal config object from ~/.eslintrc.
* @returns {Object} the personal config object (empty object if there is no personal config)
* @returns {Object} the personal config object (null if there is no personal config)
* @private
*/
function getPersonalConfig() {
@ -87,7 +87,16 @@ function getPersonalConfig() {
}
}
return config || {};
return config || null;
}
/**
* Determine if rules were explicitly passed in as options.
* @param {Object} options The options used to create our configuration.
* @returns {boolean} True if rules were passed in as options, false otherwise.
*/
function hasRules(options) {
return options.rules && Object.keys(options.rules).length > 0;
}
/**
@ -105,7 +114,8 @@ function getLocalConfig(thisConfig, directory) {
localConfigFiles = thisConfig.findLocalConfigFiles(directory),
numFiles = localConfigFiles.length,
rootPath,
projectConfigPath = ConfigFile.getFilenameForDirectory(thisConfig.options.cwd);
projectConfigPath = ConfigFile.getFilenameForDirectory(thisConfig.options.cwd),
personalConfig;
for (i = 0; i < numFiles; i++) {
@ -140,8 +150,34 @@ function getLocalConfig(thisConfig, directory) {
config = ConfigOps.merge(localConfig, config);
}
// Use the personal config file if there are no other local config files found.
return found || thisConfig.useSpecificConfig ? config : ConfigOps.merge(config, getPersonalConfig());
if (!found && !thisConfig.useSpecificConfig) {
/*
* - Is there a personal config in the user's home directory? If so,
* merge that with the passed-in config.
* - Otherwise, if no rules were manually passed in, throw and error.
* - Note: This function is not called if useEslintrc is false.
*/
personalConfig = getPersonalConfig();
if (personalConfig) {
config = ConfigOps.merge(config, personalConfig);
} else if (!hasRules(thisConfig.options)) {
// No config file, no manual configuration, and no rules, so error.
var noConfigError = new Error("No ESLint configuration found.");
noConfigError.messageTemplate = "no-config-found";
noConfigError.messageData = {
directory: directory,
filesExamined: localConfigFiles
};
throw noConfigError;
}
}
return config;
}
//------------------------------------------------------------------------------
@ -231,7 +267,7 @@ Config.prototype.getConfig = function(filePath) {
}
// Step 2: Create a copy of the baseConfig
config = ConfigOps.merge({parser: this.parser, parserOptions: this.parserOptions}, this.baseConfig);
config = ConfigOps.merge({}, this.baseConfig);
// Step 3: Merge in the user-specified configuration from .eslintrc and package.json
config = ConfigOps.merge(config, userConfig);
@ -256,6 +292,20 @@ Config.prototype.getConfig = function(filePath) {
// 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
});
}
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");

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

@ -20,9 +20,9 @@ var debug = require("debug"),
pathUtil = require("../util/path-util"),
ModuleResolver = require("../util/module-resolver"),
pathIsInside = require("path-is-inside"),
stripBom = require("strip-bom"),
stripComments = require("strip-json-comments"),
stringify = require("json-stable-stringify"),
isAbsolutePath = require("path-is-absolute"),
defaultOptions = require("../../conf/eslint.json"),
requireUncached = require("require-uncached");
@ -68,7 +68,7 @@ debug = debug("eslint:config-file");
* @private
*/
function readFile(filePath) {
return fs.readFileSync(filePath, "utf8");
return stripBom(fs.readFileSync(filePath, "utf8"));
}
/**
@ -80,7 +80,7 @@ function readFile(filePath) {
* @private
*/
function isFilePath(filePath) {
return isAbsolutePath(filePath) || !/\w|@/.test(filePath.charAt(0));
return path.isAbsolute(filePath) || !/\w|@/.test(filePath.charAt(0));
}
/**
@ -369,14 +369,20 @@ function applyExtends(config, filePath, relativeTo) {
* this lets us use the eslint.json file as the recommended rules
*/
parentPath = path.resolve(__dirname, "../../conf/eslint.json");
} else if (parentPath === "eslint:all") {
/*
* Add an explicit substitution for eslint:all to conf/eslint-all.js
*/
parentPath = path.resolve(__dirname, "../../conf/eslint-all.js");
} else if (isFilePath(parentPath)) {
/*
* If the `extends` path is relative, use the directory of the current configuration
* file as the reference point. Otherwise, use as-is.
*/
parentPath = (!isAbsolutePath(parentPath) ?
path.join(path.dirname(filePath), parentPath) :
parentPath = (!path.isAbsolute(parentPath) ?
path.join(relativeTo || path.dirname(filePath), parentPath) :
parentPath
);
}
@ -489,7 +495,6 @@ function resolve(filePath, relativeTo) {
function load(filePath, applyEnvironments, relativeTo) {
var resolvedPath = resolve(filePath, relativeTo),
dirname = path.dirname(resolvedPath.filePath),
basedir = getBaseDir(dirname),
lookupPath = getLookupPath(dirname),
config = loadConfigFile(resolvedPath);
@ -508,7 +513,7 @@ function load(filePath, applyEnvironments, relativeTo) {
// include full path of parser if present
if (config.parser) {
if (isFilePath(config.parser)) {
config.parser = path.resolve(basedir || "", config.parser);
config.parser = path.resolve(dirname || "", config.parser);
} else {
config.parser = resolver.resolve(config.parser, lookupPath);
}
@ -522,7 +527,7 @@ function load(filePath, applyEnvironments, relativeTo) {
* a "parent". Load the referenced file and merge the configuration recursively.
*/
if (config.extends) {
config = applyExtends(config, filePath, basedir);
config = applyExtends(config, filePath, dirname);
}
if (config.env && applyEnvironments) {

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

@ -46,7 +46,6 @@ function writeFile(config, format) {
extname = ".json";
}
ConfigFile.write(config, "./.eslintrc" + extname);
log.info("Successfully created .eslintrc" + extname + " file in " + process.cwd());
@ -318,7 +317,8 @@ function promptUser(callback) {
message: "Which style guide do you want to follow?",
choices: [{name: "Google", value: "google"}, {name: "AirBnB", value: "airbnb"}, {name: "Standard", value: "standard"}],
when: function(answers) {
return answers.source === "guide";
answers.packageJsonExists = npmUtil.checkPackageJson();
return answers.source === "guide" && answers.packageJsonExists;
}
},
{
@ -342,13 +342,18 @@ function promptUser(callback) {
default: "JavaScript",
choices: ["JavaScript", "YAML", "JSON"],
when: function(answers) {
return (answers.source === "guide" || answers.source === "auto");
return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto");
}
}
], function(earlyAnswers) {
// early exit if you are using a style guide
if (earlyAnswers.source === "guide") {
if (!earlyAnswers.packageJsonExists) {
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;
}
try {
config = getConfigForStyleGuide(earlyAnswers.styleguide);
writeFile(config, earlyAnswers.format);

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

@ -23,7 +23,8 @@ var RULE_SEVERITY_STRINGS = ["off", "warn", "error"],
RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce(function(map, value, index) {
map[value] = index;
return map;
}, {});
}, {}),
VALID_SEVERITIES = [0, 1, 2, "off", "warn", "error"];
//------------------------------------------------------------------------------
// Public Interface
@ -248,6 +249,30 @@ module.exports = {
}
return (typeof severity === "number" && severity === 2);
}
},
/**
* Checks whether a given config has valid severity or not.
* @param {number|string|Array} ruleConfig - The configuration for an individual rule.
* @returns {boolean} `true` if the configuration has valid severity.
*/
isValidSeverity: function(ruleConfig) {
var severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
if (typeof severity === "string") {
severity = severity.toLowerCase();
}
return VALID_SEVERITIES.indexOf(severity) !== -1;
},
/**
* Checks whether every rule of a given config has valid severity or not.
* @param {object} config - The configuration for rules.
* @returns {boolean} `true` if the configuration has valid severity.
*/
isEverySeverityValid: function(config) {
return Object.keys(config).every(function(ruleId) {
return this.isValidSeverity(config[ruleId]);
}, this);
}
};

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

@ -8,15 +8,12 @@
// Requirements
//------------------------------------------------------------------------------
var debug = require("debug"),
envs = require("../../conf/environments");
var envs = require("../../conf/environments");
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
debug = debug("eslint:enviroments");
var environments = Object.create(null);
/**

59
tools/eslint/lib/eslint.js

@ -9,25 +9,25 @@
// Requirements
//------------------------------------------------------------------------------
var lodash = require("lodash"),
Traverser = require("./util/traverser"),
var assert = require("assert"),
EventEmitter = require("events").EventEmitter,
escope = require("escope"),
Environments = require("./config/environments"),
levn = require("levn"),
lodash = require("lodash"),
blankScriptAST = require("../conf/blank-script.json"),
rules = require("./rules"),
RuleContext = require("./rule-context"),
timing = require("./timing"),
SourceCode = require("./util/source-code"),
NodeEventGenerator = require("./util/node-event-generator"),
CommentEventGenerator = require("./util/comment-event-generator"),
EventEmitter = require("events").EventEmitter,
DEFAULT_PARSER = require("../conf/eslint.json").parser,
replacements = require("../conf/replacements.json"),
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
ConfigOps = require("./config/config-ops"),
validator = require("./config/config-validator"),
replacements = require("../conf/replacements.json"),
assert = require("assert"),
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer");
var DEFAULT_PARSER = require("../conf/eslint.json").parser;
Environments = require("./config/environments"),
CommentEventGenerator = require("./util/comment-event-generator"),
NodeEventGenerator = require("./util/node-event-generator"),
SourceCode = require("./util/source-code"),
Traverser = require("./util/traverser"),
RuleContext = require("./rule-context"),
rules = require("./rules"),
timing = require("./timing");
//------------------------------------------------------------------------------
// Helpers
@ -80,6 +80,25 @@ function parseBooleanConfig(string, comment) {
function parseJsonConfig(string, location, messages) {
var items = {};
// Parses a JSON-like comment by the same way as parsing CLI option.
try {
items = levn.parse("Object", string) || {};
// Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`.
// Also, commaless notations have invalid severity:
// "no-alert: 2 no-console: 2" --> {"no-alert": "2 no-console: 2"}
// Should ignore that case as well.
if (ConfigOps.isEverySeverityValid(items)) {
return items;
}
} catch (ex) {
// ignore to parse the string by a fallback.
}
// 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,");
try {
items = JSON.parse("{" + string + "}");
@ -962,8 +981,14 @@ module.exports = (function() {
source: sourceCode.lines[location.line - 1] || ""
};
// ensure there's range and text properties as well as metadata switch, otherwise it's not a valid fix
if (fix && Array.isArray(fix.range) && (typeof fix.text === "string") && (!meta || meta.fixable)) {
// ensure there's range and text properties, otherwise it's not a valid fix
if (fix && Array.isArray(fix.range) && (typeof fix.text === "string")) {
// If rule uses fix, has metadata, but has no metadata.fixable, we should throw
if (meta && !meta.fixable) {
throw new Error("Fixable rules should export a `meta.fixable` property.");
}
problem.fix = fix;
}

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

@ -69,64 +69,6 @@ function normalizeDirectoryEntries(entries, directory, supportedConfigs) {
return fileHash;
}
/**
* Find one instance of a specified file name in directory or in a parent directory.
* Cache the results.
* Does not check if a matching directory entry is a file, and intentionally
* only searches for the first file name in this.fileNames.
* Is currently used by lib/ignored_paths.js to find an .eslintignore file.
* @param {string} directory The directory to start the search from.
* @returns {string} Path of the file found, or an empty string if not found.
*/
FileFinder.prototype.findInDirectoryOrParents = function(directory) {
var cache = this.cache,
child,
dirs,
filePath,
i,
names,
searched;
if (!directory) {
directory = this.cwd;
}
if (cache.hasOwnProperty(directory)) {
return cache[directory];
}
dirs = [];
searched = 0;
names = this.fileNames;
(function() {
while (directory !== child) {
dirs[searched++] = directory;
var filesMap = normalizeDirectoryEntries(getDirectoryEntries(directory), directory, names);
if (Object.keys(filesMap).length) {
for (var k = 0; k < names.length; k++) {
if (filesMap[names[k]]) {
filePath = filesMap[names[k]];
return;
}
}
}
child = directory;
// Assign parent directory to directory.
directory = path.dirname(directory);
}
}());
for (i = 0; i < searched; i++) {
cache[dirs[i]] = filePath;
}
return filePath || String();
};
/**
* Find all instances of files with the specified file names, in directory and
* parent directories. Cache the results.
@ -146,7 +88,9 @@ FileFinder.prototype.findAllInDirectoryAndParents = function(directory) {
j,
searched;
if (!directory) {
if (directory) {
directory = path.resolve(this.cwd, directory);
} else {
directory = this.cwd;
}

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

@ -24,9 +24,9 @@ debug = debug("eslint:ignored-paths");
//------------------------------------------------------------------------------
var ESLINT_IGNORE_FILENAME = ".eslintignore";
var DEFAULT_IGNORE_PATTERNS = [
"/node_modules/*",
"/bower_components/*"
var DEFAULT_IGNORE_DIRS = [
"node_modules/",
"bower_components/"
];
var DEFAULT_OPTIONS = {
dotfiles: false,
@ -97,7 +97,9 @@ function IgnoredPaths(options) {
return ig.add(fs.readFileSync(filepath, "utf8"));
}
this.defaultPatterns = DEFAULT_IGNORE_PATTERNS.concat(options.patterns || []);
this.defaultPatterns = DEFAULT_IGNORE_DIRS.map(function(dir) {
return "/" + dir + "*";
}).concat(options.patterns || []);
this.baseDir = options.cwd;
this.ig = {
@ -124,11 +126,6 @@ function IgnoredPaths(options) {
if (options.ignore !== false) {
var ignorePath;
if (options.ignorePattern) {
addPattern(this.ig.custom, options.ignorePattern);
addPattern(this.ig.default, options.ignorePattern);
}
if (options.ignorePath) {
debug("Using specific ignore file");
@ -159,6 +156,10 @@ function IgnoredPaths(options) {
addIgnoreFile(this.ig.default, ignorePath);
}
if (options.ignorePattern) {
addPattern(this.ig.custom, options.ignorePattern);
addPattern(this.ig.default, options.ignorePattern);
}
}
this.options = options;
@ -189,4 +190,36 @@ IgnoredPaths.prototype.contains = function(filepath, category) {
};
/**
* Returns a list of dir patterns for glob to ignore
* @returns {string[]} list of glob ignore patterns
*/
IgnoredPaths.prototype.getIgnoredFoldersGlobPatterns = function() {
var dirs = DEFAULT_IGNORE_DIRS;
if (this.options.ignore) {
/* eslint-disable no-underscore-dangle */
var patterns = this.ig.custom._rules.filter(function(rule) {
return rule.negative;
}).map(function(rule) {
return rule.origin;
});
/* eslint-enable no-underscore-dangle */
dirs = dirs.filter(function(dir) {
return patterns.every(function(p) {
return (p.indexOf("!" + dir) !== 0 && p.indexOf("!/" + dir) !== 0);
});
});
}
return dirs.map(function(dir) {
return dir + "**";
});
};
module.exports = IgnoredPaths;

3
tools/eslint/lib/options.js

@ -57,7 +57,6 @@ module.exports = optionator({
{
option: "parser",
type: "String",
default: "espree",
description: "Specify the parser to be used"
},
{
@ -115,7 +114,7 @@ module.exports = optionator({
option: "ignore",
type: "Boolean",
default: "true",
description: "Disable use of .eslintignore"
description: "Disable use of ignore files and patterns"
},
{
option: "ignore-pattern",

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

@ -15,18 +15,21 @@ var RuleFixer = require("./util/rule-fixer");
//------------------------------------------------------------------------------
var PASSTHROUGHS = [
"getAllComments",
"getAncestors",
"getComments",
"getDeclaredVariables",
"getFilename",
"getScope",
"markVariableAsUsed",
// DEPRECATED
"getAllComments",
"getComments",
"getFirstToken",
"getFirstTokens",
"getJSDocComment",
"getLastToken",
"getLastTokens",
"getNodeByRangeIndex",
"getScope",
"getSource",
"getSourceLines",
"getTokenAfter",
@ -35,8 +38,7 @@ var PASSTHROUGHS = [
"getTokens",
"getTokensAfter",
"getTokensBefore",
"getTokensBetween",
"markVariableAsUsed"
"getTokensBetween"
];
//------------------------------------------------------------------------------

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

@ -73,7 +73,7 @@ function isPropertyDescriptor(node) {
module.exports = {
meta: {
docs: {
description: "Enforces getter/setter pairs in objects",
description: "enforce getter and setter pairs in objects",
category: "Best Practices",
recommended: false
},

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

@ -13,7 +13,7 @@ var astUtils = require("../ast-utils");
module.exports = {
meta: {
docs: {
description: "Enforce spacing inside array brackets",
description: "enforce consistent spacing inside array brackets",
category: "Stylistic Issues",
recommended: false
},
@ -77,7 +77,7 @@ module.exports = {
loc: token.loc.start,
message: "There should be no space after '" + token.value + "'",
fix: function(fixer) {
var nextToken = context.getSourceCode().getTokenAfter(token);
var nextToken = sourceCode.getTokenAfter(token);
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
@ -96,7 +96,7 @@ module.exports = {
loc: token.loc.start,
message: "There should be no space before '" + token.value + "'",
fix: function(fixer) {
var previousToken = context.getSourceCode().getTokenBefore(token);
var previousToken = sourceCode.getTokenBefore(token);
return fixer.removeRange([previousToken.range[1], token.range[0]]);
}
@ -165,10 +165,10 @@ module.exports = {
return;
}
var first = context.getFirstToken(node),
second = context.getFirstToken(node, 1),
penultimate = context.getLastToken(node, 1),
last = context.getLastToken(node),
var first = sourceCode.getFirstToken(node),
second = sourceCode.getFirstToken(node, 1),
penultimate = sourceCode.getLastToken(node, 1),
last = sourceCode.getLastToken(node),
firstElement = node.elements[0],
lastElement = node.elements[node.elements.length - 1];

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

@ -16,16 +16,45 @@ module.exports = {
recommended: false
},
schema: [
{
enum: ["always", "as-needed"]
}
]
schema: {
anyOf: [
{
type: "array",
items: [
{
enum: ["always", "never"]
}
],
minItems: 0,
maxItems: 1
},
{
type: "array",
items: [
{
enum: ["as-needed"]
},
{
type: "object",
properties: {
requireReturnForObjectLiteral: {type: "boolean"}
},
additionalProperties: false
}
],
minItems: 0,
maxItems: 2
}
]
}
},
create: function(context) {
var always = context.options[0] === "always";
var asNeeded = !context.options[0] || context.options[0] === "as-needed";
var options = context.options;
var always = options[0] === "always";
var asNeeded = !options[0] || options[0] === "as-needed";
var never = options[0] === "never";
var requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
/**
* Determines whether a arrow function body needs braces
@ -36,21 +65,34 @@ module.exports = {
var arrowBody = node.body;
if (arrowBody.type === "BlockStatement") {
var blockBody = arrowBody.body;
if (blockBody.length !== 1) {
return;
}
if (asNeeded && blockBody[0].type === "ReturnStatement") {
if (never) {
context.report({
node: node,
loc: arrowBody.loc.start,
message: "Unexpected block statement surrounding arrow body."
});
} else {
var 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: node,
loc: arrowBody.loc.start,
message: "Unexpected block statement surrounding arrow body."
});
}
}
} else {
if (always) {
if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
context.report({
node: node,
loc: arrowBody.loc.start,

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

@ -16,6 +16,8 @@ module.exports = {
recommended: false
},
fixable: "code",
schema: [
{
enum: ["always", "as-needed"]
@ -28,28 +30,48 @@ module.exports = {
var asNeededMessage = "Unexpected parentheses around single function argument";
var asNeeded = context.options[0] === "as-needed";
var sourceCode = context.getSourceCode();
/**
* Determines whether a arrow function argument end with `)`
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function parens(node) {
var token = context.getFirstToken(node);
var token = sourceCode.getFirstToken(node);
// as-needed: x => x
if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") {
if (token.type === "Punctuator" && token.value === "(") {
context.report(node, asNeededMessage);
context.report({
node: node,
message: asNeededMessage,
fix: function(fixer) {
var paramToken = context.getTokenAfter(token);
var closingParenToken = context.getTokenAfter(paramToken);
return fixer.replaceTextRange([
token.range[0],
closingParenToken.range[1]
], paramToken.value);
}
});
}
return;
}
if (token.type === "Identifier") {
var after = context.getTokenAfter(token);
var after = sourceCode.getTokenAfter(token);
// (x) => x
if (after.value !== ")") {
context.report(node, message);
context.report({
node: node,
message: message,
fix: function(fixer) {
return fixer.replaceText(token, "(" + token.value + ")");
}
});
}
}
}

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

@ -43,20 +43,22 @@ module.exports = {
rule.before = option.before !== false;
rule.after = option.after !== false;
var sourceCode = context.getSourceCode();
/**
* Get tokens of arrow(`=>`) and before/after arrow.
* @param {ASTNode} node The arrow function node.
* @returns {Object} Tokens of arrow and before/after arrow.
*/
function getTokens(node) {
var t = context.getFirstToken(node);
var t = sourceCode.getFirstToken(node);
var before;
while (t.type !== "Punctuator" || t.value !== "=>") {
before = t;
t = context.getTokenAfter(t);
t = sourceCode.getTokenAfter(t);
}
var after = context.getTokenAfter(t);
var after = sourceCode.getTokenAfter(t);
return { before: before, arrow: t, after: after };
}

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

@ -39,11 +39,11 @@ module.exports = {
function getOpenBrace(node) {
if (node.type === "SwitchStatement") {
if (node.cases.length > 0) {
return context.getTokenBefore(node.cases[0]);
return sourceCode.getTokenBefore(node.cases[0]);
}
return context.getLastToken(node, 1);
return sourceCode.getLastToken(node, 1);
}
return context.getFirstToken(node);
return sourceCode.getFirstToken(node);
}
/**
@ -73,7 +73,7 @@ module.exports = {
// Gets braces and the first/last token of content.
var openBrace = getOpenBrace(node);
var closeBrace = context.getLastToken(node);
var closeBrace = sourceCode.getLastToken(node);
var firstToken = sourceCode.getTokenOrCommentAfter(openBrace);
var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace);

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

@ -24,7 +24,8 @@ module.exports = {
create: function(context) {
var callbacks = context.options[0] || ["callback", "cb", "next"];
var callbacks = context.options[0] || ["callback", "cb", "next"],
sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Helpers
@ -46,13 +47,34 @@ module.exports = {
return node.parent;
}
/**
* Check to see if a node contains only identifers
* @param {ASTNode} node The node to check
* @returns {Boolean} Whether or not the node contains only identifers
*/
function containsOnlyIdentifiers(node) {
if (node.type === "Identifier") {
return true;
}
if (node.type === "MemberExpression") {
if (node.object.type === "Identifier") {
return true;
} else if (node.object.type === "MemberExpression") {
return containsOnlyIdentifiers(node.object);
}
}
return false;
}
/**
* Check to see if a CallExpression is in our callback list.
* @param {ASTNode} node The node to check against our callback names list.
* @returns {Boolean} Whether or not this function matches our callback name.
*/
function isCallback(node) {
return node.callee.type === "Identifier" && callbacks.indexOf(node.callee.name) > -1;
return containsOnlyIdentifiers(node.callee) && callbacks.indexOf(sourceCode.getText(node.callee)) > -1;
}
/**
@ -90,7 +112,7 @@ module.exports = {
return {
CallExpression: function(node) {
// if we"re not a callback we can return
// if we're not a callback we can return
if (!isCallback(node)) {
return;
}

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

@ -31,8 +31,8 @@ module.exports = {
meta: {
docs: {
description: "require or disallow trailing commas",
category: "Possible Errors",
recommended: true
category: "Stylistic Issues",
recommended: false
},
fixable: "code",
@ -143,13 +143,13 @@ module.exports = {
}
var sourceCode = context.getSourceCode(),
trailingToken;
// last item can be surrounded by parentheses for object and array literals
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
} else {
penultimateToken = lastItem,
trailingToken = sourceCode.getTokenAfter(lastItem);
// Skip close parentheses.
while (trailingToken.value === ")") {
penultimateToken = trailingToken;
trailingToken = sourceCode.getTokenAfter(trailingToken);
}
if (trailingToken.value !== ",") {
@ -158,7 +158,7 @@ module.exports = {
loc: lastItem.loc.end,
message: MISSING_MESSAGE,
fix: function(fixer) {
return fixer.insertTextAfter(lastItem, ",");
return fixer.insertTextAfter(penultimateToken, ",");
}
});
}

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

@ -136,19 +136,19 @@ module.exports = {
* @returns {void}
*/
function addNullElementsToIgnoreList(node) {
var previousToken = context.getFirstToken(node);
var previousToken = sourceCode.getFirstToken(node);
node.elements.forEach(function(element) {
var token;
if (element === null) {
token = context.getTokenAfter(previousToken);
token = sourceCode.getTokenAfter(previousToken);
if (isComma(token)) {
commaTokensToIgnore.push(token);
}
} else {
token = context.getTokenAfter(element);
token = sourceCode.getTokenAfter(element);
}
previousToken = token;

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

@ -39,9 +39,9 @@ module.exports = {
},
create: function(context) {
var style = context.options[0] || "last",
exceptions = {};
exceptions = {},
sourceCode = context.getSourceCode();
if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
exceptions = context.options[1].exceptions;
@ -115,12 +115,18 @@ module.exports = {
if (items.length > 1 || arrayLiteral) {
// seed as opening [
previousItemToken = context.getFirstToken(node);
previousItemToken = sourceCode.getFirstToken(node);
items.forEach(function(item) {
var commaToken = item ? context.getTokenBefore(item) : previousItemToken,
currentItemToken = item ? context.getFirstToken(item) : context.getTokenAfter(commaToken),
reportItem = item || currentItemToken;
var commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken,
currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken),
reportItem = item || currentItemToken,
tokenBeforeComma = sourceCode.getTokenBefore(commaToken);
// Check if previous token is wrapped in parentheses
if (tokenBeforeComma && tokenBeforeComma.value === ")") {
previousItemToken = tokenBeforeComma;
}
/*
* This works by comparing three token locations:
@ -141,7 +147,7 @@ module.exports = {
currentItemToken, reportItem);
}
previousItemToken = item ? context.getLastToken(item) : previousItemToken;
previousItemToken = item ? sourceCode.getLastToken(item) : previousItemToken;
});
/*
@ -152,12 +158,12 @@ module.exports = {
*/
if (arrayLiteral) {
var lastToken = context.getLastToken(node),
nextToLastToken = context.getTokenBefore(lastToken);
var lastToken = sourceCode.getLastToken(node),
nextToLastToken = sourceCode.getTokenBefore(lastToken);
if (isComma(nextToLastToken)) {
validateCommaItemSpacing(
context.getTokenBefore(nextToLastToken),
sourceCode.getTokenBefore(nextToLastToken),
nextToLastToken,
lastToken,
lastToken

8
tools/eslint/lib/rules/computed-property-spacing.js

@ -119,10 +119,10 @@ module.exports = {
var property = node[propertyName];
var before = context.getTokenBefore(property),
first = context.getFirstToken(property),
last = context.getLastToken(property),
after = context.getTokenAfter(property);
var before = sourceCode.getTokenBefore(property),
first = sourceCode.getFirstToken(property),
last = sourceCode.getLastToken(property),
after = sourceCode.getTokenAfter(property);
if (astUtils.isTokenOnSameLine(before, first)) {
if (propertyNameMustBeSpaced) {

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

@ -14,6 +14,16 @@ var astUtils = require("../ast-utils");
// Helpers
//------------------------------------------------------------------------------
/**
* Checks whether or not a given node is an `Identifier` node which was named a given name.
* @param {ASTNode} node - A node to check.
* @param {string} name - An expected name of the node.
* @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
*/
function isIdentifier(node, name) {
return node.type === "Identifier" && node.name === name;
}
/**
* Checks whether or not a given code path segment is unreachable.
* @param {CodePathSegment} segment - A CodePathSegment to check.
@ -35,10 +45,20 @@ module.exports = {
recommended: false
},
schema: []
schema: [{
type: "object",
properties: {
treatUndefinedAsUnspecified: {
type: "boolean"
}
},
additionalProperties: false
}]
},
create: function(context) {
var options = context.options[0] || {};
var treatUndefinedAsUnspecified = options.treatUndefinedAsUnspecified === true;
var funcInfo = null;
/**
@ -115,7 +135,12 @@ module.exports = {
// Reports a given return statement if it's inconsistent.
ReturnStatement: function(node) {
var hasReturnValue = Boolean(node.argument);
var argument = node.argument;
var hasReturnValue = Boolean(argument);
if (treatUndefinedAsUnspecified && hasReturnValue) {
hasReturnValue = !isIdentifier(argument, "undefined") && argument.operator !== "void";
}
if (!funcInfo.hasReturn) {
funcInfo.hasReturn = true;

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

@ -58,6 +58,8 @@ module.exports = {
var multiOrNest = (context.options[0] === "multi-or-nest");
var consistent = (context.options[1] === "consistent");
var sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@ -69,8 +71,8 @@ module.exports = {
* @private
*/
function isCollapsedOneLiner(node) {
var before = context.getTokenBefore(node),
last = context.getLastToken(node);
var before = sourceCode.getTokenBefore(node),
last = sourceCode.getLastToken(node);
return before.loc.start.line === last.loc.end.line;
}
@ -82,8 +84,8 @@ module.exports = {
* @private
*/
function isOneLiner(node) {
var first = context.getFirstToken(node),
last = context.getLastToken(node);
var first = sourceCode.getFirstToken(node),
last = sourceCode.getLastToken(node);
return first.loc.start.line === last.loc.end.line;
}
@ -94,7 +96,6 @@ module.exports = {
* @returns {Token} The `else` keyword token.
*/
function getElseKeyword(node) {
var sourceCode = context.getSourceCode();
var token = sourceCode.getTokenAfter(node.consequent);
while (token.type !== "Keyword" || token.value !== "else") {

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

@ -13,7 +13,7 @@ var DEFAULT_COMMENT_PATTERN = /^no default$/;
module.exports = {
meta: {
docs: {
description: "require `default` cases in <code>switch</code> statements",
description: "require `default` cases in `switch` statements",
category: "Best Practices",
recommended: false
},
@ -35,6 +35,8 @@ module.exports = {
new RegExp(options.commentPattern) :
DEFAULT_COMMENT_PATTERN;
var sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@ -76,7 +78,7 @@ module.exports = {
var lastCase = last(node.cases);
comments = context.getComments(lastCase).trailing;
comments = sourceCode.getComments(lastCase).trailing;
if (comments.length) {
comment = last(comments);

9
tools/eslint/lib/rules/dot-location.js

@ -28,11 +28,12 @@ module.exports = {
create: function(context) {
var config = context.options[0],
onObject;
var config = context.options[0];
// default to onObject if no preference is passed
onObject = config === "object" || !config;
var onObject = config === "object" || !config;
var sourceCode = context.getSourceCode();
/**
* Reports if the dot between object and property is on the correct loccation.
@ -42,7 +43,7 @@ module.exports = {
* @returns {void}
*/
function checkDotLocation(obj, prop, node) {
var dot = context.getTokenBefore(prop);
var dot = sourceCode.getTokenBefore(prop);
if (dot.type === "Punctuator" && dot.value === ".") {
if (onObject) {

4
tools/eslint/lib/rules/eol-last.js

@ -35,8 +35,8 @@ module.exports = {
Program: function checkBadEOF(node) {
// Get the whole source code, not for node only.
var src = context.getSource(),
var sourceCode = context.getSourceCode(),
src = sourceCode.getText(),
location = {column: 1},
linebreakStyle = context.options[0] || "unix",
linebreak = linebreakStyle === "unix" ? "\n" : "\r\n";

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

@ -19,12 +19,13 @@ module.exports = {
schema: [
{
enum: ["smart", "allow-null"]
enum: ["always", "smart", "allow-null"]
}
]
},
create: function(context) {
var sourceCode = context.getSourceCode();
/**
* Checks if an expression is a typeof expression
@ -75,7 +76,7 @@ module.exports = {
* @private
*/
function getOperatorLocation(node) {
var opToken = context.getTokenAfter(node.left);
var opToken = sourceCode.getTokenAfter(node.left);
return {line: opToken.loc.start.line, column: opToken.loc.start.column};
}

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

@ -12,15 +12,20 @@
module.exports = {
meta: {
docs: {
description: "enforce named `function` expressions",
description: "require or disallow named `function` expressions",
category: "Stylistic Issues",
recommended: false
},
schema: []
schema: [
{
enum: ["always", "never"]
}
]
},
create: function(context) {
var never = context.options[0] === "never";
/**
* Determines whether the current FunctionExpression node is a get, set, or
@ -44,8 +49,14 @@ module.exports = {
var name = node.id && node.id.name;
if (!name && !isObjectOrClassMethod()) {
context.report(node, "Missing function expression name.");
if (never) {
if (name) {
context.report(node, "Unexpected function expression name.");
}
} else {
if (!name && !isObjectOrClassMethod()) {
context.report(node, "Missing function expression name.");
}
}
}
};

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

@ -52,6 +52,26 @@ module.exports = {
return option;
}(context.options[0]));
var sourceCode = context.getSourceCode();
/**
* Gets `*` token from a given node.
*
* @param {ASTNode} node - A node to get `*` token. This is one of
* FunctionDeclaration, FunctionExpression, Property, and
* MethodDefinition.
* @returns {Token} `*` token.
*/
function getStarToken(node) {
var token = sourceCode.getFirstToken(node);
while (token.value !== "*") {
token = sourceCode.getTokenAfter(token);
}
return token;
}
/**
* Checks the spacing between two tokens before or after the star token.
* @param {string} side Either "before" or "after".
@ -98,18 +118,18 @@ module.exports = {
}
if (node.parent.method || node.parent.type === "MethodDefinition") {
starToken = context.getTokenBefore(node, 1);
starToken = getStarToken(node.parent);
} else {
starToken = context.getFirstToken(node, 1);
starToken = getStarToken(node);
}
// Only check before when preceded by `function` keyword
prevToken = context.getTokenBefore(starToken);
// Only check before when preceded by `function`|`static` keyword
prevToken = sourceCode.getTokenBefore(starToken);
if (prevToken.value === "function" || prevToken.value === "static") {
checkSpacing("before", prevToken, starToken);
}
nextToken = context.getTokenAfter(starToken);
nextToken = sourceCode.getTokenAfter(starToken);
checkSpacing("after", starToken, nextToken);
}

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

@ -67,6 +67,10 @@ module.exports = {
}
}
]
},
outerIIFEBody: {
type: "integer",
minimum: 0
}
},
additionalProperties: false
@ -87,9 +91,12 @@ module.exports = {
var: DEFAULT_VARIABLE_INDENT,
let: DEFAULT_VARIABLE_INDENT,
const: DEFAULT_VARIABLE_INDENT
}
},
outerIIFEBody: null
};
var sourceCode = context.getSourceCode();
if (context.options.length) {
if (context.options[0] === "tab") {
indentSize = 1;
@ -114,6 +121,10 @@ module.exports = {
} else if (typeof variableDeclaratorRules === "object") {
lodash.assign(options.VariableDeclarator, variableDeclaratorRules);
}
if (typeof opts.outerIIFEBody === "number") {
options.outerIIFEBody = opts.outerIIFEBody;
}
}
}
@ -206,15 +217,15 @@ module.exports = {
}
/**
* Get node indent
* Get the actual indent of node
* @param {ASTNode|Token} node Node to examine
* @param {boolean} [byLastLine=false] get indent of node's last line
* @param {boolean} [excludeCommas=false] skip comma on start of line
* @returns {int} Indent
*/
function getNodeIndent(node, byLastLine, excludeCommas) {
var token = byLastLine ? context.getLastToken(node) : context.getFirstToken(node);
var src = context.getSource(token, token.loc.start.column);
var token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node);
var src = sourceCode.getText(token, token.loc.start.column);
var regExp = excludeCommas ? indentPattern.excludeCommas : indentPattern.normal;
var indent = regExp.exec(src);
@ -228,7 +239,7 @@ module.exports = {
* @returns {boolean} true if its the first in the its start line
*/
function isNodeFirstInLine(node, byEndLocation) {
var firstToken = byEndLocation === true ? context.getLastToken(node, 1) : context.getTokenBefore(node),
var firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node),
startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line,
endLine = firstToken ? firstToken.loc.end.line : -1;
@ -263,7 +274,7 @@ module.exports = {
function checkNodesIndent(nodes, indent, excludeCommas) {
nodes.forEach(function(node) {
if (node.type === "IfStatement" && node.alternate) {
var elseToken = context.getTokenBefore(node.alternate);
var elseToken = sourceCode.getTokenBefore(node.alternate);
checkNodeIndent(elseToken, indent, excludeCommas);
}
@ -278,7 +289,7 @@ module.exports = {
* @returns {void}
*/
function checkLastNodeLineIndent(node, lastLineIndent) {
var lastToken = context.getLastToken(node);
var lastToken = sourceCode.getLastToken(node);
var endIndent = getNodeIndent(lastToken, true);
if (endIndent !== lastLineIndent && isNodeFirstInLine(node, true)) {
@ -356,9 +367,25 @@ module.exports = {
return false;
}
/**
* Check to see if the node is a file level IIFE
* @param {ASTNode} node The function node to check.
* @returns {boolean} True if the node is the outer IIFE
*/
function isOuterIIFE(node) {
var parent = node.parent;
return (
parent.type === "CallExpression" &&
parent.callee === node &&
parent.parent.type === "ExpressionStatement" &&
parent.parent.parent && parent.parent.parent.type === "Program"
);
}
/**
* Check indent for function block content
* @param {ASTNode} node node to examine
* @param {ASTNode} node A BlockStatement node that is inside of a function.
* @returns {void}
*/
function checkIndentInFunctionBlock(node) {
@ -407,8 +434,14 @@ module.exports = {
}
}
// function body indent should be indent + indent size
indent += indentSize;
// function body indent should be indent + indent size, unless this
// is the outer IIFE and that option is enabled.
var functionOffset = indentSize;
if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) {
functionOffset = options.outerIIFEBody * indentSize;
}
indent += functionOffset;
// check if the node is inside a variable
var parentVarNode = getVariableDeclaratorNode(node);
@ -421,7 +454,7 @@ module.exports = {
checkNodesIndent(node.body, indent);
}
checkLastNodeLineIndent(node, indent - indentSize);
checkLastNodeLineIndent(node, indent - functionOffset);
}
@ -431,7 +464,7 @@ module.exports = {
* @returns {boolean} Whether or not the block starts and ends on the same line.
*/
function isSingleLineNode(node) {
var lastToken = context.getLastToken(node),
var lastToken = sourceCode.getLastToken(node),
startLine = node.loc.start.line,
endLine = lastToken.loc.end.line;
@ -641,11 +674,11 @@ module.exports = {
checkNodesIndent(elements, elementsIndent, true);
// Only check the last line if there is any token after the last item
if (context.getLastToken(node).loc.end.line <= lastElement.loc.end.line) {
if (sourceCode.getLastToken(node).loc.end.line <= lastElement.loc.end.line) {
return;
}
var tokenBeforeLastElement = context.getTokenBefore(lastElement);
var tokenBeforeLastElement = sourceCode.getTokenBefore(lastElement);
if (tokenBeforeLastElement.value === ",") {

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

@ -118,6 +118,8 @@ module.exports = {
recommended: false
},
fixable: "whitespace",
schema: [{
anyOf: [
{
@ -196,6 +198,8 @@ module.exports = {
multiLineOptions = initOptions({}, (options.multiLine || options)),
singleLineOptions = initOptions({}, (options.singleLine || options));
var sourceCode = context.getSourceCode();
/**
* Determines if the given property is key-value property.
* @param {ASTNode} property Property node to check.
@ -220,7 +224,7 @@ module.exports = {
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
prevNode = node;
node = context.getTokenAfter(node);
node = sourceCode.getTokenAfter(node);
}
return prevNode;
@ -235,7 +239,7 @@ module.exports = {
function getNextColon(node) {
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
node = context.getTokenAfter(node);
node = sourceCode.getTokenAfter(node);
}
return node;
@ -250,7 +254,7 @@ module.exports = {
var key = property.key;
if (property.computed) {
return context.getSource().slice(key.range[0], key.range[1]);
return sourceCode.getText().slice(key.range[0], key.range[1]);
}
return property.key.name || property.key.value;
@ -268,9 +272,16 @@ module.exports = {
*/
function report(property, side, whitespace, expected, mode) {
var diff = whitespace.length - expected,
key = property.key,
firstTokenAfterColon = context.getTokenAfter(getNextColon(key)),
location = side === "key" ? key.loc.start : firstTokenAfterColon.loc.start;
nextColon = getNextColon(property.key),
tokenBeforeColon = sourceCode.getTokenBefore(nextColon),
tokenAfterColon = sourceCode.getTokenAfter(nextColon),
isKeySide = side === "key",
locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
isExtra = diff > 0,
diffAbs = Math.abs(diff),
spaces = Array(diffAbs + 1).join(" "),
fix,
range;
if ((
diff && mode === "strict" ||
@ -278,10 +289,41 @@ module.exports = {
diff > 0 && !expected && mode === "minimum") &&
!(expected && containsLineTerminator(whitespace))
) {
context.report(property[side], location, messages[side], {
error: diff > 0 ? "Extra" : "Missing",
computed: property.computed ? "computed " : "",
key: getKey(property)
if (isExtra) {
// Remove whitespace
if (isKeySide) {
range = [tokenBeforeColon.end, tokenBeforeColon.end + diffAbs];
} else {
range = [tokenAfterColon.start - diffAbs, tokenAfterColon.start];
}
fix = function(fixer) {
return fixer.removeRange(range);
};
} else {
// Add whitespace
if (isKeySide) {
fix = function(fixer) {
return fixer.insertTextAfter(tokenBeforeColon, spaces);
};
} else {
fix = function(fixer) {
return fixer.insertTextBefore(tokenAfterColon, spaces);
};
}
}
context.report({
node: property[side],
loc: locStart,
message: messages[side],
data: {
error: isExtra ? "Extra" : "Missing",
computed: property.computed ? "computed " : "",
key: getKey(property)
},
fix: fix
});
}
}
@ -295,7 +337,7 @@ module.exports = {
function getKeyWidth(property) {
var startToken, endToken;
startToken = context.getFirstToken(property);
startToken = sourceCode.getFirstToken(property);
endToken = getLastTokenBeforeColon(property.key);
return endToken.range[1] - startToken.range[0];
@ -307,7 +349,7 @@ module.exports = {
* @returns {Object} Whitespace before and after the property's colon.
*/
function getPropertyWhitespace(property) {
var whitespace = /(\s*):(\s*)/.exec(context.getSource().slice(
var whitespace = /(\s*):(\s*)/.exec(sourceCode.getText().slice(
property.key.range[1], property.value.range[0]
));

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

@ -31,6 +31,8 @@ module.exports = {
var EXPECTED_LF_MSG = "Expected linebreaks to be 'LF' but found 'CRLF'.",
EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'.";
var sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@ -57,7 +59,7 @@ module.exports = {
var linebreakStyle = context.options[0] || "unix",
expectedLF = linebreakStyle === "unix",
expectedLFChars = expectedLF ? "\n" : "\r\n",
source = context.getSource(),
source = sourceCode.getText(),
pattern = /\r\n|\r|\n|\u2028|\u2029/g,
match,
index,
@ -77,7 +79,7 @@ module.exports = {
node: node,
loc: {
line: i,
column: context.getSourceLines()[i - 1].length
column: sourceCode.lines[i - 1].length
},
message: expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG,
fix: createFix(range, expectedLFChars)

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

@ -8,7 +8,8 @@
// Requirements
//------------------------------------------------------------------------------
var lodash = require("lodash");
var lodash = require("lodash"),
astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Helpers
@ -51,16 +52,6 @@ function getCommentLineNums(comments) {
return lines;
}
/**
* Determines if a value is an array.
* @param {number} val The value we wish to check for in the array..
* @param {Array} array An array.
* @returns {boolean} True if the value is in the array..
*/
function contains(val, array) {
return array.indexOf(val) > -1;
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -73,6 +64,8 @@ module.exports = {
recommended: false
},
fixable: "whitespace",
schema: [
{
type: "object",
@ -126,6 +119,22 @@ module.exports = {
var sourceCode = context.getSourceCode();
var lines = sourceCode.lines,
numLines = lines.length + 1,
comments = sourceCode.getAllComments(),
commentLines = getCommentLineNums(comments),
emptyLines = getEmptyLineNums(lines),
commentAndEmptyLines = commentLines.concat(emptyLines);
/**
* Returns whether or not a token is a comment node type
* @param {Token} token The token to check
* @returns {boolean} True if the token is a comment node
*/
function isCommentNodeType(token) {
return token && (token.type === "Block" || token.type === "Line");
}
/**
* Returns whether or not comments are on lines starting with or ending with code
* @param {ASTNode} node The comment node to check.
@ -137,18 +146,18 @@ module.exports = {
token = node;
do {
token = sourceCode.getTokenOrCommentBefore(token);
} while (token && (token.type === "Block" || token.type === "Line"));
} while (isCommentNodeType(token));
if (token && token.loc.end.line === node.loc.start.line) {
if (token && astUtils.isTokenOnSameLine(token, node)) {
return true;
}
token = node;
do {
token = sourceCode.getTokenOrCommentAfter(token);
} while (token && (token.type === "Block" || token.type === "Line"));
} while (isCommentNodeType(token));
if (token && token.loc.start.line === node.loc.end.line) {
if (token && astUtils.isTokenOnSameLine(node, token)) {
return true;
}
@ -267,14 +276,6 @@ module.exports = {
* @returns {void}
*/
function checkForEmptyLine(node, opts) {
var lines = context.getSourceLines(),
numLines = lines.length + 1,
comments = context.getAllComments(),
commentLines = getCommentLineNums(comments),
emptyLines = getEmptyLineNums(lines),
commentAndEmptyLines = commentLines.concat(emptyLines);
var after = opts.after,
before = opts.before;
@ -305,14 +306,34 @@ module.exports = {
return;
}
var previousTokenOrComment = sourceCode.getTokenOrCommentBefore(node);
var nextTokenOrComment = sourceCode.getTokenOrCommentAfter(node);
// check for newline before
if (!exceptionStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) {
context.report(node, "Expected line before comment.");
if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) &&
!(isCommentNodeType(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, node))) {
var lineStart = node.range[0] - node.loc.start.column;
var range = [lineStart, lineStart];
context.report({
node: node,
message: "Expected line before comment.",
fix: function(fixer) {
return fixer.insertTextBeforeRange(range, "\n");
}
});
}
// check for newline after
if (!exceptionEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) {
context.report(node, "Expected line after comment.");
if (!exceptionEndAllowed && after && !lodash.includes(commentAndEmptyLines, nextLineNum) &&
!(isCommentNodeType(nextTokenOrComment) && astUtils.isTokenOnSameLine(node, nextTokenOrComment))) {
context.report({
node: node,
message: "Expected line after comment.",
fix: function(fixer) {
return fixer.insertTextAfter(node, "\n");
}
});
}
}

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

@ -81,6 +81,8 @@ module.exports = {
*/
var URL_REGEXP = /[^:/?#]:\/\/[^?#]/;
var sourceCode = context.getSourceCode();
/**
* Computes the length of a line that may contain tabs. The width of each
* tab will be the number of spaces to the next tab stop.
@ -155,11 +157,12 @@ module.exports = {
*/
function isFullLineComment(line, lineNumber, comment) {
var start = comment.loc.start,
end = comment.loc.end;
end = comment.loc.end,
isFirstTokenOnLine = !line.slice(0, comment.loc.start.column).trim();
return comment &&
(start.line < lineNumber || (start.line === lineNumber && start.column === 0)) &&
(end.line > lineNumber || end.column === line.length);
(start.line < lineNumber || (start.line === lineNumber && isFirstTokenOnLine)) &&
(end.line > lineNumber || (end.line === lineNumber && end.column === line.length));
}
/**
@ -185,10 +188,10 @@ module.exports = {
function checkProgramForMaxLength(node) {
// split (honors line-ending)
var lines = context.getSourceLines(),
var lines = sourceCode.lines,
// list of comments to ignore
comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? context.getAllComments() : [],
comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? sourceCode.getAllComments() : [],
// we iterate over comments in parallel with the lines
commentsIndex = 0;

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

@ -0,0 +1,148 @@
/**
* @fileoverview enforce a maximum file length
* @author Alberto Rodríguez
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var lodash = require("lodash");
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "enforce a maximum number of lines per file",
category: "Stylistic Issues",
recommended: false
},
schema: [
{
oneOf: [
{
type: "integer",
minimum: 0
},
{
type: "object",
properties: {
max: {
type: "integer",
minimum: 0
},
skipComments: {
type: "boolean"
},
skipBlankLines: {
type: "boolean"
}
},
additionalProperties: false
}
]
}
]
},
create: function(context) {
var option = context.options[0],
max = 300;
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
max = option.max;
}
if (typeof option === "number") {
max = option;
}
var skipComments = option && option.skipComments;
var skipBlankLines = option && option.skipBlankLines;
var sourceCode = context.getSourceCode();
/**
* Returns whether or not a token is a comment node type
* @param {Token} token The token to check
* @returns {boolean} True if the token is a comment node
*/
function isCommentNodeType(token) {
return token && (token.type === "Block" || token.type === "Line");
}
/**
* Returns the line numbers of a comment that don't have any code on the same line
* @param {Node} comment The comment node to check
* @returns {int[]} The line numbers
*/
function getLinesWithoutCode(comment) {
var start = comment.loc.start.line;
var end = comment.loc.end.line;
var token;
token = comment;
do {
token = sourceCode.getTokenOrCommentBefore(token);
} while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(token, comment)) {
start += 1;
}
token = comment;
do {
token = sourceCode.getTokenOrCommentAfter(token);
} while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(comment, token)) {
end -= 1;
}
if (start <= end) {
return lodash.range(start, end + 1);
}
return [];
}
return {
"Program:exit": function() {
var lines = sourceCode.lines.map(function(text, i) {
return { lineNumber: i + 1, text: text };
});
if (skipBlankLines) {
lines = lines.filter(function(l) {
return l.text.trim() !== "";
});
}
if (skipComments) {
var comments = sourceCode.getAllComments();
var commentLines = lodash.flatten(comments.map(function(comment) {
return getLinesWithoutCode(comment);
}));
lines = lines.filter(function(l) {
return !lodash.includes(commentLines, l.lineNumber);
});
}
if (lines.length > max) {
context.report({
loc: { line: 1, column: 0 },
message: "File must be at most " + max + " lines long"
});
}
}
};
}
};

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

@ -21,7 +21,8 @@ module.exports = {
type: "object",
properties: {
max: {
type: "integer"
type: "integer",
minimum: 0
}
},
additionalProperties: false
@ -31,72 +32,81 @@ module.exports = {
create: function(context) {
var options = context.options[0] || {},
var sourceCode = context.getSourceCode(),
options = context.options[0] || {},
lastStatementLine = 0,
numberOfStatementsOnThisLine = 0,
maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1,
message = "This line has too many statements. Maximum allowed is " + maxStatementsPerLine + ".";
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
var SINGLE_CHILD_ALLOWED = /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/;
/**
* Reports a node
* @param {ASTNode} node The node to report
* @returns {void}
* @private
* Gets the actual last token of a given node.
*
* @param {ASTNode} node - A node to get. This is a node except EmptyStatement.
* @returns {Token} The actual last token.
*/
function report(node) {
context.report(
node,
"This line has too many statements. Maximum allowed is {{max}}.",
{ max: maxStatementsPerLine });
function getActualLastToken(node) {
var lastToken = sourceCode.getLastToken(node);
if (lastToken.value === ";") {
lastToken = sourceCode.getTokenBefore(lastToken);
}
return lastToken;
}
/**
* Enforce a maximum number of statements per line
* @param {ASTNode} nodes Array of nodes to evaluate
* Addresses a given node.
* It updates the state of this rule, then reports the node if the node violated this rule.
*
* @param {ASTNode} node - A node to check.
* @returns {void}
* @private
*/
function enforceMaxStatementsPerLine(nodes) {
if (nodes.length < 1) {
function enterStatement(node) {
var line = node.loc.start.line;
// Skip to allow non-block statements if this is direct child of control statements.
// `if (a) foo();` is counted as 1.
// But `if (a) foo(); else foo();` should be counted as 2.
if (SINGLE_CHILD_ALLOWED.test(node.parent.type) &&
node.parent.alternate !== node
) {
return;
}
for (var i = 0, l = nodes.length; i < l; ++i) {
var currentStatement = nodes[i];
// Update state.
if (line === lastStatementLine) {
numberOfStatementsOnThisLine += 1;
} else {
numberOfStatementsOnThisLine = 1;
lastStatementLine = line;
}
if (currentStatement.loc.start.line === lastStatementLine) {
++numberOfStatementsOnThisLine;
} else {
numberOfStatementsOnThisLine = 1;
lastStatementLine = currentStatement.loc.end.line;
}
if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
report(currentStatement);
}
// Reports if the node violated this rule.
if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
context.report({node: node, message: message});
}
}
/**
* Check each line in the body of a node
* @param {ASTNode} node node to evaluate
* Updates the state of this rule with the end line of leaving node to check with the next statement.
*
* @param {ASTNode} node - A node to check.
* @returns {void}
* @private
*/
function checkLinesInBody(node) {
enforceMaxStatementsPerLine(node.body);
}
function leaveStatement(node) {
var line = getActualLastToken(node).loc.end.line;
/**
* Check each line in the consequent of a switch case
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkLinesInConsequent(node) {
enforceMaxStatementsPerLine(node.consequent);
// Update state.
if (line !== lastStatementLine) {
numberOfStatementsOnThisLine = 1;
lastStatementLine = line;
}
}
//--------------------------------------------------------------------------
@ -104,10 +114,61 @@ module.exports = {
//--------------------------------------------------------------------------
return {
Program: checkLinesInBody,
BlockStatement: checkLinesInBody,
SwitchCase: checkLinesInConsequent
};
BreakStatement: enterStatement,
ClassDeclaration: enterStatement,
ContinueStatement: enterStatement,
DebuggerStatement: enterStatement,
DoWhileStatement: enterStatement,
ExpressionStatement: enterStatement,
ForInStatement: enterStatement,
ForOfStatement: enterStatement,
ForStatement: enterStatement,
FunctionDeclaration: enterStatement,
IfStatement: enterStatement,
ImportDeclaration: enterStatement,
LabeledStatement: enterStatement,
ReturnStatement: enterStatement,
SwitchStatement: enterStatement,
ThrowStatement: enterStatement,
TryStatement: enterStatement,
VariableDeclaration: enterStatement,
WhileStatement: enterStatement,
WithStatement: enterStatement,
ExportNamedDeclaration: enterStatement,
ExportDefaultDeclaration: enterStatement,
ExportAllDeclaration: enterStatement,
"BreakStatement:exit": leaveStatement,
"ClassDeclaration:exit": leaveStatement,
"ContinueStatement:exit": leaveStatement,
"DebuggerStatement:exit": leaveStatement,
"DoWhileStatement:exit": leaveStatement,
"ExpressionStatement:exit": leaveStatement,
"ForInStatement:exit": leaveStatement,
"ForOfStatement:exit": leaveStatement,
"ForStatement:exit": leaveStatement,
"FunctionDeclaration:exit": leaveStatement,
"IfStatement:exit": leaveStatement,
"ImportDeclaration:exit": leaveStatement,
"LabeledStatement:exit": leaveStatement,
"ReturnStatement:exit": leaveStatement,
"SwitchStatement:exit": leaveStatement,
"ThrowStatement:exit": leaveStatement,
"TryStatement:exit": leaveStatement,
"VariableDeclaration:exit": leaveStatement,
"WhileStatement:exit": leaveStatement,
"WithStatement:exit": leaveStatement,
"ExportNamedDeclaration:exit": leaveStatement,
"ExportDefaultDeclaration:exit": leaveStatement,
"ExportAllDeclaration:exit": leaveStatement,
// For backward compatibility.
// Empty blocks should be warned if `{max: 0}` was given.
BlockStatement: function reportIfZero(node) {
if (maxStatementsPerLine === 0 && node.body.length === 0) {
context.report({node: node, message: message});
}
}
};
}
};

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

@ -127,6 +127,8 @@ module.exports = {
var listeners = {};
var sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@ -186,7 +188,7 @@ module.exports = {
* @returns {Boolean} Returns true if the callee may be capitalized
*/
function isCapAllowed(allowedMap, node, calleeName) {
if (allowedMap[calleeName] || allowedMap[context.getSource(node.callee)]) {
if (allowedMap[calleeName] || allowedMap[sourceCode.getText(node.callee)]) {
return true;
}

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

@ -21,11 +21,12 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
return {
NewExpression: function(node) {
var tokens = context.getTokens(node);
var tokens = sourceCode.getTokens(node);
var prenticesTokens = tokens.filter(function(token) {
return token.value === "(" || token.value === ")";
});

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

@ -35,7 +35,7 @@ module.exports = {
var mode = context.options[0] === "never" ? "never" : "always";
// Cache starting and ending line numbers of comments for faster lookup
var commentEndLine = context.getAllComments().reduce(function(result, token) {
var commentEndLine = sourceCode.getAllComments().reduce(function(result, token) {
result[token.loc.start.line] = token.loc.end.line;
return result;
}, {});

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

@ -133,32 +133,17 @@ module.exports = {
return (lineNumNode - lineNumTokenBefore - commentLines) > 1;
}
/**
* Reports expected/unexpected newline before return statement
* @param {ASTNode} node - the node to report in the event of an error
* @param {boolean} isExpected - whether the newline is expected or not
* @returns {void}
* @private
*/
function reportError(node, isExpected) {
var expected = isExpected ? "Expected" : "Unexpected";
context.report({
node: node,
message: expected + " newline before return statement."
});
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
ReturnStatement: function(node) {
if (isFirstNode(node) && hasNewlineBefore(node)) {
reportError(node, false);
} else if (!isFirstNode(node) && !hasNewlineBefore(node)) {
reportError(node, true);
if (!isFirstNode(node) && !hasNewlineBefore(node)) {
context.report({
node: node,
message: "Expected newline before return statement."
});
}
}
};

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

@ -36,6 +36,23 @@ module.exports = {
var options = context.options[0] || {},
ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
var sourceCode = context.getSourceCode();
/**
* Gets the property text of a given MemberExpression node.
* If the text is multiline, this returns only the first line.
*
* @param {ASTNode} node - A MemberExpression node to get.
* @returns {string} The property text of the node.
*/
function getPropertyText(node) {
var prefix = node.computed ? "[" : ".";
var lines = sourceCode.getText(node.property).split(/\r\n|\r|\n/g);
var suffix = node.computed && lines.length === 1 ? "]" : "";
return prefix + lines[0] + suffix;
}
return {
"CallExpression:exit": function(node) {
if (!node.callee || node.callee.type !== "MemberExpression") {
@ -55,7 +72,7 @@ module.exports = {
context.report(
callee.property,
callee.property.loc.start,
"Expected line break after `" + context.getSource(callee.object).replace(/\r\n|\r|\n/g, "\\n") + "`."
"Expected line break before `" + getPropertyText(callee) + "`."
);
}
}

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

@ -34,6 +34,8 @@ module.exports = {
var prohibitAssign = (context.options[0] || "except-parens");
var sourceCode = context.getSourceCode();
/**
* Check whether an AST node is the test expression for a conditional statement.
* @param {!Object} node The node to test.
@ -68,8 +70,8 @@ module.exports = {
* @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`.
*/
function isParenthesised(node) {
var previousToken = context.getTokenBefore(node),
nextToken = context.getTokenAfter(node);
var previousToken = sourceCode.getTokenBefore(node),
nextToken = sourceCode.getTokenAfter(node);
return previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
@ -81,8 +83,8 @@ module.exports = {
* @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
*/
function isParenthesisedTwice(node) {
var previousToken = context.getTokenBefore(node, 1),
nextToken = context.getTokenAfter(node, 1);
var previousToken = sourceCode.getTokenBefore(node, 1),
nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&

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

@ -44,6 +44,7 @@ module.exports = {
create: function(context) {
var config = context.options[0] || {};
var sourceCode = context.getSourceCode();
/**
* Reports if an arrow function contains an ambiguous conditional.
@ -53,7 +54,7 @@ module.exports = {
function checkArrowFunc(node) {
var body = node.body;
if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(context, body))) {
if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(sourceCode, body))) {
context.report(node, "Arrow function used ambiguously with a conditional expression.");
}
}

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

@ -17,10 +17,23 @@ module.exports = {
recommended: true
},
schema: []
schema: [
{
type: "object",
properties: {
checkLoops: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
create: function(context) {
var options = context.options[0] || {},
checkLoops = options.checkLoops !== false;
//--------------------------------------------------------------------------
// Helpers
@ -102,6 +115,18 @@ module.exports = {
}
}
/**
* Checks node when checkLoops option is enabled
* @param {ASTNode} node The AST node to check.
* @returns {void}
* @private
*/
function checkLoop(node) {
if (checkLoops) {
checkConstantCondition(node);
}
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@ -109,9 +134,9 @@ module.exports = {
return {
ConditionalExpression: checkConstantCondition,
IfStatement: checkConstantCondition,
WhileStatement: checkConstantCondition,
DoWhileStatement: checkConstantCondition,
ForStatement: checkConstantCondition
WhileStatement: checkLoop,
DoWhileStatement: checkLoop,
ForStatement: checkLoop
};
}

3
tools/eslint/lib/rules/no-div-regex.js

@ -21,11 +21,12 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
return {
Literal: function(node) {
var token = context.getFirstToken(node);
var token = sourceCode.getFirstToken(node);
if (token.type === "RegularExpression" && token.value[1] === "=") {
context.report(node, "A regular expression literal can be confused with '/='.");

3
tools/eslint/lib/rules/no-duplicate-case.js

@ -22,13 +22,14 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
return {
SwitchStatement: function(node) {
var mapping = {};
node.cases.forEach(function(switchCase) {
var key = context.getSource(switchCase.test);
var key = sourceCode.getText(switchCase.test);
if (mapping[key]) {
context.report(switchCase, "Duplicate case label.");

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

@ -33,7 +33,7 @@ module.exports = {
* @returns {void}
*/
function displayReport(node) {
context.report(node, "Unexpected 'else' after 'return'.");
context.report(node, "Unnecessary 'else' after 'return'.");
}
/**
@ -112,14 +112,13 @@ module.exports = {
// If we have a BlockStatement, check each consequent body node.
return node.body.some(checkForReturnOrIf);
} else {
/*
* If not a block statement, make sure the consequent isn't a
* ReturnStatement or an IfStatement with returns on both paths.
*/
return checkForReturnOrIf(node);
}
/*
* If not a block statement, make sure the consequent isn't a
* ReturnStatement or an IfStatement with returns on both paths.
*/
return checkForReturnOrIf(node);
}
//--------------------------------------------------------------------------

3
tools/eslint/lib/rules/no-empty-character-class.js

@ -39,11 +39,12 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
return {
Literal: function(node) {
var token = context.getFirstToken(node);
var token = sourceCode.getFirstToken(node);
if (token.type === "RegularExpression" && !regex.test(token.value)) {
context.report(node, "Empty class.");

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

@ -121,6 +121,8 @@ module.exports = {
var options = context.options[0] || {};
var allowed = options.allow || [];
var sourceCode = context.getSourceCode();
/**
* Reports a given function node if the node matches the following patterns.
*
@ -139,7 +141,7 @@ module.exports = {
if (allowed.indexOf(kind) === -1 &&
node.body.type === "BlockStatement" &&
node.body.body.length === 0 &&
context.getComments(node.body).trailing.length === 0
sourceCode.getComments(node.body).trailing.length === 0
) {
context.report({
node: node,

4
tools/eslint/lib/rules/no-empty.js

@ -35,6 +35,8 @@ module.exports = {
var options = context.options[0] || {},
allowEmptyCatch = options.allowEmptyCatch || false;
var sourceCode = context.getSourceCode();
return {
BlockStatement: function(node) {
@ -53,7 +55,7 @@ module.exports = {
}
// any other block is only allowed to be empty, if it contains a comment
if (context.getComments(node).trailing.length > 0) {
if (sourceCode.getComments(node).trailing.length > 0) {
return;
}

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

@ -40,7 +40,8 @@ module.exports = {
type: "object",
properties: {
conditionalAssign: {type: "boolean"},
nestedBinaryExpressions: {type: "boolean"}
nestedBinaryExpressions: {type: "boolean"},
returnAssign: {type: "boolean"}
},
additionalProperties: false
}
@ -53,11 +54,14 @@ module.exports = {
},
create: function(context) {
var isParenthesised = astUtils.isParenthesised.bind(astUtils, context);
var sourceCode = context.getSourceCode();
var isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode);
var precedence = astUtils.getPrecedence;
var ALL_NODES = context.options[0] !== "functions";
var EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
var NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
var sourceCode = context.getSourceCode();
var EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
/**
* Determines if this rule should be enforced for a node given the current configuration.
@ -76,8 +80,8 @@ module.exports = {
* @private
*/
function isParenthesisedTwice(node) {
var previousToken = context.getTokenBefore(node, 1),
nextToken = context.getTokenAfter(node, 1);
var previousToken = sourceCode.getTokenBefore(node, 1),
nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) && previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
@ -115,6 +119,64 @@ module.exports = {
return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression";
}
/**
* Determines if a node is in a return statement
* @param {ASTNode} node - The node to be checked.
* @returns {boolean} True if the node is in a return statement.
* @private
*/
function isInReturnStatement(node) {
while (node) {
if (node.type === "ReturnStatement" ||
(node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement")) {
return true;
}
node = node.parent;
}
return false;
}
/**
* Determines if a node is or contains an assignment expression
* @param {ASTNode} node - The node to be checked.
* @returns {boolean} True if the node is or contains an assignment expression.
* @private
*/
function containsAssignment(node) {
if (node.type === "AssignmentExpression") {
return true;
} else if (node.type === "ConditionalExpression" &&
(node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) {
return true;
} else if ((node.left && node.left.type === "AssignmentExpression") ||
(node.right && node.right.type === "AssignmentExpression")) {
return true;
}
return false;
}
/**
* Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment
* @param {ASTNode} node - The node to be checked.
* @returns {boolean} True if the assignment can be parenthesised.
* @private
*/
function isReturnAssignException(node) {
if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) {
return false;
}
if (node.type === "ReturnStatement") {
return node.argument && containsAssignment(node.argument);
} else if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
return containsAssignment(node.body);
} else {
return containsAssignment(node);
}
}
/**
* Determines if a node following a [no LineTerminator here] restriction is
* surrounded by (potentially) invalid extra parentheses.
@ -194,98 +256,6 @@ module.exports = {
throw new Error("unreachable");
}
/**
* Get the precedence level based on the node type
* @param {ASTNode} node node to evaluate
* @returns {int} precedence level
* @private
*/
function precedence(node) {
switch (node.type) {
case "SequenceExpression":
return 0;
case "AssignmentExpression":
case "ArrowFunctionExpression":
case "YieldExpression":
return 1;
case "ConditionalExpression":
return 3;
case "LogicalExpression":
switch (node.operator) {
case "||":
return 4;
case "&&":
return 5;
// no default
}
/* falls through */
case "BinaryExpression":
switch (node.operator) {
case "|":
return 6;
case "^":
return 7;
case "&":
return 8;
case "==":
case "!=":
case "===":
case "!==":
return 9;
case "<":
case "<=":
case ">":
case ">=":
case "in":
case "instanceof":
return 10;
case "<<":
case ">>":
case ">>>":
return 11;
case "+":
case "-":
return 12;
case "*":
case "/":
case "%":
return 13;
// no default
}
/* falls through */
case "UnaryExpression":
return 14;
case "UpdateExpression":
return 15;
case "CallExpression":
// IIFE is allowed to have parens in any position (#655)
if (node.callee.type === "FunctionExpression") {
return -1;
}
return 16;
case "NewExpression":
return 17;
// no default
}
return 18;
}
/**
* Report the node
* @param {ASTNode} node node to evaluate
@ -293,7 +263,7 @@ module.exports = {
* @private
*/
function report(node) {
var previousToken = context.getTokenBefore(node);
var previousToken = sourceCode.getTokenBefore(node);
context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression.");
}
@ -368,6 +338,10 @@ module.exports = {
},
ArrowFunctionExpression: function(node) {
if (isReturnAssignException(node)) {
return;
}
if (node.body.type !== "BlockStatement") {
if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) {
report(node.body);
@ -383,6 +357,10 @@ module.exports = {
},
AssignmentExpression: function(node) {
if (isReturnAssignException(node)) {
return;
}
if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) {
report(node.right);
}
@ -392,12 +370,18 @@ module.exports = {
CallExpression: dryCallNew,
ConditionalExpression: function(node) {
if (isReturnAssignException(node)) {
return;
}
if (hasExcessParens(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) {
report(node.test);
}
if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) {
report(node.consequent);
}
if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) {
report(node.alternate);
}
@ -413,7 +397,7 @@ module.exports = {
var firstToken, secondToken, firstTokens;
if (hasExcessParens(node.expression)) {
firstTokens = context.getFirstTokens(node.expression, 2);
firstTokens = sourceCode.getFirstTokens(node.expression, 2);
firstToken = firstTokens[0];
secondToken = firstTokens[1];
@ -476,7 +460,7 @@ module.exports = {
!(
(node.object.type === "Literal" &&
typeof node.object.value === "number" &&
/^[0-9]+$/.test(context.getFirstToken(node.object).value))
/^[0-9]+$/.test(sourceCode.getFirstToken(node.object).value))
||
// RegExp literal is allowed to have parens (#1589)
@ -511,6 +495,10 @@ module.exports = {
ReturnStatement: function(node) {
var returnToken = sourceCode.getFirstToken(node);
if (isReturnAssignException(node)) {
return;
}
if (node.argument &&
hasExcessParensNoLineTerminator(returnToken, node.argument) &&

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

@ -22,6 +22,7 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
/**
* Reports an unnecessary semicolon error.
@ -48,7 +49,7 @@ module.exports = {
function checkForPartOfClassBody(firstToken) {
for (var token = firstToken;
token.type === "Punctuator" && token.value !== "}";
token = context.getTokenAfter(token)
token = sourceCode.getTokenAfter(token)
) {
if (token.value === ";") {
report(token);
@ -65,7 +66,16 @@ module.exports = {
*/
EmptyStatement: function(node) {
var parent = node.parent,
allowedParentTypes = ["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement"];
allowedParentTypes = [
"ForStatement",
"ForInStatement",
"ForOfStatement",
"WhileStatement",
"DoWhileStatement",
"IfStatement",
"LabeledStatement",
"WithStatement"
];
if (allowedParentTypes.indexOf(parent.type) === -1) {
report(node);
@ -78,7 +88,7 @@ module.exports = {
* @returns {void}
*/
ClassBody: function(node) {
checkForPartOfClassBody(context.getFirstToken(node, 1)); // 0 is `{`.
checkForPartOfClassBody(sourceCode.getFirstToken(node, 1)); // 0 is `{`.
},
/**
@ -87,7 +97,7 @@ module.exports = {
* @returns {void}
*/
MethodDefinition: function(node) {
checkForPartOfClassBody(context.getTokenAfter(node));
checkForPartOfClassBody(sourceCode.getTokenAfter(node));
}
};

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

@ -179,6 +179,8 @@ module.exports = {
var options = parseOptions(context.options[0]),
operatorAllowed = false;
var sourceCode = context.getSourceCode();
return {
UnaryExpression: function(node) {
@ -188,7 +190,7 @@ module.exports = {
context.report(
node,
"use `Boolean({{code}})` instead.", {
code: context.getSource(node.argument.argument)
code: sourceCode.getText(node.argument.argument)
});
}
@ -198,7 +200,7 @@ module.exports = {
context.report(
node,
"use `{{code}} !== -1` instead.", {
code: context.getSource(node.argument)
code: sourceCode.getText(node.argument)
});
}
@ -208,7 +210,7 @@ module.exports = {
context.report(
node,
"use `Number({{code}})` instead.", {
code: context.getSource(node.argument)
code: sourceCode.getText(node.argument)
});
}
},
@ -224,7 +226,7 @@ module.exports = {
context.report(
node,
"use `Number({{code}})` instead.", {
code: context.getSource(nonNumericOperand)
code: sourceCode.getText(nonNumericOperand)
});
}
@ -234,7 +236,7 @@ module.exports = {
context.report(
node,
"use `String({{code}})` instead.", {
code: context.getSource(getOtherOperand(node, ""))
code: sourceCode.getText(getOtherOperand(node, ""))
});
}
},
@ -247,7 +249,7 @@ module.exports = {
context.report(
node,
"use `{{code}} = String({{code}})` instead.", {
code: context.getSource(getOtherOperand(node, ""))
code: sourceCode.getText(getOtherOperand(node, ""))
});
}
}

5
tools/eslint/lib/rules/no-inline-comments.js

@ -22,6 +22,7 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
/**
* Will check that comments are not on lines starting with or ending with code
@ -32,8 +33,8 @@ module.exports = {
function testCodeAroundComment(node) {
// Get the whole line and cut it off at the start of the comment
var startLine = String(context.getSourceLines()[node.loc.start.line - 1]);
var endLine = String(context.getSourceLines()[node.loc.end.line - 1]);
var startLine = String(sourceCode.lines[node.loc.start.line - 1]);
var endLine = String(sourceCode.lines[node.loc.end.line - 1]);
var preamble = startLine.slice(0, node.loc.start.column).trim();

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

@ -6,6 +6,15 @@
"use strict";
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
var ALL_IRREGULARS = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/;
var IRREGULAR_WHITESPACE = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg;
var IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mg;
var LINE_BREAK = /\r\n|\r|\n|\u2028|\u2029/g;
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -24,6 +33,15 @@ module.exports = {
properties: {
skipComments: {
type: "boolean"
},
skipStrings: {
type: "boolean"
},
skipTemplates: {
type: "boolean"
},
skipRegExps: {
type: "boolean"
}
},
additionalProperties: false
@ -33,9 +51,6 @@ module.exports = {
create: function(context) {
var irregularWhitespace = /[\u0085\u00A0\ufeff\f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg,
irregularLineTerminators = /[\u2028\u2029]/mg;
// Module store of errors that we have found
var errors = [];
@ -45,6 +60,11 @@ module.exports = {
// Lookup the `skipComments` option, which defaults to `false`.
var options = context.options[0] || {};
var skipComments = !!options.skipComments;
var skipStrings = options.skipStrings !== false;
var skipRegExps = !!options.skipRegExps;
var skipTemplates = !!options.skipTemplates;
var sourceCode = context.getSourceCode();
/**
* Removes errors that occur inside a string node
@ -75,10 +95,27 @@ module.exports = {
* @private
*/
function removeInvalidNodeErrorsInIdentifierOrLiteral(node) {
if (typeof node.value === "string") {
var shouldCheckStrings = skipStrings && (typeof node.value === "string");
var shouldCheckRegExps = skipRegExps && (node.value instanceof RegExp);
if (shouldCheckStrings || shouldCheckRegExps) {
// If we have irregular characters remove them from the errors list
if (node.raw.match(irregularWhitespace) || node.raw.match(irregularLineTerminators)) {
if (ALL_IRREGULARS.test(node.raw)) {
removeWhitespaceError(node);
}
}
}
/**
* Checks template string literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
* @param {ASTNode} node to check for matching errors.
* @returns {void}
* @private
*/
function removeInvalidNodeErrorsInTemplateLiteral(node) {
if (typeof node.value.raw === "string") {
if (ALL_IRREGULARS.test(node.value.raw)) {
removeWhitespaceError(node);
}
}
@ -91,7 +128,7 @@ module.exports = {
* @private
*/
function removeInvalidNodeErrorsInComment(node) {
if (node.value.match(irregularWhitespace) || node.value.match(irregularLineTerminators)) {
if (ALL_IRREGULARS.test(node.value)) {
removeWhitespaceError(node);
}
}
@ -103,14 +140,14 @@ module.exports = {
* @private
*/
function checkForIrregularWhitespace(node) {
var sourceLines = context.getSourceLines();
var sourceLines = sourceCode.lines;
sourceLines.forEach(function(sourceLine, lineIndex) {
var lineNumber = lineIndex + 1,
location,
match;
while ((match = irregularWhitespace.exec(sourceLine)) !== null) {
while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) {
location = {
line: lineNumber,
column: match.index
@ -128,15 +165,15 @@ module.exports = {
* @private
*/
function checkForIrregularLineTerminators(node) {
var source = context.getSource(),
sourceLines = context.getSourceLines(),
linebreaks = source.match(/\r\n|\r|\n|\u2028|\u2029/g),
var source = sourceCode.getText(),
sourceLines = sourceCode.lines,
linebreaks = source.match(LINE_BREAK),
lastLineIndex = -1,
lineIndex,
location,
match;
while ((match = irregularLineTerminators.exec(source)) !== null) {
while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) {
lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0;
location = {
@ -166,8 +203,10 @@ module.exports = {
*/
function noop() {}
return {
Program: function(node) {
var nodes = {};
if (ALL_IRREGULARS.test(sourceCode.getText())) {
nodes.Program = function(node) {
/*
* As we can easily fire warnings for all white space issues with
@ -182,13 +221,14 @@ module.exports = {
checkForIrregularWhitespace(node);
checkForIrregularLineTerminators(node);
},
};
Identifier: removeInvalidNodeErrorsInIdentifierOrLiteral,
Literal: removeInvalidNodeErrorsInIdentifierOrLiteral,
LineComment: skipComments ? rememberCommentNode : noop,
BlockComment: skipComments ? rememberCommentNode : noop,
"Program:exit": function() {
nodes.Identifier = removeInvalidNodeErrorsInIdentifierOrLiteral;
nodes.Literal = removeInvalidNodeErrorsInIdentifierOrLiteral;
nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop;
nodes.LineComment = skipComments ? rememberCommentNode : noop;
nodes.BlockComment = skipComments ? rememberCommentNode : noop;
nodes["Program:exit"] = function() {
if (skipComments) {
@ -200,7 +240,11 @@ module.exports = {
errors.forEach(function(error) {
context.report.apply(context, error);
});
}
};
};
} else {
nodes.Program = noop;
}
return nodes;
}
};

2
tools/eslint/lib/rules/no-loop-func.js

@ -73,7 +73,7 @@ function getContainingLoopNode(node) {
* @returns {ASTNode} The most outer loop node.
*/
function getTopLoopNode(node, excludedNode) {
var retv = null;
var retv = node;
var border = excludedNode ? excludedNode.range[1] : 0;
while (node && node.range[0] >= border) {

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

@ -0,0 +1,212 @@
/**
* @fileoverview Rule to disallow mixed binary operators.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var astUtils = require("../ast-utils.js");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
var ARITHMETIC_OPERATORS = ["+", "-", "*", "/", "%", "**"];
var BITWISE_OPERATORS = ["&", "|", "^", "~", "<<", ">>", ">>>"];
var COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="];
var LOGICAL_OPERATORS = ["&&", "||"];
var RELATIONAL_OPERATORS = ["in", "instanceof"];
var ALL_OPERATORS = [].concat(
ARITHMETIC_OPERATORS,
BITWISE_OPERATORS,
COMPARISON_OPERATORS,
LOGICAL_OPERATORS,
RELATIONAL_OPERATORS
);
var DEFAULT_GROUPS = [
ARITHMETIC_OPERATORS,
BITWISE_OPERATORS,
COMPARISON_OPERATORS,
LOGICAL_OPERATORS,
RELATIONAL_OPERATORS
];
var TARGET_NODE_TYPE = /^(?:Binary|Logical)Expression$/;
/**
* Normalizes options.
*
* @param {object|undefined} options - A options object to normalize.
* @returns {object} Normalized option object.
*/
function normalizeOptions(options) {
var hasGroups = (options && options.groups && options.groups.length > 0);
var groups = hasGroups ? options.groups : DEFAULT_GROUPS;
var allowSamePrecedence = (options && options.allowSamePrecedence) !== false;
return {
groups: groups,
allowSamePrecedence: allowSamePrecedence
};
}
/**
* Checks whether any group which includes both given operator exists or not.
*
* @param {Array.<string[]>} groups - A list of groups to check.
* @param {string} left - An operator.
* @param {string} right - Another operator.
* @returns {boolean} `true` if such group existed.
*/
function includesBothInAGroup(groups, left, right) {
return groups.some(function(group) {
return group.indexOf(left) !== -1 && group.indexOf(right) !== -1;
});
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "disallow mixed binary operators",
category: "Stylistic Issues",
recommended: false
},
schema: [
{
type: "object",
properties: {
groups: {
type: "array",
items: {
type: "array",
items: {enum: ALL_OPERATORS},
minItems: 2,
uniqueItems: true
},
uniqueItems: true
},
allowSamePrecedence: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
create: function(context) {
var sourceCode = context.getSourceCode();
var options = normalizeOptions(context.options[0]);
/**
* Checks whether a given node should be ignored by options or not.
*
* @param {ASTNode} node - A node to check. This is a BinaryExpression
* node or a LogicalExpression node. This parent node is one of
* them, too.
* @returns {boolean} `true` if the node should be ignored.
*/
function shouldIgnore(node) {
var a = node;
var b = node.parent;
return (
!includesBothInAGroup(options.groups, a.operator, b.operator) ||
(
options.allowSamePrecedence &&
astUtils.getPrecedence(a) === astUtils.getPrecedence(b)
)
);
}
/**
* Checks whether the operator of a given node is mixed with parent
* node's operator or not.
*
* @param {ASTNode} node - A node to check. This is a BinaryExpression
* node or a LogicalExpression node. This parent node is one of
* them, too.
* @returns {boolean} `true` if the node was mixed.
*/
function isMixedWithParent(node) {
return (
node.operator !== node.parent.operator &&
!astUtils.isParenthesised(sourceCode, node)
);
}
/**
* Gets the operator token of a given node.
*
* @param {ASTNode} node - A node to check. This is a BinaryExpression
* node or a LogicalExpression node.
* @returns {Token} The operator token of the node.
*/
function getOperatorToken(node) {
var token = sourceCode.getTokenAfter(node.left);
while (token.value === ")") {
token = sourceCode.getTokenAfter(token);
}
return token;
}
/**
* Reports both the operator of a given node and the operator of the
* parent node.
*
* @param {ASTNode} node - A node to check. This is a BinaryExpression
* node or a LogicalExpression node. This parent node is one of
* them, too.
* @returns {void}
*/
function reportBothOperators(node) {
var parent = node.parent;
var left = (parent.left === node) ? node : parent;
var right = (parent.left !== node) ? node : parent;
var message =
"Unexpected mix of '" + left.operator + "' and '" +
right.operator + "'.";
context.report({
node: left,
loc: getOperatorToken(left).loc.start,
message: message
});
context.report({
node: right,
loc: getOperatorToken(right).loc.start,
message: message
});
}
/**
* Checks between the operator of this node and the operator of the
* parent node.
*
* @param {ASTNode} node - A node to check.
* @returns {void}
*/
function check(node) {
if (TARGET_NODE_TYPE.test(node.parent.type) &&
isMixedWithParent(node) &&
!shouldIgnore(node)
) {
reportBothOperators(node);
}
}
return {
BinaryExpression: check,
LogicalExpression: check
};
}
};

5
tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js

@ -24,6 +24,7 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
var smartTabs,
ignoredLocs = [];
@ -86,8 +87,8 @@ module.exports = {
*/
var regex = /^(?=[\t ]*(\t | \t))/,
match,
lines = context.getSourceLines(),
comments = context.getAllComments();
lines = sourceCode.lines,
comments = sourceCode.getAllComments();
comments.forEach(function(comment) {
ignoredLocs.push(comment.loc);

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

@ -95,8 +95,9 @@ module.exports = {
return {
Program: function() {
var source = context.getSource(),
allComments = context.getAllComments(),
var sourceCode = context.getSourceCode(),
source = sourceCode.getText(),
allComments = sourceCode.getAllComments(),
pattern = /[^\n\r\u2028\u2029\t ].? {2,}/g, // note: repeating space
token,
previousToken,
@ -121,12 +122,12 @@ module.exports = {
// do not flag anything inside of comments
if (!isIndexInComment(pattern.lastIndex, allComments)) {
token = context.getTokenByRangeStart(pattern.lastIndex);
token = sourceCode.getTokenByRangeStart(pattern.lastIndex);
if (token) {
previousToken = context.getTokenBefore(token);
previousToken = sourceCode.getTokenBefore(token);
if (hasExceptions) {
parent = context.getNodeByRangeIndex(pattern.lastIndex - 1);
parent = sourceCode.getNodeByRangeIndex(pattern.lastIndex - 1);
}
if (!parent || !exceptions[parent.type]) {

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

@ -17,6 +17,8 @@ module.exports = {
recommended: false
},
fixable: "whitespace",
schema: [
{
type: "object",
@ -52,10 +54,12 @@ module.exports = {
if (context.options.length) {
max = context.options[0].max;
maxEOF = context.options[0].maxEOF;
maxBOF = context.options[0].maxBOF;
maxEOF = typeof context.options[0].maxEOF !== "undefined" ? context.options[0].maxEOF : max;
maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max;
}
var sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@ -73,22 +77,35 @@ module.exports = {
},
"Program:exit": function checkBlankLines(node) {
var lines = context.getSourceLines(),
currentLocation = -1,
lastLocation,
var lines = sourceCode.lines,
fullLines = sourceCode.text.match(/.*(\r\n|\r|\n|\u2028|\u2029)/g) || [],
firstNonBlankLine = -1,
trimmedLines = [],
linesRangeStart = [],
blankCounter = 0,
currentLocation,
lastLocation,
location,
firstOfEndingBlankLines,
firstNonBlankLine = -1,
trimmedLines = [];
diff,
fix,
rangeStart,
rangeEnd;
fix = function(fixer) {
return fixer.removeRange([rangeStart, rangeEnd]);
};
linesRangeStart.push(0);
lines.forEach(function(str, i) {
var trimmed = str.trim();
var length = i < fullLines.length ? fullLines[i].length : 0,
trimmed = str.trim();
if ((firstNonBlankLine === -1) && (trimmed !== "")) {
firstNonBlankLine = i;
}
linesRangeStart.push(linesRangeStart[linesRangeStart.length - 1] + length);
trimmedLines.push(trimmed);
});
@ -120,9 +137,17 @@ module.exports = {
// Aggregate and count blank lines
if (firstNonBlankLine > maxBOF) {
context.report(node, 0,
"Too many blank lines at the beginning of file. Max of " + maxBOF + " allowed.");
diff = firstNonBlankLine - maxBOF;
rangeStart = linesRangeStart[firstNonBlankLine - diff];
rangeEnd = linesRangeStart[firstNonBlankLine];
context.report({
node: node,
loc: node.loc.start,
message: "Too many blank lines at the beginning of file. Max of " + maxBOF + " allowed.",
fix: fix
});
}
currentLocation = firstNonBlankLine - 1;
lastLocation = currentLocation;
currentLocation = trimmedLines.indexOf("", currentLocation + 1);
@ -141,20 +166,29 @@ module.exports = {
// within the file, not at the end
if (blankCounter >= max) {
diff = blankCounter - max + 1;
rangeStart = linesRangeStart[location.line - diff];
rangeEnd = linesRangeStart[location.line];
context.report({
node: node,
loc: location,
message: "More than " + max + " blank " + (max === 1 ? "line" : "lines") + " not allowed."
message: "More than " + max + " blank " + (max === 1 ? "line" : "lines") + " not allowed.",
fix: fix
});
}
} else {
// inside the last blank lines
if (blankCounter > maxEOF) {
diff = blankCounter - maxEOF + 1;
rangeStart = linesRangeStart[location.line - diff];
rangeEnd = linesRangeStart[location.line - 1];
context.report({
node: node,
loc: location,
message: "Too many blank lines at the end of file. Max of " + maxEOF + " allowed."
message: "Too many blank lines at the end of file. Max of " + maxEOF + " allowed.",
fix: fix
});
}
}

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

@ -1,5 +1,5 @@
/**
* @fileoverview Rule to flag when re-assigning native objects
* @fileoverview Rule to disallow assignments to native objects or read-only global variables
* @author Ilya Volodin
*/
@ -12,9 +12,9 @@
module.exports = {
meta: {
docs: {
description: "disallow reassigning native objects",
description: "disallow assignments to native objects or read-only global variables",
category: "Best Practices",
recommended: false
recommended: true
},
schema: [
@ -55,14 +55,14 @@ module.exports = {
) {
context.report({
node: identifier,
message: "{{name}} is a read-only native object.",
message: "Read-only global '{{name}}' should not be modified.",
data: identifier
});
}
}
/**
* Reports write references if a given variable is readonly builtin.
* Reports write references if a given variable is read-only builtin.
* @param {Variable} variable - A variable to check.
* @returns {void}
*/

52
tools/eslint/lib/rules/no-prototype-builtins.js

@ -0,0 +1,52 @@
/**
* @fileoverview Rule to disallow use of Object.prototype builtins on objects
* @author Andrew Levine
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "disallow calling some `Object.prototype` methods directly on objects",
category: "Possible Errors",
recommended: false
}
},
create: function(context) {
var DISALLOWED_PROPS = [
"hasOwnProperty",
"isPrototypeOf",
"propertyIsEnumerable"
];
/**
* Reports if a disallowed property is used in a CallExpression
* @param {ASTNode} node The CallExpression node.
* @returns {void}
*/
function disallowBuiltIns(node) {
if (node.callee.type !== "MemberExpression" || node.callee.computed) {
return;
}
var propName = node.callee.property.name;
if (DISALLOWED_PROPS.indexOf(propName) > -1) {
context.report({
message: "Do not access Object.prototype method '{{prop}}' from target object.",
loc: node.callee.property.loc.start,
data: {prop: propName},
node: node
});
}
}
return {
CallExpression: disallowBuiltIns
};
}
};

70
tools/eslint/lib/rules/no-regex-spaces.js

@ -12,7 +12,7 @@
module.exports = {
meta: {
docs: {
description: "disallow multiple spaces in regular expression literals",
description: "disallow multiple spaces in regular expressions",
category: "Possible Errors",
recommended: true
},
@ -21,24 +21,66 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
return {
/**
* Validate regular expressions
* @param {ASTNode} node node to validate
* @param {string} value regular expression to validate
* @returns {void}
* @private
*/
function checkRegex(node, value) {
var multipleSpacesRegex = /( {2,})+?/,
regexResults = multipleSpacesRegex.exec(value);
Literal: function(node) {
var token = context.getFirstToken(node),
nodeType = token.type,
nodeValue = token.value,
multipleSpacesRegex = /( {2,})+?/,
regexResults;
if (regexResults !== null) {
context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}.");
}
}
if (nodeType === "RegularExpression") {
regexResults = multipleSpacesRegex.exec(nodeValue);
/**
* Validate regular expression literals
* @param {ASTNode} node node to validate
* @returns {void}
* @private
*/
function checkLiteral(node) {
var token = sourceCode.getFirstToken(node),
nodeType = token.type,
nodeValue = token.value;
if (regexResults !== null) {
context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}.");
}
}
if (nodeType === "RegularExpression") {
checkRegex(node, nodeValue);
}
}
/**
* Check if node is a string
* @param {ASTNode} node node to evaluate
* @returns {boolean} True if its a string
* @private
*/
function isString(node) {
return node && node.type === "Literal" && typeof node.value === "string";
}
/**
* Validate strings passed to the RegExp constructor
* @param {ASTNode} node node to validate
* @returns {void}
* @private
*/
function checkFunction(node) {
if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0])) {
checkRegex(node, node.arguments[0].value);
}
}
return {
Literal: checkLiteral,
CallExpression: checkFunction,
NewExpression: checkFunction
};
}

64
tools/eslint/lib/rules/no-return-assign.js

@ -8,26 +8,19 @@
// Helpers
//------------------------------------------------------------------------------
/**
* Checks whether or not a node is an `AssignmentExpression`.
* @param {Node|null} node - A node to check.
* @returns {boolean} Whether or not the node is an `AssignmentExpression`.
*/
function isAssignment(node) {
return node && node.type === "AssignmentExpression";
}
var SENTINEL_TYPE = /^(?:[a-zA-Z]+?Statement|ArrowFunctionExpression|FunctionExpression|ClassExpression)$/;
/**
* Checks whether or not a node is enclosed in parentheses.
* @param {Node|null} node - A node to check.
* @param {RuleContext} context - The current context.
* @param {sourceCode} sourceCode - The ESLint SourceCode object.
* @returns {boolean} Whether or not the node is enclosed in parentheses.
*/
function isEnclosedInParens(node, context) {
var prevToken = context.getTokenBefore(node);
var nextToken = context.getTokenAfter(node);
function isEnclosedInParens(node, sourceCode) {
var prevToken = sourceCode.getTokenBefore(node);
var nextToken = sourceCode.getTokenAfter(node);
return prevToken.value === "(" && nextToken.value === ")";
return prevToken && prevToken.value === "(" && nextToken && nextToken.value === ")";
}
//------------------------------------------------------------------------------
@ -51,32 +44,33 @@ module.exports = {
create: function(context) {
var always = (context.options[0] || "except-parens") !== "except-parens";
/**
* Check whether return statement contains assignment
* @param {ASTNode} nodeToCheck node to check
* @param {ASTNode} nodeToReport node to report
* @param {string} message message to report
* @returns {void}
* @private
*/
function checkForAssignInReturn(nodeToCheck, nodeToReport, message) {
if (isAssignment(nodeToCheck) && (always || !isEnclosedInParens(nodeToCheck, context))) {
context.report(nodeToReport, message);
}
}
var sourceCode = context.getSourceCode();
return {
ReturnStatement: function(node) {
var message = "Return statement should not contain assignment.";
AssignmentExpression: function(node) {
if (!always && isEnclosedInParens(node, sourceCode)) {
return;
}
checkForAssignInReturn(node.argument, node, message);
},
ArrowFunctionExpression: function(node) {
if (node.body.type !== "BlockStatement") {
var message = "Arrow function should not return assignment.";
var parent = node.parent;
// Find ReturnStatement or ArrowFunctionExpression in ancestors.
while (parent && !SENTINEL_TYPE.test(parent.type)) {
node = parent;
parent = parent.parent;
}
checkForAssignInReturn(node.body, node, message);
// Reports.
if (parent && parent.type === "ReturnStatement") {
context.report({
node: parent,
message: "Return statement should not contain assignment."
});
} else if (parent && parent.type === "ArrowFunctionExpression" && parent.body === node) {
context.report({
node: parent,
message: "Arrow function should not return assignment."
});
}
}
};

2
tools/eslint/lib/rules/no-script-url.js

@ -14,7 +14,7 @@
module.exports = {
meta: {
docs: {
description: "disallow `javascript",
description: "disallow `javascript:` urls",
category: "Best Practices",
recommended: false
},

14
tools/eslint/lib/rules/no-sequences.js

@ -21,6 +21,7 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
/**
* Parts of the grammar that are required to have parens.
@ -30,7 +31,8 @@ module.exports = {
IfStatement: "test",
SwitchStatement: "discriminant",
WhileStatement: "test",
WithStatement: "object"
WithStatement: "object",
ArrowFunctionExpression: "body"
// Omitting CallExpression - commas are parsed as argument separators
// Omitting NewExpression - commas are parsed as argument separators
@ -55,8 +57,8 @@ module.exports = {
* @returns {boolean} True if the node has a paren on each side.
*/
function isParenthesised(node) {
var previousToken = context.getTokenBefore(node),
nextToken = context.getTokenAfter(node);
var previousToken = sourceCode.getTokenBefore(node),
nextToken = sourceCode.getTokenAfter(node);
return previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
@ -69,8 +71,8 @@ module.exports = {
* @returns {boolean} True if two parens surround the node on each side.
*/
function isParenthesisedTwice(node) {
var previousToken = context.getTokenBefore(node, 1),
nextToken = context.getTokenAfter(node, 1);
var previousToken = sourceCode.getTokenBefore(node, 1),
nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) && previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
@ -97,7 +99,7 @@ module.exports = {
}
}
var child = context.getTokenAfter(node.expressions[0]);
var child = sourceCode.getTokenAfter(node.expressions[0]);
context.report(node, child.loc.start, "Unexpected use of comma operator.");
}

6
tools/eslint/lib/rules/no-unexpected-multiline.js

@ -24,6 +24,8 @@ module.exports = {
var PROPERTY_MESSAGE = "Unexpected newline between object and [ of property access.";
var TAGGED_TEMPLATE_MESSAGE = "Unexpected newline between template tag and template literal.";
var sourceCode = context.getSourceCode();
/**
* Check to see if there is a newline between the node and the following open bracket
* line's expression
@ -34,12 +36,12 @@ module.exports = {
*/
function checkForBreakAfter(node, msg) {
var nodeExpressionEnd = node;
var openParen = context.getTokenAfter(node);
var openParen = sourceCode.getTokenAfter(node);
// Move along until the end of the wrapped expression
while (openParen.value === ")") {
nodeExpressionEnd = openParen;
openParen = context.getTokenAfter(nodeExpressionEnd);
openParen = sourceCode.getTokenAfter(nodeExpressionEnd);
}
if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) {

33
tools/eslint/lib/rules/no-unsafe-finally.js

@ -9,7 +9,10 @@
// Helpers
//------------------------------------------------------------------------------
var SENTINEL_NODE_TYPE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/;
var SENTINEL_NODE_TYPE_RETURN_THROW = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/;
var SENTINEL_NODE_TYPE_BREAK = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement|SwitchStatement)$/;
var SENTINEL_NODE_TYPE_CONTINUE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement)$/;
//------------------------------------------------------------------------------
// Rule Definition
@ -18,9 +21,9 @@ var SENTINEL_NODE_TYPE = /^(?:Program|(?:Function|Class)(?:Declaration|Expressio
module.exports = {
meta: {
docs: {
description: "disallow control flow statements in finally blocks",
description: "disallow control flow statements in `finally` blocks",
category: "Possible Errors",
recommended: false
recommended: true
}
},
create: function(context) {
@ -39,11 +42,29 @@ module.exports = {
* Climbs up the tree if the node is not a sentinel node
*
* @param {ASTNode} node - node to check.
* @param {String} label - label of the break or continue statement
* @returns {Boolean} - return whether the node is a finally block or a sentinel node
*/
function isInFinallyBlock(node) {
while (node && !SENTINEL_NODE_TYPE.test(node.type)) {
function isInFinallyBlock(node, label) {
var labelInside = false;
var sentinelNodeType;
if (node.type === "BreakStatement" && !node.label) {
sentinelNodeType = SENTINEL_NODE_TYPE_BREAK;
} else if (node.type === "ContinueStatement") {
sentinelNodeType = SENTINEL_NODE_TYPE_CONTINUE;
} else {
sentinelNodeType = SENTINEL_NODE_TYPE_RETURN_THROW;
}
while (node && !sentinelNodeType.test(node.type)) {
if (node.parent.label && label && (node.parent.label.name === label.name)) {
labelInside = true;
}
if (isFinallyBlock(node)) {
if (label && labelInside) {
return false;
}
return true;
}
node = node.parent;
@ -58,7 +79,7 @@ module.exports = {
* @returns {void}
*/
function check(node) {
if (isInFinallyBlock(node)) {
if (isInFinallyBlock(node, node.label)) {
context.report({
message: "Unsafe usage of " + node.type,
node: node,

219
tools/eslint/lib/rules/no-unused-vars.js

@ -10,6 +10,7 @@
//------------------------------------------------------------------------------
var lodash = require("lodash");
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
@ -95,6 +96,8 @@ module.exports = {
// Helpers
//--------------------------------------------------------------------------
var STATEMENT_TYPE = /(?:Statement|Declaration)$/;
/**
* Determines if a given variable is being exported from a module.
* @param {Variable} variable - EScope variable object.
@ -124,7 +127,7 @@ module.exports = {
/**
* Determines if a reference is a read operation.
* @param {Reference} ref - An escope Reference
* @returns {Boolean} whether the given reference represents a read operation
* @returns {boolean} whether the given reference represents a read operation
* @private
*/
function isReadRef(ref) {
@ -152,11 +155,204 @@ module.exports = {
return false;
}
/**
* Checks the position of given nodes.
*
* @param {ASTNode} inner - A node which is expected as inside.
* @param {ASTNode} outer - A node which is expected as outside.
* @returns {boolean} `true` if the `inner` node exists in the `outer` node.
*/
function isInside(inner, outer) {
return (
inner.range[0] >= outer.range[0] &&
inner.range[1] <= outer.range[1]
);
}
/**
* If a given reference is left-hand side of an assignment, this gets
* the right-hand side node of the assignment.
*
* @param {escope.Reference} ref - A reference to check.
* @param {ASTNode} prevRhsNode - The previous RHS node.
* @returns {ASTNode} The RHS node.
*/
function getRhsNode(ref, prevRhsNode) {
var id = ref.identifier;
var parent = id.parent;
var granpa = parent.parent;
var refScope = ref.from.variableScope;
var varScope = ref.resolved.scope.variableScope;
var canBeUsedLater = refScope !== varScope;
/*
* Inherits the previous node if this reference is in the node.
* This is for `a = a + a`-like code.
*/
if (prevRhsNode && isInside(id, prevRhsNode)) {
return prevRhsNode;
}
if (parent.type === "AssignmentExpression" &&
granpa.type === "ExpressionStatement" &&
id === parent.left &&
!canBeUsedLater
) {
return parent.right;
}
return null;
}
/**
* Checks whether a given function node is stored to somewhere or not.
* If the function node is stored, the function can be used later.
*
* @param {ASTNode} funcNode - A function node to check.
* @param {ASTNode} rhsNode - The RHS node of the previous assignment.
* @returns {boolean} `true` if under the following conditions:
* - the funcNode is assigned to a variable.
* - the funcNode is bound as an argument of a function call.
* - the function is bound to a property and the object satisfies above conditions.
*/
function isStorableFunction(funcNode, rhsNode) {
var node = funcNode;
var parent = funcNode.parent;
while (parent && isInside(parent, rhsNode)) {
switch (parent.type) {
case "SequenceExpression":
if (parent.expressions[parent.expressions.length - 1] !== node) {
return false;
}
break;
case "CallExpression":
case "NewExpression":
return parent.callee !== node;
case "AssignmentExpression":
case "TaggedTemplateExpression":
case "YieldExpression":
return true;
default:
if (STATEMENT_TYPE.test(parent.type)) {
/*
* If it encountered statements, this is a complex pattern.
* Since analyzeing complex patterns is hard, this returns `true` to avoid false positive.
*/
return true;
}
}
node = parent;
parent = parent.parent;
}
return false;
}
/**
* Checks whether a given Identifier node exists inside of a function node which can be used later.
*
* "can be used later" means:
* - the function is assigned to a variable.
* - the function is bound to a property and the object can be used later.
* - the function is bound as an argument of a function call.
*
* If a reference exists in a function which can be used later, the reference is read when the function is called.
*
* @param {ASTNode} id - An Identifier node to check.
* @param {ASTNode} rhsNode - The RHS node of the previous assignment.
* @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later.
*/
function isInsideOfStorableFunction(id, rhsNode) {
var funcNode = astUtils.getUpperFunction(id);
return (
funcNode &&
isInside(funcNode, rhsNode) &&
isStorableFunction(funcNode, rhsNode)
);
}
/**
* Checks whether a given reference is a read to update itself or not.
*
* @param {escope.Reference} ref - A reference to check.
* @param {ASTNode} rhsNode - The RHS node of the previous assignment.
* @returns {boolean} The reference is a read to update itself.
*/
function isReadForItself(ref, rhsNode) {
var id = ref.identifier;
var parent = id.parent;
var granpa = parent.parent;
return ref.isRead() && (
// self update. e.g. `a += 1`, `a++`
(
parent.type === "AssignmentExpression" &&
granpa.type === "ExpressionStatement" &&
parent.left === id
) ||
(
parent.type === "UpdateExpression" &&
granpa.type === "ExpressionStatement"
) ||
// in RHS of an assignment for itself. e.g. `a = a + 1`
(
rhsNode &&
isInside(id, rhsNode) &&
!isInsideOfStorableFunction(id, rhsNode)
)
);
}
/**
* Determine if an identifier is used either in for-in loops.
*
* @param {Reference} ref - The reference to check.
* @returns {boolean} whether reference is used in the for-in loops
* @private
*/
function isForInRef(ref) {
var target = ref.identifier.parent;
// "for (var ...) { return; }"
if (target.type === "VariableDeclarator") {
target = target.parent.parent;
}
if (target.type !== "ForInStatement") {
return false;
}
// "for (...) { return; }"
if (target.body.type === "BlockStatement") {
target = target.body.body[0];
// "for (...) return;"
} else {
target = target.body;
}
// For empty loop body
if (!target) {
return false;
}
return target.type === "ReturnStatement";
}
/**
* Determines if the variable is used.
* @param {Variable} variable - The variable to check.
* @param {Reference[]} references - The variable references to check.
* @returns {boolean} True if the variable is used
* @private
*/
function isUsedVariable(variable) {
var functionNodes = variable.defs.filter(function(def) {
@ -164,10 +360,23 @@ module.exports = {
}).map(function(def) {
return def.node;
}),
isFunctionDefinition = functionNodes.length > 0;
isFunctionDefinition = functionNodes.length > 0,
rhsNode = null;
return variable.references.some(function(ref) {
return isReadRef(ref) && !(isFunctionDefinition && isSelfReference(ref, functionNodes));
if (isForInRef(ref)) {
return true;
}
var forItself = isReadForItself(ref, rhsNode);
rhsNode = getRhsNode(ref, rhsNode);
return (
isReadRef(ref) &&
!forItself &&
!(isFunctionDefinition && isSelfReference(ref, functionNodes))
);
});
}
@ -268,6 +477,7 @@ module.exports = {
* @param {escope.Variable} variable - A variable to get.
* @param {ASTNode} comment - A comment node which includes the variable name.
* @returns {number} The index of the variable name's location.
* @private
*/
function getColumnInComment(variable, comment) {
var namePattern = new RegExp("[\\s,]" + lodash.escapeRegExp(variable.name) + "(?:$|[\\s,:])", "g");
@ -287,6 +497,7 @@ module.exports = {
*
* @param {escope.Variable} variable - A variable to get its location.
* @returns {{line: number, column: number}} The location object for the variable.
* @private
*/
function getLocation(variable) {
var comment = variable.eslintExplicitGlobalComment;

18
tools/eslint/lib/rules/no-useless-call.js

@ -32,12 +32,12 @@ function isCallOrNonVariadicApply(node) {
* Checks whether or not the tokens of two given nodes are same.
* @param {ASTNode} left - A node 1 to compare.
* @param {ASTNode} right - A node 2 to compare.
* @param {RuleContext} context - The ESLint rule context object.
* @param {SourceCode} sourceCode - The ESLint source code object.
* @returns {boolean} the source code for the given node.
*/
function equalTokens(left, right, context) {
var tokensL = context.getTokens(left);
var tokensR = context.getTokens(right);
function equalTokens(left, right, sourceCode) {
var tokensL = sourceCode.getTokens(left);
var tokensR = sourceCode.getTokens(right);
if (tokensL.length !== tokensR.length) {
return false;
@ -57,14 +57,14 @@ function equalTokens(left, right, context) {
* Checks whether or not `thisArg` is not changed by `.call()`/`.apply()`.
* @param {ASTNode|null} expectedThis - The node that is the owner of the applied function.
* @param {ASTNode} thisArg - The node that is given to the first argument of the `.call()`/`.apply()`.
* @param {RuleContext} context - The ESLint rule context object.
* @param {SourceCode} sourceCode - The ESLint source code object.
* @returns {boolean} Whether or not `thisArg` is not changed by `.call()`/`.apply()`.
*/
function isValidThisArg(expectedThis, thisArg, context) {
function isValidThisArg(expectedThis, thisArg, sourceCode) {
if (!expectedThis) {
return astUtils.isNullOrUndefined(thisArg);
}
return equalTokens(expectedThis, thisArg, context);
return equalTokens(expectedThis, thisArg, sourceCode);
}
//------------------------------------------------------------------------------
@ -83,6 +83,8 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
return {
CallExpression: function(node) {
if (!isCallOrNonVariadicApply(node)) {
@ -93,7 +95,7 @@ module.exports = {
var expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
var thisArg = node.arguments[0];
if (isValidThisArg(expectedThis, thisArg, context)) {
if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
context.report(
node,
"unnecessary '.{{name}}()'.",

4
tools/eslint/lib/rules/no-useless-computed-key.js

@ -19,6 +19,8 @@ module.exports = {
}
},
create: function(context) {
var sourceCode = context.getSourceCode();
return {
Property: function(node) {
if (!node.computed) {
@ -29,7 +31,7 @@ module.exports = {
nodeType = typeof key.value;
if (key.type === "Literal" && (nodeType === "string" || nodeType === "number")) {
context.report(node, MESSAGE_UNNECESSARY_COMPUTED, { property: context.getSource(key) });
context.report(node, MESSAGE_UNNECESSARY_COMPUTED, { property: sourceCode.getText(key) });
}
}
};

6
tools/eslint/lib/rules/no-useless-concat.js

@ -67,6 +67,8 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
return {
BinaryExpression: function(node) {
@ -85,10 +87,10 @@ module.exports = {
) {
// move warning location to operator
var operatorToken = context.getTokenAfter(left);
var operatorToken = sourceCode.getTokenAfter(left);
while (operatorToken.value !== "+") {
operatorToken = context.getTokenAfter(operatorToken);
operatorToken = sourceCode.getTokenAfter(operatorToken);
}
context.report(

150
tools/eslint/lib/rules/no-useless-rename.js

@ -0,0 +1,150 @@
/**
* @fileoverview Disallow renaming import, export, and destructured assignments to the same name.
* @author Kai Cataldo
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "disallow renaming import, export, and destructured assignments to the same name",
category: "ECMAScript 6",
recommended: false
},
fixable: "code",
schema: [
{
type: "object",
properties: {
ignoreDestructuring: { type: "boolean" },
ignoreImport: { type: "boolean" },
ignoreExport: { type: "boolean" }
},
additionalProperties: false
}
]
},
create: function(context) {
var options = context.options[0] || {},
ignoreDestructuring = options.ignoreDestructuring === true,
ignoreImport = options.ignoreImport === true,
ignoreExport = options.ignoreExport === true;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Reports error for unnecessarily renamed assignments
* @param {ASTNode} node - node to report
* @param {ASTNode} initial - node with initial name value
* @param {ASTNode} result - node with new name value
* @param {string} type - the type of the offending node
* @returns {void}
*/
function reportError(node, initial, result, type) {
var name = initial.type === "Identifier" ? initial.name : initial.value;
return context.report({
node: node,
message: "{{type}} {{name}} unnecessarily renamed.",
data: {
name: name,
type: type
},
fix: function(fixer) {
return fixer.replaceTextRange([
initial.range[0],
result.range[1]
], name);
}
});
}
/**
* Checks whether a destructured assignment is unnecessarily renamed
* @param {ASTNode} node - node to check
* @returns {void}
*/
function checkDestructured(node) {
var properties,
i;
if (ignoreDestructuring) {
return;
}
properties = node.properties;
for (i = 0; i < properties.length; i++) {
if (properties[i].shorthand) {
continue;
}
/**
* If an ObjectPattern property is computed, we have no idea
* if a rename is useless or not. If an ObjectPattern property
* lacks a key, it is likely an ExperimentalRestProperty and
* so there is no "renaming" occurring here.
*/
if (properties[i].computed || !properties[i].key) {
continue;
}
if (properties[i].key.type === "Identifier" && properties[i].key.name === properties[i].value.name ||
properties[i].key.type === "Literal" && properties[i].key.value === properties[i].value.name) {
reportError(properties[i], properties[i].key, properties[i].value, "Destructuring assignment");
}
}
}
/**
* Checks whether an import is unnecessarily renamed
* @param {ASTNode} node - node to check
* @returns {void}
*/
function checkImport(node) {
if (ignoreImport) {
return;
}
if (node.imported.name === node.local.name &&
node.imported.range[0] !== node.local.range[0]) {
reportError(node, node.imported, node.local, "Import");
}
}
/**
* Checks whether an export is unnecessarily renamed
* @param {ASTNode} node - node to check
* @returns {void}
*/
function checkExport(node) {
if (ignoreExport) {
return;
}
if (node.local.name === node.exported.name &&
node.local.range[0] !== node.exported.range[0]) {
reportError(node, node.local, node.exported, "Export");
}
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
ObjectPattern: checkDestructured,
ImportSpecifier: checkImport,
ExportSpecifier: checkExport
};
}
};

209
tools/eslint/lib/rules/object-curly-newline.js

@ -0,0 +1,209 @@
/**
* @fileoverview Rule to require or disallow line breaks inside braces.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
// Schema objects.
var OPTION_VALUE = {
oneOf: [
{
enum: ["always", "never"]
},
{
type: "object",
properties: {
multiline: {
type: "boolean"
},
minProperties: {
type: "integer",
minimum: 0
}
},
additionalProperties: false,
minProperties: 1
}
]
};
/**
* Normalizes a given option value.
*
* @param {string|object|undefined} value - An option value to parse.
* @returns {{multiline: boolean, minProperties: number}} Normalized option object.
*/
function normalizeOptionValue(value) {
var multiline = false;
var minProperties = Number.POSITIVE_INFINITY;
if (value) {
if (value === "always") {
minProperties = 0;
} else if (value === "never") {
minProperties = Number.POSITIVE_INFINITY;
} else {
multiline = Boolean(value.multiline);
minProperties = value.minProperties || Number.POSITIVE_INFINITY;
}
} else {
multiline = true;
}
return {multiline: multiline, minProperties: minProperties};
}
/**
* Normalizes a given option value.
*
* @param {string|object|undefined} options - An option value to parse.
* @returns {{ObjectExpression: {multiline: boolean, minProperties: number}, ObjectPattern: {multiline: boolean, minProperties: number}}} Normalized option object.
*/
function normalizeOptions(options) {
if (options && (options.ObjectExpression || options.ObjectPattern)) {
return {
ObjectExpression: normalizeOptionValue(options.ObjectExpression),
ObjectPattern: normalizeOptionValue(options.ObjectPattern)
};
}
var value = normalizeOptionValue(options);
return {ObjectExpression: value, ObjectPattern: value};
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "enforce consistent line breaks inside braces",
category: "Stylistic Issues",
recommended: false
},
fixable: "whitespace",
schema: [
{
oneOf: [
OPTION_VALUE,
{
type: "object",
properties: {
ObjectExpression: OPTION_VALUE,
ObjectPattern: OPTION_VALUE
},
additionalProperties: false,
minProperties: 1
}
]
}
]
},
create: function(context) {
var sourceCode = context.getSourceCode();
var normalizedOptions = normalizeOptions(context.options[0]);
/**
* Reports a given node if it violated this rule.
*
* @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node.
* @param {{multiline: boolean, minProperties: number}} options - An option object.
* @returns {void}
*/
function check(node) {
var options = normalizedOptions[node.type];
var openBrace = sourceCode.getFirstToken(node);
var closeBrace = sourceCode.getLastToken(node);
var first = sourceCode.getTokenOrCommentAfter(openBrace);
var last = sourceCode.getTokenOrCommentBefore(closeBrace);
var needsLinebreaks = (
node.properties.length >= options.minProperties ||
(
options.multiline &&
node.properties.length > 0 &&
first.loc.start.line !== last.loc.end.line
)
);
/*
* Use tokens or comments to check multiline or not.
* But use only tokens to check whether line breaks are needed.
* This allows:
* var obj = { // eslint-disable-line foo
* a: 1
* }
*/
first = sourceCode.getTokenAfter(openBrace);
last = sourceCode.getTokenBefore(closeBrace);
if (needsLinebreaks) {
if (astUtils.isTokenOnSameLine(openBrace, first)) {
context.report({
message: "Expected a line break after this open brace.",
node: node,
loc: openBrace.loc.start,
fix: function(fixer) {
return fixer.insertTextAfter(openBrace, "\n");
}
});
}
if (astUtils.isTokenOnSameLine(last, closeBrace)) {
context.report({
message: "Expected a line break before this close brace.",
node: node,
loc: closeBrace.loc.start,
fix: function(fixer) {
return fixer.insertTextBefore(closeBrace, "\n");
}
});
}
} else {
if (!astUtils.isTokenOnSameLine(openBrace, first)) {
context.report({
message: "Unexpected a line break after this open brace.",
node: node,
loc: openBrace.loc.start,
fix: function(fixer) {
return fixer.removeRange([
openBrace.range[1],
first.range[0]
]);
}
});
}
if (!astUtils.isTokenOnSameLine(last, closeBrace)) {
context.report({
message: "Unexpected a line break before this close brace.",
node: node,
loc: closeBrace.loc.start,
fix: function(fixer) {
return fixer.removeRange([
last.range[1],
closeBrace.range[0]
]);
}
});
}
}
}
return {
ObjectExpression: check,
ObjectPattern: check
};
}
};

2
tools/eslint/lib/rules/object-curly-spacing.js

@ -171,7 +171,7 @@ module.exports = {
closingCurlyBraceMustBeSpaced = (
options.arraysInObjectsException && penultimateType === "ArrayExpression" ||
options.objectsInObjectsException && penultimateType === "ObjectExpression"
options.objectsInObjectsException && (penultimateType === "ObjectExpression" || penultimateType === "ObjectPattern")
) ? !options.spaced : options.spaced;
lastSpaced = sourceCode.isSpaceBetweenTokens(penultimate, last);

73
tools/eslint/lib/rules/object-property-newline.js

@ -0,0 +1,73 @@
/**
* @fileoverview Rule to enforce placing object properties on separate lines.
* @author Vitor Balocco
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "enforce placing object properties on separate lines",
category: "Stylistic Issues",
recommended: false
},
schema: [
{
type: "object",
properties: {
allowMultiplePropertiesPerLine: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
create: function(context) {
var allowSameLine = context.options[0] && Boolean(context.options[0].allowMultiplePropertiesPerLine);
var errorMessage = allowSameLine ?
"Object properties must go on a new line if they aren't all on the same line" :
"Object properties must go on a new line";
var sourceCode = context.getSourceCode();
return {
ObjectExpression: function(node) {
var lastTokenOfPreviousProperty, firstTokenOfCurrentProperty;
if (allowSameLine) {
if (node.properties.length > 1) {
var firstTokenOfFirstProperty = sourceCode.getFirstToken(node.properties[0]);
var lastTokenOfLastProperty = sourceCode.getLastToken(node.properties[node.properties.length - 1]);
if (firstTokenOfFirstProperty.loc.end.line === lastTokenOfLastProperty.loc.start.line) {
// All keys and values are on the same line
return;
}
}
}
for (var i = 1; i < node.properties.length; i++) {
lastTokenOfPreviousProperty = sourceCode.getLastToken(node.properties[i - 1]);
firstTokenOfCurrentProperty = sourceCode.getFirstToken(node.properties[i]);
if (lastTokenOfPreviousProperty.loc.end.line === firstTokenOfCurrentProperty.loc.start.line) {
context.report({
node: node,
loc: firstTokenOfCurrentProperty.loc.start,
message: errorMessage
});
}
}
}
};
}
};

146
tools/eslint/lib/rules/object-shorthand.js

@ -23,6 +23,8 @@ module.exports = {
recommended: false
},
fixable: "code",
schema: {
anyOf: [
{
@ -35,6 +37,25 @@ module.exports = {
minItems: 0,
maxItems: 1
},
{
type: "array",
items: [
{
enum: ["always", "methods", "properties"]
},
{
type: "object",
properties: {
avoidQuotes: {
type: "boolean"
}
},
additionalProperties: false
}
],
minItems: 0,
maxItems: 2
},
{
type: "array",
items: [
@ -46,6 +67,9 @@ module.exports = {
properties: {
ignoreConstructors: {
type: "boolean"
},
avoidQuotes: {
type: "boolean"
}
},
additionalProperties: false
@ -66,6 +90,7 @@ module.exports = {
var PARAMS = context.options[1] || {};
var IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors;
var AVOID_QUOTES = PARAMS.avoidQuotes;
//--------------------------------------------------------------------------
// Helpers
@ -83,6 +108,15 @@ module.exports = {
return firstChar === firstChar.toUpperCase();
}
/**
* Checks whether a node is a string literal.
* @param {ASTNode} node - Any AST node.
* @returns {boolean} `true` if it is a string literal.
*/
function isStringLiteral(node) {
return node.type === "Literal" && typeof node.value === "string";
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@ -97,45 +131,127 @@ module.exports = {
return;
}
// if we're "never" and concise we should warn now
if (APPLY_NEVER && isConciseProperty) {
type = node.method ? "method" : "property";
context.report(node, "Expected longform " + type + " syntax.");
}
// at this point if we're concise or if we're "never" we can leave
if (APPLY_NEVER || isConciseProperty) {
// getters and setters are ignored
if (node.kind === "get" || node.kind === "set") {
return;
}
// only computed methods can fail the following checks
if (!APPLY_TO_METHODS && node.computed) {
if (node.computed && node.value.type !== "FunctionExpression") {
return;
}
// getters and setters are ignored
if (node.kind === "get" || node.kind === "set") {
//--------------------------------------------------------------
// Checks for property/method shorthand.
if (isConciseProperty) {
// if we're "never" and concise we should warn now
if (APPLY_NEVER) {
type = node.method ? "method" : "property";
context.report({
node: node,
message: "Expected longform " + type + " syntax.",
fix: function(fixer) {
if (node.method) {
if (node.value.generator) {
return fixer.replaceTextRange([node.range[0], node.key.range[1]], node.key.name + ": function*");
}
return fixer.insertTextAfter(node.key, ": function");
}
return fixer.insertTextAfter(node.key, ": " + node.key.name);
}
});
}
// {'xyz'() {}} should be written as {'xyz': function() {}}
if (AVOID_QUOTES && isStringLiteral(node.key)) {
context.report({
node: node,
message: "Expected longform method syntax for string literal keys.",
fix: function(fixer) {
if (node.computed) {
return fixer.insertTextAfterRange([node.key.range[0], node.key.range[1] + 1], ": function");
}
return fixer.insertTextAfter(node.key, ": function");
}
});
}
return;
}
//--------------------------------------------------------------
// Checks for longform properties.
if (node.value.type === "FunctionExpression" && !node.value.id && APPLY_TO_METHODS) {
if (IGNORE_CONSTRUCTORS && isConstructor(node.key.name)) {
return;
}
if (AVOID_QUOTES && isStringLiteral(node.key)) {
return;
}
// {[x]: function(){}} should be written as {[x]() {}}
if (node.computed) {
context.report({
node: node,
message: "Expected method shorthand.",
fix: function(fixer) {
if (node.value.generator) {
return fixer.replaceTextRange(
[node.key.range[0], node.value.range[0] + "function*".length],
"*[" + node.key.name + "]"
);
}
return fixer.removeRange([node.key.range[1] + 1, node.value.range[0] + "function".length]);
}
});
return;
}
// {x: function(){}} should be written as {x() {}}
context.report(node, "Expected method shorthand.");
context.report({
node: node,
message: "Expected method shorthand.",
fix: function(fixer) {
if (node.value.generator) {
return fixer.replaceTextRange(
[node.key.range[0], node.value.range[0] + "function*".length],
"*" + node.key.name
);
}
return fixer.removeRange([node.key.range[1], node.value.range[0] + "function".length]);
}
});
} else if (node.value.type === "Identifier" && node.key.name === node.value.name && APPLY_TO_PROPS) {
// {x: x} should be written as {x}
context.report(node, "Expected property shorthand.");
context.report({
node: node,
message: "Expected property shorthand.",
fix: function(fixer) {
return fixer.replaceText(node, node.value.name);
}
});
} else if (node.value.type === "Identifier" && node.key.type === "Literal" && node.key.value === node.value.name && APPLY_TO_PROPS) {
if (AVOID_QUOTES) {
return;
}
// {"x": x} should be written as {x}
context.report(node, "Expected property shorthand.");
context.report({
node: node,
message: "Expected property shorthand.",
fix: function(fixer) {
return fixer.replaceText(node, node.value.name);
}
});
}
}
};
}
};

3
tools/eslint/lib/rules/one-var.js

@ -286,6 +286,9 @@ module.exports = {
context.report(node, "Combine this with the previous '" + type + "' statement with initialized variables.");
}
if (options[type].uninitialized === MODE_ALWAYS) {
if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) {
return;
}
context.report(node, "Combine this with the previous '" + type + "' statement with uninitialized variables.");
}
}

10
tools/eslint/lib/rules/operator-linebreak.js

@ -57,6 +57,8 @@ module.exports = {
styleOverrides[":"] = "before";
}
var sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@ -69,8 +71,8 @@ module.exports = {
* @returns {void}
*/
function validateNode(node, leftSide) {
var leftToken = context.getLastToken(leftSide);
var operatorToken = context.getTokenAfter(leftToken);
var leftToken = sourceCode.getLastToken(leftSide);
var operatorToken = sourceCode.getTokenAfter(leftToken);
// When the left part of a binary expression is a single expression wrapped in
// parentheses (ex: `(a) + b`), leftToken will be the last token of the expression
@ -79,10 +81,10 @@ module.exports = {
// should be the token right after that.
while (operatorToken.value === ")") {
leftToken = operatorToken;
operatorToken = context.getTokenAfter(operatorToken);
operatorToken = sourceCode.getTokenAfter(operatorToken);
}
var rightToken = context.getTokenAfter(operatorToken);
var rightToken = sourceCode.getTokenAfter(operatorToken);
var operator = operatorToken.value;
var operatorStyleOverride = styleOverrides[operator];
var style = operatorStyleOverride || globalStyle;

20
tools/eslint/lib/rules/padded-blocks.js

@ -17,6 +17,8 @@ module.exports = {
recommended: false
},
fixable: "whitespace",
schema: [
{
oneOf: [
@ -164,6 +166,9 @@ module.exports = {
context.report({
node: node,
loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column },
fix: function(fixer) {
return fixer.insertTextAfter(openBrace, "\n");
},
message: ALWAYS_MESSAGE
});
}
@ -171,23 +176,36 @@ module.exports = {
context.report({
node: node,
loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 },
fix: function(fixer) {
return fixer.insertTextBefore(closeBrace, "\n");
},
message: ALWAYS_MESSAGE
});
}
} else {
if (blockHasTopPadding) {
var nextToken = sourceCode.getTokenOrCommentAfter(openBrace);
context.report({
node: node,
loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column },
fix: function(fixer) {
return fixer.replaceTextRange([openBrace.end, nextToken.start - nextToken.loc.start.column], "\n");
},
message: NEVER_MESSAGE
});
}
if (blockHasBottomPadding) {
var previousToken = sourceCode.getTokenOrCommentBefore(closeBrace);
context.report({
node: node,
loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 },
message: NEVER_MESSAGE
message: NEVER_MESSAGE,
fix: function(fixer) {
return fixer.replaceTextRange([previousToken.end, closeBrace.start - closeBrace.loc.start.column], "\n");
}
});
}
}

196
tools/eslint/lib/rules/prefer-const.js

@ -10,6 +10,7 @@
//------------------------------------------------------------------------------
var Map = require("es6-map");
var lodash = require("lodash");
//------------------------------------------------------------------------------
// Helpers
@ -62,45 +63,72 @@ function canBecomeVariableDeclaration(identifier) {
}
/**
* Gets the WriteReference of a given variable if the variable should be
* declared as const.
* Gets an identifier node of a given variable.
*
* If the initialization exists or one or more reading references exist before
* the first assignment, the identifier node is the node of the declaration.
* Otherwise, the identifier node is the node of the first assignment.
*
* If the variable should not change to const, this function returns null.
* - If the variable is reassigned.
* - If the variable is never initialized and assigned.
* - If the variable is initialized in a different scope from the declaration.
* - If the unique assignment of the variable cannot change to a declaration.
*
* @param {escope.Variable} variable - A variable to get.
* @returns {escope.Reference|null} The singular WriteReference or null.
* @param {boolean} ignoreReadBeforeAssign -
* The value of `ignoreReadBeforeAssign` option.
* @returns {ASTNode|null}
* An Identifier node if the variable should change to const.
* Otherwise, null.
*/
function getWriteReferenceIfShouldBeConst(variable) {
function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
if (variable.eslintUsed) {
return null;
}
// Finds the singular WriteReference.
var retv = null;
// Finds the unique WriteReference.
var writer = null;
var isReadBeforeInit = false;
var references = variable.references;
for (var i = 0; i < references.length; ++i) {
var reference = references[i];
if (reference.isWrite()) {
var isReassigned = Boolean(
retv && retv.identifier !== reference.identifier
var isReassigned = (
writer !== null &&
writer.identifier !== reference.identifier
);
if (isReassigned) {
return null;
}
retv = reference;
writer = reference;
} else if (reference.isRead() && writer === null) {
if (ignoreReadBeforeAssign) {
return null;
}
isReadBeforeInit = true;
}
}
// Checks the writer is located in the same scope and can be modified to
// const.
var isSameScopeAndCanBecomeVariableDeclaration = Boolean(
retv &&
retv.from === variable.scope &&
canBecomeVariableDeclaration(retv.identifier)
// If the assignment is from a different scope, ignore it.
// If the assignment cannot change to a declaration, ignore it.
var shouldBeConst = (
writer !== null &&
writer.from === variable.scope &&
canBecomeVariableDeclaration(writer.identifier)
);
return isSameScopeAndCanBecomeVariableDeclaration ? retv : null;
if (!shouldBeConst) {
return null;
}
if (isReadBeforeInit) {
return variable.defs[0].name;
}
return writer.identifier;
}
/**
@ -136,15 +164,17 @@ function getDestructuringHost(reference) {
* destructuring.
*
* @param {escope.Variable[]} variables - Variables to group by destructuring.
* @returns {Map<ASTNode, (escope.Reference|null)[]>} Grouped references.
* @param {boolean} ignoreReadBeforeAssign -
* The value of `ignoreReadBeforeAssign` option.
* @returns {Map<ASTNode, ASTNode[]>} Grouped identifier nodes.
*/
function groupByDestructuring(variables) {
var writersMap = new Map();
function groupByDestructuring(variables, ignoreReadBeforeAssign) {
var identifierMap = new Map();
for (var i = 0; i < variables.length; ++i) {
var variable = variables[i];
var references = variable.references;
var writer = getWriteReferenceIfShouldBeConst(variable);
var identifier = getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign);
var prevId = null;
for (var j = 0; j < references.length; ++j) {
@ -158,20 +188,38 @@ function groupByDestructuring(variables) {
}
prevId = id;
// Add the writer into the destructuring group.
// Add the identifier node into the destructuring group.
var group = getDestructuringHost(reference);
if (group) {
if (writersMap.has(group)) {
writersMap.get(group).push(writer);
if (identifierMap.has(group)) {
identifierMap.get(group).push(identifier);
} else {
writersMap.set(group, [writer]);
identifierMap.set(group, [identifier]);
}
}
}
}
return writersMap;
return identifierMap;
}
/**
* Finds the nearest parent of node with a given type.
*
* @param {ASTNode} node The node to search from.
* @param {string} type The type field of the parent node.
* @param {function} shouldStop a predicate that returns true if the traversal should stop, and false otherwise.
* @returns {ASTNode} The closest ancestor with the specified type; null if no such ancestor exists.
*/
function findUp(node, type, shouldStop) {
if (!node || shouldStop(node)) {
return null;
}
if (node.type === type) {
return node;
}
return findUp(node.parent, type, shouldStop);
}
//------------------------------------------------------------------------------
@ -186,11 +234,14 @@ module.exports = {
recommended: false
},
fixable: "code",
schema: [
{
type: "object",
properties: {
destructuring: {enum: ["any", "all"]}
destructuring: {enum: ["any", "all"]},
ignoreReadBeforeAssign: {type: "boolean"}
},
additionalProperties: false
}
@ -200,22 +251,64 @@ module.exports = {
create: function(context) {
var options = context.options[0] || {};
var checkingMixedDestructuring = options.destructuring !== "all";
var ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true;
var variables = null;
/**
* Reports a given reference.
* Reports a given Identifier node.
*
* @param {escope.Reference} reference - A reference to report.
* @param {ASTNode} node - An Identifier node to report.
* @returns {void}
*/
function report(reference) {
var id = reference.identifier;
function report(node) {
var reportArgs = {
node: node,
message: "'{{name}}' is never reassigned. Use 'const' instead.",
data: node
},
varDeclParent = findUp(node, "VariableDeclaration", function(parentNode) {
return lodash.endsWith(parentNode.type, "Statement");
}),
isNormalVarDecl = (node.parent.parent.parent.type === "ForInStatement" ||
node.parent.parent.parent.type === "ForOfStatement" ||
node.parent.init),
isDestructuringVarDecl =
// {let {a} = obj} should be written as {const {a} = obj}
(node.parent.parent.type === "ObjectPattern" &&
// If options.destucturing is "all", then this warning will not occur unless
// every assignment in the destructuring should be const. In that case, it's safe
// to apply the fix. Otherwise, it's safe to apply the fix if there's only one
// assignment occurring. If there is more than one assignment and options.destructuring
// is not "all", then it's not clear how the developer would want to resolve the issue,
// so we should not attempt to do it programmatically.
(options.destructuring === "all" || node.parent.parent.properties.length === 1)) ||
// {let [a] = [1]} should be written as {const [a] = [1]}
(node.parent.type === "ArrayPattern" &&
// See note above about fixing multiple warnings at once.
(options.destructuring === "all" || node.parent.elements.length === 1));
if (varDeclParent &&
(isNormalVarDecl || isDestructuringVarDecl) &&
// If there are multiple variable declarations, like {let a = 1, b = 2}, then
// do not attempt to fix if one of the declarations should be `const`. It's
// too hard to know how the developer would want to automatically resolve the issue.
varDeclParent.declarations.length === 1) {
reportArgs.fix = function(fixer) {
return fixer.replaceTextRange(
[varDeclParent.start, varDeclParent.start + "let".length],
"const"
);
};
}
context.report({
node: id,
message: "'{{name}}' is never reassigned, use 'const' instead.",
data: id
});
context.report(reportArgs);
}
/**
@ -225,30 +318,30 @@ module.exports = {
* @returns {void}
*/
function checkVariable(variable) {
var writer = getWriteReferenceIfShouldBeConst(variable);
var node = getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign);
if (writer) {
report(writer);
if (node) {
report(node);
}
}
/**
* Reports given references if all of the reference should be declared as
* const.
* Reports given identifier nodes if all of the nodes should be declared
* as const.
*
* The argument 'writers' is an array of references.
* This reference is the result of
* 'getWriteReferenceIfShouldBeConst(variable)', so it's nullable.
* In simple declaration or assignment cases, the length of the array is 1.
* In destructuring cases, the length of the array can be 2 or more.
* The argument 'nodes' is an array of Identifier nodes.
* This node is the result of 'getIdentifierIfShouldBeConst()', so it's
* nullable. In simple declaration or assignment cases, the length of
* the array is 1. In destructuring cases, the length of the array can
* be 2 or more.
*
* @param {(escope.Reference|null)[]} writers - References which are grouped
* by destructuring to report.
* @param {(escope.Reference|null)[]} nodes -
* References which are grouped by destructuring to report.
* @returns {void}
*/
function checkGroup(writers) {
if (writers.every(Boolean)) {
writers.forEach(report);
function checkGroup(nodes) {
if (nodes.every(Boolean)) {
nodes.forEach(report);
}
}
@ -261,7 +354,8 @@ module.exports = {
if (checkingMixedDestructuring) {
variables.forEach(checkVariable);
} else {
groupByDestructuring(variables).forEach(checkGroup);
groupByDestructuring(variables, ignoreReadBeforeAssign)
.forEach(checkGroup);
}
variables = null;

12
tools/eslint/lib/rules/prefer-spread.js

@ -31,12 +31,12 @@ function isVariadicApplyCalling(node) {
* Checks whether or not the tokens of two given nodes are same.
* @param {ASTNode} left - A node 1 to compare.
* @param {ASTNode} right - A node 2 to compare.
* @param {RuleContext} context - The ESLint rule context object.
* @param {SourceCode} sourceCode - The ESLint source code object.
* @returns {boolean} the source code for the given node.
*/
function equalTokens(left, right, context) {
var tokensL = context.getTokens(left);
var tokensR = context.getTokens(right);
function equalTokens(left, right, sourceCode) {
var tokensL = sourceCode.getTokens(left);
var tokensR = sourceCode.getTokens(right);
if (tokensL.length !== tokensR.length) {
return false;
@ -82,6 +82,8 @@ module.exports = {
},
create: function(context) {
var sourceCode = context.getSourceCode();
return {
CallExpression: function(node) {
if (!isVariadicApplyCalling(node)) {
@ -92,7 +94,7 @@ module.exports = {
var expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
var thisArg = node.arguments[0];
if (isValidThisArg(expectedThis, thisArg, context)) {
if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
context.report(node, "use the spread operator instead of the '.apply()'.");
}
}

2
tools/eslint/lib/rules/require-yield.js

@ -14,7 +14,7 @@ module.exports = {
docs: {
description: "require generator functions to contain `yield`",
category: "ECMAScript 6",
recommended: false
recommended: true
},
schema: []

107
tools/eslint/lib/rules/rest-spread-spacing.js

@ -0,0 +1,107 @@
/**
* @fileoverview Enforce spacing between rest and spread operators and their expressions.
* @author Kai Cataldo
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "enforce spacing between rest and spread operators and their expressions",
category: "ECMAScript 6",
recommended: false
},
fixable: "whitespace",
schema: [
{
enum: ["always", "never"]
}
]
},
create: function(context) {
var sourceCode = context.getSourceCode(),
alwaysSpace = context.options[0] === "always";
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Checks whitespace between rest/spread operators and their expressions
* @param {ASTNode} node - The node to check
* @returns {void}
*/
function checkWhiteSpace(node) {
var operator = sourceCode.getFirstToken(node),
nextToken = sourceCode.getTokenAfter(operator),
hasWhitespace = sourceCode.isSpaceBetweenTokens(operator, nextToken),
type;
switch (node.type) {
case "SpreadElement":
type = "spread";
break;
case "RestElement":
type = "rest";
break;
case "ExperimentalSpreadProperty":
type = "spread property";
break;
case "ExperimentalRestProperty":
type = "rest property";
break;
default:
return;
}
if (alwaysSpace && !hasWhitespace) {
context.report({
node: node,
loc: {
line: operator.loc.end.line,
column: operator.loc.end.column
},
message: "Expected whitespace after {{type}} operator",
data: {
type: type
},
fix: function(fixer) {
return fixer.replaceTextRange([operator.range[1], nextToken.range[0]], " ");
}
});
} else if (!alwaysSpace && hasWhitespace) {
context.report({
node: node,
loc: {
line: operator.loc.end.line,
column: operator.loc.end.column
},
message: "Unexpected whitespace after {{type}} operator",
data: {
type: type
},
fix: function(fixer) {
return fixer.removeRange([operator.range[1], nextToken.range[0]]);
}
});
}
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
SpreadElement: checkWhiteSpace,
RestElement: checkWhiteSpace,
ExperimentalSpreadProperty: checkWhiteSpace,
ExperimentalRestProperty: checkWhiteSpace
};
}
};

20
tools/eslint/lib/rules/semi-spacing.js

@ -59,7 +59,7 @@ module.exports = {
* @returns {boolean} True if the given token has leading space, false if not.
*/
function hasLeadingSpace(token) {
var tokenBefore = context.getTokenBefore(token);
var tokenBefore = sourceCode.getTokenBefore(token);
return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token);
}
@ -70,7 +70,7 @@ module.exports = {
* @returns {boolean} True if the given token has trailing space, false if not.
*/
function hasTrailingSpace(token) {
var tokenAfter = context.getTokenAfter(token);
var tokenAfter = sourceCode.getTokenAfter(token);
return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter);
}
@ -81,7 +81,7 @@ module.exports = {
* @returns {boolean} Whether or not the token is the last in its line.
*/
function isLastTokenInCurrentLine(token) {
var tokenAfter = context.getTokenAfter(token);
var tokenAfter = sourceCode.getTokenAfter(token);
return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter));
}
@ -92,7 +92,7 @@ module.exports = {
* @returns {boolean} Whether or not the token is the first in its line.
*/
function isFirstTokenInCurrentLine(token) {
var tokenBefore = context.getTokenBefore(token);
var tokenBefore = sourceCode.getTokenBefore(token);
return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore));
}
@ -103,7 +103,7 @@ module.exports = {
* @returns {boolean} Whether or not the next token of a given token is a closing parenthesis.
*/
function isBeforeClosingParen(token) {
var nextToken = context.getTokenAfter(token);
var nextToken = sourceCode.getTokenAfter(token);
return (
nextToken &&
@ -140,7 +140,7 @@ module.exports = {
loc: location,
message: "Unexpected whitespace before semicolon.",
fix: function(fixer) {
var tokenBefore = context.getTokenBefore(token);
var tokenBefore = sourceCode.getTokenBefore(token);
return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
}
@ -167,7 +167,7 @@ module.exports = {
loc: location,
message: "Unexpected whitespace after semicolon.",
fix: function(fixer) {
var tokenAfter = context.getTokenAfter(token);
var tokenAfter = sourceCode.getTokenAfter(token);
return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
}
@ -195,7 +195,7 @@ module.exports = {
* @returns {void}
*/
function checkNode(node) {
var token = context.getLastToken(node);
var token = sourceCode.getLastToken(node);
checkSemicolonSpacing(token, node);
}
@ -210,11 +210,11 @@ module.exports = {
ThrowStatement: checkNode,
ForStatement: function(node) {
if (node.init) {
checkSemicolonSpacing(context.getTokenAfter(node.init), node);
checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
}
if (node.test) {
checkSemicolonSpacing(context.getTokenAfter(node.test), node);
checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node);
}
}
};

6
tools/eslint/lib/rules/semi.js

@ -121,7 +121,7 @@ module.exports = {
return false;
}
nextToken = context.getTokenAfter(lastToken);
nextToken = sourceCode.getTokenAfter(lastToken);
if (!nextToken) {
return true;
@ -141,7 +141,7 @@ module.exports = {
* @returns {boolean} whether the node is in a one-liner block statement.
*/
function isOneLinerBlock(node) {
var nextToken = context.getTokenAfter(node);
var nextToken = sourceCode.getTokenAfter(node);
if (!nextToken || nextToken.value !== "}") {
return false;
@ -159,7 +159,7 @@ module.exports = {
* @returns {void}
*/
function checkForSemicolon(node) {
var lastToken = context.getLastToken(node);
var lastToken = sourceCode.getLastToken(node);
if (never) {
if (isUnnecessarySemicolon(lastToken)) {

6
tools/eslint/lib/rules/space-before-blocks.js

@ -81,7 +81,7 @@ module.exports = {
* @returns {void} undefined.
*/
function checkPrecedingSpace(node) {
var precedingToken = context.getTokenBefore(node),
var precedingToken = sourceCode.getTokenBefore(node),
hasSpace,
parent,
requireSpace;
@ -133,9 +133,9 @@ module.exports = {
if (cases.length > 0) {
firstCase = cases[0];
openingBrace = context.getTokenBefore(firstCase);
openingBrace = sourceCode.getTokenBefore(firstCase);
} else {
openingBrace = context.getLastToken(node, 1);
openingBrace = sourceCode.getLastToken(node, 1);
}
checkPrecedingSpace(openingBrace);

2
tools/eslint/lib/rules/space-before-function-paren.js

@ -106,7 +106,7 @@ module.exports = {
while (rightToken.value !== "(") {
rightToken = sourceCode.getTokenAfter(rightToken);
}
leftToken = context.getTokenBefore(rightToken);
leftToken = sourceCode.getTokenBefore(rightToken);
location = leftToken.loc.end;
if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) {

10
tools/eslint/lib/rules/space-infix-ops.js

@ -41,6 +41,8 @@ module.exports = {
"?", ":", ",", "**"
];
var sourceCode = context.getSourceCode();
/**
* Returns the first token which violates the rule
* @param {ASTNode} left - The left node of the main node
@ -50,7 +52,7 @@ module.exports = {
*/
function getFirstNonSpacedToken(left, right) {
var op,
tokens = context.getTokensBetween(left, right, 1);
tokens = sourceCode.getTokensBetween(left, right, 1);
for (var i = 1, l = tokens.length - 1; i < l; ++i) {
op = tokens[i];
@ -78,8 +80,8 @@ module.exports = {
loc: culpritToken.loc.start,
message: "Infix operators must be spaced.",
fix: function(fixer) {
var previousToken = context.getTokenBefore(culpritToken);
var afterToken = context.getTokenAfter(culpritToken);
var previousToken = sourceCode.getTokenBefore(culpritToken);
var afterToken = sourceCode.getTokenAfter(culpritToken);
var fixString = "";
if (culpritToken.range[0] - previousToken.range[1] === 0) {
@ -107,7 +109,7 @@ module.exports = {
var nonSpacedNode = getFirstNonSpacedToken(node.left, node.right);
if (nonSpacedNode) {
if (!(int32Hint && context.getSource(node).substr(-2) === "|0")) {
if (!(int32Hint && sourceCode.getText(node).substr(-2) === "|0")) {
report(node, nonSpacedNode);
}
}

6
tools/eslint/lib/rules/space-unary-ops.js

@ -43,6 +43,8 @@ module.exports = {
create: function(context) {
var options = context.options && Array.isArray(context.options) && context.options[0] || { words: true, nonwords: false };
var sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@ -158,7 +160,7 @@ module.exports = {
* @returns {void}
*/
function checkForSpacesAfterYield(node) {
var tokens = context.getFirstTokens(node, 3),
var tokens = sourceCode.getFirstTokens(node, 3),
word = "yield";
if (!node.argument || node.delegate) {
@ -239,7 +241,7 @@ module.exports = {
* @returns {void}
*/
function checkForSpaces(node) {
var tokens = context.getFirstTokens(node, 2),
var tokens = sourceCode.getFirstTokens(node, 2),
firstToken = tokens[0],
secondToken = tokens[1];

43
tools/eslint/lib/rules/strict.js

@ -23,7 +23,9 @@ var messages = {
unnecessary: "Unnecessary 'use strict' directive.",
module: "'use strict' is unnecessary inside of modules.",
implied: "'use strict' is unnecessary when implied strict mode is enabled.",
unnecessaryInClasses: "'use strict' is unnecessary inside of classes."
unnecessaryInClasses: "'use strict' is unnecessary inside of classes.",
nonSimpleParameterList: "'use strict' directive inside a function with non-simple parameter list throws a syntax error since ES2016.",
wrap: "Wrap this function in a function with 'use strict' directive."
};
/**
@ -53,6 +55,26 @@ function getUseStrictDirectives(statements) {
return directives;
}
/**
* Checks whether a given parameter is a simple parameter.
*
* @param {ASTNode} node - A pattern node to check.
* @returns {boolean} `true` if the node is an Identifier node.
*/
function isSimpleParameter(node) {
return node.type === "Identifier";
}
/**
* Checks whether a given parameter list is a simple parameter list.
*
* @param {ASTNode[]} params - A parameter list to check.
* @returns {boolean} `true` if the every parameter is an Identifier node.
*/
function isSimpleParameterList(params) {
return params.every(isSimpleParameter);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -136,7 +158,9 @@ module.exports = {
isStrict = useStrictDirectives.length > 0;
if (isStrict) {
if (isParentStrict) {
if (!isSimpleParameterList(node.params)) {
context.report(useStrictDirectives[0], messages.nonSimpleParameterList);
} else if (isParentStrict) {
context.report(useStrictDirectives[0], messages.unnecessary);
} else if (isInClass) {
context.report(useStrictDirectives[0], messages.unnecessaryInClasses);
@ -144,7 +168,11 @@ module.exports = {
reportAllExceptFirst(useStrictDirectives, messages.multiple);
} else if (isParentGlobal) {
context.report(node, messages.function);
if (isSimpleParameterList(node.params)) {
context.report(node, messages.function);
} else {
context.report(node, messages.wrap);
}
}
scopes.push(isParentStrict || isStrict);
@ -172,8 +200,13 @@ module.exports = {
if (mode === "function") {
enterFunctionInFunctionMode(node, useStrictDirectives);
} else {
reportAll(useStrictDirectives, messages[mode]);
} else if (useStrictDirectives.length > 0) {
if (isSimpleParameterList(node.params)) {
reportAll(useStrictDirectives, messages[mode]);
} else {
context.report(useStrictDirectives[0], messages.nonSimpleParameterList);
reportAllExceptFirst(useStrictDirectives, messages.multiple);
}
}
}

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

Loading…
Cancel
Save