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 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. * 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. **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 * `"off"` or `0` - turn the rule off
* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) * `"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 ## Sponsors
* Development is sponsored by [Box](https://box.com)
* Site search ([eslint.org](http://eslint.org)) is sponsored by [Algolia](https://www.algolia.com) * Site search ([eslint.org](http://eslint.org)) is sponsored by [Algolia](https://www.algolia.com)
## Team ## Team
These folks keep the project moving and are resources for help: These folks keep the project moving and are resources for help.
* Nicholas C. Zakas ([@nzakas](https://github.com/nzakas)) - project lead ### Technical Steering Committee (TSC)
* Ilya Volodin ([@ilyavolodin](https://github.com/ilyavolodin)) - reviewer
* Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer * Nicholas C. Zakas ([@nzakas](https://github.com/nzakas))
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer * Ilya Volodin ([@ilyavolodin](https://github.com/ilyavolodin))
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - reviewer * Brandon Mills ([@btmills](https://github.com/btmills))
* Alberto Rodríguez ([@alberto](https://github.com/alberto)) - reviewer * Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps))
* Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - committer * Toru Nagashima ([@mysticatea](https://github.com/mysticatea))
* Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - committer * Alberto Rodríguez ([@alberto](https://github.com/alberto))
* Ian VanSchooten ([@ianvs](https://github.com/ianvs)) - committer
* Burak Yiğit Kaya ([@byk](https://github.com/byk)) - committer ### Development Team
* Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo)) - committer
* Michael Ficarra ([@michaelficarra](https://github.com/michaelficarra)) - committer * Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma))
* Mark Pedrotti ([@pedrottimark](https://github.com/pedrottimark)) - committer * Jamund Ferguson ([@xjamundx](https://github.com/xjamundx))
* Oleg Gaidarenko ([@markelog](https://github.com/markelog)) - committer * Ian VanSchooten ([@ianvs](https://github.com/ianvs))
* Mike Sherov [@mikesherov](https://github.com/mikesherov)) - committer * Burak Yiğit Kaya ([@byk](https://github.com/byk))
* Henry Zhu ([@hzoo](https://github.com/hzoo)) - committer * Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo))
* Marat Dulin ([@mdevils](https://github.com/mdevils)) - committer * Michael Ficarra ([@michaelficarra](https://github.com/michaelficarra))
* Alexej Yaroshevich ([@zxqfox](https://github.com/zxqfox)) - committer * 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 ## 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) * [Proposing a Rule Change](http://eslint.org/docs/developer-guide/contributing/rule-changes)
* [Request a Change](http://eslint.org/docs/developer-guide/contributing/changes) * [Request a Change](http://eslint.org/docs/developer-guide/contributing/changes)
## Frequently Asked Questions ## Semantic Versioning Policy
### Why don't you like JSHint??? 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? ### How does ESLint performance compare to JSHint?

14
tools/eslint/bin/eslint.js

@ -77,16 +77,4 @@ if (useStdIn) {
exitCode = cli.execute(process.argv); exitCode = cli.execute(process.argv);
} }
// https://github.com/eslint/eslint/issues/4691 process.exitCode = exitCode;
// 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);
});
}

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

@ -5,8 +5,6 @@
"use strict"; "use strict";
var DEFAULT_PARSER = require("../conf/eslint.json").parser;
module.exports = { module.exports = {
configFile: null, configFile: null,
baseConfig: false, baseConfig: false,
@ -18,8 +16,9 @@ module.exports = {
extensions: [".js"], extensions: [".js"],
ignore: true, ignore: true,
ignorePath: null, ignorePath: null,
parser: DEFAULT_PARSER, parser: "", // must be empty
cache: false, cache: false,
// in order to honor the cacheFile option if specified // in order to honor the cacheFile option if specified
// this option should not have a default value otherwise // this option should not have a default value otherwise
// it will always be used // 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-debugger": "error",
"no-delete-var": "error", "no-delete-var": "error",
"no-div-regex": "off", "no-div-regex": "off",
"no-dupe-args": "error",
"no-dupe-class-members": "error", "no-dupe-class-members": "error",
"no-dupe-keys": "error", "no-dupe-keys": "error",
"no-dupe-args": "error",
"no-duplicate-case": "error", "no-duplicate-case": "error",
"no-duplicate-imports": "off", "no-duplicate-imports": "off",
"no-else-return": "off", "no-else-return": "off",
@ -55,13 +55,14 @@
"no-lone-blocks": "off", "no-lone-blocks": "off",
"no-lonely-if": "off", "no-lonely-if": "off",
"no-loop-func": "off", "no-loop-func": "off",
"no-magic-numbers": "off",
"no-mixed-operators": "off",
"no-mixed-requires": "off", "no-mixed-requires": "off",
"no-mixed-spaces-and-tabs": "error", "no-mixed-spaces-and-tabs": "error",
"linebreak-style": "off",
"no-multi-spaces": "off", "no-multi-spaces": "off",
"no-multi-str": "off", "no-multi-str": "off",
"no-multiple-empty-lines": "off", "no-multiple-empty-lines": "off",
"no-native-reassign": "off", "no-native-reassign": "error",
"no-negated-condition": "off", "no-negated-condition": "off",
"no-negated-in-lhs": "error", "no-negated-in-lhs": "error",
"no-nested-ternary": "off", "no-nested-ternary": "off",
@ -80,6 +81,7 @@
"no-process-env": "off", "no-process-env": "off",
"no-process-exit": "off", "no-process-exit": "off",
"no-proto": "off", "no-proto": "off",
"no-prototype-builtins": "off",
"no-redeclare": "error", "no-redeclare": "error",
"no-regex-spaces": "error", "no-regex-spaces": "error",
"no-restricted-globals": "off", "no-restricted-globals": "off",
@ -109,7 +111,7 @@
"no-unmodified-loop-condition": "off", "no-unmodified-loop-condition": "off",
"no-unneeded-ternary": "off", "no-unneeded-ternary": "off",
"no-unreachable": "error", "no-unreachable": "error",
"no-unsafe-finally": "off", "no-unsafe-finally": "error",
"no-unused-expressions": "off", "no-unused-expressions": "off",
"no-unused-labels": "error", "no-unused-labels": "error",
"no-unused-vars": "error", "no-unused-vars": "error",
@ -119,11 +121,11 @@
"no-useless-concat": "off", "no-useless-concat": "off",
"no-useless-constructor": "off", "no-useless-constructor": "off",
"no-useless-escape": "off", "no-useless-escape": "off",
"no-useless-rename": "off",
"no-void": "off", "no-void": "off",
"no-var": "off", "no-var": "off",
"no-warning-comments": "off", "no-warning-comments": "off",
"no-with": "off", "no-with": "off",
"no-magic-numbers": "off",
"array-bracket-spacing": "off", "array-bracket-spacing": "off",
"array-callback-return": "off", "array-callback-return": "off",
"arrow-body-style": "off", "arrow-body-style": "off",
@ -135,10 +137,10 @@
"brace-style": "off", "brace-style": "off",
"callback-return": "off", "callback-return": "off",
"camelcase": "off", "camelcase": "off",
"comma-dangle": "error", "comma-dangle": "off",
"comma-spacing": "off", "comma-spacing": "off",
"comma-style": "off", "comma-style": "off",
"complexity": ["off", 11], "complexity": "off",
"computed-property-spacing": "off", "computed-property-spacing": "off",
"consistent-return": "off", "consistent-return": "off",
"consistent-this": "off", "consistent-this": "off",
@ -155,15 +157,19 @@
"global-require": "off", "global-require": "off",
"guard-for-in": "off", "guard-for-in": "off",
"handle-callback-err": "off", "handle-callback-err": "off",
"id-blacklist": "off",
"id-length": "off", "id-length": "off",
"id-match": "off",
"indent": "off", "indent": "off",
"init-declarations": "off", "init-declarations": "off",
"jsx-quotes": "off", "jsx-quotes": "off",
"key-spacing": "off", "key-spacing": "off",
"keyword-spacing": "off", "keyword-spacing": "off",
"linebreak-style": "off",
"lines-around-comment": "off", "lines-around-comment": "off",
"max-depth": "off", "max-depth": "off",
"max-len": "off", "max-len": "off",
"max-lines": "off",
"max-nested-callbacks": "off", "max-nested-callbacks": "off",
"max-params": "off", "max-params": "off",
"max-statements": "off", "max-statements": "off",
@ -173,7 +179,9 @@
"newline-after-var": "off", "newline-after-var": "off",
"newline-before-return": "off", "newline-before-return": "off",
"newline-per-chained-call": "off", "newline-per-chained-call": "off",
"object-curly-newline": "off",
"object-curly-spacing": ["off", "never"], "object-curly-spacing": ["off", "never"],
"object-property-newline": "off",
"object-shorthand": "off", "object-shorthand": "off",
"one-var": "off", "one-var": "off",
"one-var-declaration-per-line": "off", "one-var-declaration-per-line": "off",
@ -189,14 +197,13 @@
"quote-props": "off", "quote-props": "off",
"quotes": "off", "quotes": "off",
"radix": "off", "radix": "off",
"id-match": "off",
"id-blacklist": "off",
"require-jsdoc": "off", "require-jsdoc": "off",
"require-yield": "off", "require-yield": "error",
"rest-spread-spacing": "off",
"semi": "off", "semi": "off",
"semi-spacing": "off", "semi-spacing": "off",
"sort-vars": "off",
"sort-imports": "off", "sort-imports": "off",
"sort-vars": "off",
"space-before-blocks": "off", "space-before-blocks": "off",
"space-before-function-paren": "off", "space-before-function-paren": "off",
"space-in-parens": "off", "space-in-parens": "off",
@ -205,6 +212,7 @@
"spaced-comment": "off", "spaced-comment": "off",
"strict": "off", "strict": "off",
"template-curly-spacing": "off", "template-curly-spacing": "off",
"unicode-bom": "off",
"use-isnan": "error", "use-isnan": "error",
"valid-jsdoc": "off", "valid-jsdoc": "off",
"valid-typeof": "error", "valid-typeof": "error",

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

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

9
tools/eslint/lib/cli.js

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

62
tools/eslint/lib/config.js

@ -71,7 +71,7 @@ function loadConfig(configToLoad) {
/** /**
* Get personal config object from ~/.eslintrc. * 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 * @private
*/ */
function getPersonalConfig() { 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), localConfigFiles = thisConfig.findLocalConfigFiles(directory),
numFiles = localConfigFiles.length, numFiles = localConfigFiles.length,
rootPath, rootPath,
projectConfigPath = ConfigFile.getFilenameForDirectory(thisConfig.options.cwd); projectConfigPath = ConfigFile.getFilenameForDirectory(thisConfig.options.cwd),
personalConfig;
for (i = 0; i < numFiles; i++) { for (i = 0; i < numFiles; i++) {
@ -140,8 +150,34 @@ function getLocalConfig(thisConfig, directory) {
config = ConfigOps.merge(localConfig, config); config = ConfigOps.merge(localConfig, config);
} }
// Use the personal config file if there are no other local config files found. if (!found && !thisConfig.useSpecificConfig) {
return found || thisConfig.useSpecificConfig ? config : ConfigOps.merge(config, getPersonalConfig());
/*
* - 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 // 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 // Step 3: Merge in the user-specified configuration from .eslintrc and package.json
config = ConfigOps.merge(config, userConfig); config = ConfigOps.merge(config, userConfig);
@ -256,6 +292,20 @@ Config.prototype.getConfig = function(filePath) {
// Step 7: Merge in command line globals // Step 7: Merge in command line globals
config = ConfigOps.merge(config, { globals: this.globals }); config = ConfigOps.merge(config, { globals: this.globals });
// Only override parser if it is passed explicitly through the command line or if it's not
// 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 // Step 8: Merge in command line plugins
if (this.options.plugins) { if (this.options.plugins) {
debug("Merging command line plugins"); debug("Merging command line plugins");

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

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

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

@ -46,7 +46,6 @@ function writeFile(config, format) {
extname = ".json"; extname = ".json";
} }
ConfigFile.write(config, "./.eslintrc" + extname); ConfigFile.write(config, "./.eslintrc" + extname);
log.info("Successfully created .eslintrc" + extname + " file in " + process.cwd()); log.info("Successfully created .eslintrc" + extname + " file in " + process.cwd());
@ -318,7 +317,8 @@ function promptUser(callback) {
message: "Which style guide do you want to follow?", message: "Which style guide do you want to follow?",
choices: [{name: "Google", value: "google"}, {name: "AirBnB", value: "airbnb"}, {name: "Standard", value: "standard"}], choices: [{name: "Google", value: "google"}, {name: "AirBnB", value: "airbnb"}, {name: "Standard", value: "standard"}],
when: function(answers) { 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", default: "JavaScript",
choices: ["JavaScript", "YAML", "JSON"], choices: ["JavaScript", "YAML", "JSON"],
when: function(answers) { when: function(answers) {
return (answers.source === "guide" || answers.source === "auto"); return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto");
} }
} }
], function(earlyAnswers) { ], function(earlyAnswers) {
// early exit if you are using a style guide // early exit if you are using a style guide
if (earlyAnswers.source === "guide") { if (earlyAnswers.source === "guide") {
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 { try {
config = getConfigForStyleGuide(earlyAnswers.styleguide); config = getConfigForStyleGuide(earlyAnswers.styleguide);
writeFile(config, earlyAnswers.format); 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) { RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce(function(map, value, index) {
map[value] = index; map[value] = index;
return map; return map;
}, {}); }, {}),
VALID_SEVERITIES = [0, 1, 2, "off", "warn", "error"];
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Public Interface // Public Interface
@ -248,6 +249,30 @@ module.exports = {
} }
return (typeof severity === "number" && severity === 2); 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 // Requirements
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
var debug = require("debug"), var envs = require("../../conf/environments");
envs = require("../../conf/environments");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Private // Private
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
debug = debug("eslint:enviroments");
var environments = Object.create(null); var environments = Object.create(null);
/** /**

59
tools/eslint/lib/eslint.js

@ -9,25 +9,25 @@
// Requirements // Requirements
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
var lodash = require("lodash"), var assert = require("assert"),
Traverser = require("./util/traverser"), EventEmitter = require("events").EventEmitter,
escope = require("escope"), escope = require("escope"),
Environments = require("./config/environments"), levn = require("levn"),
lodash = require("lodash"),
blankScriptAST = require("../conf/blank-script.json"), blankScriptAST = require("../conf/blank-script.json"),
rules = require("./rules"), DEFAULT_PARSER = require("../conf/eslint.json").parser,
RuleContext = require("./rule-context"), replacements = require("../conf/replacements.json"),
timing = require("./timing"), CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
SourceCode = require("./util/source-code"),
NodeEventGenerator = require("./util/node-event-generator"),
CommentEventGenerator = require("./util/comment-event-generator"),
EventEmitter = require("events").EventEmitter,
ConfigOps = require("./config/config-ops"), ConfigOps = require("./config/config-ops"),
validator = require("./config/config-validator"), validator = require("./config/config-validator"),
replacements = require("../conf/replacements.json"), Environments = require("./config/environments"),
assert = require("assert"), CommentEventGenerator = require("./util/comment-event-generator"),
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"); NodeEventGenerator = require("./util/node-event-generator"),
SourceCode = require("./util/source-code"),
var DEFAULT_PARSER = require("../conf/eslint.json").parser; Traverser = require("./util/traverser"),
RuleContext = require("./rule-context"),
rules = require("./rules"),
timing = require("./timing");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
@ -80,6 +80,25 @@ function parseBooleanConfig(string, comment) {
function parseJsonConfig(string, location, messages) { function parseJsonConfig(string, location, messages) {
var items = {}; 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,"); string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,");
try { try {
items = JSON.parse("{" + string + "}"); items = JSON.parse("{" + string + "}");
@ -962,8 +981,14 @@ module.exports = (function() {
source: sourceCode.lines[location.line - 1] || "" 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 // ensure there's range and text properties, otherwise it's not a valid fix
if (fix && Array.isArray(fix.range) && (typeof fix.text === "string") && (!meta || meta.fixable)) { 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; problem.fix = fix;
} }

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

@ -69,64 +69,6 @@ function normalizeDirectoryEntries(entries, directory, supportedConfigs) {
return fileHash; 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 * Find all instances of files with the specified file names, in directory and
* parent directories. Cache the results. * parent directories. Cache the results.
@ -146,7 +88,9 @@ FileFinder.prototype.findAllInDirectoryAndParents = function(directory) {
j, j,
searched; searched;
if (!directory) { if (directory) {
directory = path.resolve(this.cwd, directory);
} else {
directory = this.cwd; 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 ESLINT_IGNORE_FILENAME = ".eslintignore";
var DEFAULT_IGNORE_PATTERNS = [ var DEFAULT_IGNORE_DIRS = [
"/node_modules/*", "node_modules/",
"/bower_components/*" "bower_components/"
]; ];
var DEFAULT_OPTIONS = { var DEFAULT_OPTIONS = {
dotfiles: false, dotfiles: false,
@ -97,7 +97,9 @@ function IgnoredPaths(options) {
return ig.add(fs.readFileSync(filepath, "utf8")); 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.baseDir = options.cwd;
this.ig = { this.ig = {
@ -124,11 +126,6 @@ function IgnoredPaths(options) {
if (options.ignore !== false) { if (options.ignore !== false) {
var ignorePath; var ignorePath;
if (options.ignorePattern) {
addPattern(this.ig.custom, options.ignorePattern);
addPattern(this.ig.default, options.ignorePattern);
}
if (options.ignorePath) { if (options.ignorePath) {
debug("Using specific ignore file"); debug("Using specific ignore file");
@ -159,6 +156,10 @@ function IgnoredPaths(options) {
addIgnoreFile(this.ig.default, ignorePath); addIgnoreFile(this.ig.default, ignorePath);
} }
if (options.ignorePattern) {
addPattern(this.ig.custom, options.ignorePattern);
addPattern(this.ig.default, options.ignorePattern);
}
} }
this.options = options; 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; module.exports = IgnoredPaths;

3
tools/eslint/lib/options.js

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

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

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

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

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

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

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

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

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

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

@ -16,6 +16,8 @@ module.exports = {
recommended: false recommended: false
}, },
fixable: "code",
schema: [ schema: [
{ {
enum: ["always", "as-needed"] enum: ["always", "as-needed"]
@ -28,28 +30,48 @@ module.exports = {
var asNeededMessage = "Unexpected parentheses around single function argument"; var asNeededMessage = "Unexpected parentheses around single function argument";
var asNeeded = context.options[0] === "as-needed"; var asNeeded = context.options[0] === "as-needed";
var sourceCode = context.getSourceCode();
/** /**
* Determines whether a arrow function argument end with `)` * Determines whether a arrow function argument end with `)`
* @param {ASTNode} node The arrow function node. * @param {ASTNode} node The arrow function node.
* @returns {void} * @returns {void}
*/ */
function parens(node) { function parens(node) {
var token = context.getFirstToken(node); var token = sourceCode.getFirstToken(node);
// as-needed: x => x // as-needed: x => x
if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") { if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") {
if (token.type === "Punctuator" && token.value === "(") { 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; return;
} }
if (token.type === "Identifier") { if (token.type === "Identifier") {
var after = context.getTokenAfter(token); var after = sourceCode.getTokenAfter(token);
// (x) => x // (x) => x
if (after.value !== ")") { 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.before = option.before !== false;
rule.after = option.after !== false; rule.after = option.after !== false;
var sourceCode = context.getSourceCode();
/** /**
* Get tokens of arrow(`=>`) and before/after arrow. * Get tokens of arrow(`=>`) and before/after arrow.
* @param {ASTNode} node The arrow function node. * @param {ASTNode} node The arrow function node.
* @returns {Object} Tokens of arrow and before/after arrow. * @returns {Object} Tokens of arrow and before/after arrow.
*/ */
function getTokens(node) { function getTokens(node) {
var t = context.getFirstToken(node); var t = sourceCode.getFirstToken(node);
var before; var before;
while (t.type !== "Punctuator" || t.value !== "=>") { while (t.type !== "Punctuator" || t.value !== "=>") {
before = t; 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 }; return { before: before, arrow: t, after: after };
} }

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

@ -39,11 +39,11 @@ module.exports = {
function getOpenBrace(node) { function getOpenBrace(node) {
if (node.type === "SwitchStatement") { if (node.type === "SwitchStatement") {
if (node.cases.length > 0) { 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. // Gets braces and the first/last token of content.
var openBrace = getOpenBrace(node); var openBrace = getOpenBrace(node);
var closeBrace = context.getLastToken(node); var closeBrace = sourceCode.getLastToken(node);
var firstToken = sourceCode.getTokenOrCommentAfter(openBrace); var firstToken = sourceCode.getTokenOrCommentAfter(openBrace);
var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace); var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace);

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

@ -24,7 +24,8 @@ module.exports = {
create: function(context) { create: function(context) {
var callbacks = context.options[0] || ["callback", "cb", "next"]; var callbacks = context.options[0] || ["callback", "cb", "next"],
sourceCode = context.getSourceCode();
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Helpers // Helpers
@ -46,13 +47,34 @@ module.exports = {
return node.parent; 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. * Check to see if a CallExpression is in our callback list.
* @param {ASTNode} node The node to check against our callback names list. * @param {ASTNode} node The node to check against our callback names list.
* @returns {Boolean} Whether or not this function matches our callback name. * @returns {Boolean} Whether or not this function matches our callback name.
*/ */
function isCallback(node) { 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 { return {
CallExpression: function(node) { CallExpression: function(node) {
// if we"re not a callback we can return // if we're not a callback we can return
if (!isCallback(node)) { if (!isCallback(node)) {
return; return;
} }

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

@ -31,8 +31,8 @@ module.exports = {
meta: { meta: {
docs: { docs: {
description: "require or disallow trailing commas", description: "require or disallow trailing commas",
category: "Possible Errors", category: "Stylistic Issues",
recommended: true recommended: false
}, },
fixable: "code", fixable: "code",
@ -143,13 +143,13 @@ module.exports = {
} }
var sourceCode = context.getSourceCode(), var sourceCode = context.getSourceCode(),
trailingToken; penultimateToken = lastItem,
// 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 {
trailingToken = sourceCode.getTokenAfter(lastItem); trailingToken = sourceCode.getTokenAfter(lastItem);
// Skip close parentheses.
while (trailingToken.value === ")") {
penultimateToken = trailingToken;
trailingToken = sourceCode.getTokenAfter(trailingToken);
} }
if (trailingToken.value !== ",") { if (trailingToken.value !== ",") {
@ -158,7 +158,7 @@ module.exports = {
loc: lastItem.loc.end, loc: lastItem.loc.end,
message: MISSING_MESSAGE, message: MISSING_MESSAGE,
fix: function(fixer) { 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} * @returns {void}
*/ */
function addNullElementsToIgnoreList(node) { function addNullElementsToIgnoreList(node) {
var previousToken = context.getFirstToken(node); var previousToken = sourceCode.getFirstToken(node);
node.elements.forEach(function(element) { node.elements.forEach(function(element) {
var token; var token;
if (element === null) { if (element === null) {
token = context.getTokenAfter(previousToken); token = sourceCode.getTokenAfter(previousToken);
if (isComma(token)) { if (isComma(token)) {
commaTokensToIgnore.push(token); commaTokensToIgnore.push(token);
} }
} else { } else {
token = context.getTokenAfter(element); token = sourceCode.getTokenAfter(element);
} }
previousToken = token; previousToken = token;

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

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

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

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

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

@ -14,6 +14,16 @@ var astUtils = require("../ast-utils");
// Helpers // 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. * Checks whether or not a given code path segment is unreachable.
* @param {CodePathSegment} segment - A CodePathSegment to check. * @param {CodePathSegment} segment - A CodePathSegment to check.
@ -35,10 +45,20 @@ module.exports = {
recommended: false recommended: false
}, },
schema: [] schema: [{
type: "object",
properties: {
treatUndefinedAsUnspecified: {
type: "boolean"
}
},
additionalProperties: false
}]
}, },
create: function(context) { create: function(context) {
var options = context.options[0] || {};
var treatUndefinedAsUnspecified = options.treatUndefinedAsUnspecified === true;
var funcInfo = null; var funcInfo = null;
/** /**
@ -115,7 +135,12 @@ module.exports = {
// Reports a given return statement if it's inconsistent. // Reports a given return statement if it's inconsistent.
ReturnStatement: function(node) { 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) { if (!funcInfo.hasReturn) {
funcInfo.hasReturn = true; 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 multiOrNest = (context.options[0] === "multi-or-nest");
var consistent = (context.options[1] === "consistent"); var consistent = (context.options[1] === "consistent");
var sourceCode = context.getSourceCode();
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Helpers // Helpers
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -69,8 +71,8 @@ module.exports = {
* @private * @private
*/ */
function isCollapsedOneLiner(node) { function isCollapsedOneLiner(node) {
var before = context.getTokenBefore(node), var before = sourceCode.getTokenBefore(node),
last = context.getLastToken(node); last = sourceCode.getLastToken(node);
return before.loc.start.line === last.loc.end.line; return before.loc.start.line === last.loc.end.line;
} }
@ -82,8 +84,8 @@ module.exports = {
* @private * @private
*/ */
function isOneLiner(node) { function isOneLiner(node) {
var first = context.getFirstToken(node), var first = sourceCode.getFirstToken(node),
last = context.getLastToken(node); last = sourceCode.getLastToken(node);
return first.loc.start.line === last.loc.end.line; return first.loc.start.line === last.loc.end.line;
} }
@ -94,7 +96,6 @@ module.exports = {
* @returns {Token} The `else` keyword token. * @returns {Token} The `else` keyword token.
*/ */
function getElseKeyword(node) { function getElseKeyword(node) {
var sourceCode = context.getSourceCode();
var token = sourceCode.getTokenAfter(node.consequent); var token = sourceCode.getTokenAfter(node.consequent);
while (token.type !== "Keyword" || token.value !== "else") { 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 = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "require `default` cases in <code>switch</code> statements", description: "require `default` cases in `switch` statements",
category: "Best Practices", category: "Best Practices",
recommended: false recommended: false
}, },
@ -35,6 +35,8 @@ module.exports = {
new RegExp(options.commentPattern) : new RegExp(options.commentPattern) :
DEFAULT_COMMENT_PATTERN; DEFAULT_COMMENT_PATTERN;
var sourceCode = context.getSourceCode();
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Helpers // Helpers
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -76,7 +78,7 @@ module.exports = {
var lastCase = last(node.cases); var lastCase = last(node.cases);
comments = context.getComments(lastCase).trailing; comments = sourceCode.getComments(lastCase).trailing;
if (comments.length) { if (comments.length) {
comment = last(comments); comment = last(comments);

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

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

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

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

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

@ -19,12 +19,13 @@ module.exports = {
schema: [ schema: [
{ {
enum: ["smart", "allow-null"] enum: ["always", "smart", "allow-null"]
} }
] ]
}, },
create: function(context) { create: function(context) {
var sourceCode = context.getSourceCode();
/** /**
* Checks if an expression is a typeof expression * Checks if an expression is a typeof expression
@ -75,7 +76,7 @@ module.exports = {
* @private * @private
*/ */
function getOperatorLocation(node) { 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}; 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 = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "enforce named `function` expressions", description: "require or disallow named `function` expressions",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false recommended: false
}, },
schema: [] schema: [
{
enum: ["always", "never"]
}
]
}, },
create: function(context) { create: function(context) {
var never = context.options[0] === "never";
/** /**
* Determines whether the current FunctionExpression node is a get, set, or * Determines whether the current FunctionExpression node is a get, set, or
@ -44,8 +49,14 @@ module.exports = {
var name = node.id && node.id.name; var name = node.id && node.id.name;
if (!name && !isObjectOrClassMethod()) { if (never) {
context.report(node, "Missing function expression name."); 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; return option;
}(context.options[0])); }(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. * Checks the spacing between two tokens before or after the star token.
* @param {string} side Either "before" or "after". * @param {string} side Either "before" or "after".
@ -98,18 +118,18 @@ module.exports = {
} }
if (node.parent.method || node.parent.type === "MethodDefinition") { if (node.parent.method || node.parent.type === "MethodDefinition") {
starToken = context.getTokenBefore(node, 1); starToken = getStarToken(node.parent);
} else { } else {
starToken = context.getFirstToken(node, 1); starToken = getStarToken(node);
} }
// Only check before when preceded by `function` keyword // Only check before when preceded by `function`|`static` keyword
prevToken = context.getTokenBefore(starToken); prevToken = sourceCode.getTokenBefore(starToken);
if (prevToken.value === "function" || prevToken.value === "static") { if (prevToken.value === "function" || prevToken.value === "static") {
checkSpacing("before", prevToken, starToken); checkSpacing("before", prevToken, starToken);
} }
nextToken = context.getTokenAfter(starToken); nextToken = sourceCode.getTokenAfter(starToken);
checkSpacing("after", starToken, nextToken); checkSpacing("after", starToken, nextToken);
} }

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

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

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

@ -118,6 +118,8 @@ module.exports = {
recommended: false recommended: false
}, },
fixable: "whitespace",
schema: [{ schema: [{
anyOf: [ anyOf: [
{ {
@ -196,6 +198,8 @@ module.exports = {
multiLineOptions = initOptions({}, (options.multiLine || options)), multiLineOptions = initOptions({}, (options.multiLine || options)),
singleLineOptions = initOptions({}, (options.singleLine || options)); singleLineOptions = initOptions({}, (options.singleLine || options));
var sourceCode = context.getSourceCode();
/** /**
* Determines if the given property is key-value property. * Determines if the given property is key-value property.
* @param {ASTNode} property Property node to check. * @param {ASTNode} property Property node to check.
@ -220,7 +224,7 @@ module.exports = {
while (node && (node.type !== "Punctuator" || node.value !== ":")) { while (node && (node.type !== "Punctuator" || node.value !== ":")) {
prevNode = node; prevNode = node;
node = context.getTokenAfter(node); node = sourceCode.getTokenAfter(node);
} }
return prevNode; return prevNode;
@ -235,7 +239,7 @@ module.exports = {
function getNextColon(node) { function getNextColon(node) {
while (node && (node.type !== "Punctuator" || node.value !== ":")) { while (node && (node.type !== "Punctuator" || node.value !== ":")) {
node = context.getTokenAfter(node); node = sourceCode.getTokenAfter(node);
} }
return node; return node;
@ -250,7 +254,7 @@ module.exports = {
var key = property.key; var key = property.key;
if (property.computed) { 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; return property.key.name || property.key.value;
@ -268,9 +272,16 @@ module.exports = {
*/ */
function report(property, side, whitespace, expected, mode) { function report(property, side, whitespace, expected, mode) {
var diff = whitespace.length - expected, var diff = whitespace.length - expected,
key = property.key, nextColon = getNextColon(property.key),
firstTokenAfterColon = context.getTokenAfter(getNextColon(key)), tokenBeforeColon = sourceCode.getTokenBefore(nextColon),
location = side === "key" ? key.loc.start : firstTokenAfterColon.loc.start; 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 (( if ((
diff && mode === "strict" || diff && mode === "strict" ||
@ -278,10 +289,41 @@ module.exports = {
diff > 0 && !expected && mode === "minimum") && diff > 0 && !expected && mode === "minimum") &&
!(expected && containsLineTerminator(whitespace)) !(expected && containsLineTerminator(whitespace))
) { ) {
context.report(property[side], location, messages[side], { if (isExtra) {
error: diff > 0 ? "Extra" : "Missing",
computed: property.computed ? "computed " : "", // Remove whitespace
key: getKey(property) 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) { function getKeyWidth(property) {
var startToken, endToken; var startToken, endToken;
startToken = context.getFirstToken(property); startToken = sourceCode.getFirstToken(property);
endToken = getLastTokenBeforeColon(property.key); endToken = getLastTokenBeforeColon(property.key);
return endToken.range[1] - startToken.range[0]; return endToken.range[1] - startToken.range[0];
@ -307,7 +349,7 @@ module.exports = {
* @returns {Object} Whitespace before and after the property's colon. * @returns {Object} Whitespace before and after the property's colon.
*/ */
function getPropertyWhitespace(property) { 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] 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'.", var EXPECTED_LF_MSG = "Expected linebreaks to be 'LF' but found 'CRLF'.",
EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'."; EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'.";
var sourceCode = context.getSourceCode();
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Helpers // Helpers
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -57,7 +59,7 @@ module.exports = {
var linebreakStyle = context.options[0] || "unix", var linebreakStyle = context.options[0] || "unix",
expectedLF = linebreakStyle === "unix", expectedLF = linebreakStyle === "unix",
expectedLFChars = expectedLF ? "\n" : "\r\n", expectedLFChars = expectedLF ? "\n" : "\r\n",
source = context.getSource(), source = sourceCode.getText(),
pattern = /\r\n|\r|\n|\u2028|\u2029/g, pattern = /\r\n|\r|\n|\u2028|\u2029/g,
match, match,
index, index,
@ -77,7 +79,7 @@ module.exports = {
node: node, node: node,
loc: { loc: {
line: i, line: i,
column: context.getSourceLines()[i - 1].length column: sourceCode.lines[i - 1].length
}, },
message: expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG, message: expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG,
fix: createFix(range, expectedLFChars) fix: createFix(range, expectedLFChars)

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

@ -8,7 +8,8 @@
// Requirements // Requirements
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
var lodash = require("lodash"); var lodash = require("lodash"),
astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
@ -51,16 +52,6 @@ function getCommentLineNums(comments) {
return lines; 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 // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -73,6 +64,8 @@ module.exports = {
recommended: false recommended: false
}, },
fixable: "whitespace",
schema: [ schema: [
{ {
type: "object", type: "object",
@ -126,6 +119,22 @@ module.exports = {
var sourceCode = context.getSourceCode(); 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 * Returns whether or not comments are on lines starting with or ending with code
* @param {ASTNode} node The comment node to check. * @param {ASTNode} node The comment node to check.
@ -137,18 +146,18 @@ module.exports = {
token = node; token = node;
do { do {
token = sourceCode.getTokenOrCommentBefore(token); 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; return true;
} }
token = node; token = node;
do { do {
token = sourceCode.getTokenOrCommentAfter(token); 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; return true;
} }
@ -267,14 +276,6 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function checkForEmptyLine(node, opts) { 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, var after = opts.after,
before = opts.before; before = opts.before;
@ -305,14 +306,34 @@ module.exports = {
return; return;
} }
var previousTokenOrComment = sourceCode.getTokenOrCommentBefore(node);
var nextTokenOrComment = sourceCode.getTokenOrCommentAfter(node);
// check for newline before // check for newline before
if (!exceptionStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) { if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) &&
context.report(node, "Expected line before comment."); !(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 // check for newline after
if (!exceptionEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) { if (!exceptionEndAllowed && after && !lodash.includes(commentAndEmptyLines, nextLineNum) &&
context.report(node, "Expected line after comment."); !(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 URL_REGEXP = /[^:/?#]:\/\/[^?#]/;
var sourceCode = context.getSourceCode();
/** /**
* Computes the length of a line that may contain tabs. The width of each * 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. * tab will be the number of spaces to the next tab stop.
@ -155,11 +157,12 @@ module.exports = {
*/ */
function isFullLineComment(line, lineNumber, comment) { function isFullLineComment(line, lineNumber, comment) {
var start = comment.loc.start, var start = comment.loc.start,
end = comment.loc.end; end = comment.loc.end,
isFirstTokenOnLine = !line.slice(0, comment.loc.start.column).trim();
return comment && return comment &&
(start.line < lineNumber || (start.line === lineNumber && start.column === 0)) && (start.line < lineNumber || (start.line === lineNumber && isFirstTokenOnLine)) &&
(end.line > lineNumber || end.column === line.length); (end.line > lineNumber || (end.line === lineNumber && end.column === line.length));
} }
/** /**
@ -185,10 +188,10 @@ module.exports = {
function checkProgramForMaxLength(node) { function checkProgramForMaxLength(node) {
// split (honors line-ending) // split (honors line-ending)
var lines = context.getSourceLines(), var lines = sourceCode.lines,
// list of comments to ignore // 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 // we iterate over comments in parallel with the lines
commentsIndex = 0; 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", type: "object",
properties: { properties: {
max: { max: {
type: "integer" type: "integer",
minimum: 0
} }
}, },
additionalProperties: false additionalProperties: false
@ -31,72 +32,81 @@ module.exports = {
create: function(context) { create: function(context) {
var options = context.options[0] || {}, var sourceCode = context.getSourceCode(),
options = context.options[0] || {},
lastStatementLine = 0, lastStatementLine = 0,
numberOfStatementsOnThisLine = 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 // Helpers
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
var SINGLE_CHILD_ALLOWED = /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/;
/** /**
* Reports a node * Gets the actual last token of a given node.
* @param {ASTNode} node The node to report *
* @returns {void} * @param {ASTNode} node - A node to get. This is a node except EmptyStatement.
* @private * @returns {Token} The actual last token.
*/ */
function report(node) { function getActualLastToken(node) {
context.report( var lastToken = sourceCode.getLastToken(node);
node,
"This line has too many statements. Maximum allowed is {{max}}.", if (lastToken.value === ";") {
{ max: maxStatementsPerLine }); lastToken = sourceCode.getTokenBefore(lastToken);
}
return lastToken;
} }
/** /**
* Enforce a maximum number of statements per line * Addresses a given node.
* @param {ASTNode} nodes Array of nodes to evaluate * 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} * @returns {void}
* @private
*/ */
function enforceMaxStatementsPerLine(nodes) { function enterStatement(node) {
if (nodes.length < 1) { 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; return;
} }
for (var i = 0, l = nodes.length; i < l; ++i) { // Update state.
var currentStatement = nodes[i]; if (line === lastStatementLine) {
numberOfStatementsOnThisLine += 1;
} else {
numberOfStatementsOnThisLine = 1;
lastStatementLine = line;
}
if (currentStatement.loc.start.line === lastStatementLine) { // Reports if the node violated this rule.
++numberOfStatementsOnThisLine; if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
} else { context.report({node: node, message: message});
numberOfStatementsOnThisLine = 1;
lastStatementLine = currentStatement.loc.end.line;
}
if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
report(currentStatement);
}
} }
} }
/** /**
* Check each line in the body of a node * Updates the state of this rule with the end line of leaving node to check with the next statement.
* @param {ASTNode} node node to evaluate *
* @param {ASTNode} node - A node to check.
* @returns {void} * @returns {void}
* @private
*/ */
function checkLinesInBody(node) { function leaveStatement(node) {
enforceMaxStatementsPerLine(node.body); var line = getActualLastToken(node).loc.end.line;
}
/** // Update state.
* Check each line in the consequent of a switch case if (line !== lastStatementLine) {
* @param {ASTNode} node node to evaluate numberOfStatementsOnThisLine = 1;
* @returns {void} lastStatementLine = line;
* @private }
*/
function checkLinesInConsequent(node) {
enforceMaxStatementsPerLine(node.consequent);
} }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -104,10 +114,61 @@ module.exports = {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
return { return {
Program: checkLinesInBody, BreakStatement: enterStatement,
BlockStatement: checkLinesInBody, ClassDeclaration: enterStatement,
SwitchCase: checkLinesInConsequent 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 listeners = {};
var sourceCode = context.getSourceCode();
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Helpers // Helpers
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -186,7 +188,7 @@ module.exports = {
* @returns {Boolean} Returns true if the callee may be capitalized * @returns {Boolean} Returns true if the callee may be capitalized
*/ */
function isCapAllowed(allowedMap, node, calleeName) { function isCapAllowed(allowedMap, node, calleeName) {
if (allowedMap[calleeName] || allowedMap[context.getSource(node.callee)]) { if (allowedMap[calleeName] || allowedMap[sourceCode.getText(node.callee)]) {
return true; return true;
} }

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

@ -21,11 +21,12 @@ module.exports = {
}, },
create: function(context) { create: function(context) {
var sourceCode = context.getSourceCode();
return { return {
NewExpression: function(node) { NewExpression: function(node) {
var tokens = context.getTokens(node); var tokens = sourceCode.getTokens(node);
var prenticesTokens = tokens.filter(function(token) { var prenticesTokens = tokens.filter(function(token) {
return token.value === "(" || token.value === ")"; 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"; var mode = context.options[0] === "never" ? "never" : "always";
// Cache starting and ending line numbers of comments for faster lookup // Cache starting and ending line numbers of comments for faster lookup
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; result[token.loc.start.line] = token.loc.end.line;
return result; return result;
}, {}); }, {});

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

@ -133,32 +133,17 @@ module.exports = {
return (lineNumNode - lineNumTokenBefore - commentLines) > 1; 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 // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
return { return {
ReturnStatement: function(node) { ReturnStatement: function(node) {
if (isFirstNode(node) && hasNewlineBefore(node)) { if (!isFirstNode(node) && !hasNewlineBefore(node)) {
reportError(node, false); context.report({
} else if (!isFirstNode(node) && !hasNewlineBefore(node)) { node: node,
reportError(node, true); 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] || {}, var options = context.options[0] || {},
ignoreChainWithDepth = options.ignoreChainWithDepth || 2; 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 { return {
"CallExpression:exit": function(node) { "CallExpression:exit": function(node) {
if (!node.callee || node.callee.type !== "MemberExpression") { if (!node.callee || node.callee.type !== "MemberExpression") {
@ -55,7 +72,7 @@ module.exports = {
context.report( context.report(
callee.property, callee.property,
callee.property.loc.start, 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 prohibitAssign = (context.options[0] || "except-parens");
var sourceCode = context.getSourceCode();
/** /**
* Check whether an AST node is the test expression for a conditional statement. * Check whether an AST node is the test expression for a conditional statement.
* @param {!Object} node The node to test. * @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`. * @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`.
*/ */
function isParenthesised(node) { function isParenthesised(node) {
var previousToken = context.getTokenBefore(node), var previousToken = sourceCode.getTokenBefore(node),
nextToken = context.getTokenAfter(node); nextToken = sourceCode.getTokenAfter(node);
return previousToken.value === "(" && previousToken.range[1] <= node.range[0] && return previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1]; 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`. * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
*/ */
function isParenthesisedTwice(node) { function isParenthesisedTwice(node) {
var previousToken = context.getTokenBefore(node, 1), var previousToken = sourceCode.getTokenBefore(node, 1),
nextToken = context.getTokenAfter(node, 1); nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) && return isParenthesised(node) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] && 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) { create: function(context) {
var config = context.options[0] || {}; var config = context.options[0] || {};
var sourceCode = context.getSourceCode();
/** /**
* Reports if an arrow function contains an ambiguous conditional. * Reports if an arrow function contains an ambiguous conditional.
@ -53,7 +54,7 @@ module.exports = {
function checkArrowFunc(node) { function checkArrowFunc(node) {
var body = node.body; 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."); 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 recommended: true
}, },
schema: [] schema: [
{
type: "object",
properties: {
checkLoops: {
type: "boolean"
}
},
additionalProperties: false
}
]
}, },
create: function(context) { create: function(context) {
var options = context.options[0] || {},
checkLoops = options.checkLoops !== false;
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Helpers // 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 // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -109,9 +134,9 @@ module.exports = {
return { return {
ConditionalExpression: checkConstantCondition, ConditionalExpression: checkConstantCondition,
IfStatement: checkConstantCondition, IfStatement: checkConstantCondition,
WhileStatement: checkConstantCondition, WhileStatement: checkLoop,
DoWhileStatement: checkConstantCondition, DoWhileStatement: checkLoop,
ForStatement: checkConstantCondition ForStatement: checkLoop
}; };
} }

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

@ -21,11 +21,12 @@ module.exports = {
}, },
create: function(context) { create: function(context) {
var sourceCode = context.getSourceCode();
return { return {
Literal: function(node) { Literal: function(node) {
var token = context.getFirstToken(node); var token = sourceCode.getFirstToken(node);
if (token.type === "RegularExpression" && token.value[1] === "=") { if (token.type === "RegularExpression" && token.value[1] === "=") {
context.report(node, "A regular expression literal can be confused with '/='."); 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) { create: function(context) {
var sourceCode = context.getSourceCode();
return { return {
SwitchStatement: function(node) { SwitchStatement: function(node) {
var mapping = {}; var mapping = {};
node.cases.forEach(function(switchCase) { node.cases.forEach(function(switchCase) {
var key = context.getSource(switchCase.test); var key = sourceCode.getText(switchCase.test);
if (mapping[key]) { if (mapping[key]) {
context.report(switchCase, "Duplicate case label."); context.report(switchCase, "Duplicate case label.");

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

@ -33,7 +33,7 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function displayReport(node) { 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. // If we have a BlockStatement, check each consequent body node.
return node.body.some(checkForReturnOrIf); 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) { create: function(context) {
var sourceCode = context.getSourceCode();
return { return {
Literal: function(node) { Literal: function(node) {
var token = context.getFirstToken(node); var token = sourceCode.getFirstToken(node);
if (token.type === "RegularExpression" && !regex.test(token.value)) { if (token.type === "RegularExpression" && !regex.test(token.value)) {
context.report(node, "Empty class."); 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 options = context.options[0] || {};
var allowed = options.allow || []; var allowed = options.allow || [];
var sourceCode = context.getSourceCode();
/** /**
* Reports a given function node if the node matches the following patterns. * Reports a given function node if the node matches the following patterns.
* *
@ -139,7 +141,7 @@ module.exports = {
if (allowed.indexOf(kind) === -1 && if (allowed.indexOf(kind) === -1 &&
node.body.type === "BlockStatement" && node.body.type === "BlockStatement" &&
node.body.body.length === 0 && node.body.body.length === 0 &&
context.getComments(node.body).trailing.length === 0 sourceCode.getComments(node.body).trailing.length === 0
) { ) {
context.report({ context.report({
node: node, node: node,

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

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

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

@ -40,7 +40,8 @@ module.exports = {
type: "object", type: "object",
properties: { properties: {
conditionalAssign: {type: "boolean"}, conditionalAssign: {type: "boolean"},
nestedBinaryExpressions: {type: "boolean"} nestedBinaryExpressions: {type: "boolean"},
returnAssign: {type: "boolean"}
}, },
additionalProperties: false additionalProperties: false
} }
@ -53,11 +54,14 @@ module.exports = {
}, },
create: function(context) { 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 ALL_NODES = context.options[0] !== "functions";
var EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false; 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 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. * Determines if this rule should be enforced for a node given the current configuration.
@ -76,8 +80,8 @@ module.exports = {
* @private * @private
*/ */
function isParenthesisedTwice(node) { function isParenthesisedTwice(node) {
var previousToken = context.getTokenBefore(node, 1), var previousToken = sourceCode.getTokenBefore(node, 1),
nextToken = context.getTokenAfter(node, 1); nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) && previousToken && nextToken && return isParenthesised(node) && previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] && previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
@ -115,6 +119,64 @@ module.exports = {
return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression"; 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 * Determines if a node following a [no LineTerminator here] restriction is
* surrounded by (potentially) invalid extra parentheses. * surrounded by (potentially) invalid extra parentheses.
@ -194,98 +256,6 @@ module.exports = {
throw new Error("unreachable"); 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 * Report the node
* @param {ASTNode} node node to evaluate * @param {ASTNode} node node to evaluate
@ -293,7 +263,7 @@ module.exports = {
* @private * @private
*/ */
function report(node) { function report(node) {
var previousToken = context.getTokenBefore(node); var previousToken = sourceCode.getTokenBefore(node);
context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression."); context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression.");
} }
@ -368,6 +338,10 @@ module.exports = {
}, },
ArrowFunctionExpression: function(node) { ArrowFunctionExpression: function(node) {
if (isReturnAssignException(node)) {
return;
}
if (node.body.type !== "BlockStatement") { if (node.body.type !== "BlockStatement") {
if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) { if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) {
report(node.body); report(node.body);
@ -383,6 +357,10 @@ module.exports = {
}, },
AssignmentExpression: function(node) { AssignmentExpression: function(node) {
if (isReturnAssignException(node)) {
return;
}
if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) { if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) {
report(node.right); report(node.right);
} }
@ -392,12 +370,18 @@ module.exports = {
CallExpression: dryCallNew, CallExpression: dryCallNew,
ConditionalExpression: function(node) { ConditionalExpression: function(node) {
if (isReturnAssignException(node)) {
return;
}
if (hasExcessParens(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) { if (hasExcessParens(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) {
report(node.test); report(node.test);
} }
if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) { if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) {
report(node.consequent); report(node.consequent);
} }
if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) { if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) {
report(node.alternate); report(node.alternate);
} }
@ -413,7 +397,7 @@ module.exports = {
var firstToken, secondToken, firstTokens; var firstToken, secondToken, firstTokens;
if (hasExcessParens(node.expression)) { if (hasExcessParens(node.expression)) {
firstTokens = context.getFirstTokens(node.expression, 2); firstTokens = sourceCode.getFirstTokens(node.expression, 2);
firstToken = firstTokens[0]; firstToken = firstTokens[0];
secondToken = firstTokens[1]; secondToken = firstTokens[1];
@ -476,7 +460,7 @@ module.exports = {
!( !(
(node.object.type === "Literal" && (node.object.type === "Literal" &&
typeof node.object.value === "number" && 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) // RegExp literal is allowed to have parens (#1589)
@ -511,6 +495,10 @@ module.exports = {
ReturnStatement: function(node) { ReturnStatement: function(node) {
var returnToken = sourceCode.getFirstToken(node); var returnToken = sourceCode.getFirstToken(node);
if (isReturnAssignException(node)) {
return;
}
if (node.argument && if (node.argument &&
hasExcessParensNoLineTerminator(returnToken, node.argument) && hasExcessParensNoLineTerminator(returnToken, node.argument) &&

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

@ -22,6 +22,7 @@ module.exports = {
}, },
create: function(context) { create: function(context) {
var sourceCode = context.getSourceCode();
/** /**
* Reports an unnecessary semicolon error. * Reports an unnecessary semicolon error.
@ -48,7 +49,7 @@ module.exports = {
function checkForPartOfClassBody(firstToken) { function checkForPartOfClassBody(firstToken) {
for (var token = firstToken; for (var token = firstToken;
token.type === "Punctuator" && token.value !== "}"; token.type === "Punctuator" && token.value !== "}";
token = context.getTokenAfter(token) token = sourceCode.getTokenAfter(token)
) { ) {
if (token.value === ";") { if (token.value === ";") {
report(token); report(token);
@ -65,7 +66,16 @@ module.exports = {
*/ */
EmptyStatement: function(node) { EmptyStatement: function(node) {
var parent = node.parent, 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) { if (allowedParentTypes.indexOf(parent.type) === -1) {
report(node); report(node);
@ -78,7 +88,7 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
ClassBody: function(node) { 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} * @returns {void}
*/ */
MethodDefinition: function(node) { 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]), var options = parseOptions(context.options[0]),
operatorAllowed = false; operatorAllowed = false;
var sourceCode = context.getSourceCode();
return { return {
UnaryExpression: function(node) { UnaryExpression: function(node) {
@ -188,7 +190,7 @@ module.exports = {
context.report( context.report(
node, node,
"use `Boolean({{code}})` instead.", { "use `Boolean({{code}})` instead.", {
code: context.getSource(node.argument.argument) code: sourceCode.getText(node.argument.argument)
}); });
} }
@ -198,7 +200,7 @@ module.exports = {
context.report( context.report(
node, node,
"use `{{code}} !== -1` instead.", { "use `{{code}} !== -1` instead.", {
code: context.getSource(node.argument) code: sourceCode.getText(node.argument)
}); });
} }
@ -208,7 +210,7 @@ module.exports = {
context.report( context.report(
node, node,
"use `Number({{code}})` instead.", { "use `Number({{code}})` instead.", {
code: context.getSource(node.argument) code: sourceCode.getText(node.argument)
}); });
} }
}, },
@ -224,7 +226,7 @@ module.exports = {
context.report( context.report(
node, node,
"use `Number({{code}})` instead.", { "use `Number({{code}})` instead.", {
code: context.getSource(nonNumericOperand) code: sourceCode.getText(nonNumericOperand)
}); });
} }
@ -234,7 +236,7 @@ module.exports = {
context.report( context.report(
node, node,
"use `String({{code}})` instead.", { "use `String({{code}})` instead.", {
code: context.getSource(getOtherOperand(node, "")) code: sourceCode.getText(getOtherOperand(node, ""))
}); });
} }
}, },
@ -247,7 +249,7 @@ module.exports = {
context.report( context.report(
node, node,
"use `{{code}} = String({{code}})` instead.", { "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) { create: function(context) {
var sourceCode = context.getSourceCode();
/** /**
* Will check that comments are not on lines starting with or ending with code * Will check that comments are not on lines starting with or ending with code
@ -32,8 +33,8 @@ module.exports = {
function testCodeAroundComment(node) { function testCodeAroundComment(node) {
// Get the whole line and cut it off at the start of the comment // 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 startLine = String(sourceCode.lines[node.loc.start.line - 1]);
var endLine = String(context.getSourceLines()[node.loc.end.line - 1]); var endLine = String(sourceCode.lines[node.loc.end.line - 1]);
var preamble = startLine.slice(0, node.loc.start.column).trim(); 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"; "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 // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -24,6 +33,15 @@ module.exports = {
properties: { properties: {
skipComments: { skipComments: {
type: "boolean" type: "boolean"
},
skipStrings: {
type: "boolean"
},
skipTemplates: {
type: "boolean"
},
skipRegExps: {
type: "boolean"
} }
}, },
additionalProperties: false additionalProperties: false
@ -33,9 +51,6 @@ module.exports = {
create: function(context) { 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 // Module store of errors that we have found
var errors = []; var errors = [];
@ -45,6 +60,11 @@ module.exports = {
// Lookup the `skipComments` option, which defaults to `false`. // Lookup the `skipComments` option, which defaults to `false`.
var options = context.options[0] || {}; var options = context.options[0] || {};
var skipComments = !!options.skipComments; 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 * Removes errors that occur inside a string node
@ -75,10 +95,27 @@ module.exports = {
* @private * @private
*/ */
function removeInvalidNodeErrorsInIdentifierOrLiteral(node) { 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 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); removeWhitespaceError(node);
} }
} }
@ -91,7 +128,7 @@ module.exports = {
* @private * @private
*/ */
function removeInvalidNodeErrorsInComment(node) { function removeInvalidNodeErrorsInComment(node) {
if (node.value.match(irregularWhitespace) || node.value.match(irregularLineTerminators)) { if (ALL_IRREGULARS.test(node.value)) {
removeWhitespaceError(node); removeWhitespaceError(node);
} }
} }
@ -103,14 +140,14 @@ module.exports = {
* @private * @private
*/ */
function checkForIrregularWhitespace(node) { function checkForIrregularWhitespace(node) {
var sourceLines = context.getSourceLines(); var sourceLines = sourceCode.lines;
sourceLines.forEach(function(sourceLine, lineIndex) { sourceLines.forEach(function(sourceLine, lineIndex) {
var lineNumber = lineIndex + 1, var lineNumber = lineIndex + 1,
location, location,
match; match;
while ((match = irregularWhitespace.exec(sourceLine)) !== null) { while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) {
location = { location = {
line: lineNumber, line: lineNumber,
column: match.index column: match.index
@ -128,15 +165,15 @@ module.exports = {
* @private * @private
*/ */
function checkForIrregularLineTerminators(node) { function checkForIrregularLineTerminators(node) {
var source = context.getSource(), var source = sourceCode.getText(),
sourceLines = context.getSourceLines(), sourceLines = sourceCode.lines,
linebreaks = source.match(/\r\n|\r|\n|\u2028|\u2029/g), linebreaks = source.match(LINE_BREAK),
lastLineIndex = -1, lastLineIndex = -1,
lineIndex, lineIndex,
location, location,
match; match;
while ((match = irregularLineTerminators.exec(source)) !== null) { while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) {
lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0; lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0;
location = { location = {
@ -166,8 +203,10 @@ module.exports = {
*/ */
function noop() {} function noop() {}
return { var nodes = {};
Program: function(node) {
if (ALL_IRREGULARS.test(sourceCode.getText())) {
nodes.Program = function(node) {
/* /*
* As we can easily fire warnings for all white space issues with * As we can easily fire warnings for all white space issues with
@ -182,13 +221,14 @@ module.exports = {
checkForIrregularWhitespace(node); checkForIrregularWhitespace(node);
checkForIrregularLineTerminators(node); checkForIrregularLineTerminators(node);
}, };
Identifier: removeInvalidNodeErrorsInIdentifierOrLiteral, nodes.Identifier = removeInvalidNodeErrorsInIdentifierOrLiteral;
Literal: removeInvalidNodeErrorsInIdentifierOrLiteral, nodes.Literal = removeInvalidNodeErrorsInIdentifierOrLiteral;
LineComment: skipComments ? rememberCommentNode : noop, nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop;
BlockComment: skipComments ? rememberCommentNode : noop, nodes.LineComment = skipComments ? rememberCommentNode : noop;
"Program:exit": function() { nodes.BlockComment = skipComments ? rememberCommentNode : noop;
nodes["Program:exit"] = function() {
if (skipComments) { if (skipComments) {
@ -200,7 +240,11 @@ module.exports = {
errors.forEach(function(error) { errors.forEach(function(error) {
context.report.apply(context, 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. * @returns {ASTNode} The most outer loop node.
*/ */
function getTopLoopNode(node, excludedNode) { function getTopLoopNode(node, excludedNode) {
var retv = null; var retv = node;
var border = excludedNode ? excludedNode.range[1] : 0; var border = excludedNode ? excludedNode.range[1] : 0;
while (node && node.range[0] >= border) { 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) { create: function(context) {
var sourceCode = context.getSourceCode();
var smartTabs, var smartTabs,
ignoredLocs = []; ignoredLocs = [];
@ -86,8 +87,8 @@ module.exports = {
*/ */
var regex = /^(?=[\t ]*(\t | \t))/, var regex = /^(?=[\t ]*(\t | \t))/,
match, match,
lines = context.getSourceLines(), lines = sourceCode.lines,
comments = context.getAllComments(); comments = sourceCode.getAllComments();
comments.forEach(function(comment) { comments.forEach(function(comment) {
ignoredLocs.push(comment.loc); ignoredLocs.push(comment.loc);

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

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

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

@ -17,6 +17,8 @@ module.exports = {
recommended: false recommended: false
}, },
fixable: "whitespace",
schema: [ schema: [
{ {
type: "object", type: "object",
@ -52,10 +54,12 @@ module.exports = {
if (context.options.length) { if (context.options.length) {
max = context.options[0].max; max = context.options[0].max;
maxEOF = context.options[0].maxEOF; maxEOF = typeof context.options[0].maxEOF !== "undefined" ? context.options[0].maxEOF : max;
maxBOF = context.options[0].maxBOF; maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max;
} }
var sourceCode = context.getSourceCode();
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -73,22 +77,35 @@ module.exports = {
}, },
"Program:exit": function checkBlankLines(node) { "Program:exit": function checkBlankLines(node) {
var lines = context.getSourceLines(), var lines = sourceCode.lines,
currentLocation = -1, fullLines = sourceCode.text.match(/.*(\r\n|\r|\n|\u2028|\u2029)/g) || [],
lastLocation, firstNonBlankLine = -1,
trimmedLines = [],
linesRangeStart = [],
blankCounter = 0, blankCounter = 0,
currentLocation,
lastLocation,
location, location,
firstOfEndingBlankLines, firstOfEndingBlankLines,
firstNonBlankLine = -1, diff,
trimmedLines = []; fix,
rangeStart,
rangeEnd;
fix = function(fixer) {
return fixer.removeRange([rangeStart, rangeEnd]);
};
linesRangeStart.push(0);
lines.forEach(function(str, i) { 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 !== "")) { if ((firstNonBlankLine === -1) && (trimmed !== "")) {
firstNonBlankLine = i; firstNonBlankLine = i;
} }
linesRangeStart.push(linesRangeStart[linesRangeStart.length - 1] + length);
trimmedLines.push(trimmed); trimmedLines.push(trimmed);
}); });
@ -120,9 +137,17 @@ module.exports = {
// Aggregate and count blank lines // Aggregate and count blank lines
if (firstNonBlankLine > maxBOF) { if (firstNonBlankLine > maxBOF) {
context.report(node, 0, diff = firstNonBlankLine - maxBOF;
"Too many blank lines at the beginning of file. Max of " + maxBOF + " allowed."); 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; lastLocation = currentLocation;
currentLocation = trimmedLines.indexOf("", currentLocation + 1); currentLocation = trimmedLines.indexOf("", currentLocation + 1);
@ -141,20 +166,29 @@ module.exports = {
// within the file, not at the end // within the file, not at the end
if (blankCounter >= max) { if (blankCounter >= max) {
diff = blankCounter - max + 1;
rangeStart = linesRangeStart[location.line - diff];
rangeEnd = linesRangeStart[location.line];
context.report({ context.report({
node: node, node: node,
loc: location, 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 { } else {
// inside the last blank lines // inside the last blank lines
if (blankCounter > maxEOF) { if (blankCounter > maxEOF) {
diff = blankCounter - maxEOF + 1;
rangeStart = linesRangeStart[location.line - diff];
rangeEnd = linesRangeStart[location.line - 1];
context.report({ context.report({
node: node, node: node,
loc: location, 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 * @author Ilya Volodin
*/ */
@ -12,9 +12,9 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow reassigning native objects", description: "disallow assignments to native objects or read-only global variables",
category: "Best Practices", category: "Best Practices",
recommended: false recommended: true
}, },
schema: [ schema: [
@ -55,14 +55,14 @@ module.exports = {
) { ) {
context.report({ context.report({
node: identifier, node: identifier,
message: "{{name}} is a read-only native object.", message: "Read-only global '{{name}}' should not be modified.",
data: identifier 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. * @param {Variable} variable - A variable to check.
* @returns {void} * @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 = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow multiple spaces in regular expression literals", description: "disallow multiple spaces in regular expressions",
category: "Possible Errors", category: "Possible Errors",
recommended: true recommended: true
}, },
@ -21,24 +21,66 @@ module.exports = {
}, },
create: function(context) { 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) { if (regexResults !== null) {
var token = context.getFirstToken(node), context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}.");
nodeType = token.type, }
nodeValue = token.value, }
multipleSpacesRegex = /( {2,})+?/,
regexResults;
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) { if (nodeType === "RegularExpression") {
context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}."); 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 // Helpers
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** var SENTINEL_TYPE = /^(?:[a-zA-Z]+?Statement|ArrowFunctionExpression|FunctionExpression|ClassExpression)$/;
* 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";
}
/** /**
* Checks whether or not a node is enclosed in parentheses. * Checks whether or not a node is enclosed in parentheses.
* @param {Node|null} node - A node to check. * @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. * @returns {boolean} Whether or not the node is enclosed in parentheses.
*/ */
function isEnclosedInParens(node, context) { function isEnclosedInParens(node, sourceCode) {
var prevToken = context.getTokenBefore(node); var prevToken = sourceCode.getTokenBefore(node);
var nextToken = context.getTokenAfter(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) { create: function(context) {
var always = (context.options[0] || "except-parens") !== "except-parens"; var always = (context.options[0] || "except-parens") !== "except-parens";
var sourceCode = context.getSourceCode();
/**
* 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);
}
}
return { return {
ReturnStatement: function(node) { AssignmentExpression: function(node) {
var message = "Return statement should not contain assignment."; if (!always && isEnclosedInParens(node, sourceCode)) {
return;
}
checkForAssignInReturn(node.argument, node, message); var parent = node.parent;
},
ArrowFunctionExpression: function(node) { // Find ReturnStatement or ArrowFunctionExpression in ancestors.
if (node.body.type !== "BlockStatement") { while (parent && !SENTINEL_TYPE.test(parent.type)) {
var message = "Arrow function should not return assignment."; 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 = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow `javascript", description: "disallow `javascript:` urls",
category: "Best Practices", category: "Best Practices",
recommended: false recommended: false
}, },

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

@ -21,6 +21,7 @@ module.exports = {
}, },
create: function(context) { create: function(context) {
var sourceCode = context.getSourceCode();
/** /**
* Parts of the grammar that are required to have parens. * Parts of the grammar that are required to have parens.
@ -30,7 +31,8 @@ module.exports = {
IfStatement: "test", IfStatement: "test",
SwitchStatement: "discriminant", SwitchStatement: "discriminant",
WhileStatement: "test", WhileStatement: "test",
WithStatement: "object" WithStatement: "object",
ArrowFunctionExpression: "body"
// Omitting CallExpression - commas are parsed as argument separators // Omitting CallExpression - commas are parsed as argument separators
// Omitting NewExpression - 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. * @returns {boolean} True if the node has a paren on each side.
*/ */
function isParenthesised(node) { function isParenthesised(node) {
var previousToken = context.getTokenBefore(node), var previousToken = sourceCode.getTokenBefore(node),
nextToken = context.getTokenAfter(node); nextToken = sourceCode.getTokenAfter(node);
return previousToken && nextToken && return previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] && 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. * @returns {boolean} True if two parens surround the node on each side.
*/ */
function isParenthesisedTwice(node) { function isParenthesisedTwice(node) {
var previousToken = context.getTokenBefore(node, 1), var previousToken = sourceCode.getTokenBefore(node, 1),
nextToken = context.getTokenAfter(node, 1); nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) && previousToken && nextToken && return isParenthesised(node) && previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] && 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."); 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 PROPERTY_MESSAGE = "Unexpected newline between object and [ of property access.";
var TAGGED_TEMPLATE_MESSAGE = "Unexpected newline between template tag and template literal."; 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 * Check to see if there is a newline between the node and the following open bracket
* line's expression * line's expression
@ -34,12 +36,12 @@ module.exports = {
*/ */
function checkForBreakAfter(node, msg) { function checkForBreakAfter(node, msg) {
var nodeExpressionEnd = node; var nodeExpressionEnd = node;
var openParen = context.getTokenAfter(node); var openParen = sourceCode.getTokenAfter(node);
// Move along until the end of the wrapped expression // Move along until the end of the wrapped expression
while (openParen.value === ")") { while (openParen.value === ")") {
nodeExpressionEnd = openParen; nodeExpressionEnd = openParen;
openParen = context.getTokenAfter(nodeExpressionEnd); openParen = sourceCode.getTokenAfter(nodeExpressionEnd);
} }
if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) { if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) {

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

@ -9,7 +9,10 @@
// Helpers // 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 // Rule Definition
@ -18,9 +21,9 @@ var SENTINEL_NODE_TYPE = /^(?:Program|(?:Function|Class)(?:Declaration|Expressio
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow control flow statements in finally blocks", description: "disallow control flow statements in `finally` blocks",
category: "Possible Errors", category: "Possible Errors",
recommended: false recommended: true
} }
}, },
create: function(context) { create: function(context) {
@ -39,11 +42,29 @@ module.exports = {
* Climbs up the tree if the node is not a sentinel node * Climbs up the tree if the node is not a sentinel node
* *
* @param {ASTNode} node - node to check. * @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 * @returns {Boolean} - return whether the node is a finally block or a sentinel node
*/ */
function isInFinallyBlock(node) { function isInFinallyBlock(node, label) {
while (node && !SENTINEL_NODE_TYPE.test(node.type)) { 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 (isFinallyBlock(node)) {
if (label && labelInside) {
return false;
}
return true; return true;
} }
node = node.parent; node = node.parent;
@ -58,7 +79,7 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function check(node) { function check(node) {
if (isInFinallyBlock(node)) { if (isInFinallyBlock(node, node.label)) {
context.report({ context.report({
message: "Unsafe usage of " + node.type, message: "Unsafe usage of " + node.type,
node: node, node: node,

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

@ -10,6 +10,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
var lodash = require("lodash"); var lodash = require("lodash");
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
@ -95,6 +96,8 @@ module.exports = {
// Helpers // Helpers
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
var STATEMENT_TYPE = /(?:Statement|Declaration)$/;
/** /**
* Determines if a given variable is being exported from a module. * Determines if a given variable is being exported from a module.
* @param {Variable} variable - EScope variable object. * @param {Variable} variable - EScope variable object.
@ -124,7 +127,7 @@ module.exports = {
/** /**
* Determines if a reference is a read operation. * Determines if a reference is a read operation.
* @param {Reference} ref - An escope Reference * @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 * @private
*/ */
function isReadRef(ref) { function isReadRef(ref) {
@ -152,11 +155,204 @@ module.exports = {
return false; 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. * Determines if the variable is used.
* @param {Variable} variable - The variable to check. * @param {Variable} variable - The variable to check.
* @param {Reference[]} references - The variable references to check.
* @returns {boolean} True if the variable is used * @returns {boolean} True if the variable is used
* @private
*/ */
function isUsedVariable(variable) { function isUsedVariable(variable) {
var functionNodes = variable.defs.filter(function(def) { var functionNodes = variable.defs.filter(function(def) {
@ -164,10 +360,23 @@ module.exports = {
}).map(function(def) { }).map(function(def) {
return def.node; return def.node;
}), }),
isFunctionDefinition = functionNodes.length > 0; isFunctionDefinition = functionNodes.length > 0,
rhsNode = null;
return variable.references.some(function(ref) { 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 {escope.Variable} variable - A variable to get.
* @param {ASTNode} comment - A comment node which includes the variable name. * @param {ASTNode} comment - A comment node which includes the variable name.
* @returns {number} The index of the variable name's location. * @returns {number} The index of the variable name's location.
* @private
*/ */
function getColumnInComment(variable, comment) { function getColumnInComment(variable, comment) {
var namePattern = new RegExp("[\\s,]" + lodash.escapeRegExp(variable.name) + "(?:$|[\\s,:])", "g"); 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. * @param {escope.Variable} variable - A variable to get its location.
* @returns {{line: number, column: number}} The location object for the variable. * @returns {{line: number, column: number}} The location object for the variable.
* @private
*/ */
function getLocation(variable) { function getLocation(variable) {
var comment = variable.eslintExplicitGlobalComment; 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. * Checks whether or not the tokens of two given nodes are same.
* @param {ASTNode} left - A node 1 to compare. * @param {ASTNode} left - A node 1 to compare.
* @param {ASTNode} right - A node 2 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. * @returns {boolean} the source code for the given node.
*/ */
function equalTokens(left, right, context) { function equalTokens(left, right, sourceCode) {
var tokensL = context.getTokens(left); var tokensL = sourceCode.getTokens(left);
var tokensR = context.getTokens(right); var tokensR = sourceCode.getTokens(right);
if (tokensL.length !== tokensR.length) { if (tokensL.length !== tokensR.length) {
return false; return false;
@ -57,14 +57,14 @@ function equalTokens(left, right, context) {
* Checks whether or not `thisArg` is not changed by `.call()`/`.apply()`. * 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|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 {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()`. * @returns {boolean} Whether or not `thisArg` is not changed by `.call()`/`.apply()`.
*/ */
function isValidThisArg(expectedThis, thisArg, context) { function isValidThisArg(expectedThis, thisArg, sourceCode) {
if (!expectedThis) { if (!expectedThis) {
return astUtils.isNullOrUndefined(thisArg); return astUtils.isNullOrUndefined(thisArg);
} }
return equalTokens(expectedThis, thisArg, context); return equalTokens(expectedThis, thisArg, sourceCode);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -83,6 +83,8 @@ module.exports = {
}, },
create: function(context) { create: function(context) {
var sourceCode = context.getSourceCode();
return { return {
CallExpression: function(node) { CallExpression: function(node) {
if (!isCallOrNonVariadicApply(node)) { if (!isCallOrNonVariadicApply(node)) {
@ -93,7 +95,7 @@ module.exports = {
var expectedThis = (applied.type === "MemberExpression") ? applied.object : null; var expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
var thisArg = node.arguments[0]; var thisArg = node.arguments[0];
if (isValidThisArg(expectedThis, thisArg, context)) { if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
context.report( context.report(
node, node,
"unnecessary '.{{name}}()'.", "unnecessary '.{{name}}()'.",

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

@ -19,6 +19,8 @@ module.exports = {
} }
}, },
create: function(context) { create: function(context) {
var sourceCode = context.getSourceCode();
return { return {
Property: function(node) { Property: function(node) {
if (!node.computed) { if (!node.computed) {
@ -29,7 +31,7 @@ module.exports = {
nodeType = typeof key.value; nodeType = typeof key.value;
if (key.type === "Literal" && (nodeType === "string" || nodeType === "number")) { 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) { create: function(context) {
var sourceCode = context.getSourceCode();
return { return {
BinaryExpression: function(node) { BinaryExpression: function(node) {
@ -85,10 +87,10 @@ module.exports = {
) { ) {
// move warning location to operator // move warning location to operator
var operatorToken = context.getTokenAfter(left); var operatorToken = sourceCode.getTokenAfter(left);
while (operatorToken.value !== "+") { while (operatorToken.value !== "+") {
operatorToken = context.getTokenAfter(operatorToken); operatorToken = sourceCode.getTokenAfter(operatorToken);
} }
context.report( 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 = ( closingCurlyBraceMustBeSpaced = (
options.arraysInObjectsException && penultimateType === "ArrayExpression" || options.arraysInObjectsException && penultimateType === "ArrayExpression" ||
options.objectsInObjectsException && penultimateType === "ObjectExpression" options.objectsInObjectsException && (penultimateType === "ObjectExpression" || penultimateType === "ObjectPattern")
) ? !options.spaced : options.spaced; ) ? !options.spaced : options.spaced;
lastSpaced = sourceCode.isSpaceBetweenTokens(penultimate, last); 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 recommended: false
}, },
fixable: "code",
schema: { schema: {
anyOf: [ anyOf: [
{ {
@ -35,6 +37,25 @@ module.exports = {
minItems: 0, minItems: 0,
maxItems: 1 maxItems: 1
}, },
{
type: "array",
items: [
{
enum: ["always", "methods", "properties"]
},
{
type: "object",
properties: {
avoidQuotes: {
type: "boolean"
}
},
additionalProperties: false
}
],
minItems: 0,
maxItems: 2
},
{ {
type: "array", type: "array",
items: [ items: [
@ -46,6 +67,9 @@ module.exports = {
properties: { properties: {
ignoreConstructors: { ignoreConstructors: {
type: "boolean" type: "boolean"
},
avoidQuotes: {
type: "boolean"
} }
}, },
additionalProperties: false additionalProperties: false
@ -66,6 +90,7 @@ module.exports = {
var PARAMS = context.options[1] || {}; var PARAMS = context.options[1] || {};
var IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors; var IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors;
var AVOID_QUOTES = PARAMS.avoidQuotes;
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Helpers // Helpers
@ -83,6 +108,15 @@ module.exports = {
return firstChar === firstChar.toUpperCase(); 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 // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -97,45 +131,127 @@ module.exports = {
return; return;
} }
// if we're "never" and concise we should warn now // getters and setters are ignored
if (APPLY_NEVER && isConciseProperty) { if (node.kind === "get" || node.kind === "set") {
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) {
return; return;
} }
// only computed methods can fail the following checks // only computed methods can fail the following checks
if (!APPLY_TO_METHODS && node.computed) { if (node.computed && node.value.type !== "FunctionExpression") {
return; 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; return;
} }
//--------------------------------------------------------------
// Checks for longform properties.
if (node.value.type === "FunctionExpression" && !node.value.id && APPLY_TO_METHODS) { if (node.value.type === "FunctionExpression" && !node.value.id && APPLY_TO_METHODS) {
if (IGNORE_CONSTRUCTORS && isConstructor(node.key.name)) { if (IGNORE_CONSTRUCTORS && isConstructor(node.key.name)) {
return; 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() {}} // {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) { } else if (node.value.type === "Identifier" && node.key.name === node.value.name && APPLY_TO_PROPS) {
// {x: x} should be written as {x} // {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) { } 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} // {"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."); context.report(node, "Combine this with the previous '" + type + "' statement with initialized variables.");
} }
if (options[type].uninitialized === MODE_ALWAYS) { 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."); 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"; styleOverrides[":"] = "before";
} }
var sourceCode = context.getSourceCode();
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Helpers // Helpers
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -69,8 +71,8 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function validateNode(node, leftSide) { function validateNode(node, leftSide) {
var leftToken = context.getLastToken(leftSide); var leftToken = sourceCode.getLastToken(leftSide);
var operatorToken = context.getTokenAfter(leftToken); var operatorToken = sourceCode.getTokenAfter(leftToken);
// When the left part of a binary expression is a single expression wrapped in // 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 // 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. // should be the token right after that.
while (operatorToken.value === ")") { while (operatorToken.value === ")") {
leftToken = operatorToken; 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 operator = operatorToken.value;
var operatorStyleOverride = styleOverrides[operator]; var operatorStyleOverride = styleOverrides[operator];
var style = operatorStyleOverride || globalStyle; var style = operatorStyleOverride || globalStyle;

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

@ -17,6 +17,8 @@ module.exports = {
recommended: false recommended: false
}, },
fixable: "whitespace",
schema: [ schema: [
{ {
oneOf: [ oneOf: [
@ -164,6 +166,9 @@ module.exports = {
context.report({ context.report({
node: node, node: node,
loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column }, loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column },
fix: function(fixer) {
return fixer.insertTextAfter(openBrace, "\n");
},
message: ALWAYS_MESSAGE message: ALWAYS_MESSAGE
}); });
} }
@ -171,23 +176,36 @@ module.exports = {
context.report({ context.report({
node: node, node: node,
loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 }, loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 },
fix: function(fixer) {
return fixer.insertTextBefore(closeBrace, "\n");
},
message: ALWAYS_MESSAGE message: ALWAYS_MESSAGE
}); });
} }
} else { } else {
if (blockHasTopPadding) { if (blockHasTopPadding) {
var nextToken = sourceCode.getTokenOrCommentAfter(openBrace);
context.report({ context.report({
node: node, node: node,
loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column }, 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 message: NEVER_MESSAGE
}); });
} }
if (blockHasBottomPadding) { if (blockHasBottomPadding) {
var previousToken = sourceCode.getTokenOrCommentBefore(closeBrace);
context.report({ context.report({
node: node, node: node,
loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 }, 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 Map = require("es6-map");
var lodash = require("lodash");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
@ -62,45 +63,72 @@ function canBecomeVariableDeclaration(identifier) {
} }
/** /**
* Gets the WriteReference of a given variable if the variable should be * Gets an identifier node of a given variable.
* declared as const. *
* 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. * @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) { if (variable.eslintUsed) {
return null; return null;
} }
// Finds the singular WriteReference. // Finds the unique WriteReference.
var retv = null; var writer = null;
var isReadBeforeInit = false;
var references = variable.references; var references = variable.references;
for (var i = 0; i < references.length; ++i) { for (var i = 0; i < references.length; ++i) {
var reference = references[i]; var reference = references[i];
if (reference.isWrite()) { if (reference.isWrite()) {
var isReassigned = Boolean( var isReassigned = (
retv && retv.identifier !== reference.identifier writer !== null &&
writer.identifier !== reference.identifier
); );
if (isReassigned) { if (isReassigned) {
return null; 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 // If the assignment is from a different scope, ignore it.
// const. // If the assignment cannot change to a declaration, ignore it.
var isSameScopeAndCanBecomeVariableDeclaration = Boolean( var shouldBeConst = (
retv && writer !== null &&
retv.from === variable.scope && writer.from === variable.scope &&
canBecomeVariableDeclaration(retv.identifier) 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. * destructuring.
* *
* @param {escope.Variable[]} variables - Variables to group by 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) { function groupByDestructuring(variables, ignoreReadBeforeAssign) {
var writersMap = new Map(); var identifierMap = new Map();
for (var i = 0; i < variables.length; ++i) { for (var i = 0; i < variables.length; ++i) {
var variable = variables[i]; var variable = variables[i];
var references = variable.references; var references = variable.references;
var writer = getWriteReferenceIfShouldBeConst(variable); var identifier = getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign);
var prevId = null; var prevId = null;
for (var j = 0; j < references.length; ++j) { for (var j = 0; j < references.length; ++j) {
@ -158,20 +188,38 @@ function groupByDestructuring(variables) {
} }
prevId = id; prevId = id;
// Add the writer into the destructuring group. // Add the identifier node into the destructuring group.
var group = getDestructuringHost(reference); var group = getDestructuringHost(reference);
if (group) { if (group) {
if (writersMap.has(group)) { if (identifierMap.has(group)) {
writersMap.get(group).push(writer); identifierMap.get(group).push(identifier);
} else { } 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 recommended: false
}, },
fixable: "code",
schema: [ schema: [
{ {
type: "object", type: "object",
properties: { properties: {
destructuring: {enum: ["any", "all"]} destructuring: {enum: ["any", "all"]},
ignoreReadBeforeAssign: {type: "boolean"}
}, },
additionalProperties: false additionalProperties: false
} }
@ -200,22 +251,64 @@ module.exports = {
create: function(context) { create: function(context) {
var options = context.options[0] || {}; var options = context.options[0] || {};
var checkingMixedDestructuring = options.destructuring !== "all"; var checkingMixedDestructuring = options.destructuring !== "all";
var ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true;
var variables = null; 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} * @returns {void}
*/ */
function report(reference) { function report(node) {
var id = reference.identifier; 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({ context.report(reportArgs);
node: id,
message: "'{{name}}' is never reassigned, use 'const' instead.",
data: id
});
} }
/** /**
@ -225,30 +318,30 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function checkVariable(variable) { function checkVariable(variable) {
var writer = getWriteReferenceIfShouldBeConst(variable); var node = getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign);
if (writer) { if (node) {
report(writer); report(node);
} }
} }
/** /**
* Reports given references if all of the reference should be declared as * Reports given identifier nodes if all of the nodes should be declared
* const. * as const.
* *
* The argument 'writers' is an array of references. * The argument 'nodes' is an array of Identifier nodes.
* This reference is the result of * This node is the result of 'getIdentifierIfShouldBeConst()', so it's
* 'getWriteReferenceIfShouldBeConst(variable)', so it's nullable. * nullable. In simple declaration or assignment cases, the length of
* In simple declaration or assignment cases, the length of the array is 1. * the array is 1. In destructuring cases, the length of the array can
* In destructuring cases, the length of the array can be 2 or more. * be 2 or more.
* *
* @param {(escope.Reference|null)[]} writers - References which are grouped * @param {(escope.Reference|null)[]} nodes -
* by destructuring to report. * References which are grouped by destructuring to report.
* @returns {void} * @returns {void}
*/ */
function checkGroup(writers) { function checkGroup(nodes) {
if (writers.every(Boolean)) { if (nodes.every(Boolean)) {
writers.forEach(report); nodes.forEach(report);
} }
} }
@ -261,7 +354,8 @@ module.exports = {
if (checkingMixedDestructuring) { if (checkingMixedDestructuring) {
variables.forEach(checkVariable); variables.forEach(checkVariable);
} else { } else {
groupByDestructuring(variables).forEach(checkGroup); groupByDestructuring(variables, ignoreReadBeforeAssign)
.forEach(checkGroup);
} }
variables = null; 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. * Checks whether or not the tokens of two given nodes are same.
* @param {ASTNode} left - A node 1 to compare. * @param {ASTNode} left - A node 1 to compare.
* @param {ASTNode} right - A node 2 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. * @returns {boolean} the source code for the given node.
*/ */
function equalTokens(left, right, context) { function equalTokens(left, right, sourceCode) {
var tokensL = context.getTokens(left); var tokensL = sourceCode.getTokens(left);
var tokensR = context.getTokens(right); var tokensR = sourceCode.getTokens(right);
if (tokensL.length !== tokensR.length) { if (tokensL.length !== tokensR.length) {
return false; return false;
@ -82,6 +82,8 @@ module.exports = {
}, },
create: function(context) { create: function(context) {
var sourceCode = context.getSourceCode();
return { return {
CallExpression: function(node) { CallExpression: function(node) {
if (!isVariadicApplyCalling(node)) { if (!isVariadicApplyCalling(node)) {
@ -92,7 +94,7 @@ module.exports = {
var expectedThis = (applied.type === "MemberExpression") ? applied.object : null; var expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
var thisArg = node.arguments[0]; 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()'."); 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: { docs: {
description: "require generator functions to contain `yield`", description: "require generator functions to contain `yield`",
category: "ECMAScript 6", category: "ECMAScript 6",
recommended: false recommended: true
}, },
schema: [] 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. * @returns {boolean} True if the given token has leading space, false if not.
*/ */
function hasLeadingSpace(token) { function hasLeadingSpace(token) {
var tokenBefore = context.getTokenBefore(token); var tokenBefore = sourceCode.getTokenBefore(token);
return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, 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. * @returns {boolean} True if the given token has trailing space, false if not.
*/ */
function hasTrailingSpace(token) { function hasTrailingSpace(token) {
var tokenAfter = context.getTokenAfter(token); var tokenAfter = sourceCode.getTokenAfter(token);
return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter); 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. * @returns {boolean} Whether or not the token is the last in its line.
*/ */
function isLastTokenInCurrentLine(token) { function isLastTokenInCurrentLine(token) {
var tokenAfter = context.getTokenAfter(token); var tokenAfter = sourceCode.getTokenAfter(token);
return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter)); 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. * @returns {boolean} Whether or not the token is the first in its line.
*/ */
function isFirstTokenInCurrentLine(token) { function isFirstTokenInCurrentLine(token) {
var tokenBefore = context.getTokenBefore(token); var tokenBefore = sourceCode.getTokenBefore(token);
return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore)); 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. * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis.
*/ */
function isBeforeClosingParen(token) { function isBeforeClosingParen(token) {
var nextToken = context.getTokenAfter(token); var nextToken = sourceCode.getTokenAfter(token);
return ( return (
nextToken && nextToken &&
@ -140,7 +140,7 @@ module.exports = {
loc: location, loc: location,
message: "Unexpected whitespace before semicolon.", message: "Unexpected whitespace before semicolon.",
fix: function(fixer) { fix: function(fixer) {
var tokenBefore = context.getTokenBefore(token); var tokenBefore = sourceCode.getTokenBefore(token);
return fixer.removeRange([tokenBefore.range[1], token.range[0]]); return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
} }
@ -167,7 +167,7 @@ module.exports = {
loc: location, loc: location,
message: "Unexpected whitespace after semicolon.", message: "Unexpected whitespace after semicolon.",
fix: function(fixer) { fix: function(fixer) {
var tokenAfter = context.getTokenAfter(token); var tokenAfter = sourceCode.getTokenAfter(token);
return fixer.removeRange([token.range[1], tokenAfter.range[0]]); return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
} }
@ -195,7 +195,7 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function checkNode(node) { function checkNode(node) {
var token = context.getLastToken(node); var token = sourceCode.getLastToken(node);
checkSemicolonSpacing(token, node); checkSemicolonSpacing(token, node);
} }
@ -210,11 +210,11 @@ module.exports = {
ThrowStatement: checkNode, ThrowStatement: checkNode,
ForStatement: function(node) { ForStatement: function(node) {
if (node.init) { if (node.init) {
checkSemicolonSpacing(context.getTokenAfter(node.init), node); checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
} }
if (node.test) { 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; return false;
} }
nextToken = context.getTokenAfter(lastToken); nextToken = sourceCode.getTokenAfter(lastToken);
if (!nextToken) { if (!nextToken) {
return true; return true;
@ -141,7 +141,7 @@ module.exports = {
* @returns {boolean} whether the node is in a one-liner block statement. * @returns {boolean} whether the node is in a one-liner block statement.
*/ */
function isOneLinerBlock(node) { function isOneLinerBlock(node) {
var nextToken = context.getTokenAfter(node); var nextToken = sourceCode.getTokenAfter(node);
if (!nextToken || nextToken.value !== "}") { if (!nextToken || nextToken.value !== "}") {
return false; return false;
@ -159,7 +159,7 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function checkForSemicolon(node) { function checkForSemicolon(node) {
var lastToken = context.getLastToken(node); var lastToken = sourceCode.getLastToken(node);
if (never) { if (never) {
if (isUnnecessarySemicolon(lastToken)) { if (isUnnecessarySemicolon(lastToken)) {

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

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

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

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

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

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

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

@ -23,7 +23,9 @@ var messages = {
unnecessary: "Unnecessary 'use strict' directive.", unnecessary: "Unnecessary 'use strict' directive.",
module: "'use strict' is unnecessary inside of modules.", module: "'use strict' is unnecessary inside of modules.",
implied: "'use strict' is unnecessary when implied strict mode is enabled.", 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; 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 // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -136,7 +158,9 @@ module.exports = {
isStrict = useStrictDirectives.length > 0; isStrict = useStrictDirectives.length > 0;
if (isStrict) { if (isStrict) {
if (isParentStrict) { if (!isSimpleParameterList(node.params)) {
context.report(useStrictDirectives[0], messages.nonSimpleParameterList);
} else if (isParentStrict) {
context.report(useStrictDirectives[0], messages.unnecessary); context.report(useStrictDirectives[0], messages.unnecessary);
} else if (isInClass) { } else if (isInClass) {
context.report(useStrictDirectives[0], messages.unnecessaryInClasses); context.report(useStrictDirectives[0], messages.unnecessaryInClasses);
@ -144,7 +168,11 @@ module.exports = {
reportAllExceptFirst(useStrictDirectives, messages.multiple); reportAllExceptFirst(useStrictDirectives, messages.multiple);
} else if (isParentGlobal) { } 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); scopes.push(isParentStrict || isStrict);
@ -172,8 +200,13 @@ module.exports = {
if (mode === "function") { if (mode === "function") {
enterFunctionInFunctionMode(node, useStrictDirectives); enterFunctionInFunctionMode(node, useStrictDirectives);
} else { } else if (useStrictDirectives.length > 0) {
reportAll(useStrictDirectives, messages[mode]); 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