Browse Source

tools: update ESLint to 2.7.0

PR-URL: https://github.com/nodejs/node/pull/6132
Reviewed-By: Brian White <mscdex@mscdex.net>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: thefourtheye <thechargingvolcano@gmail.com>
process-exit-stdio-flushing
silverwind 9 years ago
parent
commit
2f6ff1bb64
No known key found for this signature in database GPG Key ID: 2E62B41C93869443
  1. 2892
      tools/eslint/CHANGELOG.md
  2. 32
      tools/eslint/README.md
  3. 21
      tools/eslint/bin/eslint.js
  4. 3
      tools/eslint/conf/cli-options.js
  5. 414
      tools/eslint/conf/eslint.json
  6. 47
      tools/eslint/lib/ast-utils.js
  7. 193
      tools/eslint/lib/cli-engine.js
  8. 2
      tools/eslint/lib/cli.js
  9. 50
      tools/eslint/lib/code-path-analysis/code-path-analyzer.js
  10. 30
      tools/eslint/lib/code-path-analysis/code-path-segment.js
  11. 199
      tools/eslint/lib/code-path-analysis/code-path-state.js
  12. 118
      tools/eslint/lib/code-path-analysis/code-path.js
  13. 4
      tools/eslint/lib/code-path-analysis/debug-helpers.js
  14. 6
      tools/eslint/lib/code-path-analysis/fork-context.js
  15. 2
      tools/eslint/lib/code-path-analysis/id-generator.js
  16. 17
      tools/eslint/lib/config.js
  17. 44
      tools/eslint/lib/config/autoconfig.js
  18. 159
      tools/eslint/lib/config/config-file.js
  19. 34
      tools/eslint/lib/config/config-initializer.js
  20. 77
      tools/eslint/lib/config/config-ops.js
  21. 21
      tools/eslint/lib/config/config-rule.js
  22. 14
      tools/eslint/lib/config/config-validator.js
  23. 4
      tools/eslint/lib/config/plugins.js
  24. 105
      tools/eslint/lib/eslint.js
  25. 3
      tools/eslint/lib/file-finder.js
  26. 1
      tools/eslint/lib/formatters/compact.js
  27. 2
      tools/eslint/lib/formatters/html.js
  28. 1
      tools/eslint/lib/formatters/junit.js
  29. 1
      tools/eslint/lib/formatters/tap.js
  30. 1
      tools/eslint/lib/formatters/unix.js
  31. 1
      tools/eslint/lib/formatters/visualstudio.js
  32. 101
      tools/eslint/lib/ignored-paths.js
  33. 1
      tools/eslint/lib/load-rules.js
  34. 3
      tools/eslint/lib/logging.js
  35. 7
      tools/eslint/lib/options.js
  36. 4
      tools/eslint/lib/rule-context.js
  37. 1
      tools/eslint/lib/rules.js
  38. 11
      tools/eslint/lib/rules/accessor-pairs.js
  39. 2
      tools/eslint/lib/rules/array-bracket-spacing.js
  40. 11
      tools/eslint/lib/rules/array-callback-return.js
  41. 1
      tools/eslint/lib/rules/arrow-body-style.js
  42. 13
      tools/eslint/lib/rules/arrow-spacing.js
  43. 4
      tools/eslint/lib/rules/block-scoped-var.js
  44. 1
      tools/eslint/lib/rules/block-spacing.js
  45. 23
      tools/eslint/lib/rules/brace-style.js
  46. 1
      tools/eslint/lib/rules/callback-return.js
  47. 7
      tools/eslint/lib/rules/camelcase.js
  48. 26
      tools/eslint/lib/rules/comma-dangle.js
  49. 32
      tools/eslint/lib/rules/complexity.js
  50. 20
      tools/eslint/lib/rules/consistent-return.js
  51. 1
      tools/eslint/lib/rules/consistent-this.js
  52. 150
      tools/eslint/lib/rules/constructor-super.js
  53. 10
      tools/eslint/lib/rules/curly.js
  54. 8
      tools/eslint/lib/rules/default-case.js
  55. 2
      tools/eslint/lib/rules/dot-location.js
  56. 5
      tools/eslint/lib/rules/dot-notation.js
  57. 2
      tools/eslint/lib/rules/eol-last.js
  58. 1
      tools/eslint/lib/rules/eqeqeq.js
  59. 4
      tools/eslint/lib/rules/generator-star-spacing.js
  60. 1
      tools/eslint/lib/rules/global-require.js
  61. 2
      tools/eslint/lib/rules/handle-callback-err.js
  62. 3
      tools/eslint/lib/rules/id-length.js
  63. 2
      tools/eslint/lib/rules/id-match.js
  64. 91
      tools/eslint/lib/rules/indent.js
  65. 2
      tools/eslint/lib/rules/init-declarations.js
  66. 5
      tools/eslint/lib/rules/key-spacing.js
  67. 14
      tools/eslint/lib/rules/keyword-spacing.js
  68. 3
      tools/eslint/lib/rules/linebreak-style.js
  69. 9
      tools/eslint/lib/rules/lines-around-comment.js
  70. 31
      tools/eslint/lib/rules/max-depth.js
  71. 40
      tools/eslint/lib/rules/max-len.js
  72. 31
      tools/eslint/lib/rules/max-nested-callbacks.js
  73. 31
      tools/eslint/lib/rules/max-params.js
  74. 106
      tools/eslint/lib/rules/max-statements-per-line.js
  75. 33
      tools/eslint/lib/rules/max-statements.js
  76. 8
      tools/eslint/lib/rules/new-cap.js
  77. 1
      tools/eslint/lib/rules/new-parens.js
  78. 3
      tools/eslint/lib/rules/newline-after-var.js
  79. 143
      tools/eslint/lib/rules/newline-before-return.js
  80. 93
      tools/eslint/lib/rules/newline-per-chained-call.js
  81. 1
      tools/eslint/lib/rules/no-alert.js
  82. 1
      tools/eslint/lib/rules/no-case-declarations.js
  83. 1
      tools/eslint/lib/rules/no-cond-assign.js
  84. 18
      tools/eslint/lib/rules/no-confusing-arrow.js
  85. 5
      tools/eslint/lib/rules/no-constant-condition.js
  86. 2
      tools/eslint/lib/rules/no-control-regex.js
  87. 2
      tools/eslint/lib/rules/no-dupe-args.js
  88. 2
      tools/eslint/lib/rules/no-dupe-class-members.js
  89. 1
      tools/eslint/lib/rules/no-duplicate-case.js
  90. 126
      tools/eslint/lib/rules/no-duplicate-imports.js
  91. 10
      tools/eslint/lib/rules/no-else-return.js
  92. 1
      tools/eslint/lib/rules/no-empty-character-class.js
  93. 1
      tools/eslint/lib/rules/no-empty-function.js
  94. 1
      tools/eslint/lib/rules/no-empty.js
  95. 19
      tools/eslint/lib/rules/no-eval.js
  96. 3
      tools/eslint/lib/rules/no-extend-native.js
  97. 2
      tools/eslint/lib/rules/no-extra-bind.js
  98. 2
      tools/eslint/lib/rules/no-extra-boolean-cast.js
  99. 63
      tools/eslint/lib/rules/no-extra-parens.js
  100. 1
      tools/eslint/lib/rules/no-extra-semi.js

2892
tools/eslint/CHANGELOG.md

File diff suppressed because it is too large

32
tools/eslint/README.md

@ -8,7 +8,7 @@
# ESLint # ESLint
[Website](http://eslint.org) | [Configuring](http://eslint.org/docs/user-guide/configuring) | [Rules](http://eslint.org/docs/rules/) | [Contributing](http://eslint.org/docs/developer-guide/contributing) | [Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) | [Twitter](https://twitter.com/geteslint) | [Mailing List](https://groups.google.com/group/eslint) [Website](http://eslint.org) | [Configuring](http://eslint.org/docs/user-guide/configuring) | [Rules](http://eslint.org/docs/rules/) | [Contributing](http://eslint.org/docs/developer-guide/contributing) | [Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) | [Twitter](https://twitter.com/geteslint) | [Mailing List](https://groups.google.com/group/eslint) | [Chat Room](https://gitter.im/eslint/eslint)
ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions: ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions:
@ -39,17 +39,17 @@ After running `eslint --init`, you'll have a `.eslintrc` file in your directory.
```json ```json
{ {
"rules": { "rules": {
"semi": [2, "always"], "semi": ["error", "always"],
"quotes": [2, "double"] "quotes": ["error", "double"]
} }
} }
``` ```
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 number is the error level of the rule and can be one of the three values:
* `0` - turn the rule off * `"off"` or `0` - turn the rule off
* `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)
* `2` - turn the rule on as an error (exit code will be 1) * `"error"` or `2` - turn the rule on as an error (exit code will be 1)
The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](http://eslint.org/docs/user-guide/configuring)). The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](http://eslint.org/docs/user-guide/configuring)).
@ -66,12 +66,14 @@ These folks keep the project moving and are resources for help:
* Ilya Volodin ([@ilyavolodin](https://github.com/ilyavolodin)) - reviewer * Ilya Volodin ([@ilyavolodin](https://github.com/ilyavolodin)) - reviewer
* Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer * Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer * Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - reviewer
* Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - committer * Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - committer
* Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - committer * Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - committer
* Ian VanSchooten ([@ianvs](https://github.com/ianvs)) - committer * Ian VanSchooten ([@ianvs](https://github.com/ianvs)) - committer
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - committer
* Burak Yiğit Kaya ([@byk](https://github.com/byk)) - committer * Burak Yiğit Kaya ([@byk](https://github.com/byk)) - committer
* Alberto Rodríguez ([@alberto](https://github.com/alberto)) - committer * Alberto Rodríguez ([@alberto](https://github.com/alberto)) - committer
* Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo)) - committer
* Michael Ficarra ([@michaelficarra](https://github.com/michaelficarra)) - committer
## Releases ## Releases
@ -83,6 +85,7 @@ Before filing an issue, please be sure to read the guidelines for what you're re
* [Bug Report](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) * [Bug Report](http://eslint.org/docs/developer-guide/contributing/reporting-bugs)
* [Propose a New Rule](http://eslint.org/docs/developer-guide/contributing/new-rules) * [Propose a New Rule](http://eslint.org/docs/developer-guide/contributing/new-rules)
* [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 ## Frequently Asked Questions
@ -109,18 +112,23 @@ If you are using both JSHint and JSCS on your files, then using just ESLint will
ESLint does both traditional linting (looking for problematic patterns) and style checking (enforcement of conventions). You can use it for both. ESLint does both traditional linting (looking for problematic patterns) and style checking (enforcement of conventions). You can use it for both.
### What about ECMAScript 6 support?
ESLint has full support for ECMAScript 6. By default, this support is off. You can enable ECMAScript 6 support through [configuration](http://eslint.org/docs/user-guide/configuring).
### Does ESLint support JSX? ### Does ESLint support JSX?
Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](http://eslint.org/docs/user-guide/configuring).). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics. Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](http://eslint.org/docs/user-guide/configuring).). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics.
### What about ECMAScript 7/2016 and experimental features? ### What about ECMAScript 6 support?
ESLint has full support for ECMAScript 6. By default, this support is off. You can enable ECMAScript 6 support through [configuration](http://eslint.org/docs/user-guide/configuring).
### What about experimental features?
ESLint doesn't natively support experimental ECMAScript language features. You can use [babel-eslint](https://github.com/babel/babel-eslint) to use any option available in Babel. ESLint doesn't natively support experimental ECMAScript language features. You can use [babel-eslint](https://github.com/babel/babel-eslint) to use any option available in Babel.
Once a language feature has been adopted into the ECMAScript standard, we will accept
issues and pull requests related to the new feature, subject to our [contributing
guidelines](http://eslint.org/docs/developer-guide/contributing). Until then, please use
the appropriate parser and plugin(s) for your experimental feature.
### Where to ask for help? ### Where to ask for help?
Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint) Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint)

21
tools/eslint/bin/eslint.js

@ -29,12 +29,31 @@ if (debug) {
// now we can safely include the other modules that use debug // now we can safely include the other modules that use debug
var concat = require("concat-stream"), var concat = require("concat-stream"),
cli = require("../lib/cli"); cli = require("../lib/cli"),
path = require("path"),
fs = require("fs");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Execution // Execution
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
process.on("uncaughtException", function(err){
// lazy load
var lodash = require("lodash");
if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) {
var template = lodash.template(fs.readFileSync(path.resolve(__dirname, "../messages/" + err.messageTemplate + ".txt"), "utf-8"));
console.log("\nOops! Something went wrong! :(");
console.log("\n" + template(err.messageData || {}));
} else {
console.log(err.message);
console.log(err.stack);
}
process.exit(1);
});
if (useStdIn) { if (useStdIn) {
process.stdin.pipe(concat({ encoding: "string" }, function(text) { process.stdin.pipe(concat({ encoding: "string" }, function(text) {
try { try {

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

@ -28,6 +28,5 @@ module.exports = {
cacheLocation: "", cacheLocation: "",
cacheFile: ".eslintcache", cacheFile: ".eslintcache",
fix: false, fix: false,
allowInlineConfig: true, allowInlineConfig: true
cwd: process.cwd()
}; };

414
tools/eslint/conf/eslint.json

@ -2,210 +2,214 @@
"parser": "espree", "parser": "espree",
"ecmaFeatures": {}, "ecmaFeatures": {},
"rules": { "rules": {
"no-alert": 0, "no-alert": "off",
"no-array-constructor": 0, "no-array-constructor": "off",
"no-bitwise": 0, "no-bitwise": "off",
"no-caller": 0, "no-caller": "off",
"no-case-declarations": 2, "no-case-declarations": "error",
"no-catch-shadow": 0, "no-catch-shadow": "off",
"no-class-assign": 2, "no-class-assign": "error",
"no-cond-assign": 2, "no-cond-assign": "error",
"no-confusing-arrow": 0, "no-confusing-arrow": "off",
"no-console": 2, "no-console": "error",
"no-const-assign": 2, "no-const-assign": "error",
"no-constant-condition": 2, "no-constant-condition": "error",
"no-continue": 0, "no-continue": "off",
"no-control-regex": 2, "no-control-regex": "error",
"no-debugger": 2, "no-debugger": "error",
"no-delete-var": 2, "no-delete-var": "error",
"no-div-regex": 0, "no-div-regex": "off",
"no-dupe-class-members": 2, "no-dupe-class-members": "error",
"no-dupe-keys": 2, "no-dupe-keys": "error",
"no-dupe-args": 2, "no-dupe-args": "error",
"no-duplicate-case": 2, "no-duplicate-case": "error",
"no-else-return": 0, "no-duplicate-imports": "off",
"no-empty": 2, "no-else-return": "off",
"no-empty-character-class": 2, "no-empty": "error",
"no-empty-function": 0, "no-empty-character-class": "error",
"no-empty-pattern": 2, "no-empty-function": "off",
"no-eq-null": 0, "no-empty-pattern": "error",
"no-eval": 0, "no-eq-null": "off",
"no-ex-assign": 2, "no-eval": "off",
"no-extend-native": 0, "no-ex-assign": "error",
"no-extra-bind": 0, "no-extend-native": "off",
"no-extra-boolean-cast": 2, "no-extra-bind": "off",
"no-extra-label": 0, "no-extra-boolean-cast": "error",
"no-extra-parens": 0, "no-extra-label": "off",
"no-extra-semi": 2, "no-extra-parens": "off",
"no-fallthrough": 2, "no-extra-semi": "error",
"no-floating-decimal": 0, "no-fallthrough": "error",
"no-func-assign": 2, "no-floating-decimal": "off",
"no-implicit-coercion": 0, "no-func-assign": "error",
"no-implicit-globals": 0, "no-implicit-coercion": "off",
"no-implied-eval": 0, "no-implicit-globals": "off",
"no-inline-comments": 0, "no-implied-eval": "off",
"no-inner-declarations": 2, "no-inline-comments": "off",
"no-invalid-regexp": 2, "no-inner-declarations": "error",
"no-invalid-this": 0, "no-invalid-regexp": "error",
"no-irregular-whitespace": 2, "no-invalid-this": "off",
"no-iterator": 0, "no-irregular-whitespace": "error",
"no-label-var": 0, "no-iterator": "off",
"no-labels": 0, "no-label-var": "off",
"no-lone-blocks": 0, "no-labels": "off",
"no-lonely-if": 0, "no-lone-blocks": "off",
"no-loop-func": 0, "no-lonely-if": "off",
"no-mixed-requires": 0, "no-loop-func": "off",
"no-mixed-spaces-and-tabs": 2, "no-mixed-requires": "off",
"linebreak-style": 0, "no-mixed-spaces-and-tabs": "error",
"no-multi-spaces": 0, "linebreak-style": "off",
"no-multi-str": 0, "no-multi-spaces": "off",
"no-multiple-empty-lines": 0, "no-multi-str": "off",
"no-native-reassign": 0, "no-multiple-empty-lines": "off",
"no-negated-condition": 0, "no-native-reassign": "off",
"no-negated-in-lhs": 2, "no-negated-condition": "off",
"no-nested-ternary": 0, "no-negated-in-lhs": "error",
"no-new": 0, "no-nested-ternary": "off",
"no-new-func": 0, "no-new": "off",
"no-new-object": 0, "no-new-func": "off",
"no-new-require": 0, "no-new-object": "off",
"no-new-symbol": 2, "no-new-require": "off",
"no-new-wrappers": 0, "no-new-symbol": "error",
"no-obj-calls": 2, "no-new-wrappers": "off",
"no-octal": 2, "no-obj-calls": "error",
"no-octal-escape": 0, "no-octal": "error",
"no-param-reassign": 0, "no-octal-escape": "off",
"no-path-concat": 0, "no-param-reassign": "off",
"no-plusplus": 0, "no-path-concat": "off",
"no-process-env": 0, "no-plusplus": "off",
"no-process-exit": 0, "no-process-env": "off",
"no-proto": 0, "no-process-exit": "off",
"no-redeclare": 2, "no-proto": "off",
"no-regex-spaces": 2, "no-redeclare": "error",
"no-restricted-imports": 0, "no-regex-spaces": "error",
"no-restricted-modules": 0, "no-restricted-globals": "off",
"no-restricted-syntax": 0, "no-restricted-imports": "off",
"no-return-assign": 0, "no-restricted-modules": "off",
"no-script-url": 0, "no-restricted-syntax": "off",
"no-self-assign": 2, "no-return-assign": "off",
"no-self-compare": 0, "no-script-url": "off",
"no-sequences": 0, "no-self-assign": "error",
"no-shadow": 0, "no-self-compare": "off",
"no-shadow-restricted-names": 0, "no-sequences": "off",
"no-whitespace-before-property": 0, "no-shadow": "off",
"no-spaced-func": 0, "no-shadow-restricted-names": "off",
"no-sparse-arrays": 2, "no-whitespace-before-property": "off",
"no-sync": 0, "no-spaced-func": "off",
"no-ternary": 0, "no-sparse-arrays": "error",
"no-trailing-spaces": 0, "no-sync": "off",
"no-this-before-super": 2, "no-ternary": "off",
"no-throw-literal": 0, "no-trailing-spaces": "off",
"no-undef": 2, "no-this-before-super": "error",
"no-undef-init": 0, "no-throw-literal": "off",
"no-undefined": 0, "no-undef": "error",
"no-unexpected-multiline": 2, "no-undef-init": "off",
"no-underscore-dangle": 0, "no-undefined": "off",
"no-unmodified-loop-condition": 0, "no-unexpected-multiline": "error",
"no-unneeded-ternary": 0, "no-underscore-dangle": "off",
"no-unreachable": 2, "no-unmodified-loop-condition": "off",
"no-unused-expressions": 0, "no-unneeded-ternary": "off",
"no-unused-labels": 2, "no-unreachable": "error",
"no-unused-vars": 2, "no-unused-expressions": "off",
"no-use-before-define": 0, "no-unused-labels": "error",
"no-useless-call": 0, "no-unused-vars": "error",
"no-useless-concat": 0, "no-use-before-define": "off",
"no-useless-constructor": 0, "no-useless-call": "off",
"no-void": 0, "no-useless-concat": "off",
"no-var": 0, "no-useless-constructor": "off",
"no-warning-comments": 0, "no-useless-escape": "off",
"no-with": 0, "no-void": "off",
"no-magic-numbers": 0, "no-var": "off",
"no-warning-comments": "off",
"array-bracket-spacing": 0, "no-with": "off",
"array-callback-return": 0, "no-magic-numbers": "off",
"arrow-body-style": 0, "array-bracket-spacing": "off",
"arrow-parens": 0, "array-callback-return": "off",
"arrow-spacing": 0, "arrow-body-style": "off",
"accessor-pairs": 0, "arrow-parens": "off",
"block-scoped-var": 0, "arrow-spacing": "off",
"block-spacing": 0, "accessor-pairs": "off",
"brace-style": 0, "block-scoped-var": "off",
"callback-return": 0, "block-spacing": "off",
"camelcase": 0, "brace-style": "off",
"comma-dangle": 2, "callback-return": "off",
"comma-spacing": 0, "camelcase": "off",
"comma-style": 0, "comma-dangle": "error",
"complexity": [0, 11], "comma-spacing": "off",
"computed-property-spacing": 0, "comma-style": "off",
"consistent-return": 0, "complexity": ["off", 11],
"consistent-this": 0, "computed-property-spacing": "off",
"constructor-super": 2, "consistent-return": "off",
"curly": 0, "consistent-this": "off",
"default-case": 0, "constructor-super": "error",
"dot-location": 0, "curly": "off",
"dot-notation": 0, "default-case": "off",
"eol-last": 0, "dot-location": "off",
"eqeqeq": 0, "dot-notation": "off",
"func-names": 0, "eol-last": "off",
"func-style": 0, "eqeqeq": "off",
"generator-star-spacing": 0, "func-names": "off",
"global-require": 0, "func-style": "off",
"guard-for-in": 0, "generator-star-spacing": "off",
"handle-callback-err": 0, "global-require": "off",
"id-length": 0, "guard-for-in": "off",
"indent": 0, "handle-callback-err": "off",
"init-declarations": 0, "id-length": "off",
"jsx-quotes": 0, "indent": "off",
"key-spacing": 0, "init-declarations": "off",
"keyword-spacing": 0, "jsx-quotes": "off",
"lines-around-comment": 0, "key-spacing": "off",
"max-depth": 0, "keyword-spacing": "off",
"max-len": 0, "lines-around-comment": "off",
"max-nested-callbacks": 0, "max-depth": "off",
"max-params": 0, "max-len": "off",
"max-statements": 0, "max-nested-callbacks": "off",
"new-cap": 0, "max-params": "off",
"new-parens": 0, "max-statements": "off",
"newline-after-var": 0, "max-statements-per-line": "off",
"newline-per-chained-call": 0, "new-cap": "off",
"object-curly-spacing": [0, "never"], "new-parens": "off",
"object-shorthand": 0, "newline-after-var": "off",
"one-var": 0, "newline-before-return": "off",
"one-var-declaration-per-line": 0, "newline-per-chained-call": "off",
"operator-assignment": 0, "object-curly-spacing": ["off", "never"],
"operator-linebreak": 0, "object-shorthand": "off",
"padded-blocks": 0, "one-var": "off",
"prefer-arrow-callback": 0, "one-var-declaration-per-line": "off",
"prefer-const": 0, "operator-assignment": "off",
"prefer-reflect": 0, "operator-linebreak": "off",
"prefer-rest-params": 0, "padded-blocks": "off",
"prefer-spread": 0, "prefer-arrow-callback": "off",
"prefer-template": 0, "prefer-const": "off",
"quote-props": 0, "prefer-reflect": "off",
"quotes": 0, "prefer-rest-params": "off",
"radix": 0, "prefer-spread": "off",
"id-match": 0, "prefer-template": "off",
"id-blacklist": 0, "quote-props": "off",
"require-jsdoc": 0, "quotes": "off",
"require-yield": 0, "radix": "off",
"semi": 0, "id-match": "off",
"semi-spacing": 0, "id-blacklist": "off",
"sort-vars": 0, "require-jsdoc": "off",
"sort-imports": 0, "require-yield": "off",
"space-before-blocks": 0, "semi": "off",
"space-before-function-paren": 0, "semi-spacing": "off",
"space-in-parens": 0, "sort-vars": "off",
"space-infix-ops": 0, "sort-imports": "off",
"space-unary-ops": 0, "space-before-blocks": "off",
"spaced-comment": 0, "space-before-function-paren": "off",
"strict": 0, "space-in-parens": "off",
"template-curly-spacing": 0, "space-infix-ops": "off",
"use-isnan": 2, "space-unary-ops": "off",
"valid-jsdoc": 0, "spaced-comment": "off",
"valid-typeof": 2, "strict": "off",
"vars-on-top": 0, "template-curly-spacing": "off",
"wrap-iife": 0, "use-isnan": "error",
"wrap-regex": 0, "valid-jsdoc": "off",
"yield-star-spacing": 0, "valid-typeof": "error",
"yoda": 0 "vars-on-top": "off",
"wrap-iife": "off",
"wrap-regex": "off",
"yield-star-spacing": "off",
"yoda": "off"
} }
} }

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

@ -33,14 +33,21 @@ var thisTagPattern = /^[\s\*]*@this/m;
* @private * @private
*/ */
function isModifyingReference(reference, index, references) { function isModifyingReference(reference, index, references) {
var identifier = reference.identifier; var identifier = reference.identifier,
modifyingDifferentIdentifier;
/*
* Destructuring assignments can have multiple default value, so
* possibly there are multiple writeable references for the same
* identifier.
*/
modifyingDifferentIdentifier = index === 0 ||
references[index - 1].identifier !== identifier;
return (identifier && return (identifier &&
reference.init === false && reference.init === false &&
reference.isWrite() && reference.isWrite() &&
// Destructuring assignments can have multiple default value, modifyingDifferentIdentifier
// so possibly there are multiple writeable references for the same identifier.
(index === 0 || references[index - 1].identifier !== identifier)
); );
} }
@ -155,6 +162,7 @@ function isMethodWhichHasThisArg(node) {
*/ */
function hasJSDocThisTag(node, sourceCode) { function hasJSDocThisTag(node, sourceCode) {
var jsdocComment = sourceCode.getJSDocComment(node); var jsdocComment = sourceCode.getJSDocComment(node);
if (jsdocComment && thisTagPattern.test(jsdocComment.value)) { if (jsdocComment && thisTagPattern.test(jsdocComment.value)) {
return true; return true;
} }
@ -168,6 +176,22 @@ function hasJSDocThisTag(node, sourceCode) {
}); });
} }
/**
* Determines if a node is surrounded by parentheses.
* @param {RuleContext} context The context object passed to the rule
* @param {ASTNode} node The node to be checked.
* @returns {boolean} True if the node is parenthesised.
* @private
*/
function isParenthesised(context, node) {
var previousToken = context.getTokenBefore(node),
nextToken = context.getTokenAfter(node);
return Boolean(previousToken && nextToken) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Public Interface // Public Interface
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -187,8 +211,10 @@ module.exports = {
isNullOrUndefined: isNullOrUndefined, isNullOrUndefined: isNullOrUndefined,
isCallee: isCallee, isCallee: isCallee,
isES5Constructor: isES5Constructor,
getUpperFunction: getUpperFunction, getUpperFunction: getUpperFunction,
isArrayFromMethod: isArrayFromMethod, isArrayFromMethod: isArrayFromMethod,
isParenthesised: isParenthesised,
/** /**
* Checks whether or not a given node is a string literal. * Checks whether or not a given node is a string literal.
@ -261,6 +287,7 @@ module.exports = {
*/ */
isDirectiveComment: function(node) { isDirectiveComment: function(node) {
var comment = node.value.trim(); var comment = node.value.trim();
return ( return (
node.type === "Line" && comment.indexOf("eslint-") === 0 || node.type === "Line" && comment.indexOf("eslint-") === 0 ||
node.type === "Block" && ( node.type === "Block" && (
@ -293,8 +320,10 @@ module.exports = {
*/ */
getVariableByName: function(initScope, name) { getVariableByName: function(initScope, name) {
var scope = initScope; var scope = initScope;
while (scope) { while (scope) {
var variable = scope.set.get(name); var variable = scope.set.get(name);
if (variable) { if (variable) {
return variable; return variable;
} }
@ -333,10 +362,13 @@ module.exports = {
while (node) { while (node) {
var parent = node.parent; var parent = node.parent;
switch (parent.type) { switch (parent.type) {
// Looks up the destination.
// e.g. /*
// obj.foo = nativeFoo || function foo() { ... }; * Looks up the destination.
* e.g., obj.foo = nativeFoo || function foo() { ... };
*/
case "LogicalExpression": case "LogicalExpression":
case "ConditionalExpression": case "ConditionalExpression":
node = parent; node = parent;
@ -350,6 +382,7 @@ module.exports = {
// })(); // })();
case "ReturnStatement": case "ReturnStatement":
var func = getUpperFunction(parent); var func = getUpperFunction(parent);
if (func === null || !isCallee(func)) { if (func === null || !isCallee(func)) {
return true; return true;
} }

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

@ -22,8 +22,7 @@ var fs = require("fs"),
lodash = require("lodash"), lodash = require("lodash"),
debug = require("debug"), debug = require("debug"),
glob = require("glob"), isAbsolute = require("path-is-absolute"),
shell = require("shelljs"),
rules = require("./rules"), rules = require("./rules"),
eslint = require("./eslint"), eslint = require("./eslint"),
@ -36,8 +35,8 @@ var fs = require("fs"),
SourceCodeFixer = require("./util/source-code-fixer"), SourceCodeFixer = require("./util/source-code-fixer"),
validator = require("./config/config-validator"), validator = require("./config/config-validator"),
stringify = require("json-stable-stringify"), stringify = require("json-stable-stringify"),
hash = require("./util/hash"),
crypto = require( "crypto" ),
pkg = require("../package.json"); pkg = require("../package.json");
@ -73,12 +72,6 @@ var fs = require("fs"),
* @property {LintMessage[]} messages All of the messages for the result. * @property {LintMessage[]} messages All of the messages for the result.
*/ */
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
defaultOptions = lodash.assign({}, defaultOptions, {cwd: process.cwd()});
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -141,13 +134,14 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
config, config,
messages, messages,
stats, stats,
fileExtension = path.extname(filename), fileExtension,
processor, processor,
loadedPlugins, loadedPlugins,
fixedResult; fixedResult;
if (filename) { if (filename) {
filePath = path.resolve(filename); filePath = path.resolve(filename);
fileExtension = path.extname(filename);
} }
filename = filename || "<text>"; filename = filename || "<text>";
@ -171,6 +165,7 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
debug("Using processor"); debug("Using processor");
var parsedBlocks = processor.preprocess(text, filename); var parsedBlocks = processor.preprocess(text, filename);
var unprocessedMessages = []; var unprocessedMessages = [];
parsedBlocks.forEach(function(block) { parsedBlocks.forEach(function(block) {
unprocessedMessages.push(eslint.verify(block, config, { unprocessedMessages.push(eslint.verify(block, config, {
filename: filename, filename: filename,
@ -262,17 +257,6 @@ function isErrorMessage(message) {
return message.severity === 2; return message.severity === 2;
} }
/**
* create a md5Hash of a given string
* @param {string} str the string to calculate the hash for
* @returns {string} the calculated hash
*/
function md5Hash(str) {
return crypto
.createHash("md5")
.update(str, "utf8")
.digest("hex");
}
/** /**
* return the cacheFile to be used by eslint, based on whether the provided parameter is * return the cacheFile to be used by eslint, based on whether the provided parameter is
@ -286,8 +270,11 @@ function md5Hash(str) {
* @returns {string} the resolved path to the cache file * @returns {string} the resolved path to the cache file
*/ */
function getCacheFile(cacheFile, cwd) { function getCacheFile(cacheFile, cwd) {
// make sure the path separators are normalized for the environment/os
// keeping the trailing path separator if present /*
* make sure the path separators are normalized for the environment/os
* keeping the trailing path separator if present
*/
cacheFile = path.normalize(cacheFile); cacheFile = path.normalize(cacheFile);
var resolvedCacheFile = path.resolve(cwd, cacheFile); var resolvedCacheFile = path.resolve(cwd, cacheFile);
@ -298,7 +285,7 @@ function getCacheFile(cacheFile, cwd) {
* @returns {string} the resolved path to the cacheFile * @returns {string} the resolved path to the cacheFile
*/ */
function getCacheFileForDirectory() { function getCacheFileForDirectory() {
return path.join(resolvedCacheFile, ".cache_" + md5Hash(cwd)); return path.join(resolvedCacheFile, ".cache_" + hash(cwd));
} }
var fileStats; var fileStats;
@ -310,23 +297,31 @@ function getCacheFile(cacheFile, cwd) {
} }
// in case the file exists we need to verify if the provided path /*
// is a directory or a file. If it is a directory we want to create a file * in case the file exists we need to verify if the provided path
// inside that directory * is a directory or a file. If it is a directory we want to create a file
* inside that directory
*/
if (fileStats) { if (fileStats) {
// is a directory or is a file, but the original file the user provided
// looks like a directory but `path.resolve` removed the `last path.sep` /*
// so we need to still treat this like a directory * is a directory or is a file, but the original file the user provided
* looks like a directory but `path.resolve` removed the `last path.sep`
* so we need to still treat this like a directory
*/
if (fileStats.isDirectory() || looksLikeADirectory) { if (fileStats.isDirectory() || looksLikeADirectory) {
return getCacheFileForDirectory(); return getCacheFileForDirectory();
} }
// is file so just use that file // is file so just use that file
return resolvedCacheFile; return resolvedCacheFile;
} }
// here we known the file or directory doesn't exist, /*
// so we will try to infer if its a directory if it looks like a directory * here we known the file or directory doesn't exist,
// for the current operating system. * so we will try to infer if its a directory if it looks like a directory
* for the current operating system.
*/
// if the last character passed is a path separator we assume is a directory // if the last character passed is a path separator we assume is a directory
if (looksLikeADirectory) { if (looksLikeADirectory) {
@ -347,7 +342,12 @@ function getCacheFile(cacheFile, cwd) {
*/ */
function CLIEngine(options) { function CLIEngine(options) {
options = lodash.assign(Object.create(null), defaultOptions, options); options = lodash.assign(
Object.create(null),
defaultOptions,
{cwd: process.cwd()},
options
);
/** /**
* Stored options for this instance * Stored options for this instance
@ -358,8 +358,9 @@ function CLIEngine(options) {
var cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd); var cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
/** /**
* cache used to not operate on files that haven't changed since last successful * Cache used to avoid operating on files that haven't changed since the
* execution (e.g. file passed with no errors and no warnings * last successful execution (e.g., file passed linting with no errors and
* no warnings).
* @type {Object} * @type {Object}
*/ */
this._fileCache = fileEntryCache.create(cacheFile); this._fileCache = fileEntryCache.create(cacheFile);
@ -371,6 +372,7 @@ function CLIEngine(options) {
// 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;
this.options.rulePaths.forEach(function(rulesdir) { this.options.rulePaths.forEach(function(rulesdir) {
debug("Loading rules from " + rulesdir); debug("Loading rules from " + rulesdir);
rules.load(rulesdir, cwd); rules.load(rulesdir, cwd);
@ -404,7 +406,9 @@ CLIEngine.getFormatter = function(format) {
// if there's a slash, then it's a file // if there's a slash, then it's a file
if (format.indexOf("/") > -1) { if (format.indexOf("/") > -1) {
formatterPath = path.resolve(this.options.cwd, format); var cwd = this.options ? this.options.cwd : process.cwd();
formatterPath = path.resolve(cwd, format);
} else { } else {
formatterPath = "./formatters/" + format; formatterPath = "./formatters/" + format;
} }
@ -476,7 +480,7 @@ CLIEngine.prototype = {
* @returns {string[]} The equivalent glob patterns. * @returns {string[]} The equivalent glob patterns.
*/ */
resolveFileGlobPatterns: function(patterns) { resolveFileGlobPatterns: function(patterns) {
return globUtil.resolveFileGlobPatterns(patterns, this.options.extensions); return globUtil.resolveFileGlobPatterns(patterns, this.options);
}, },
/** /**
@ -490,26 +494,11 @@ CLIEngine.prototype = {
options = this.options, options = this.options,
fileCache = this._fileCache, fileCache = this._fileCache,
configHelper = new Config(options), configHelper = new Config(options),
ignoredPaths = new IgnoredPaths(options), fileList,
globOptions,
stats, stats,
startTime, startTime,
prevConfig; // the previous configuration used prevConfig; // the previous configuration used
startTime = Date.now();
globOptions = {
nodir: true
};
var cwd = options.cwd || process.cwd;
patterns = this.resolveFileGlobPatterns(patterns.map(function(pattern) {
if (pattern.indexOf("/") > 0) {
return path.join(cwd, pattern);
}
return pattern;
}));
/** /**
* Calculates the hash of the config file used to validate a given file * Calculates the hash of the config file used to validate a given file
* @param {string} filename The path of the file to retrieve a config object for to calculate the hash * @param {string} filename The path of the file to retrieve a config object for to calculate the hash
@ -524,13 +513,16 @@ CLIEngine.prototype = {
// reuse the previously hashed config if the config hasn't changed // reuse the previously hashed config if the config hasn't changed
if (prevConfig.config !== config) { if (prevConfig.config !== config) {
// config changed so we need to calculate the hash of the config
// and the hash of the plugins being used /*
* config changed so we need to calculate the hash of the config
* and the hash of the plugins being used
*/
prevConfig.config = config; prevConfig.config = config;
var eslintVersion = pkg.version; var eslintVersion = pkg.version;
prevConfig.hash = md5Hash(eslintVersion + "_" + stringify(config)); prevConfig.hash = hash(eslintVersion + "_" + stringify(config));
} }
return prevConfig.hash; return prevConfig.hash;
@ -546,48 +538,41 @@ CLIEngine.prototype = {
function executeOnFile(filename, warnIgnored) { function executeOnFile(filename, warnIgnored) {
var hashOfConfig; var hashOfConfig;
if (options.ignore !== false) {
if (ignoredPaths.contains(filename, "custom")) {
if (warnIgnored) { if (warnIgnored) {
results.push(createIgnoreResult(filename)); results.push(createIgnoreResult(filename));
}
return;
}
if (ignoredPaths.contains(filename, "default")) {
return;
}
}
filename = path.resolve(filename);
if (processed[filename]) {
return; return;
} }
if (options.cache) { if (options.cache) {
// get the descriptor for this file
// with the metadata and the flag that determines if /*
// the file has changed * get the descriptor for this file
* with the metadata and the flag that determines if
* the file has changed
*/
var descriptor = fileCache.getFileDescriptor(filename); var descriptor = fileCache.getFileDescriptor(filename);
var meta = descriptor.meta || {}; var meta = descriptor.meta || {};
hashOfConfig = hashOfConfigFor(filename); hashOfConfig = hashOfConfigFor(filename);
var changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig; var changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig;
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 * Adding the filename to the processed hashmap
// when it is not really used) * so the reporting is not affected (showing a warning about .eslintignore being used
* when it is not really used)
*/
processed[filename] = true; processed[filename] = true;
// Add the the cached results (always will be 0 error and 0 warnings) /*
// cause we don't save to cache files that failed * Add the the cached results (always will be 0 error and
// to guarantee that next execution will process those files as well * 0 warnings). We should not cache results for files that
* failed, in order to guarantee that next execution will
* process those files as well.
*/
results.push(descriptor.meta.results); results.push(descriptor.meta.results);
// move to the next file // move to the next file
@ -602,17 +587,25 @@ CLIEngine.prototype = {
var res = processFile(filename, configHelper, options); var res = processFile(filename, configHelper, options);
if (options.cache) { if (options.cache) {
// if a file contains errors or warnings we don't want to
// store the file in the cache so we can guarantee that /*
// next execution will also operate on this file * if a file contains errors or warnings we don't want to
* store the file in the cache so we can guarantee that
* next execution will also operate on this file
*/
if (res.errorCount > 0 || res.warningCount > 0) { if (res.errorCount > 0 || res.warningCount > 0) {
debug("File has problems, skipping it: " + filename); debug("File has problems, skipping it: " + filename);
// remove the entry from the cache // remove the entry from the cache
fileCache.removeEntry(filename); fileCache.removeEntry(filename);
} else { } else {
// since the file passed we store the result here
// TODO: check this as we might not need to store the /*
// successful runs as it will always should be 0 error 0 warnings * since the file passed we store the result here
* TODO: check this as we might not need to store the
* successful runs as it will always should be 0 errors and
* 0 warnings.
*/
descriptor.meta.hashOfConfig = hashOfConfig; descriptor.meta.hashOfConfig = hashOfConfig;
descriptor.meta.results = res; descriptor.meta.results = res;
} }
@ -621,23 +614,20 @@ CLIEngine.prototype = {
results.push(res); results.push(res);
} }
patterns.forEach(function(pattern) { startTime = Date.now();
var file = path.resolve(pattern);
if (shell.test("-f", file)) {
executeOnFile(fs.realpathSync(pattern), !shell.test("-d", file));
} else {
glob.sync(pattern, globOptions).forEach(function(globMatch) {
executeOnFile(globMatch, false);
});
}
patterns = this.resolveFileGlobPatterns(patterns);
fileList = globUtil.listFilesToProcess(patterns, options);
fileList.forEach(function(fileInfo) {
executeOnFile(fileInfo.filename, fileInfo.ignored);
}); });
stats = calculateStatsPerRun(results); stats = calculateStatsPerRun(results);
if (options.cache) { if (options.cache) {
// persist the cache to disk // persist the cache to disk
fileCache.reconcile(); fileCache.reconcile();
} }
@ -665,7 +655,12 @@ CLIEngine.prototype = {
configHelper = new Config(options), configHelper = new Config(options),
ignoredPaths = new IgnoredPaths(options); ignoredPaths = new IgnoredPaths(options);
// resolve filename based on options.cwd (for reporting, ignoredPaths also resolves)
if (filename && !isAbsolute(filename)) {
filename = path.resolve(options.cwd, filename);
}
if (filename && (options.ignore !== false) && ignoredPaths.contains(filename)) { if (filename && (options.ignore !== false) && ignoredPaths.contains(filename)) {
results.push(createIgnoreResult(filename)); 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));
@ -689,6 +684,7 @@ CLIEngine.prototype = {
*/ */
getConfigForFile: function(filePath) { getConfigForFile: function(filePath) {
var configHelper = new Config(this.options); var configHelper = new Config(this.options);
return configHelper.getConfig(filePath); return configHelper.getConfig(filePath);
}, },
@ -699,10 +695,11 @@ CLIEngine.prototype = {
*/ */
isPathIgnored: function(filePath) { isPathIgnored: function(filePath) {
var ignoredPaths; var ignoredPaths;
var resolvedPath = path.resolve(this.options.cwd, filePath);
if (this.options.ignore) { if (this.options.ignore) {
ignoredPaths = new IgnoredPaths(this.options); ignoredPaths = new IgnoredPaths(this.options);
return ignoredPaths.contains(filePath); return ignoredPaths.contains(resolvedPath);
} }
return false; return false;

2
tools/eslint/lib/cli.js

@ -51,6 +51,7 @@ function translateOptions(cliOptions) {
rulePaths: cliOptions.rulesdir, rulePaths: cliOptions.rulesdir,
useEslintrc: cliOptions.eslintrc, useEslintrc: cliOptions.eslintrc,
parser: cliOptions.parser, parser: cliOptions.parser,
parserOptions: cliOptions.parserOptions,
cache: cliOptions.cache, cache: cliOptions.cache,
cacheFile: cliOptions.cacheFile, cacheFile: cliOptions.cacheFile,
cacheLocation: cliOptions.cacheLocation, cacheLocation: cliOptions.cacheLocation,
@ -171,6 +172,7 @@ var cli = {
} }
var fileConfig = engine.getConfigForFile(files[0]); var fileConfig = engine.getConfigForFile(files[0]);
log.info(JSON.stringify(fileConfig, null, " ")); log.info(JSON.stringify(fileConfig, null, " "));
return 0; return 0;
} }

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

@ -41,6 +41,7 @@ function isCaseNode(node) {
*/ */
function isForkingByTrueOrFalse(node) { function isForkingByTrueOrFalse(node) {
var parent = node.parent; var parent = node.parent;
switch (parent.type) { switch (parent.type) {
case "ConditionalExpression": case "ConditionalExpression":
case "IfStatement": case "IfStatement":
@ -128,8 +129,8 @@ function isIdentifierReference(node) {
* *
* To separate the current and the head is in order to not make useless segments. * To separate the current and the head is in order to not make useless segments.
* *
* In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd" events * In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd"
* are fired. * events are fired.
* *
* @param {CodePathAnalyzer} analyzer - The instance. * @param {CodePathAnalyzer} analyzer - The instance.
* @param {ASTNode} node - The current AST node. * @param {ASTNode} node - The current AST node.
@ -206,6 +207,7 @@ function leaveFromCurrentSegment(analyzer, node) {
node); node);
} }
} }
state.currentSegments = []; state.currentSegments = [];
} }
@ -234,9 +236,12 @@ function preprocess(analyzer, node) {
case "ConditionalExpression": case "ConditionalExpression":
case "IfStatement": case "IfStatement":
// Fork if this node is at `consequent`/`alternate`.
// `popForkContext()` exists at `IfStatement:exit` and /*
// `ConditionalExpression:exit`. * Fork if this node is at `consequent`/`alternate`.
* `popForkContext()` exists at `IfStatement:exit` and
* `ConditionalExpression:exit`.
*/
if (parent.consequent === node) { if (parent.consequent === node) {
state.makeIfConsequent(); state.makeIfConsequent();
} else if (parent.alternate === node) { } else if (parent.alternate === node) {
@ -299,9 +304,12 @@ function preprocess(analyzer, node) {
break; break;
case "AssignmentPattern": case "AssignmentPattern":
// Fork if this node is at `right`.
// `left` is executed always, so it uses the current path. /*
// `popForkContext()` exists at `AssignmentPattern:exit`. * Fork if this node is at `right`.
* `left` is executed always, so it uses the current path.
* `popForkContext()` exists at `AssignmentPattern:exit`.
*/
if (parent.right === node) { if (parent.right === node) {
state.pushForkContext(); state.pushForkContext();
state.forkBypassPath(); state.forkBypassPath();
@ -332,6 +340,7 @@ function processCodePathToEnter(analyzer, node) {
case "FunctionExpression": case "FunctionExpression":
case "ArrowFunctionExpression": case "ArrowFunctionExpression":
if (codePath) { if (codePath) {
// Emits onCodePathSegmentStart events if updated. // Emits onCodePathSegmentStart events if updated.
forwardCurrentToHead(analyzer, node); forwardCurrentToHead(analyzer, node);
debug.dumpState(node, state, false); debug.dumpState(node, state, false);
@ -370,9 +379,12 @@ function processCodePathToEnter(analyzer, node) {
break; break;
case "SwitchCase": case "SwitchCase":
// Fork if this node is after the 2st node in `cases`.
// It's similar to `else` blocks. /*
// The next `test` node is processed in this path. * Fork if this node is after the 2st node in `cases`.
* It's similar to `else` blocks.
* The next `test` node is processed in this path.
*/
if (parent.discriminant !== node && parent.cases[0] !== node) { if (parent.discriminant !== node && parent.cases[0] !== node) {
state.forkPath(); state.forkPath();
} }
@ -425,9 +437,12 @@ function processCodePathToExit(analyzer, node) {
break; break;
case "SwitchCase": case "SwitchCase":
// This is the same as the process at the 1st `consequent` node in
// `preprocess` function. /*
// Must do if this `consequent` is empty. * This is the same as the process at the 1st `consequent` node in
* `preprocess` function.
* Must do if this `consequent` is empty.
*/
if (node.consequent.length === 0) { if (node.consequent.length === 0) {
state.makeSwitchCaseBody(true, !node.test); state.makeSwitchCaseBody(true, !node.test);
} }
@ -499,9 +514,12 @@ function processCodePathToExit(analyzer, node) {
break; break;
} }
// Skip updating the current segment to avoid creating useless segments if /*
// the node type is the same as the parent node type. * Skip updating the current segment to avoid creating useless segments if
* the node type is the same as the parent node type.
*/
if (!dontForward && (!node.parent || node.type !== node.parent.type)) { if (!dontForward && (!node.parent || node.type !== node.parent.type)) {
// Emits onCodePathSegmentStart events if updated. // Emits onCodePathSegmentStart events if updated.
forwardCurrentToHead(analyzer, node); forwardCurrentToHead(analyzer, node);
} }

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

@ -79,6 +79,7 @@ function isReachable(segment) {
* @param {boolean} reachable - A flag which shows this is reachable. * @param {boolean} reachable - A flag which shows this is reachable.
*/ */
function CodePathSegment(id, allPrevSegments, reachable) { function CodePathSegment(id, allPrevSegments, reachable) {
/** /**
* The identifier of this code path. * The identifier of this code path.
* Rules use it to store additional information of each rule. * Rules use it to store additional information of each rule.
@ -120,7 +121,8 @@ function CodePathSegment(id, allPrevSegments, reachable) {
// Internal data. // Internal data.
Object.defineProperty(this, "internal", {value: { Object.defineProperty(this, "internal", {value: {
used: false used: false,
loopedPrevSegments: []
}}); }});
/* istanbul ignore if */ /* istanbul ignore if */
@ -130,6 +132,20 @@ function CodePathSegment(id, allPrevSegments, reachable) {
} }
} }
CodePathSegment.prototype = {
constructor: CodePathSegment,
/**
* Checks a given previous segment is coming from the end of a loop.
*
* @param {CodePathSegment} segment - A previous segment to check.
* @returns {boolean} `true` if the segment is coming from the end of a loop.
*/
isLoopedPrevSegment: function(segment) {
return this.internal.loopedPrevSegments.indexOf(segment) !== -1;
}
};
/** /**
* Creates the root segment. * Creates the root segment.
* *
@ -191,6 +207,7 @@ CodePathSegment.markUsed = function(segment) {
segment.internal.used = true; segment.internal.used = true;
var i; var i;
if (segment.reachable) { if (segment.reachable) {
for (i = 0; i < segment.allPrevSegments.length; ++i) { for (i = 0; i < segment.allPrevSegments.length; ++i) {
var prevSegment = segment.allPrevSegments[i]; var prevSegment = segment.allPrevSegments[i];
@ -205,4 +222,15 @@ CodePathSegment.markUsed = function(segment) {
} }
}; };
/**
* Marks a previous segment as looped.
*
* @param {CodePathSegment} segment - A segment.
* @param {CodePathSegment} prevSegment - A previous segment to mark.
* @returns {void}
*/
CodePathSegment.markPrevSegmentAsLooped = function(segment, prevSegment) {
segment.internal.loopedPrevSegments.push(prevSegment);
};
module.exports = CodePathSegment; module.exports = CodePathSegment;

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

@ -55,6 +55,7 @@ function getContinueContext(state, label) {
} }
var context = state.loopContext; var context = state.loopContext;
while (context) { while (context) {
if (context.label === label) { if (context.label === label) {
return context; return context;
@ -75,6 +76,7 @@ function getContinueContext(state, label) {
*/ */
function getBreakContext(state, label) { function getBreakContext(state, label) {
var context = state.breakContext; var context = state.breakContext;
while (context) { while (context) {
if (label ? context.label === label : context.breakable) { if (label ? context.label === label : context.breakable) {
return context; return context;
@ -94,6 +96,7 @@ function getBreakContext(state, label) {
*/ */
function getReturnContext(state) { function getReturnContext(state) {
var context = state.tryContext; var context = state.tryContext;
while (context) { while (context) {
if (context.hasFinalizer && context.position !== "finally") { if (context.hasFinalizer && context.position !== "finally") {
return context; return context;
@ -112,6 +115,7 @@ function getReturnContext(state) {
*/ */
function getThrowContext(state) { function getThrowContext(state) {
var context = state.tryContext; var context = state.tryContext;
while (context) { while (context) {
if (context.position === "try" || if (context.position === "try" ||
(context.hasFinalizer && context.position === "catch") (context.hasFinalizer && context.position === "catch")
@ -168,6 +172,7 @@ function removeConnection(prevSegments, nextSegments) {
*/ */
function makeLooped(state, fromSegments, toSegments) { function makeLooped(state, fromSegments, toSegments) {
var end = Math.min(fromSegments.length, toSegments.length); var end = Math.min(fromSegments.length, toSegments.length);
for (var i = 0; i < end; ++i) { for (var i = 0; i < end; ++i) {
var fromSegment = fromSegments[i]; var fromSegment = fromSegments[i];
var toSegment = toSegments[i]; var toSegment = toSegments[i];
@ -181,6 +186,10 @@ function makeLooped(state, fromSegments, toSegments) {
fromSegment.allNextSegments.push(toSegment); fromSegment.allNextSegments.push(toSegment);
toSegment.allPrevSegments.push(fromSegment); toSegment.allPrevSegments.push(fromSegment);
if (toSegment.allPrevSegments.length >= 2) {
CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment);
}
state.notifyLooped(fromSegment, toSegment); state.notifyLooped(fromSegment, toSegment);
} }
} }
@ -237,6 +246,7 @@ function CodePathState(idGenerator, onLooped) {
var final = this.finalSegments = []; var final = this.finalSegments = [];
var returned = this.returnedForkContext = []; var returned = this.returnedForkContext = [];
var thrown = this.thrownForkContext = []; var thrown = this.thrownForkContext = [];
returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final); returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final); thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
} }
@ -259,6 +269,7 @@ CodePathState.prototype = {
*/ */
get parentForkContext() { get parentForkContext() {
var current = this.forkContext; var current = this.forkContext;
return current && current.upper; return current && current.upper;
}, },
@ -362,6 +373,7 @@ CodePathState.prototype = {
*/ */
popChoiceContext: function() { popChoiceContext: function() {
var context = this.choiceContext; var context = this.choiceContext;
this.choiceContext = context.upper; this.choiceContext = context.upper;
var forkContext = this.forkContext; var forkContext = this.forkContext;
@ -370,43 +382,61 @@ CodePathState.prototype = {
switch (context.kind) { switch (context.kind) {
case "&&": case "&&":
case "||": case "||":
// If any result were not transferred from child contexts,
// this sets the head segments to both cases. /*
// The head segments are the path of the right-hand operand. * If any result were not transferred from child contexts,
* this sets the head segments to both cases.
* The head segments are the path of the right-hand operand.
*/
if (!context.processed) { if (!context.processed) {
context.trueForkContext.add(headSegments); context.trueForkContext.add(headSegments);
context.falseForkContext.add(headSegments); context.falseForkContext.add(headSegments);
} }
// Transfers results to upper context if this context is in /*
// test chunk. * Transfers results to upper context if this context is in
* test chunk.
*/
if (context.isForkingAsResult) { if (context.isForkingAsResult) {
var parentContext = this.choiceContext; var parentContext = this.choiceContext;
parentContext.trueForkContext.addAll(context.trueForkContext); parentContext.trueForkContext.addAll(context.trueForkContext);
parentContext.falseForkContext.addAll(context.falseForkContext); parentContext.falseForkContext.addAll(context.falseForkContext);
parentContext.processed = true; parentContext.processed = true;
return context; return context;
} }
break; break;
case "test": case "test":
if (!context.processed) { if (!context.processed) {
// The head segments are the path of the `if` block here.
// Updates the `true` path with the end of the `if` block. /*
* The head segments are the path of the `if` block here.
* Updates the `true` path with the end of the `if` block.
*/
context.trueForkContext.clear(); context.trueForkContext.clear();
context.trueForkContext.add(headSegments); context.trueForkContext.add(headSegments);
} else { } else {
// The head segments are the path of the `else` block here.
// Updates the `false` path with the end of the `else` block. /*
* The head segments are the path of the `else` block here.
* Updates the `false` path with the end of the `else`
* block.
*/
context.falseForkContext.clear(); context.falseForkContext.clear();
context.falseForkContext.add(headSegments); context.falseForkContext.add(headSegments);
} }
break; break;
case "loop": case "loop":
// Loops are addressed in popLoopContext().
// This is called from popLoopContext(). /*
* Loops are addressed in popLoopContext().
* This is called from popLoopContext().
*/
return context; return context;
/* istanbul ignore next */ /* istanbul ignore next */
@ -416,6 +446,7 @@ CodePathState.prototype = {
// Merges all paths. // Merges all paths.
var prevForkContext = context.trueForkContext; var prevForkContext = context.trueForkContext;
prevForkContext.addAll(context.falseForkContext); prevForkContext.addAll(context.falseForkContext);
forkContext.replaceHead(prevForkContext.makeNext(0, -1)); forkContext.replaceHead(prevForkContext.makeNext(0, -1));
@ -433,8 +464,11 @@ CodePathState.prototype = {
var forkContext = this.forkContext; var forkContext = this.forkContext;
if (context.processed) { if (context.processed) {
// This got segments already from the child choice context.
// Creates the next path from own true/false fork context. /*
* This got segments already from the child choice context.
* Creates the next path from own true/false fork context.
*/
var prevForkContext = var prevForkContext =
context.kind === "&&" ? context.trueForkContext : context.kind === "&&" ? context.trueForkContext :
/* kind === "||" */ context.falseForkContext; /* kind === "||" */ context.falseForkContext;
@ -444,13 +478,18 @@ CodePathState.prototype = {
context.processed = false; context.processed = false;
} else { } else {
// This did not get segments from the child choice context.
// So addresses the head segments. /*
// The head segments are the path of the left-hand operand. * This did not get segments from the child choice context.
* So addresses the head segments.
* The head segments are the path of the left-hand operand.
*/
if (context.kind === "&&") { if (context.kind === "&&") {
// The path does short-circuit if false. // The path does short-circuit if false.
context.falseForkContext.add(forkContext.head); context.falseForkContext.add(forkContext.head);
} else { } else {
// The path does short-circuit if true. // The path does short-circuit if true.
context.trueForkContext.add(forkContext.head); context.trueForkContext.add(forkContext.head);
} }
@ -468,13 +507,16 @@ CodePathState.prototype = {
var context = this.choiceContext; var context = this.choiceContext;
var forkContext = this.forkContext; var forkContext = this.forkContext;
// If any result were not transferred from child contexts, /*
// this sets the head segments to both cases. * If any result were not transferred from child contexts,
// The head segments are the path of the test expression. * this sets the head segments to both cases.
* The head segments are the path of the test expression.
*/
if (!context.processed) { if (!context.processed) {
context.trueForkContext.add(forkContext.head); context.trueForkContext.add(forkContext.head);
context.falseForkContext.add(forkContext.head); context.falseForkContext.add(forkContext.head);
} }
context.processed = false; context.processed = false;
// Creates new path from the `true` case. // Creates new path from the `true` case.
@ -492,8 +534,10 @@ CodePathState.prototype = {
var context = this.choiceContext; var context = this.choiceContext;
var forkContext = this.forkContext; var forkContext = this.forkContext;
// The head segments are the path of the `if` block. /*
// Updates the `true` path with the end of the `if` block. * The head segments are the path of the `if` block.
* Updates the `true` path with the end of the `if` block.
*/
context.trueForkContext.clear(); context.trueForkContext.clear();
context.trueForkContext.add(forkContext.head); context.trueForkContext.add(forkContext.head);
context.processed = true; context.processed = true;
@ -526,6 +570,7 @@ CodePathState.prototype = {
lastIsDefault: false, lastIsDefault: false,
countForks: 0 countForks: 0
}; };
this.pushBreakContext(true, label); this.pushBreakContext(true, label);
}, },
@ -541,41 +586,57 @@ CodePathState.prototype = {
*/ */
popSwitchContext: function() { popSwitchContext: function() {
var context = this.switchContext; var context = this.switchContext;
this.switchContext = context.upper; this.switchContext = context.upper;
var forkContext = this.forkContext; var forkContext = this.forkContext;
var brokenForkContext = this.popBreakContext().brokenForkContext; var brokenForkContext = this.popBreakContext().brokenForkContext;
if (context.countForks === 0) { if (context.countForks === 0) {
// When there is only one `default` chunk and there is one or more
// `break` statements, even if forks are nothing, it needs to merge /*
// those. * When there is only one `default` chunk and there is one or more
* `break` statements, even if forks are nothing, it needs to merge
* those.
*/
if (!brokenForkContext.empty) { if (!brokenForkContext.empty) {
brokenForkContext.add(forkContext.makeNext(-1, -1)); brokenForkContext.add(forkContext.makeNext(-1, -1));
forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
} }
return; return;
} }
var lastSegments = forkContext.head; var lastSegments = forkContext.head;
this.forkBypassPath(); this.forkBypassPath();
var lastCaseSegments = forkContext.head; var lastCaseSegments = forkContext.head;
// `brokenForkContext` is used to make the next segment. /*
// It must add the last segment into `brokenForkContext`. * `brokenForkContext` is used to make the next segment.
* It must add the last segment into `brokenForkContext`.
*/
brokenForkContext.add(lastSegments); brokenForkContext.add(lastSegments);
// A path which is failed in all case test should be connected to path /*
// of `default` chunk. * A path which is failed in all case test should be connected to path
* of `default` chunk.
*/
if (!context.lastIsDefault) { if (!context.lastIsDefault) {
if (context.defaultBodySegments) { if (context.defaultBodySegments) {
// Remove a link from `default` label to its chunk.
// It's false route. /*
* Remove a link from `default` label to its chunk.
* It's false route.
*/
removeConnection(context.defaultSegments, context.defaultBodySegments); removeConnection(context.defaultSegments, context.defaultBodySegments);
makeLooped(this, lastCaseSegments, context.defaultBodySegments); makeLooped(this, lastCaseSegments, context.defaultBodySegments);
} else { } else {
// It handles the last case body as broken if `default` chunk
// does not exist. /*
* It handles the last case body as broken if `default` chunk
* does not exist.
*/
brokenForkContext.add(lastCaseSegments); brokenForkContext.add(lastCaseSegments);
} }
} }
@ -584,8 +645,11 @@ CodePathState.prototype = {
for (var i = 0; i < context.countForks; ++i) { for (var i = 0; i < context.countForks; ++i) {
this.forkContext = this.forkContext.upper; this.forkContext = this.forkContext.upper;
} }
// Creates a path from all brokenForkContext paths.
// This is a path after switch statement. /*
* Creates a path from all brokenForkContext paths.
* This is a path after switch statement.
*/
this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
}, },
@ -598,20 +662,26 @@ CodePathState.prototype = {
*/ */
makeSwitchCaseBody: function(isEmpty, isDefault) { makeSwitchCaseBody: function(isEmpty, isDefault) {
var context = this.switchContext; var context = this.switchContext;
if (!context.hasCase) { if (!context.hasCase) {
return; return;
} }
// Merge forks. /*
// The parent fork context has two segments. * Merge forks.
// Those are from the current case and the body of the previous case. * The parent fork context has two segments.
* Those are from the current case and the body of the previous case.
*/
var parentForkContext = this.forkContext; var parentForkContext = this.forkContext;
var forkContext = this.pushForkContext(); var forkContext = this.pushForkContext();
forkContext.add(parentForkContext.makeNext(0, -1)); forkContext.add(parentForkContext.makeNext(0, -1));
// Save `default` chunk info. /*
// If the `default` label is not at the last, we must make a path from * Save `default` chunk info.
// the last `case` to the `default` chunk. * If the `default` label is not at the last, we must make a path from
* the last `case` to the `default` chunk.
*/
if (isDefault) { if (isDefault) {
context.defaultSegments = parentForkContext.head; context.defaultSegments = parentForkContext.head;
if (isEmpty) { if (isEmpty) {
@ -625,6 +695,7 @@ CodePathState.prototype = {
context.defaultBodySegments = forkContext.head; context.defaultBodySegments = forkContext.head;
} }
} }
context.lastIsDefault = isDefault; context.lastIsDefault = isDefault;
context.countForks += 1; context.countForks += 1;
}, },
@ -645,9 +716,11 @@ CodePathState.prototype = {
upper: this.tryContext, upper: this.tryContext,
position: "try", position: "try",
hasFinalizer: hasFinalizer, hasFinalizer: hasFinalizer,
returnedForkContext: hasFinalizer returnedForkContext: hasFinalizer
? ForkContext.newEmpty(this.forkContext) ? ForkContext.newEmpty(this.forkContext)
: null, : null,
thrownForkContext: ForkContext.newEmpty(this.forkContext), thrownForkContext: ForkContext.newEmpty(this.forkContext),
lastOfTryIsReachable: false, lastOfTryIsReachable: false,
lastOfCatchIsReachable: false lastOfCatchIsReachable: false
@ -661,24 +734,31 @@ CodePathState.prototype = {
*/ */
popTryContext: function() { popTryContext: function() {
var context = this.tryContext; var context = this.tryContext;
this.tryContext = context.upper; this.tryContext = context.upper;
if (context.position === "catch") { if (context.position === "catch") {
// Merges two paths from the `try` block and `catch` block merely. // Merges two paths from the `try` block and `catch` block merely.
this.popForkContext(); this.popForkContext();
return; return;
} }
// The following process is executed only when there is the `finally`
// block. /*
* The following process is executed only when there is the `finally`
* block.
*/
var returned = context.returnedForkContext; var returned = context.returnedForkContext;
var thrown = context.thrownForkContext; var thrown = context.thrownForkContext;
if (returned.empty && thrown.empty) { if (returned.empty && thrown.empty) {
return; return;
} }
// Separate head to normal paths and leaving paths. // Separate head to normal paths and leaving paths.
var headSegments = this.forkContext.head; var headSegments = this.forkContext.head;
this.forkContext = this.forkContext.upper; this.forkContext = this.forkContext.upper;
var normalSegments = headSegments.slice(0, headSegments.length / 2 | 0); var normalSegments = headSegments.slice(0, headSegments.length / 2 | 0);
var leavingSegments = headSegments.slice(headSegments.length / 2 | 0); var leavingSegments = headSegments.slice(headSegments.length / 2 | 0);
@ -744,6 +824,7 @@ CodePathState.prototype = {
// Update state. // Update state.
if (context.position === "catch") { if (context.position === "catch") {
// Merges two paths from the `try` block and `catch` block. // Merges two paths from the `try` block and `catch` block.
this.popForkContext(); this.popForkContext();
forkContext = this.forkContext; forkContext = this.forkContext;
@ -755,14 +836,18 @@ CodePathState.prototype = {
context.position = "finally"; context.position = "finally";
if (returned.empty && thrown.empty) { if (returned.empty && thrown.empty) {
// This path does not leave. // This path does not leave.
return; return;
} }
// Create a parallel segment from merging returned and thrown. /*
// This segment will leave at the end of this finally block. * Create a parallel segment from merging returned and thrown.
* This segment will leave at the end of this finally block.
*/
var segments = forkContext.makeNext(-1, -1); var segments = forkContext.makeNext(-1, -1);
var j; var j;
for (var i = 0; i < forkContext.count; ++i) { for (var i = 0; i < forkContext.count; ++i) {
var prevSegsOfLeavingSegment = [headOfLeavingSegments[i]]; var prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
@ -790,11 +875,13 @@ CodePathState.prototype = {
*/ */
makeFirstThrowablePathInTryBlock: function() { makeFirstThrowablePathInTryBlock: function() {
var forkContext = this.forkContext; var forkContext = this.forkContext;
if (!forkContext.reachable) { if (!forkContext.reachable) {
return; return;
} }
var context = getThrowContext(this); var context = getThrowContext(this);
if (context === this || if (context === this ||
context.position !== "try" || context.position !== "try" ||
!context.thrownForkContext.empty !context.thrownForkContext.empty
@ -893,6 +980,7 @@ CodePathState.prototype = {
*/ */
popLoopContext: function() { popLoopContext: function() {
var context = this.loopContext; var context = this.loopContext;
this.loopContext = context.upper; this.loopContext = context.upper;
var forkContext = this.forkContext; var forkContext = this.forkContext;
@ -923,6 +1011,7 @@ CodePathState.prototype = {
// `true` paths go to looping. // `true` paths go to looping.
var segmentsList = choiceContext.trueForkContext.segmentsList; var segmentsList = choiceContext.trueForkContext.segmentsList;
for (var i = 0; i < segmentsList.length; ++i) { for (var i = 0; i < segmentsList.length; ++i) {
makeLooped( makeLooped(
this, this,
@ -1069,6 +1158,7 @@ CodePathState.prototype = {
// Update state. // Update state.
var updateSegments = forkContext.makeDisconnected(-1, -1); var updateSegments = forkContext.makeDisconnected(-1, -1);
context.continueDestSegments = context.updateSegments = updateSegments; context.continueDestSegments = context.updateSegments = updateSegments;
forkContext.replaceHead(updateSegments); forkContext.replaceHead(updateSegments);
}, },
@ -1104,10 +1194,15 @@ CodePathState.prototype = {
} }
var bodySegments = context.endOfTestSegments; var bodySegments = context.endOfTestSegments;
if (!bodySegments) { if (!bodySegments) {
// If there is not the `test` part, the `body` path comes from the
// `init` part and the `update` part. /*
* If there is not the `test` part, the `body` path comes from the
* `init` part and the `update` part.
*/
var prevForkContext = ForkContext.newEmpty(forkContext); var prevForkContext = ForkContext.newEmpty(forkContext);
prevForkContext.add(context.endOfInitSegments); prevForkContext.add(context.endOfInitSegments);
if (context.endOfUpdateSegments) { if (context.endOfUpdateSegments) {
prevForkContext.add(context.endOfUpdateSegments); prevForkContext.add(context.endOfUpdateSegments);
@ -1146,6 +1241,7 @@ CodePathState.prototype = {
var context = this.loopContext; var context = this.loopContext;
var forkContext = this.forkContext; var forkContext = this.forkContext;
var temp = ForkContext.newEmpty(forkContext); var temp = ForkContext.newEmpty(forkContext);
temp.add(context.prevSegments); temp.add(context.prevSegments);
var rightSegments = temp.makeNext(-1, -1); var rightSegments = temp.makeNext(-1, -1);
@ -1164,6 +1260,7 @@ CodePathState.prototype = {
var context = this.loopContext; var context = this.loopContext;
var forkContext = this.forkContext; var forkContext = this.forkContext;
var temp = ForkContext.newEmpty(forkContext); var temp = ForkContext.newEmpty(forkContext);
temp.add(context.endOfLeftSegments); temp.add(context.endOfLeftSegments);
var bodySegments = temp.makeNext(-1, -1); var bodySegments = temp.makeNext(-1, -1);
@ -1205,11 +1302,13 @@ CodePathState.prototype = {
popBreakContext: function() { popBreakContext: function() {
var context = this.breakContext; var context = this.breakContext;
var forkContext = this.forkContext; var forkContext = this.forkContext;
this.breakContext = context.upper; this.breakContext = context.upper;
// Process this context here for other than switches and loops. // Process this context here for other than switches and loops.
if (!context.breakable) { if (!context.breakable) {
var brokenForkContext = context.brokenForkContext; var brokenForkContext = context.brokenForkContext;
if (!brokenForkContext.empty) { if (!brokenForkContext.empty) {
brokenForkContext.add(forkContext.head); brokenForkContext.add(forkContext.head);
forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
@ -1230,15 +1329,18 @@ CodePathState.prototype = {
*/ */
makeBreak: function(label) { makeBreak: function(label) {
var forkContext = this.forkContext; var forkContext = this.forkContext;
if (!forkContext.reachable) { if (!forkContext.reachable) {
return; return;
} }
var context = getBreakContext(this, label); var context = getBreakContext(this, label);
/* istanbul ignore else: foolproof (syntax error) */ /* istanbul ignore else: foolproof (syntax error) */
if (context) { if (context) {
context.brokenForkContext.add(forkContext.head); context.brokenForkContext.add(forkContext.head);
} }
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
}, },
@ -1253,11 +1355,13 @@ CodePathState.prototype = {
*/ */
makeContinue: function(label) { makeContinue: function(label) {
var forkContext = this.forkContext; var forkContext = this.forkContext;
if (!forkContext.reachable) { if (!forkContext.reachable) {
return; return;
} }
var context = getContinueContext(this, label); var context = getContinueContext(this, label);
/* istanbul ignore else: foolproof (syntax error) */ /* istanbul ignore else: foolproof (syntax error) */
if (context) { if (context) {
if (context.continueDestSegments) { if (context.continueDestSegments) {
@ -1286,6 +1390,7 @@ CodePathState.prototype = {
*/ */
makeReturn: function() { makeReturn: function() {
var forkContext = this.forkContext; var forkContext = this.forkContext;
if (forkContext.reachable) { if (forkContext.reachable) {
getReturnContext(this).returnedForkContext.add(forkContext.head); getReturnContext(this).returnedForkContext.add(forkContext.head);
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
@ -1302,6 +1407,7 @@ CodePathState.prototype = {
*/ */
makeThrow: function() { makeThrow: function() {
var forkContext = this.forkContext; var forkContext = this.forkContext;
if (forkContext.reachable) { if (forkContext.reachable) {
getThrowContext(this).thrownForkContext.add(forkContext.head); getThrowContext(this).thrownForkContext.add(forkContext.head);
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
@ -1314,6 +1420,7 @@ CodePathState.prototype = {
*/ */
makeFinal: function() { makeFinal: function() {
var segments = this.currentSegments; var segments = this.currentSegments;
if (segments.length > 0 && segments[0].reachable) { if (segments.length > 0 && segments[0].reachable) {
this.returnedForkContext.add(segments); this.returnedForkContext.add(segments);
} }

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

@ -27,6 +27,7 @@ var IdGenerator = require("./id-generator");
* @param {function} onLooped - A callback function to notify looping. * @param {function} onLooped - A callback function to notify looping.
*/ */
function CodePath(id, upper, onLooped) { function CodePath(id, upper, onLooped) {
/** /**
* The identifier of this code path. * The identifier of this code path.
* Rules use it to store additional information of each rule. * Rules use it to store additional information of each rule.
@ -102,6 +103,123 @@ CodePath.prototype = {
*/ */
get currentSegments() { get currentSegments() {
return this.internal.currentSegments; return this.internal.currentSegments;
},
/**
* Traverses all segments in this code path.
*
* codePath.traverseSegments(function(segment, controller) {
* // do something.
* });
*
* This method enumerates segments in order from the head.
*
* The `controller` object has two methods.
*
* - `controller.skip()` - Skip the following segments in this branch.
* - `controller.break()` - Skip all following segments.
*
* @param {object} [options] - Omittable.
* @param {CodePathSegment} [options.first] - The first segment to traverse.
* @param {CodePathSegment} [options.last] - The last segment to traverse.
* @param {function} callback - A callback function.
* @returns {void}
*/
traverseSegments: function(options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
options = options || {};
var startSegment = options.first || this.internal.initialSegment;
var lastSegment = options.last;
var item = null;
var index = 0;
var end = 0;
var segment = null;
var visited = Object.create(null);
var stack = [[startSegment, 0]];
var skippedSegment = null;
var broken = false;
var controller = {
skip: function() {
if (stack.length <= 1) {
broken = true;
} else {
skippedSegment = stack[stack.length - 2][0];
}
},
break: function() {
broken = true;
}
};
/**
* Checks a given previous segment has been visited.
* @param {CodePathSegment} prevSegment - A previous segment to check.
* @returns {boolean} `true` if the segment has been visited.
*/
function isVisited(prevSegment) {
return (
visited[prevSegment.id] ||
segment.isLoopedPrevSegment(prevSegment)
);
}
while (stack.length > 0) {
item = stack[stack.length - 1];
segment = item[0];
index = item[1];
if (index === 0) {
// Skip if this segment has been visited already.
if (visited[segment.id]) {
stack.pop();
continue;
}
// Skip if all previous segments have not been visited.
if (segment !== startSegment &&
segment.prevSegments.length > 0 &&
!segment.prevSegments.every(isVisited)
) {
stack.pop();
continue;
}
// Reset the flag of skipping if all branches have been skipped.
if (skippedSegment && segment.prevSegments.indexOf(skippedSegment) !== -1) {
skippedSegment = null;
}
visited[segment.id] = true;
// Call the callback when the first time.
if (!skippedSegment) {
callback.call(this, segment, controller); // eslint-disable-line callback-return
if (segment === lastSegment) {
controller.skip();
}
if (broken) {
break;
}
}
}
// Update the stack.
end = segment.nextSegments.length - 1;
if (index < end) {
item[1] += 1;
stack.push([segment.nextSegments[index], 0]);
} else if (index === end) {
item[0] = segment.nextSegments[index];
item[1] = 0;
} else {
stack.pop();
}
}
} }
}; };

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

@ -32,6 +32,7 @@ function getId(segment) { // eslint-disable-line require-jsdoc
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = { module.exports = {
/** /**
* A flag that debug dumping is enabled or not. * A flag that debug dumping is enabled or not.
* @type {boolean} * @type {boolean}
@ -57,6 +58,7 @@ module.exports = {
dumpState: !debug.enabled ? debug : /* istanbul ignore next */ function(node, state, leaving) { dumpState: !debug.enabled ? debug : /* istanbul ignore next */ function(node, state, leaving) {
for (var i = 0; i < state.currentSegments.length; ++i) { for (var i = 0; i < state.currentSegments.length; ++i) {
var segInternal = state.currentSegments[i].internal; var segInternal = state.currentSegments[i].internal;
if (leaving) { if (leaving) {
segInternal.exitNodes.push(node); segInternal.exitNodes.push(node);
} else { } else {
@ -153,12 +155,14 @@ module.exports = {
var item = stack.pop(); var item = stack.pop();
var segment = item[0]; var segment = item[0];
var index = item[1]; var index = item[1];
if (done[segment.id] && index === 0) { if (done[segment.id] && index === 0) {
continue; continue;
} }
done[segment.id] = segment; done[segment.id] = segment;
var nextSegment = segment.allNextSegments[index]; var nextSegment = segment.allNextSegments[index];
if (!nextSegment) { if (!nextSegment) {
continue; continue;
} }

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

@ -47,6 +47,7 @@ function isReachable(segment) {
*/ */
function makeSegments(context, begin, end, create) { function makeSegments(context, begin, end, create) {
var list = context.segmentsList; var list = context.segmentsList;
if (begin < 0) { if (begin < 0) {
begin = list.length + begin; begin = list.length + begin;
} }
@ -55,6 +56,7 @@ function makeSegments(context, begin, end, create) {
} }
var segments = []; var segments = [];
for (var i = 0; i < context.count; ++i) { for (var i = 0; i < context.count; ++i) {
var allPrevSegments = []; var allPrevSegments = [];
@ -81,6 +83,7 @@ function makeSegments(context, begin, end, create) {
function mergeExtraSegments(context, segments) { function mergeExtraSegments(context, segments) {
while (segments.length > context.count) { while (segments.length > context.count) {
var merged = []; var merged = [];
for (var i = 0, length = segments.length / 2 | 0; i < length; ++i) { for (var i = 0, length = segments.length / 2 | 0; i < length; ++i) {
merged.push(CodePathSegment.newNext( merged.push(CodePathSegment.newNext(
context.idGenerator.next(), context.idGenerator.next(),
@ -120,6 +123,7 @@ ForkContext.prototype = {
*/ */
get head() { get head() {
var list = this.segmentsList; var list = this.segmentsList;
return list.length === 0 ? [] : list[list.length - 1]; return list.length === 0 ? [] : list[list.length - 1];
}, },
@ -137,6 +141,7 @@ ForkContext.prototype = {
*/ */
get reachable() { get reachable() {
var segments = this.head; var segments = this.head;
return segments.length > 0 && segments.some(isReachable); return segments.length > 0 && segments.some(isReachable);
}, },
@ -212,6 +217,7 @@ ForkContext.prototype = {
assert(context.count === this.count); assert(context.count === this.count);
var source = context.segmentsList; var source = context.segmentsList;
for (var i = 0; i < source.length; ++i) { for (var i = 0; i < source.length; ++i) {
this.segmentsList.push(source[i]); this.segmentsList.push(source[i]);
} }

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

@ -33,10 +33,12 @@ function IdGenerator(prefix) {
*/ */
IdGenerator.prototype.next = function() { IdGenerator.prototype.next = function() {
this.n = 1 + this.n | 0; this.n = 1 + this.n | 0;
/* istanbul ignore if */ /* istanbul ignore if */
if (this.n < 0) { if (this.n < 0) {
this.n = 1; this.n = 1;
} }
return this.prefix + this.n; return this.prefix + this.n;
}; };

17
tools/eslint/lib/config.js

@ -78,7 +78,7 @@ function loadConfig(configToLoad) {
* @private * @private
*/ */
function getPersonalConfig() { function getPersonalConfig() {
var config = {}, var config,
filename; filename;
if (PERSONAL_CONFIG_DIR) { if (PERSONAL_CONFIG_DIR) {
@ -90,7 +90,7 @@ function getPersonalConfig() {
} }
} }
return config; return config || {};
} }
/** /**
@ -166,6 +166,7 @@ function Config(options) {
this.ignorePath = options.ignorePath; this.ignorePath = options.ignorePath;
this.cache = {}; this.cache = {};
this.parser = options.parser; this.parser = options.parser;
this.parserOptions = options.parserOptions || {};
this.baseConfig = options.baseConfig ? loadConfig(options.baseConfig) : { rules: {} }; this.baseConfig = options.baseConfig ? loadConfig(options.baseConfig) : { rules: {} };
@ -176,10 +177,17 @@ function Config(options) {
return envs; return envs;
}, {}); }, {});
/*
* Handle declared globals.
* For global variable foo, handle "foo:false" and "foo:true" to set
* whether global is writable.
* If user declares "foo", convert to "foo:false".
*/
this.globals = (options.globals || []).reduce(function(globals, def) { this.globals = (options.globals || []).reduce(function(globals, def) {
// Default "foo" to false and handle "foo:false" and "foo:true"
var parts = def.split(":"); var parts = def.split(":");
globals[parts[0]] = (parts.length > 1 && parts[1] === "true"); globals[parts[0]] = (parts.length > 1 && parts[1] === "true");
return globals; return globals;
}, {}); }, {});
@ -226,7 +234,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}, this.baseConfig); config = ConfigOps.merge({parser: this.parser, parserOptions: this.parserOptions}, 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);
@ -237,6 +245,7 @@ Config.prototype.getConfig = function(filePath) {
config = ConfigOps.merge(config, this.useSpecificConfig); config = ConfigOps.merge(config, this.useSpecificConfig);
} }
// Step 5: Merge in command line environments // Step 5: Merge in command line environments
debug("Merging command line environment settings"); debug("Merging command line environment settings");
config = ConfigOps.merge(config, { env: this.env }); config = ConfigOps.merge(config, { env: this.env });

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

@ -15,6 +15,7 @@ var lodash = require("lodash"),
debug = require("debug"), debug = require("debug"),
eslint = require("../eslint"), eslint = require("../eslint"),
configRule = require("./config-rule"), configRule = require("./config-rule"),
ConfigOps = require("./config-ops"),
recConfig = require("../../conf/eslint.json"); recConfig = require("../../conf/eslint.json");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -91,6 +92,7 @@ Registry.prototype = {
*/ */
populateFromCoreRules: function() { populateFromCoreRules: function() {
var rulesConfig = configRule.createCoreRuleConfigs(); var rulesConfig = configRule.createCoreRuleConfigs();
this.rules = makeRegistryItems(rulesConfig); this.rules = makeRegistryItems(rulesConfig);
}, },
@ -123,18 +125,32 @@ Registry.prototype = {
* @returns {void} * @returns {void}
*/ */
var addRuleToRuleSet = function(rule) { var addRuleToRuleSet = function(rule) {
// This check ensures that there is a rule configuration, and that
// it either has fewer than the max cominbations allowed, or if it has /*
// too many configs, we will only use the most basic of them. * This check ensures that there is a rule configuration and that
* it has fewer than the max combinations allowed.
* If it has too many configs, we will only use the most basic of
* the possible configurations.
*/
var hasFewCombos = (this.rules[rule].length <= MAX_CONFIG_COMBINATIONS); var hasFewCombos = (this.rules[rule].length <= MAX_CONFIG_COMBINATIONS);
if (this.rules[rule][idx] && (hasFewCombos || this.rules[rule][idx].specificity <= 2)) { if (this.rules[rule][idx] && (hasFewCombos || this.rules[rule][idx].specificity <= 2)) {
// If the rule has too many possible combinations, only take simple ones, avoiding objects.
/*
* If the rule has too many possible combinations, only take
* simple ones, avoiding objects.
*/
if (!hasFewCombos && typeof this.rules[rule][idx].config[1] === "object") { if (!hasFewCombos && typeof this.rules[rule][idx].config[1] === "object") {
return; return;
} }
ruleSets[idx] = ruleSets[idx] || {}; ruleSets[idx] = ruleSets[idx] || {};
ruleSets[idx][rule] = this.rules[rule][idx].config; ruleSets[idx][rule] = this.rules[rule][idx].config;
// Initialize errorCount to zero, since this is a config which will be linted
/*
* Initialize errorCount to zero, since this is a config which
* will be linted.
*/
this.rules[rule][idx].errorCount = 0; this.rules[rule][idx].errorCount = 0;
} }
}.bind(this); }.bind(this);
@ -164,6 +180,7 @@ Registry.prototype = {
var errorFreeItems = newRegistry.rules[ruleId].filter(function(registryItem) { var errorFreeItems = newRegistry.rules[ruleId].filter(function(registryItem) {
return (registryItem.errorCount === 0); return (registryItem.errorCount === 0);
}); });
if (errorFreeItems.length > 0) { if (errorFreeItems.length > 0) {
newRegistry.rules[ruleId] = errorFreeItems; newRegistry.rules[ruleId] = errorFreeItems;
} else { } else {
@ -208,6 +225,7 @@ Registry.prototype = {
var failingConfigs = this.rules[ruleId].filter(function(registryItem) { var failingConfigs = this.rules[ruleId].filter(function(registryItem) {
return (registryItem.errorCount > 0); return (registryItem.errorCount > 0);
}); });
if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) { if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) {
failingRegistry.rules[ruleId] = failingConfigs; failingRegistry.rules[ruleId] = failingConfigs;
} }
@ -273,26 +291,37 @@ Registry.prototype = {
lintedRegistry = new Registry(); lintedRegistry = new Registry();
lintedRegistry.rules = lodash.assign({}, this.rules); lintedRegistry.rules = lodash.assign({}, this.rules);
ruleSets = lintedRegistry.buildRuleSets(); ruleSets = lintedRegistry.buildRuleSets();
lintedRegistry = lintedRegistry.stripExtraConfigs(); lintedRegistry = lintedRegistry.stripExtraConfigs();
debug("Linting with all possible rule combinations"); debug("Linting with all possible rule combinations");
filenames = Object.keys(sourceCodes); filenames = Object.keys(sourceCodes);
totalFilesLinting = filenames.length * ruleSets.length; totalFilesLinting = filenames.length * ruleSets.length;
filenames.forEach(function(filename) { filenames.forEach(function(filename) {
debug("Linting file: " + filename); debug("Linting file: " + filename);
ruleSetIdx = 0; ruleSetIdx = 0;
ruleSets.forEach(function(ruleSet) { ruleSets.forEach(function(ruleSet) {
lintConfig = lodash.assign({}, config, {rules: ruleSet}); lintConfig = lodash.assign({}, config, {rules: ruleSet});
var lintResults = eslint.verify(sourceCodes[filename], lintConfig); var lintResults = eslint.verify(sourceCodes[filename], lintConfig);
lintResults.forEach(function(result) { lintResults.forEach(function(result) {
lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1; lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
}); });
ruleSetIdx += 1; ruleSetIdx += 1;
if (cb) { if (cb) {
cb(totalFilesLinting); // eslint-disable-line callback-return cb(totalFilesLinting); // eslint-disable-line callback-return
} }
}); });
// Deallocate for GC // Deallocate for GC
sourceCodes[filename] = null; sourceCodes[filename] = null;
}); });
@ -312,8 +341,11 @@ Registry.prototype = {
*/ */
function extendFromRecommended(config) { function extendFromRecommended(config) {
var newConfig = lodash.assign({}, config); var newConfig = lodash.assign({}, config);
ConfigOps.normalizeToStrings(newConfig);
var recRules = Object.keys(recConfig.rules).filter(function(ruleId) { var recRules = Object.keys(recConfig.rules).filter(function(ruleId) {
return (recConfig.rules[ruleId] === 2 || recConfig.rules[ruleId][0] === 2); return ConfigOps.isErrorSeverity(recConfig.rules[ruleId]);
}); });
recRules.forEach(function(ruleId) { recRules.forEach(function(ruleId) {

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

@ -4,7 +4,9 @@
* @copyright 2015 Nicholas C. Zakas. All rights reserved. * @copyright 2015 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license. * See LICENSE file in root directory for full license.
*/ */
/* eslint no-use-before-define: 0 */ /* eslint no-use-before-define: 0 */
"use strict"; "use strict";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -17,11 +19,14 @@ var debug = require("debug"),
ConfigOps = require("./config-ops"), ConfigOps = require("./config-ops"),
validator = require("./config-validator"), validator = require("./config-validator"),
Plugins = require("./plugins"), Plugins = require("./plugins"),
resolveModule = require("resolve"), pathUtil = require("../util/path-util"),
ModuleResolver = require("../util/module-resolver"),
pathIsInside = require("path-is-inside"), pathIsInside = require("path-is-inside"),
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"); isAbsolutePath = require("path-is-absolute"),
defaultOptions = require("../../conf/eslint.json"),
requireUncached = require("require-uncached");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -54,6 +59,8 @@ var CONFIG_FILES = [
"package.json" "package.json"
]; ];
var resolver = new ModuleResolver();
debug = debug("eslint:config-file"); debug = debug("eslint:config-file");
/** /**
@ -92,6 +99,7 @@ function loadYAMLConfigFile(filePath) {
var yaml = require("js-yaml"); var yaml = require("js-yaml");
try { try {
// empty YAML file can be null, so always use // empty YAML file can be null, so always use
return yaml.safeLoad(readFile(filePath)) || {}; return yaml.safeLoad(readFile(filePath)) || {};
} catch (e) { } catch (e) {
@ -152,7 +160,7 @@ function loadLegacyConfigFile(filePath) {
function loadJSConfigFile(filePath) { function loadJSConfigFile(filePath) {
debug("Loading JS config file: " + filePath); debug("Loading JS config file: " + filePath);
try { try {
return require(filePath); return requireUncached(filePath);
} catch (e) { } catch (e) {
debug("Error reading JavaScript file: " + filePath); debug("Error reading JavaScript file: " + filePath);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message; e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
@ -186,9 +194,8 @@ function loadPackageJSONConfigFile(filePath) {
* @private * @private
*/ */
function loadConfigFile(file) { function loadConfigFile(file) {
var config; var config,
filePath = file.filePath;
var filePath = file.filePath;
switch (path.extname(filePath)) { switch (path.extname(filePath)) {
case ".js": case ".js":
@ -232,6 +239,7 @@ function writeJSONConfigFile(config, filePath) {
debug("Writing JSON config file: " + filePath); debug("Writing JSON config file: " + filePath);
var content = stringify(config, {cmp: sortByKey, space: 4}); var content = stringify(config, {cmp: sortByKey, space: 4});
fs.writeFileSync(filePath, content, "utf8"); fs.writeFileSync(filePath, content, "utf8");
} }
@ -249,6 +257,7 @@ function writeYAMLConfigFile(config, filePath) {
var yaml = require("js-yaml"); var yaml = require("js-yaml");
var content = yaml.safeDump(config, {sortKeys: true}); var content = yaml.safeDump(config, {sortKeys: true});
fs.writeFileSync(filePath, content, "utf8"); fs.writeFileSync(filePath, content, "utf8");
} }
@ -263,6 +272,7 @@ function writeJSConfigFile(config, filePath) {
debug("Writing JS config file: " + filePath); debug("Writing JS config file: " + filePath);
var content = "module.exports = " + stringify(config, {cmp: sortByKey, space: 4}) + ";"; var content = "module.exports = " + stringify(config, {cmp: sortByKey, space: 4}) + ";";
fs.writeFileSync(filePath, content, "utf8"); fs.writeFileSync(filePath, content, "utf8");
} }
@ -295,24 +305,42 @@ function write(config, filePath) {
} }
/** /**
* Determines the lookup path for node packages referenced in a config file. * Determines the base directory for node packages referenced in a config file.
* If the config * This does not include node_modules in the path so it can be used for all
* references relative to a config file.
* @param {string} configFilePath The config file referencing the file. * @param {string} configFilePath The config file referencing the file.
* @returns {string} The lookup path for the file path. * @returns {string} The base directory for the file path.
* @private * @private
*/ */
function getLookupPath(configFilePath) { function getBaseDir(configFilePath) {
// calculates the path of the project including ESLint as dependency // calculates the path of the project including ESLint as dependency
var projectPath = path.resolve(__dirname, "../../../"); var projectPath = path.resolve(__dirname, "../../../");
if (configFilePath && pathIsInside(configFilePath, projectPath)) { if (configFilePath && pathIsInside(configFilePath, projectPath)) {
// be careful of https://github.com/substack/node-resolve/issues/78 // be careful of https://github.com/substack/node-resolve/issues/78
return path.resolve(configFilePath); return path.join(path.resolve(configFilePath));
} }
// default to ESLint project path since it's unlikely that plugins will be /*
// in this directory * default to ESLint project path since it's unlikely that plugins will be
return projectPath; * in this directory
*/
return path.join(projectPath);
}
/**
* Determines the lookup path, including node_modules, for package
* references relative to a config file.
* @param {string} configFilePath The config file referencing the file.
* @returns {string} The lookup path for the file path.
* @private
*/
function getLookupPath(configFilePath) {
var basedir = getBaseDir(configFilePath);
return path.join(basedir, "node_modules");
} }
/** /**
@ -320,11 +348,12 @@ function getLookupPath(configFilePath) {
* @param {Object} config The configuration information. * @param {Object} config The configuration information.
* @param {string} filePath The file path from which the configuration information * @param {string} filePath The file path from which the configuration information
* was loaded. * was loaded.
* @param {string} [relativeTo] The path to resolve relative to.
* @returns {Object} A new configuration object with all of the "extends" fields * @returns {Object} A new configuration object with all of the "extends" fields
* loaded and merged. * loaded and merged.
* @private * @private
*/ */
function applyExtends(config, filePath) { function applyExtends(config, filePath, relativeTo) {
var configExtends = config.extends; var configExtends = config.extends;
// normalize into an array for easier handling // normalize into an array for easier handling
@ -336,12 +365,18 @@ function applyExtends(config, filePath) {
config = configExtends.reduceRight(function(previousValue, parentPath) { config = configExtends.reduceRight(function(previousValue, parentPath) {
if (parentPath === "eslint:recommended") { if (parentPath === "eslint:recommended") {
// Add an explicit substitution for eslint:recommended to conf/eslint.json
// this lets us use the eslint.json file as the recommended rules /*
* Add an explicit substitution for eslint:recommended to conf/eslint.json
* 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 (isFilePath(parentPath)) { } else if (isFilePath(parentPath)) {
// If the `extends` path is relative, use the directory of the current configuration
// file as the reference point. Otherwise, use as-is. /*
* If the `extends` path is relative, use the directory of the current configuration
* file as the reference point. Otherwise, use as-is.
*/
parentPath = (!isAbsolutePath(parentPath) ? parentPath = (!isAbsolutePath(parentPath) ?
path.join(path.dirname(filePath), parentPath) : path.join(path.dirname(filePath), parentPath) :
parentPath parentPath
@ -350,11 +385,15 @@ function applyExtends(config, filePath) {
try { try {
debug("Loading " + parentPath); debug("Loading " + parentPath);
return ConfigOps.merge(load(parentPath), previousValue); return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue);
} catch (e) { } catch (e) {
// If the file referenced by `extends` failed to load, add the path to the
// configuration file that referenced it to the error message so the user is /*
// able to see where it was referenced from, then re-throw * If the file referenced by `extends` failed to load, add the path
* to the configuration file that referenced it to the error
* message so the user is able to see where it was referenced from,
* then re-throw.
*/
e.message += "\nReferenced from: " + filePath; e.message += "\nReferenced from: " + filePath;
throw e; throw e;
} }
@ -372,16 +411,33 @@ function applyExtends(config, filePath) {
* @private * @private
*/ */
function normalizePackageName(name, prefix) { function normalizePackageName(name, prefix) {
/*
* On Windows, name can come in with Windows slashes instead of Unix slashes.
* Normalize to Unix first to avoid errors later on.
* https://github.com/eslint/eslint/issues/5644
*/
if (name.indexOf("\\") > -1) {
name = pathUtil.convertPathToPosix(name);
}
if (name.charAt(0) === "@") { if (name.charAt(0) === "@") {
// it's a scoped package
// package name is "eslint-config", or just a username /*
* it's a scoped package
* package name is "eslint-config", or just a username
*/
var scopedPackageShortcutRegex = new RegExp("^(@[^\/]+)(?:\/(?:" + prefix + ")?)?$"), var scopedPackageShortcutRegex = new RegExp("^(@[^\/]+)(?:\/(?:" + prefix + ")?)?$"),
scopedPackageNameRegex = new RegExp("^" + prefix + "(-|$)"); scopedPackageNameRegex = new RegExp("^" + prefix + "(-|$)");
if (scopedPackageShortcutRegex.test(name)) { if (scopedPackageShortcutRegex.test(name)) {
name = name.replace(scopedPackageShortcutRegex, "$1/" + prefix); name = name.replace(scopedPackageShortcutRegex, "$1/" + prefix);
} else if (!scopedPackageNameRegex.test(name.split("/")[1])) { } else if (!scopedPackageNameRegex.test(name.split("/")[1])) {
// for scoped packages, insert the eslint-config after the first / unless
// the path is already @scope/eslint or @scope/eslint-config-xxx /*
* for scoped packages, insert the eslint-config after the first / unless
* the path is already @scope/eslint or @scope/eslint-config-xxx
*/
name = name.replace(/^@([^\/]+)\/(.*)$/, "@$1/" + prefix + "-$2"); name = name.replace(/^@([^\/]+)\/(.*)$/, "@$1/" + prefix + "-$2");
} }
} else if (name.indexOf(prefix + "-") !== 0) { } else if (name.indexOf(prefix + "-") !== 0) {
@ -400,21 +456,23 @@ function normalizePackageName(name, prefix) {
* @private * @private
*/ */
function resolve(filePath, relativeTo) { function resolve(filePath, relativeTo) {
if (isFilePath(filePath)) { if (isFilePath(filePath)) {
return { filePath: path.resolve(relativeTo || "", filePath) }; return { filePath: path.resolve(relativeTo || "", filePath) };
} else { } else {
var normalizedPackageName;
if (filePath.indexOf("plugin:") === 0) { if (filePath.indexOf("plugin:") === 0) {
var packagePath = filePath.substr(7, filePath.lastIndexOf("/") - 7); var packagePath = filePath.substr(7, filePath.lastIndexOf("/") - 7);
var configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1); var configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1);
filePath = resolveModule.sync(normalizePackageName(packagePath, "eslint-plugin"), {
basedir: getLookupPath(relativeTo) normalizedPackageName = normalizePackageName(packagePath, "eslint-plugin");
}); debug("Attempting to resolve " + normalizedPackageName);
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
return { filePath: filePath, configName: configName }; return { filePath: filePath, configName: configName };
} else { } else {
filePath = resolveModule.sync(normalizePackageName(filePath, "eslint-config"), { normalizedPackageName = normalizePackageName(filePath, "eslint-config");
basedir: getLookupPath(relativeTo) debug("Attempting to resolve " + normalizedPackageName);
}); filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
return { filePath: filePath }; return { filePath: filePath };
} }
} }
@ -426,12 +484,15 @@ function resolve(filePath, relativeTo) {
* @param {string} filePath The filename or package name to load the configuration * @param {string} filePath The filename or package name to load the configuration
* information from. * information from.
* @param {boolean} [applyEnvironments=false] Set to true to merge in environment settings. * @param {boolean} [applyEnvironments=false] Set to true to merge in environment settings.
* @param {string} [relativeTo] The path to resolve relative to.
* @returns {Object} The configuration information. * @returns {Object} The configuration information.
* @private * @private
*/ */
function load(filePath, applyEnvironments) { function load(filePath, applyEnvironments, relativeTo) {
var resolvedPath = resolve(filePath, relativeTo),
var resolvedPath = resolve(filePath), dirname = path.dirname(resolvedPath.filePath),
basedir = getBaseDir(dirname),
lookupPath = getLookupPath(dirname),
config = loadConfigFile(resolvedPath); config = loadConfigFile(resolvedPath);
if (config) { if (config) {
@ -441,23 +502,33 @@ function load(filePath, applyEnvironments) {
Plugins.loadAll(config.plugins); Plugins.loadAll(config.plugins);
} }
// remove parser from config if it is the default parser
if (config.parser === defaultOptions.parser) {
config.parser = null;
}
// include full path of parser if present // include full path of parser if present
if (config.parser) { if (config.parser) {
config.parser = resolveModule.sync(config.parser, { if (isFilePath(config.parser)) {
basedir: getLookupPath(path.dirname(path.resolve(filePath))) config.parser = path.resolve(basedir || "", config.parser);
}); } else {
config.parser = resolver.resolve(config.parser, lookupPath);
}
} }
// validate the configuration before continuing // validate the configuration before continuing
validator.validate(config, filePath); validator.validate(config, filePath);
// If an `extends` property is defined, it represents a configuration file to use as /*
// a "parent". Load the referenced file and merge the configuration recursively. * If an `extends` property is defined, it represents a configuration file to use as
* a "parent". Load the referenced file and merge the configuration recursively.
*/
if (config.extends) { if (config.extends) {
config = applyExtends(config, filePath); config = applyExtends(config, filePath, basedir);
} }
if (config.env && applyEnvironments) { if (config.env && applyEnvironments) {
// Merge in environment-specific globals and parserOptions. // Merge in environment-specific globals and parserOptions.
config = ConfigOps.applyEnvironments(config); config = ConfigOps.applyEnvironments(config);
} }
@ -473,11 +544,13 @@ function load(filePath, applyEnvironments) {
module.exports = { module.exports = {
getBaseDir: getBaseDir,
getLookupPath: getLookupPath, getLookupPath: getLookupPath,
load: load, load: load,
resolve: resolve, resolve: resolve,
write: write, write: write,
applyExtends: applyExtends, applyExtends: applyExtends,
normalizePackageName: normalizePackageName,
CONFIG_FILES: CONFIG_FILES, CONFIG_FILES: CONFIG_FILES,
/** /**

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

@ -17,6 +17,7 @@ var util = require("util"),
ProgressBar = require("progress"), ProgressBar = require("progress"),
autoconfig = require("./autoconfig.js"), autoconfig = require("./autoconfig.js"),
ConfigFile = require("./config-file"), ConfigFile = require("./config-file"),
ConfigOps = require("./config-ops"),
getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles, getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles,
npmUtil = require("../util/npm-util"), npmUtil = require("../util/npm-util"),
recConfig = require("../../conf/eslint.json"), recConfig = require("../../conf/eslint.json"),
@ -39,6 +40,7 @@ function writeFile(config, format) {
// default is .js // default is .js
var extname = ".js"; var extname = ".js";
if (format === "YAML") { if (format === "YAML") {
extname = ".yml"; extname = ".yml";
} else if (format === "JSON") { } else if (format === "JSON") {
@ -148,12 +150,14 @@ function configureRules(answers, config) {
// Create a list of recommended rules, because we don't want to disable them // Create a list of recommended rules, because we don't want to disable them
var recRules = Object.keys(recConfig.rules).filter(function(ruleId) { var recRules = Object.keys(recConfig.rules).filter(function(ruleId) {
return (recConfig.rules[ruleId] === 2 || recConfig.rules[ruleId][0] === 2); return ConfigOps.isErrorSeverity(recConfig.rules[ruleId]);
}); });
// Find and disable rules which had no error-free configuration // Find and disable rules which had no error-free configuration
failingRegistry = registry.getFailingRulesRegistry(); failingRegistry = registry.getFailingRulesRegistry();
Object.keys(failingRegistry.rules).forEach(function(ruleId) { Object.keys(failingRegistry.rules).forEach(function(ruleId) {
// If the rule is recommended, set it to error, otherwise disable it // If the rule is recommended, set it to error, otherwise disable it
disabledConfigs[ruleId] = (recRules.indexOf(ruleId) !== -1) ? 2 : 0; disabledConfigs[ruleId] = (recRules.indexOf(ruleId) !== -1) ? 2 : 0;
}); });
@ -184,8 +188,9 @@ function configureRules(answers, config) {
bar.update(BAR_TOTAL); bar.update(BAR_TOTAL);
// Log out some stats to let the user know what happened // Log out some stats to let the user know what happened
var totalRules = Object.keys(newConfig.rules).length; var finalRuleIds = Object.keys(newConfig.rules),
var enabledRules = Object.keys(newConfig.rules).filter(function(ruleId) { totalRules = finalRuleIds.length;
var enabledRules = finalRuleIds.filter(function(ruleId) {
return (newConfig.rules[ruleId] !== 0); return (newConfig.rules[ruleId] !== 0);
}).length; }).length;
var resultMessage = [ var resultMessage = [
@ -193,7 +198,10 @@ function configureRules(answers, config) {
"rules based on " + fileQty, "rules based on " + fileQty,
"file" + ((fileQty === 1) ? "." : "s.") "file" + ((fileQty === 1) ? "." : "s.")
].join(" "); ].join(" ");
log.info(resultMessage); log.info(resultMessage);
ConfigOps.normalizeToStrings(newConfig);
return newConfig; return newConfig;
} }
@ -230,10 +238,10 @@ function processAnswers(answers) {
if (answers.source === "prompt") { if (answers.source === "prompt") {
config.extends = "eslint:recommended"; config.extends = "eslint:recommended";
config.rules.indent = [2, answers.indent]; config.rules.indent = ["error", answers.indent];
config.rules.quotes = [2, answers.quotes]; config.rules.quotes = ["error", answers.quotes];
config.rules["linebreak-style"] = [2, answers.linebreak]; config.rules["linebreak-style"] = ["error", answers.linebreak];
config.rules.semi = [2, answers.semi ? "always" : "never"]; config.rules.semi = ["error", answers.semi ? "always" : "never"];
} }
installModules(config); installModules(config);
@ -242,6 +250,8 @@ function processAnswers(answers) {
config = configureRules(answers, config); config = configureRules(answers, config);
config = autoconfig.extendFromRecommended(config); config = autoconfig.extendFromRecommended(config);
} }
ConfigOps.normalizeToStrings(config);
return config; return config;
} }
@ -256,6 +266,7 @@ function getConfigForStyleGuide(guide) {
airbnb: {extends: "airbnb", plugins: ["react"]}, airbnb: {extends: "airbnb", plugins: ["react"]},
standard: {extends: "standard", plugins: ["standard"]} standard: {extends: "standard", plugins: ["standard"]}
}; };
if (!guides[guide]) { if (!guides[guide]) {
throw new Error("You referenced an unsupported guide."); throw new Error("You referenced an unsupported guide.");
} }
@ -273,6 +284,7 @@ function getConfigForStyleGuide(guide) {
*/ */
function promptUser(callback) { function promptUser(callback) {
var config; var config;
inquirer.prompt([ inquirer.prompt([
{ {
type: "list", type: "list",
@ -387,11 +399,8 @@ function promptUser(callback) {
// early exit if you are using automatic style generation // early exit if you are using automatic style generation
if (earlyAnswers.source === "auto") { if (earlyAnswers.source === "auto") {
try { try {
if (secondAnswers.jsx) {
log.error("Unfortunately, autoconfig does not yet work for JSX code.\nPlease see https://github.com/eslint/eslint/issues/5007 for current status.");
return;
}
var combinedAnswers = lodash.assign({}, earlyAnswers, secondAnswers); var combinedAnswers = lodash.assign({}, earlyAnswers, secondAnswers);
config = processAnswers(combinedAnswers); config = processAnswers(combinedAnswers);
installModules(config); installModules(config);
writeFile(config, earlyAnswers.format); writeFile(config, earlyAnswers.format);
@ -408,7 +417,7 @@ function promptUser(callback) {
type: "list", type: "list",
name: "indent", name: "indent",
message: "What style of indentation do you use?", message: "What style of indentation do you use?",
default: "tabs", default: "tab",
choices: [{name: "Tabs", value: "tab"}, {name: "Spaces", value: 4}] choices: [{name: "Tabs", value: "tab"}, {name: "Spaces", value: 4}]
}, },
{ {
@ -441,6 +450,7 @@ function promptUser(callback) {
], function(answers) { ], function(answers) {
try { try {
var totalAnswers = lodash.assign({}, earlyAnswers, secondAnswers, answers); var totalAnswers = lodash.assign({}, earlyAnswers, secondAnswers, answers);
config = processAnswers(totalAnswers); config = processAnswers(totalAnswers);
installModules(config); installModules(config);
writeFile(config, answers.format); writeFile(config, answers.format);

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

@ -21,6 +21,12 @@ var lodash = require("lodash"),
debug = debug("eslint:config-ops"); debug = debug("eslint:config-ops");
var RULE_SEVERITY_STRINGS = ["off", "warn", "error"],
RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce(function(map, value, index) {
map[value] = index;
return map;
}, {});
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Public Interface // Public Interface
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -99,6 +105,7 @@ module.exports = {
* @returns {Object} merged config object. * @returns {Object} merged config object.
*/ */
merge: function deepmerge(target, src, combine, isRule) { merge: function deepmerge(target, src, combine, isRule) {
/* /*
The MIT License (MIT) The MIT License (MIT)
@ -122,7 +129,12 @@ module.exports = {
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
*/ */
// This code is taken from deepmerge repo (https://github.com/KyleAMathews/deepmerge) and modified to meet our needs.
/*
* This code is taken from deepmerge repo
* (https://github.com/KyleAMathews/deepmerge)
* and modified to meet our needs.
*/
var array = Array.isArray(src) || Array.isArray(target); var array = Array.isArray(src) || Array.isArray(target);
var dst = array && [] || {}; var dst = array && [] || {};
@ -130,7 +142,9 @@ module.exports = {
isRule = !!isRule; isRule = !!isRule;
if (array) { if (array) {
target = target || []; target = target || [];
if (isRule && src.length > 1) {
// src could be a string, so check for array
if (isRule && Array.isArray(src) && src.length > 1) {
dst = dst.concat(src); dst = dst.concat(src);
} else { } else {
dst = dst.concat(target); dst = dst.concat(target);
@ -176,7 +190,66 @@ module.exports = {
} }
return dst; return dst;
},
/**
* Converts new-style severity settings (off, warn, error) into old-style
* severity settings (0, 1, 2) for all rules. Assumption is that severity
* values have already been validated as correct.
* @param {Object} config The config object to normalize.
* @returns {void}
*/
normalize: function(config) {
if (config.rules) {
Object.keys(config.rules).forEach(function(ruleId) {
var ruleConfig = config.rules[ruleId];
if (typeof ruleConfig === "string") {
config.rules[ruleId] = RULE_SEVERITY[ruleConfig.toLowerCase()] || 0;
} else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "string") {
ruleConfig[0] = RULE_SEVERITY[ruleConfig[0].toLowerCase()] || 0;
} }
});
}
},
/**
* Converts old-style severity settings (0, 1, 2) into new-style
* severity settings (off, warn, error) for all rules. Assumption is that severity
* values have already been validated as correct.
* @param {Object} config The config object to normalize.
* @returns {void}
*/
normalizeToStrings: function(config) {
if (config.rules) {
Object.keys(config.rules).forEach(function(ruleId) {
var ruleConfig = config.rules[ruleId];
if (typeof ruleConfig === "number") {
config.rules[ruleId] = RULE_SEVERITY_STRINGS[ruleConfig] || RULE_SEVERITY_STRINGS[0];
} else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "number") {
ruleConfig[0] = RULE_SEVERITY_STRINGS[ruleConfig[0]] || RULE_SEVERITY_STRINGS[0];
}
});
}
},
/**
* Determines if the severity for the given rule configuration represents an error.
* @param {int|string|Array} ruleConfig The configuration for an individual rule.
* @returns {boolean} True if the rule represents an error, false if not.
*/
isErrorSeverity: function(ruleConfig) {
var severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
if (typeof severity === "string") {
severity = RULE_SEVERITY[severity.toLowerCase()] || 0;
}
return (typeof severity === "number" && severity === 2);
}
}; };

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

@ -44,6 +44,7 @@ function explodeArray(xs) {
*/ */
function combineArrays(arr1, arr2) { function combineArrays(arr1, arr2) {
var res = []; var res = [];
if (arr1.length === 0) { if (arr1.length === 0) {
return explodeArray(arr2); return explodeArray(arr2);
} }
@ -81,9 +82,11 @@ function combineArrays(arr1, arr2) {
function groupByProperty(objects) { function groupByProperty(objects) {
var groupedObj = objects.reduce(function(accumulator, obj) { var groupedObj = objects.reduce(function(accumulator, obj) {
var prop = Object.keys(obj)[0]; var prop = Object.keys(obj)[0];
accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj]; accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj];
return accumulator; return accumulator;
}, {}); }, {});
return Object.keys(groupedObj).map(function(prop) { return Object.keys(groupedObj).map(function(prop) {
return groupedObj[prop]; return groupedObj[prop];
}); });
@ -144,6 +147,7 @@ function groupByProperty(objects) {
*/ */
function combinePropertyObjects(objArr1, objArr2) { function combinePropertyObjects(objArr1, objArr2) {
var res = []; var res = [];
if (objArr1.length === 0) { if (objArr1.length === 0) {
return objArr2; return objArr2;
} }
@ -155,6 +159,7 @@ function combinePropertyObjects(objArr1, objArr2) {
var combinedObj = {}; var combinedObj = {};
var obj1Props = Object.keys(obj1); var obj1Props = Object.keys(obj1);
var obj2Props = Object.keys(obj2); var obj2Props = Object.keys(obj2);
obj1Props.forEach(function(prop1) { obj1Props.forEach(function(prop1) {
combinedObj[prop1] = obj1[prop1]; combinedObj[prop1] = obj1[prop1];
}); });
@ -201,10 +206,12 @@ RuleConfigSet.prototype = {
*/ */
addErrorSeverity: function(severity) { addErrorSeverity: function(severity) {
severity = severity || 2; severity = severity || 2;
this.ruleConfigs = this.ruleConfigs.map(function(config) { this.ruleConfigs = this.ruleConfigs.map(function(config) {
config.unshift(severity); config.unshift(severity);
return config; return config;
}); });
// Add a single config at the beginning consisting of only the severity // Add a single config at the beginning consisting of only the severity
this.ruleConfigs.unshift(severity); this.ruleConfigs.unshift(severity);
}, },
@ -228,6 +235,7 @@ RuleConfigSet.prototype = {
objectConfigs: [], objectConfigs: [],
add: function(property, values) { add: function(property, values) {
var optionObj; var optionObj;
for (var idx = 0; idx < values.length; idx++) { for (var idx = 0; idx < values.length; idx++) {
optionObj = {}; optionObj = {};
optionObj[property] = values[idx]; optionObj[property] = values[idx];
@ -241,8 +249,11 @@ RuleConfigSet.prototype = {
}, []); }, []);
} }
}; };
// The object schema could have multiple independent properties.
// If any contain enums or booleans, they can be added and then combined /*
* The object schema could have multiple independent properties.
* If any contain enums or booleans, they can be added and then combined
*/
Object.keys(obj.properties).forEach(function(prop) { Object.keys(obj.properties).forEach(function(prop) {
if (obj.properties[prop].enum) { if (obj.properties[prop].enum) {
objectConfigSet.add(prop, obj.properties[prop].enum); objectConfigSet.add(prop, obj.properties[prop].enum);
@ -266,15 +277,19 @@ RuleConfigSet.prototype = {
*/ */
function generateConfigsFromSchema(schema) { function generateConfigsFromSchema(schema) {
var configSet = new RuleConfigSet(); var configSet = new RuleConfigSet();
if (Array.isArray(schema)) { if (Array.isArray(schema)) {
schema.forEach(function(opt) { schema.forEach(function(opt) {
if (opt.enum) { if (opt.enum) {
configSet.addEnums(opt.enum); configSet.addEnums(opt.enum);
} }
if (opt.type && opt.type === "object") { if (opt.type && opt.type === "object") {
configSet.addObject(opt); configSet.addObject(opt);
} }
if (opt.oneOf) { if (opt.oneOf) {
// TODO (IanVS): not yet implemented // TODO (IanVS): not yet implemented
} }
}); });
@ -289,9 +304,11 @@ function generateConfigsFromSchema(schema) {
*/ */
function createCoreRuleConfigs() { function createCoreRuleConfigs() {
var ruleList = loadRules(); var ruleList = loadRules();
return Object.keys(ruleList).reduce(function(accumulator, id) { return Object.keys(ruleList).reduce(function(accumulator, id) {
var rule = rules.get(id); var rule = rules.get(id);
var schema = (typeof rule === "function") ? rule.schema : rule.meta.schema; var schema = (typeof rule === "function") ? rule.schema : rule.meta.schema;
accumulator[id] = generateConfigsFromSchema(schema); accumulator[id] = generateConfigsFromSchema(schema);
return accumulator; return accumulator;
}, {}); }, {});

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

@ -13,7 +13,8 @@
var rules = require("../rules"), var rules = require("../rules"),
Environments = require("./environments"), Environments = require("./environments"),
schemaValidator = require("is-my-json-valid"); schemaValidator = require("is-my-json-valid"),
util = require("util");
var validators = { var validators = {
rules: Object.create(null) rules: Object.create(null)
@ -83,7 +84,10 @@ function validateRuleOptions(id, options, source) {
localOptions = []; localOptions = [];
} }
validSeverity = (severity === 0 || severity === 1 || severity === 2); validSeverity = (
severity === 0 || severity === 1 || severity === 2 ||
(typeof severity === "string" && /^(?:off|warn|error)$/i.test(severity))
);
if (validateRule) { if (validateRule) {
validateRule(localOptions); validateRule(localOptions);
@ -97,7 +101,10 @@ function validateRuleOptions(id, options, source) {
if (!validSeverity) { if (!validSeverity) {
message.push( message.push(
"\tSeverity should be one of the following: 0 = off, 1 = warning, 2 = error (you passed \"", severity, "\").\n"); "\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '",
util.inspect(severity).replace(/'/g, "\"").replace(/\n/g, ""),
"').\n"
);
} }
if (validateRule && validateRule.errors) { if (validateRule && validateRule.errors) {
@ -136,6 +143,7 @@ function validateEnvironment(environment, source) {
source, ":\n", source, ":\n",
"\tEnvironment key \"", env, "\" is unknown\n" "\tEnvironment key \"", env, "\" is unknown\n"
]; ];
throw new Error(message.join("")); throw new Error(message.join(""));
} }
}); });

4
tools/eslint/lib/config/plugins.js

@ -117,6 +117,10 @@ module.exports = {
} catch (err) { } catch (err) {
debug("Failed to load plugin eslint-plugin-" + pluginNameWithoutPrefix + ". Proceeding without it."); debug("Failed to load plugin eslint-plugin-" + pluginNameWithoutPrefix + ". Proceeding without it.");
err.message = "Failed to load plugin " + pluginName + ": " + err.message; err.message = "Failed to load plugin " + pluginName + ": " + err.message;
err.messageTemplate = "plugin-missing";
err.messageData = {
pluginName: pluginNameWithoutPrefix
};
throw err; throw err;
} }

105
tools/eslint/lib/eslint.js

@ -11,7 +11,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
var lodash = require("lodash"), var lodash = require("lodash"),
estraverse = require("./util/estraverse"), Traverser = require("./util/traverser"),
escope = require("escope"), escope = require("escope"),
Environments = require("./config/environments"), Environments = require("./config/environments"),
blankScriptAST = require("../conf/blank-script.json"), blankScriptAST = require("../conf/blank-script.json"),
@ -43,16 +43,20 @@ var DEFAULT_PARSER = require("../conf/eslint.json").parser;
*/ */
function parseBooleanConfig(string, comment) { function parseBooleanConfig(string, comment) {
var items = {}; var items = {};
// Collapse whitespace around : to make parsing easier // Collapse whitespace around : to make parsing easier
string = string.replace(/\s*:\s*/g, ":"); string = string.replace(/\s*:\s*/g, ":");
// Collapse whitespace around , // Collapse whitespace around ,
string = string.replace(/\s*,\s*/g, ","); string = string.replace(/\s*,\s*/g, ",");
string.split(/\s|,+/).forEach(function(name) { string.split(/\s|,+/).forEach(function(name) {
if (!name) { if (!name) {
return; return;
} }
var pos = name.indexOf(":"), var pos = name.indexOf(":"),
value; value;
if (pos !== -1) { if (pos !== -1) {
value = name.substring(pos + 1, name.length); value = name.substring(pos + 1, name.length);
name = name.substring(0, pos); name = name.substring(0, pos);
@ -76,6 +80,7 @@ function parseBooleanConfig(string, comment) {
*/ */
function parseJsonConfig(string, location, messages) { function parseJsonConfig(string, location, messages) {
var items = {}; var 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 + "}");
@ -103,8 +108,10 @@ function parseJsonConfig(string, location, messages) {
*/ */
function parseListConfig(string) { function parseListConfig(string) {
var items = {}; var items = {};
// Collapse whitespace around , // Collapse whitespace around ,
string = string.replace(/\s*,\s*/g, ","); string = string.replace(/\s*,\s*/g, ",");
string.split(/,+/).forEach(function(name) { string.split(/,+/).forEach(function(name) {
name = name.trim(); name = name.trim();
if (!name) { if (!name) {
@ -136,6 +143,7 @@ function addDeclaredGlobals(program, globalScope, config) {
if (config.env[name]) { if (config.env[name]) {
var env = Environments.get(name), var env = Environments.get(name),
environmentGlobals = env && env.globals; environmentGlobals = env && env.globals;
if (environmentGlobals) { if (environmentGlobals) {
lodash.assign(declaredGlobals, environmentGlobals); lodash.assign(declaredGlobals, environmentGlobals);
} }
@ -148,6 +156,7 @@ function addDeclaredGlobals(program, globalScope, config) {
Object.keys(declaredGlobals).forEach(function(name) { Object.keys(declaredGlobals).forEach(function(name) {
var variable = globalScope.set.get(name); var variable = globalScope.set.get(name);
if (!variable) { if (!variable) {
variable = new escope.Variable(name, globalScope); variable = new escope.Variable(name, globalScope);
variable.eslintExplicitGlobal = false; variable.eslintExplicitGlobal = false;
@ -159,6 +168,7 @@ function addDeclaredGlobals(program, globalScope, config) {
Object.keys(explicitGlobals).forEach(function(name) { Object.keys(explicitGlobals).forEach(function(name) {
var variable = globalScope.set.get(name); var variable = globalScope.set.get(name);
if (!variable) { if (!variable) {
variable = new escope.Variable(name, globalScope); variable = new escope.Variable(name, globalScope);
variable.eslintExplicitGlobal = true; variable.eslintExplicitGlobal = true;
@ -172,23 +182,33 @@ function addDeclaredGlobals(program, globalScope, config) {
// mark all exported variables as such // mark all exported variables as such
Object.keys(exportedGlobals).forEach(function(name) { Object.keys(exportedGlobals).forEach(function(name) {
var variable = globalScope.set.get(name); var variable = globalScope.set.get(name);
if (variable) { if (variable) {
variable.eslintUsed = true; variable.eslintUsed = true;
} }
}); });
// "through" contains all references that their definition cannot be found. /*
// Since we augment the global scope using configuration, we need to update references and remove the ones that were added by configuration. * "through" contains all references which definitions cannot be found.
* Since we augment the global scope using configuration, we need to update
* references and remove the ones that were added by configuration.
*/
globalScope.through = globalScope.through.filter(function(reference) { globalScope.through = globalScope.through.filter(function(reference) {
var name = reference.identifier.name; var name = reference.identifier.name;
var variable = globalScope.set.get(name); var variable = globalScope.set.get(name);
if (variable) { if (variable) {
// Links the variable and the reference.
// And this reference is removed from `Scope#through`. /*
* Links the variable and the reference.
* And this reference is removed from `Scope#through`.
*/
reference.resolved = variable; reference.resolved = variable;
variable.references.push(reference); variable.references.push(reference);
return false; return false;
} }
return true; return true;
}); });
} }
@ -241,8 +261,10 @@ function enableReporting(reportingConfig, start, rulesToEnable) {
} }
}); });
} else { } else {
// find all previous disabled locations if they was started as list of rules // find all previous disabled locations if they was started as list of rules
var prevStart; var prevStart;
for (i = reportingConfig.length - 1; i >= 0; i--) { for (i = reportingConfig.length - 1; i >= 0; i--) {
if (prevStart && prevStart !== reportingConfig[i].start) { if (prevStart && prevStart !== reportingConfig[i].start) {
break; break;
@ -280,7 +302,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
ast.comments.forEach(function(comment) { ast.comments.forEach(function(comment) {
var value = comment.value.trim(); var value = comment.value.trim();
var match = /^(eslint-\w+|eslint-\w+-\w+|eslint|exported|globals?)(\s|$)/.exec(value); var match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value);
if (match) { if (match) {
value = value.substring(match.index + match[1].length); value = value.substring(match.index + match[1].length);
@ -310,8 +332,10 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
case "eslint": case "eslint":
var items = parseJsonConfig(value, comment.loc, messages); var items = parseJsonConfig(value, comment.loc, messages);
Object.keys(items).forEach(function(name) { Object.keys(items).forEach(function(name) {
var ruleValue = items[name]; var ruleValue = items[name];
validator.validateRuleOptions(name, ruleValue, filename + " line " + comment.loc.start.line); validator.validateRuleOptions(name, ruleValue, filename + " line " + comment.loc.start.line);
commentRules[name] = ruleValue; commentRules[name] = ruleValue;
}); });
@ -319,11 +343,13 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
// no default // no default
} }
} else { } else { // comment.type === "Line"
// comment.type === "Line"
if (match[1] === "eslint-disable-line") { if (match[1] === "eslint-disable-line") {
disableReporting(reportingConfig, { "line": comment.loc.start.line, "column": 0 }, Object.keys(parseListConfig(value))); disableReporting(reportingConfig, { "line": comment.loc.start.line, "column": 0 }, Object.keys(parseListConfig(value)));
enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value))); enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value)));
} else if (match[1] === "eslint-disable-next-line") {
disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
enableReporting(reportingConfig, { "line": comment.loc.start.line + 2 }, Object.keys(parseListConfig(value)));
} }
} }
} }
@ -332,6 +358,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
// apply environment configs // apply environment configs
Object.keys(commentConfig.env).forEach(function(name) { Object.keys(commentConfig.env).forEach(function(name) {
var env = Environments.get(name); var env = Environments.get(name);
if (env) { if (env) {
commentConfig = ConfigOps.merge(commentConfig, env); commentConfig = ConfigOps.merge(commentConfig, env);
} }
@ -353,6 +380,7 @@ function isDisabledByReportingConfig(reportingConfig, ruleId, location) {
for (var i = 0, c = reportingConfig.length; i < c; i++) { for (var i = 0, c = reportingConfig.length; i < c; i++) {
var ignore = reportingConfig[i]; var ignore = reportingConfig[i];
if ((!ignore.rule || ignore.rule === ruleId) && if ((!ignore.rule || ignore.rule === ruleId) &&
(location.line > ignore.start.line || (location.line === ignore.start.line && location.column >= ignore.start.column)) && (location.line > ignore.start.line || (location.line === ignore.start.line && location.column >= ignore.start.column)) &&
(!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column <= ignore.end.column)))) { (!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column <= ignore.end.column)))) {
@ -380,6 +408,7 @@ function prepareConfig(config) {
if (typeof config.rules === "object") { if (typeof config.rules === "object") {
Object.keys(config.rules).forEach(function(k) { Object.keys(config.rules).forEach(function(k) {
var rule = config.rules[k]; var rule = config.rules[k];
if (rule === null) { if (rule === null) {
throw new Error("Invalid config for rule '" + k + "'\."); throw new Error("Invalid config for rule '" + k + "'\.");
} }
@ -395,6 +424,7 @@ function prepareConfig(config) {
if (typeof config.env === "object") { if (typeof config.env === "object") {
Object.keys(config.env).forEach(function(envName) { Object.keys(config.env).forEach(function(envName) {
var env = Environments.get(envName); var env = Environments.get(envName);
if (config.env[envName] && env && env.parserOptions) { if (config.env[envName] && env && env.parserOptions) {
parserOptions = ConfigOps.merge(parserOptions, env.parserOptions); parserOptions = ConfigOps.merge(parserOptions, env.parserOptions);
} }
@ -418,7 +448,7 @@ function prepareConfig(config) {
// can't have global return inside of modules // can't have global return inside of modules
preparedConfig.parserOptions.ecmaFeatures.globalReturn = false; preparedConfig.parserOptions.ecmaFeatures.globalReturn = false;
// also need at least ES6 six for modules // also need at least ES6 for modules
if (!preparedConfig.parserOptions.ecmaVersion || preparedConfig.parserOptions.ecmaVersion < 6) { if (!preparedConfig.parserOptions.ecmaVersion || preparedConfig.parserOptions.ecmaVersion < 6) {
preparedConfig.parserOptions.ecmaVersion = 6; preparedConfig.parserOptions.ecmaVersion = 6;
} }
@ -462,8 +492,10 @@ function createStubRule(message) {
function getRuleReplacementMessage(ruleId) { function getRuleReplacementMessage(ruleId) {
if (ruleId in replacements.rules) { if (ruleId in replacements.rules) {
var newRules = replacements.rules[ruleId]; var newRules = replacements.rules[ruleId];
return "Rule \'" + ruleId + "\' was removed and replaced by: " + newRules.join(", "); return "Rule \'" + ruleId + "\' was removed and replaced by: " + newRules.join(", ");
} }
return null; return null;
} }
@ -478,6 +510,7 @@ function findEslintEnv(text) {
var match, retv; var match, retv;
eslintEnvPattern.lastIndex = 0; eslintEnvPattern.lastIndex = 0;
while ((match = eslintEnvPattern.exec(text))) { while ((match = eslintEnvPattern.exec(text))) {
retv = lodash.assign(retv || {}, parseListConfig(match[1])); retv = lodash.assign(retv || {}, parseListConfig(match[1]));
} }
@ -492,9 +525,12 @@ function findEslintEnv(text) {
* @returns {string} The stripped text. * @returns {string} The stripped text.
*/ */
function stripUnicodeBOM(text) { function stripUnicodeBOM(text) {
// Check Unicode BOM.
// In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF. /*
// http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters * Check Unicode BOM.
* In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF.
* http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters
*/
if (text.charCodeAt(0) === 0xFEFF) { if (text.charCodeAt(0) === 0xFEFF) {
return text.slice(1); return text.slice(1);
} }
@ -518,7 +554,7 @@ module.exports = (function() {
scopeMap = null, scopeMap = null,
scopeManager = null, scopeManager = null,
currentFilename = null, currentFilename = null,
controller = null, traverser = null,
reportingConfig = [], reportingConfig = [],
sourceCode = null; sourceCode = null;
@ -636,7 +672,7 @@ module.exports = (function() {
currentScopes = null; currentScopes = null;
scopeMap = null; scopeMap = null;
scopeManager = null; scopeManager = null;
controller = null; traverser = null;
reportingConfig = []; reportingConfig = [];
sourceCode = null; sourceCode = null;
}; };
@ -689,6 +725,7 @@ module.exports = (function() {
// search and apply "eslint-env *". // search and apply "eslint-env *".
var envInFile = findEslintEnv(text || textOrSourceCode.text); var envInFile = findEslintEnv(text || textOrSourceCode.text);
if (envInFile) { if (envInFile) {
if (!config || !config.env) { if (!config || !config.env) {
config = lodash.assign({}, config || {}, {env: envInFile}); config = lodash.assign({}, config || {}, {env: envInFile});
@ -703,6 +740,7 @@ module.exports = (function() {
// only do this for text // only do this for text
if (text !== null) { if (text !== null) {
// there's no input, just exit here // there's no input, just exit here
if (text.trim().length === 0) { if (text.trim().length === 0) {
sourceCode = new SourceCode(text, blankScriptAST); sourceCode = new SourceCode(text, blankScriptAST);
@ -734,6 +772,9 @@ module.exports = (function() {
config = modifyConfigsFromComments(currentFilename, ast, config, reportingConfig, messages); config = modifyConfigsFromComments(currentFilename, ast, config, reportingConfig, messages);
} }
// ensure that severities are normalized in the config
ConfigOps.normalize(config);
// enable appropriate rules // enable appropriate rules
Object.keys(config.rules).filter(function(key) { Object.keys(config.rules).filter(function(key) {
return getRuleSeverity(config.rules[key]) > 0; return getRuleSeverity(config.rules[key]) > 0;
@ -744,8 +785,10 @@ module.exports = (function() {
rule; rule;
ruleCreator = rules.get(key); ruleCreator = rules.get(key);
if (!ruleCreator) { if (!ruleCreator) {
var replacementMsg = getRuleReplacementMessage(key); var replacementMsg = getRuleReplacementMessage(key);
if (replacementMsg) { if (replacementMsg) {
ruleCreator = createStubRule(replacementMsg); ruleCreator = createStubRule(replacementMsg);
} else { } else {
@ -761,6 +804,7 @@ module.exports = (function() {
var ruleContext = new RuleContext( var ruleContext = new RuleContext(
key, api, severity, options, key, api, severity, options,
config.settings, config.parserOptions, config.parser, ruleCreator.meta); config.settings, config.parserOptions, config.parser, ruleCreator.meta);
rule = ruleCreator.create ? ruleCreator.create(ruleContext) : rule = ruleCreator.create ? ruleCreator.create(ruleContext) :
ruleCreator(ruleContext); ruleCreator(ruleContext);
@ -779,19 +823,21 @@ module.exports = (function() {
// save config so rules can access as necessary // save config so rules can access as necessary
currentConfig = config; currentConfig = config;
controller = new estraverse.Controller(); traverser = new Traverser();
ecmaFeatures = currentConfig.parserOptions.ecmaFeatures || {}; ecmaFeatures = currentConfig.parserOptions.ecmaFeatures || {};
ecmaVersion = currentConfig.parserOptions.ecmaVersion || 5; ecmaVersion = currentConfig.parserOptions.ecmaVersion || 5;
// gather data that may be needed by the rules // gather scope data that may be needed by the rules
scopeManager = escope.analyze(ast, { scopeManager = escope.analyze(ast, {
ignoreEval: true, ignoreEval: true,
nodejsScope: ecmaFeatures.globalReturn, nodejsScope: ecmaFeatures.globalReturn,
impliedStrict: ecmaFeatures.impliedStrict, impliedStrict: ecmaFeatures.impliedStrict,
ecmaVersion: ecmaVersion, ecmaVersion: ecmaVersion,
sourceType: currentConfig.parserOptions.sourceType || "script" sourceType: currentConfig.parserOptions.sourceType || "script",
fallback: Traverser.getKeys
}); });
currentScopes = scopeManager.scopes; currentScopes = scopeManager.scopes;
/* /*
@ -799,11 +845,14 @@ module.exports = (function() {
* lookup in getScope. * lookup in getScope.
*/ */
scopeMap = []; scopeMap = [];
currentScopes.forEach(function(scope, index) { currentScopes.forEach(function(scope, index) {
var range = scope.block.range[0]; var range = scope.block.range[0];
// Sometimes two scopes are returned for a given node. This is /*
// handled later in a known way, so just don't overwrite here. * Sometimes two scopes are returned for a given node. This is
* handled later in a known way, so just don't overwrite here.
*/
if (!scopeMap[range]) { if (!scopeMap[range]) {
scopeMap[range] = index; scopeMap[range] = index;
} }
@ -822,15 +871,17 @@ module.exports = (function() {
} }
var eventGenerator = new NodeEventGenerator(api); var eventGenerator = new NodeEventGenerator(api);
eventGenerator = new CodePathAnalyzer(eventGenerator); eventGenerator = new CodePathAnalyzer(eventGenerator);
eventGenerator = new CommentEventGenerator(eventGenerator, sourceCode); eventGenerator = new CommentEventGenerator(eventGenerator, sourceCode);
/* /*
* Each node has a type property. Whenever a particular type of node is found, * Each node has a type property. Whenever a particular type of
* an event is fired. This allows any listeners to automatically be informed * node is found, an event is fired. This allows any listeners to
* that this type of node has been found and react accordingly. * automatically be informed that this type of node has been found
* and react accordingly.
*/ */
controller.traverse(ast, { traverser.traverse(ast, {
enter: function(node, parent) { enter: function(node, parent) {
node.parent = parent; node.parent = parent;
eventGenerator.enterNode(node); eventGenerator.enterNode(node);
@ -884,6 +935,7 @@ module.exports = (function() {
message = location; message = location;
location = node.loc.start; location = node.loc.start;
} }
// else, assume location was provided, so node may be omitted // else, assume location was provided, so node may be omitted
if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) { if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) {
@ -966,7 +1018,7 @@ module.exports = (function() {
* @returns {ASTNode[]} Array of objects representing ancestors. * @returns {ASTNode[]} Array of objects representing ancestors.
*/ */
api.getAncestors = function() { api.getAncestors = function() {
return controller.parents(); return traverser.parents();
}; };
/** /**
@ -974,14 +1026,15 @@ module.exports = (function() {
* @returns {Object} An object representing the current node's scope. * @returns {Object} An object representing the current node's scope.
*/ */
api.getScope = function() { api.getScope = function() {
var parents = controller.parents(), var parents = traverser.parents(),
scope = currentScopes[0]; scope = currentScopes[0];
// Don't do this for Program nodes - they have no parents // Don't do this for Program nodes - they have no parents
if (parents.length) { if (parents.length) {
// if current node introduces a scope, add it to the list // if current node introduces a scope, add it to the list
var current = controller.current(); var current = traverser.current();
if (currentConfig.parserOptions.ecmaVersion >= 6) { if (currentConfig.parserOptions.ecmaVersion >= 6) {
if (["BlockStatement", "SwitchStatement", "CatchClause", "FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) { if (["BlockStatement", "SwitchStatement", "CatchClause", "FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
parents.push(current); parents.push(current);

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

@ -58,9 +58,11 @@ function FileFinder(files, cwd) {
*/ */
function normalizeDirectoryEntries(entries, directory, supportedConfigs) { function normalizeDirectoryEntries(entries, directory, supportedConfigs) {
var fileHash = {}; var fileHash = {};
entries.forEach(function(entry) { entries.forEach(function(entry) {
if (supportedConfigs.indexOf(entry) >= 0) { if (supportedConfigs.indexOf(entry) >= 0) {
var resolvedEntry = path.resolve(directory, entry); var resolvedEntry = path.resolve(directory, entry);
if (fs.statSync(resolvedEntry).isFile()) { if (fs.statSync(resolvedEntry).isFile()) {
fileHash[entry] = resolvedEntry; fileHash[entry] = resolvedEntry;
} }
@ -103,6 +105,7 @@ FileFinder.prototype.findInDirectoryOrParents = function(directory) {
while (directory !== child) { while (directory !== child) {
dirs[searched++] = directory; dirs[searched++] = directory;
var filesMap = normalizeDirectoryEntries(getDirectoryEntries(directory), directory, names); var filesMap = normalizeDirectoryEntries(getDirectoryEntries(directory), directory, names);
if (Object.keys(filesMap).length) { if (Object.keys(filesMap).length) {
for (var k = 0; k < names.length; k++) { for (var k = 0; k < names.length; k++) {
if (filesMap[names[k]]) { if (filesMap[names[k]]) {

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

@ -35,6 +35,7 @@ module.exports = function(results) {
results.forEach(function(result) { results.forEach(function(result) {
var messages = result.messages; var messages = result.messages;
total += messages.length; total += messages.length;
messages.forEach(function(message) { messages.forEach(function(message) {

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

@ -36,6 +36,7 @@ function pluralize(word, count) {
function renderSummary(totalErrors, totalWarnings) { function renderSummary(totalErrors, totalWarnings) {
var totalProblems = totalErrors + totalWarnings; var totalProblems = totalErrors + totalWarnings;
var renderedText = totalProblems + " " + pluralize("problem", totalProblems); var renderedText = totalProblems + " " + pluralize("problem", totalProblems);
if (totalProblems !== 0) { if (totalProblems !== 0) {
renderedText += " (" + totalErrors + " " + pluralize("error", totalErrors) + ", " + totalWarnings + " " + pluralize("warning", totalWarnings) + ")"; renderedText += " (" + totalErrors + " " + pluralize("error", totalErrors) + ", " + totalWarnings + " " + pluralize("warning", totalWarnings) + ")";
} }
@ -64,6 +65,7 @@ function renderColor(totalErrors, totalWarnings) {
* @returns {string} HTML (table rows) describing the messages. * @returns {string} HTML (table rows) describing the messages.
*/ */
function renderMessages(messages, parentIndex) { function renderMessages(messages, parentIndex) {
/** /**
* Get HTML (table row) describing a message. * Get HTML (table row) describing a message.
* @param {Object} message Message. * @param {Object} message Message.

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

@ -45,6 +45,7 @@ module.exports = function(results) {
messages.forEach(function(message) { messages.forEach(function(message) {
var type = message.fatal ? "error" : "failure"; var type = message.fatal ? "error" : "failure";
output += "<testcase time=\"0\" name=\"org.eslint." + (message.ruleId || "unknown") + "\">"; output += "<testcase time=\"0\" name=\"org.eslint." + (message.ruleId || "unknown") + "\">";
output += "<" + type + " message=\"" + lodash.escape(message.message || "") + "\">"; output += "<" + type + " message=\"" + lodash.escape(message.message || "") + "\">";
output += "<![CDATA["; output += "<![CDATA[";

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

@ -31,6 +31,7 @@ function getMessageType(message) {
function outputDiagnostics(diagnostic) { function outputDiagnostics(diagnostic) {
var prefix = " "; var prefix = " ";
var output = prefix + "---\n"; var output = prefix + "---\n";
output += prefix + yaml.safeDump(diagnostic).split("\n").join("\n" + prefix); output += prefix + yaml.safeDump(diagnostic).split("\n").join("\n" + prefix);
output += "...\n"; output += "...\n";
return output; return output;

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

@ -35,6 +35,7 @@ module.exports = function(results) {
results.forEach(function(result) { results.forEach(function(result) {
var messages = result.messages; var messages = result.messages;
total += messages.length; total += messages.length;
messages.forEach(function(message) { messages.forEach(function(message) {

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

@ -38,6 +38,7 @@ module.exports = function(results) {
results.forEach(function(result) { results.forEach(function(result) {
var messages = result.messages; var messages = result.messages;
total += messages.length; total += messages.length;
messages.forEach(function(message) { messages.forEach(function(message) {

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

@ -12,7 +12,8 @@ var lodash = require("lodash"),
fs = require("fs"), fs = require("fs"),
path = require("path"), path = require("path"),
debug = require("debug"), debug = require("debug"),
ignore = require("ignore"); ignore = require("ignore"),
pathUtil = require("./util/path-util");
debug = debug("eslint:ignored-paths"); debug = debug("eslint:ignored-paths");
@ -23,8 +24,8 @@ debug = debug("eslint:ignored-paths");
var ESLINT_IGNORE_FILENAME = ".eslintignore"; var ESLINT_IGNORE_FILENAME = ".eslintignore";
var DEFAULT_IGNORE_PATTERNS = [ var DEFAULT_IGNORE_PATTERNS = [
"/node_modules/", "/node_modules/*",
"/bower_components/" "/bower_components/*"
]; ];
var DEFAULT_OPTIONS = { var DEFAULT_OPTIONS = {
dotfiles: false, dotfiles: false,
@ -46,59 +47,8 @@ function findIgnoreFile(cwd) {
cwd = cwd || DEFAULT_OPTIONS.cwd; cwd = cwd || DEFAULT_OPTIONS.cwd;
var ignoreFilePath = path.resolve(cwd, ESLINT_IGNORE_FILENAME); var ignoreFilePath = path.resolve(cwd, ESLINT_IGNORE_FILENAME);
return fs.existsSync(ignoreFilePath) ? ignoreFilePath : "";
}
/** return fs.existsSync(ignoreFilePath) ? ignoreFilePath : "";
* Replace Windows with Unix style paths and remove ./ prefix
* @param {string} filepath Path to normalize
* @returns {string} Normalized filepath
*/
function normalizeFilepath(filepath) {
filepath = filepath.replace(/\\/g, "/");
filepath = filepath.replace(/^\.\//, "");
return filepath;
}
/**
* Remove a prefix from a filepath
* @param {string} filepath Path to remove the prefix from
* @param {string} prefix Prefix to remove from filepath
* @returns {string} Normalized filepath
*/
function removePrefixFromFilepath(filepath, prefix) {
prefix += "/";
if (filepath.indexOf(prefix) === 0) {
filepath = filepath.substr(prefix.length);
}
return filepath;
}
/**
* Resolves a filepath
* @param {string} filepath Path resolve
* @param {string} baseDir Base directory to resolve the filepath from
* @returns {string} Resolved filepath
*/
function resolveFilepath(filepath, baseDir) {
if (baseDir) {
var base = normalizeFilepath(path.resolve(baseDir));
filepath = removePrefixFromFilepath(filepath, base);
filepath = removePrefixFromFilepath(filepath, fs.realpathSync(base));
}
filepath.replace(/^\//, "");
return filepath;
}
/**
* Normalize and resolve a filepath relative to a given base directory
* @param {string} filepath Path resolve
* @param {string} baseDir Base directory to resolve the filepath from
* @returns {string} Normalized and resolved filepath
*/
function normalizeAndResolveFilepath(filepath, baseDir) {
filepath = normalizeFilepath(filepath);
return resolveFilepath(filepath, baseDir);
} }
/** /**
@ -142,35 +92,40 @@ function IgnoredPaths(options) {
* @returns {array} raw ignore rules * @returns {array} raw ignore rules
*/ */
function addIgnoreFile(ig, filepath) { function addIgnoreFile(ig, filepath) {
return ig.addIgnoreFile(filepath); ig.ignoreFiles.push(filepath);
return ig.add(fs.readFileSync(filepath, "utf8"));
} }
this.defaultPatterns = DEFAULT_IGNORE_PATTERNS.concat(options.patterns || []); this.defaultPatterns = DEFAULT_IGNORE_PATTERNS.concat(options.patterns || []);
this.baseDir = "."; this.baseDir = options.cwd;
this.ig = { this.ig = {
custom: new ignore.Ignore({ custom: ignore(),
twoGlobstars: true, default: ignore()
ignore: []
}),
default: new ignore.Ignore({
twoGlobstars: true,
ignore: []
})
}; };
// Add a way to keep track of ignored files. This was present in node-ignore
// 2.x, but dropped for now as of 3.0.10.
this.ig.custom.ignoreFiles = [];
this.ig.default.ignoreFiles = [];
if (options.dotfiles !== true) { if (options.dotfiles !== true) {
addPattern(this.ig.default, ".*");
}
if (options.ignore !== false) { /*
* ignore files beginning with a dot, but not files in a parent or
* ancestor directory (which in relative format will begin with `../`).
*/
addPattern(this.ig.default, [".*", "!../"]);
}
addPattern(this.ig.default, this.defaultPatterns); addPattern(this.ig.default, this.defaultPatterns);
if (options.ignore !== false) {
var ignorePath; var ignorePath;
if (options.ignorePattern) { if (options.ignorePattern) {
addPattern(this.ig.custom, options.ignorePattern); addPattern(this.ig.custom, options.ignorePattern);
addPattern(this.ig.default, options.ignorePattern);
} }
if (options.ignorePath) { if (options.ignorePath) {
@ -198,8 +153,9 @@ function IgnoredPaths(options) {
if (ignorePath) { if (ignorePath) {
debug("Adding " + ignorePath); debug("Adding " + ignorePath);
this.baseDir = path.dirname(ignorePath); this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath));
addIgnoreFile(this.ig.custom, ignorePath); addIgnoreFile(this.ig.custom, ignorePath);
addIgnoreFile(this.ig.default, ignorePath);
} }
} }
@ -217,14 +173,15 @@ function IgnoredPaths(options) {
IgnoredPaths.prototype.contains = function(filepath, category) { IgnoredPaths.prototype.contains = function(filepath, category) {
var result = false; var result = false;
filepath = normalizeAndResolveFilepath(filepath, this.baseDir); var absolutePath = path.resolve(this.options.cwd, filepath);
var relativePath = pathUtil.getRelativePath(absolutePath, this.options.cwd);
if ((typeof category === "undefined") || (category === "default")) { if ((typeof category === "undefined") || (category === "default")) {
result = result || (this.ig.default.filter([filepath]).length === 0); result = result || (this.ig.default.filter([relativePath]).length === 0);
} }
if ((typeof category === "undefined") || (category === "custom")) { if ((typeof category === "undefined") || (category === "custom")) {
result = result || (this.ig.custom.filter([filepath]).length === 0); result = result || (this.ig.custom.filter([relativePath]).length === 0);
} }
return result; return result;

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

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

3
tools/eslint/lib/logging.js

@ -1,5 +1,5 @@
/** /**
* @fileoverview Handle logging for Eslint * @fileoverview Handle logging for ESLint
* @author Gyandeep Singh * @author Gyandeep Singh
* @copyright 2015 Gyandeep Singh. All rights reserved. * @copyright 2015 Gyandeep Singh. All rights reserved.
*/ */
@ -7,6 +7,7 @@
/* istanbul ignore next */ /* istanbul ignore next */
module.exports = { module.exports = {
/** /**
* Cover for console.log * Cover for console.log
* @returns {void} * @returns {void}

7
tools/eslint/lib/options.js

@ -60,6 +60,11 @@ module.exports = optionator({
default: "espree", default: "espree",
description: "Specify the parser to be used" description: "Specify the parser to be used"
}, },
{
option: "parser-options",
type: "Object",
description: "Specify parser options"
},
{ {
heading: "Caching" heading: "Caching"
}, },
@ -202,7 +207,7 @@ module.exports = optionator({
option: "version", option: "version",
alias: "v", alias: "v",
type: "Boolean", type: "Boolean",
description: "Outputs the version number" description: "Output the version number"
}, },
{ {
option: "inline-config", option: "inline-config",

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

@ -73,6 +73,7 @@ var PASSTHROUGHS = [
* @param {Object} meta The metadata of the rule * @param {Object} meta The metadata of the rule
*/ */
function RuleContext(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta) { function RuleContext(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta) {
// public. // public.
this.id = ruleId; this.id = ruleId;
this.options = options; this.options = options;
@ -149,9 +150,8 @@ RuleContext.prototype = {
} }
}; };
// copy over passthrough methods // Copy over passthrough methods. All functions will have 5 or fewer parameters.
PASSTHROUGHS.forEach(function(name) { PASSTHROUGHS.forEach(function(name) {
// All functions expected to have less arguments than 5.
this[name] = function(a, b, c, d, e) { this[name] = function(a, b, c, d, e) {
return this.eslint[name](a, b, c, d, e); return this.eslint[name](a, b, c, d, e);
}; };

1
tools/eslint/lib/rules.js

@ -39,6 +39,7 @@ function define(ruleId, ruleModule) {
*/ */
function load(rulesDir, cwd) { function load(rulesDir, cwd) {
var newRules = loadRules(rulesDir, cwd); var newRules = loadRules(rulesDir, cwd);
Object.keys(newRules).forEach(function(ruleId) { Object.keys(newRules).forEach(function(ruleId) {
define(ruleId, newRules[ruleId]); define(ruleId, newRules[ruleId]);
}); });

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

@ -30,6 +30,7 @@ function isIdentifier(node, name) {
*/ */
function isArgumentOfMethodCall(node, index, object, property) { function isArgumentOfMethodCall(node, index, object, property) {
var parent = node.parent; var parent = node.parent;
return ( return (
parent.type === "CallExpression" && parent.type === "CallExpression" &&
parent.callee.type === "MemberExpression" && parent.callee.type === "MemberExpression" &&
@ -46,6 +47,7 @@ function isArgumentOfMethodCall(node, index, object, property) {
* @returns {boolean} `true` if the node is a property descriptor. * @returns {boolean} `true` if the node is a property descriptor.
*/ */
function isPropertyDescriptor(node) { function isPropertyDescriptor(node) {
// Object.defineProperty(obj, "foo", {set: ...}) // Object.defineProperty(obj, "foo", {set: ...})
if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") || if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") ||
isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty") isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty")
@ -53,9 +55,12 @@ function isPropertyDescriptor(node) {
return true; return true;
} }
// Object.defineProperties(obj, {foo: {set: ...}}) /*
// Object.create(proto, {foo: {set: ...}}) * Object.defineProperties(obj, {foo: {set: ...}})
* Object.create(proto, {foo: {set: ...}})
*/
node = node.parent.parent; node = node.parent.parent;
return node.type === "ObjectExpression" && ( return node.type === "ObjectExpression" && (
isArgumentOfMethodCall(node, 1, "Object", "create") || isArgumentOfMethodCall(node, 1, "Object", "create") ||
isArgumentOfMethodCall(node, 1, "Object", "defineProperties") isArgumentOfMethodCall(node, 1, "Object", "defineProperties")
@ -106,6 +111,7 @@ module.exports = {
var property = node.properties[i]; var property = node.properties[i];
var propToCheck = ""; var propToCheck = "";
if (property.kind === "init") { if (property.kind === "init") {
if (isDescriptor && !property.computed) { if (isDescriptor && !property.computed) {
propToCheck = property.key.name; propToCheck = property.key.name;
@ -124,6 +130,7 @@ module.exports = {
break; break;
default: default:
// Do nothing // Do nothing
} }

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

@ -82,6 +82,7 @@ module.exports = {
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 = context.getSourceCode().getTokenAfter(token);
return fixer.removeRange([token.range[1], nextToken.range[0]]); return fixer.removeRange([token.range[1], nextToken.range[0]]);
} }
}); });
@ -100,6 +101,7 @@ module.exports = {
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 = context.getSourceCode().getTokenBefore(token);
return fixer.removeRange([previousToken.range[1], token.range[0]]); return fixer.removeRange([previousToken.range[1], token.range[0]]);
} }
}); });

11
tools/eslint/lib/rules/array-callback-return.js

@ -107,10 +107,13 @@ function isTargetMethod(node) {
function isCallbackOfArrayMethod(node) { function isCallbackOfArrayMethod(node) {
while (node) { while (node) {
var parent = node.parent; var parent = node.parent;
switch (parent.type) { switch (parent.type) {
// Looks up the destination.
// e.g. /*
// foo.every(nativeFoo || function foo() { ... }); * Looks up the destination. e.g.,
* foo.every(nativeFoo || function foo() { ... });
*/
case "LogicalExpression": case "LogicalExpression":
case "ConditionalExpression": case "ConditionalExpression":
node = parent; node = parent;
@ -124,6 +127,7 @@ function isCallbackOfArrayMethod(node) {
// })()); // })());
case "ReturnStatement": case "ReturnStatement":
var func = astUtils.getUpperFunction(parent); var func = astUtils.getUpperFunction(parent);
if (func === null || !astUtils.isCallee(func)) { if (func === null || !astUtils.isCallee(func)) {
return false; return false;
} }
@ -195,6 +199,7 @@ module.exports = function(context) {
} }
return { return {
// Stacks this function's information. // Stacks this function's information.
"onCodePathStart": function(codePath, node) { "onCodePathStart": function(codePath, node) {
funcInfo = { funcInfo = {

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

@ -21,6 +21,7 @@ module.exports = function(context) {
*/ */
function validate(node) { function validate(node) {
var arrowBody = node.body; var arrowBody = node.body;
if (arrowBody.type === "BlockStatement") { if (arrowBody.type === "BlockStatement") {
var blockBody = arrowBody.body; var blockBody = arrowBody.body;

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

@ -10,9 +10,11 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = function(context) {
// merge rules with default // merge rules with default
var rule = { before: true, after: true }; var rule = { before: true, after: true },
var option = context.options[0] || {}; option = context.options[0] || {};
rule.before = option.before !== false; rule.before = option.before !== false;
rule.after = option.after !== false; rule.after = option.after !== false;
@ -24,11 +26,13 @@ module.exports = function(context) {
function getTokens(node) { function getTokens(node) {
var t = context.getFirstToken(node); var t = context.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 = context.getTokenAfter(t);
} }
var after = context.getTokenAfter(t); var after = context.getTokenAfter(t);
return { before: before, arrow: t, after: after }; return { before: before, arrow: t, after: after };
} }
@ -40,6 +44,7 @@ module.exports = function(context) {
function countSpaces(tokens) { function countSpaces(tokens) {
var before = tokens.arrow.range[0] - tokens.before.range[1]; var before = tokens.arrow.range[0] - tokens.before.range[1];
var after = tokens.after.range[0] - tokens.arrow.range[1]; var after = tokens.after.range[0] - tokens.arrow.range[1];
return { before: before, after: after }; return { before: before, after: after };
} }
@ -55,6 +60,7 @@ module.exports = function(context) {
var countSpace = countSpaces(tokens); var countSpace = countSpaces(tokens);
if (rule.before) { if (rule.before) {
// should be space(s) before arrow // should be space(s) before arrow
if (countSpace.before === 0) { if (countSpace.before === 0) {
context.report({ context.report({
@ -66,6 +72,7 @@ module.exports = function(context) {
}); });
} }
} else { } else {
// should be no space before arrow // should be no space before arrow
if (countSpace.before > 0) { if (countSpace.before > 0) {
context.report({ context.report({
@ -79,6 +86,7 @@ module.exports = function(context) {
} }
if (rule.after) { if (rule.after) {
// should be space(s) after arrow // should be space(s) after arrow
if (countSpace.after === 0) { if (countSpace.after === 0) {
context.report({ context.report({
@ -90,6 +98,7 @@ module.exports = function(context) {
}); });
} }
} else { } else {
// should be no space after arrow // should be no space after arrow
if (countSpace.after > 0) { if (countSpace.after > 0) {
context.report({ context.report({

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

@ -37,6 +37,7 @@ module.exports = function(context) {
*/ */
function report(reference) { function report(reference) {
var identifier = reference.identifier; var identifier = reference.identifier;
context.report( context.report(
identifier, identifier,
"'{{name}}' used outside of binding context.", "'{{name}}' used outside of binding context.",
@ -64,12 +65,15 @@ module.exports = function(context) {
*/ */
function isOutsideOfScope(reference) { function isOutsideOfScope(reference) {
var idRange = reference.identifier.range; var idRange = reference.identifier.range;
return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1]; return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1];
} }
// Gets declared variables, and checks its references. // Gets declared variables, and checks its references.
var variables = context.getDeclaredVariables(node); var variables = context.getDeclaredVariables(node);
for (var i = 0; i < variables.length; ++i) { for (var i = 0; i < variables.length; ++i) {
// Reports. // Reports.
variables[i] variables[i]
.references .references

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

@ -56,6 +56,7 @@ module.exports = function(context) {
* @returns {void} * @returns {void}
*/ */
function checkSpacingInsideBraces(node) { function checkSpacingInsideBraces(node) {
// 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 = context.getLastToken(node);

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

@ -57,8 +57,12 @@ module.exports = function(context) {
var blockProperties = arguments; var blockProperties = arguments;
return function(node) { return function(node) {
[].forEach.call(blockProperties, function(blockProp) { Array.prototype.forEach.call(blockProperties, function(blockProp) {
var block = node[blockProp], previousToken, curlyToken, curlyTokenEnd, curlyTokensOnSameLine; var block = node[blockProp],
previousToken,
curlyToken,
curlyTokenEnd,
allOnSameLine;
if (!isBlock(block)) { if (!isBlock(block)) {
return; return;
@ -67,21 +71,27 @@ module.exports = function(context) {
previousToken = sourceCode.getTokenBefore(block); previousToken = sourceCode.getTokenBefore(block);
curlyToken = sourceCode.getFirstToken(block); curlyToken = sourceCode.getFirstToken(block);
curlyTokenEnd = sourceCode.getLastToken(block); curlyTokenEnd = sourceCode.getLastToken(block);
curlyTokensOnSameLine = curlyToken.loc.start.line === curlyTokenEnd.loc.start.line; allOnSameLine = previousToken.loc.start.line === curlyTokenEnd.loc.start.line;
if (allOnSameLine && params.allowSingleLine) {
return;
}
if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) { if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) {
context.report(node, OPEN_MESSAGE); context.report(node, OPEN_MESSAGE);
} else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line && !params.allowSingleLine) { } else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) {
context.report(node, OPEN_MESSAGE_ALLMAN); context.report(node, OPEN_MESSAGE_ALLMAN);
} }
if (!block.body.length || curlyTokensOnSameLine && params.allowSingleLine) { if (!block.body.length) {
return; return;
} }
if (curlyToken.loc.start.line === block.body[0].loc.start.line) { if (curlyToken.loc.start.line === block.body[0].loc.start.line) {
context.report(block.body[0], BODY_MESSAGE); context.report(block.body[0], BODY_MESSAGE);
} else if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.start.line) { }
if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.start.line) {
context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE); context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE);
} }
}); });
@ -172,6 +182,7 @@ module.exports = function(context) {
*/ */
function checkSwitchStatement(node) { function checkSwitchStatement(node) {
var tokens; var tokens;
if (node.cases && node.cases.length) { if (node.cases && node.cases.length) {
tokens = sourceCode.getTokensBefore(node.cases[0], 2); tokens = sourceCode.getTokensBefore(node.cases[0], 2);
} else { } else {

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

@ -49,6 +49,7 @@ module.exports = function(context) {
* @returns {boolean} Whether or not this is part of a callback expression * @returns {boolean} Whether or not this is part of a callback expression
*/ */
function isCallbackExpression(node, parentNode) { function isCallbackExpression(node, parentNode) {
// ensure the parent node exists and is an expression // ensure the parent node exists and is an expression
if (!parentNode || parentNode.type !== "ExpressionStatement") { if (!parentNode || parentNode.type !== "ExpressionStatement") {
return false; return false;

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

@ -54,7 +54,11 @@ module.exports = function(context) {
return { return {
"Identifier": function(node) { "Identifier": function(node) {
// Leading and trailing underscores are commonly used to flag private/protected identifiers, strip them
/*
* Leading and trailing underscores are commonly used to flag
* private/protected identifiers, strip them
*/
var name = node.name.replace(/^_+|_+$/g, ""), var name = node.name.replace(/^_+|_+$/g, ""),
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
@ -83,6 +87,7 @@ module.exports = function(context) {
// Properties have their own rules // Properties have their own rules
} else if (node.parent.type === "Property") { } else if (node.parent.type === "Property") {
// "never" check properties // "never" check properties
if (properties === "never") { if (properties === "never") {
return; return;

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

@ -10,21 +10,10 @@
"use strict"; "use strict";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Requirements
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** var lodash = require("lodash");
* Gets the last element of a given array.
*
* @param {*[]} xs - An array to get.
* @returns {*} The last element, or undefined.
*/
function getLast(xs) {
if (xs.length === 0) {
return null;
}
return xs[xs.length - 1];
}
/** /**
* Checks whether or not a trailing comma is allowed in a given node. * Checks whether or not a trailing comma is allowed in a given node.
@ -37,6 +26,7 @@ function getLast(xs) {
function isTrailingCommaAllowed(node, lastItem) { function isTrailingCommaAllowed(node, lastItem) {
switch (node.type) { switch (node.type) {
case "ArrayPattern": case "ArrayPattern":
// TODO(t-nagashima): Remove SpreadElement after https://github.com/eslint/espree/issues/194 was fixed. // TODO(t-nagashima): Remove SpreadElement after https://github.com/eslint/espree/issues/194 was fixed.
return ( return (
lastItem.type !== "RestElement" && lastItem.type !== "RestElement" &&
@ -74,7 +64,8 @@ module.exports = function(context) {
* @returns {boolean} `true` if the node is multiline. * @returns {boolean} `true` if the node is multiline.
*/ */
function isMultiline(node) { function isMultiline(node) {
var lastItem = getLast(node.properties || node.elements || node.specifiers); var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
if (!lastItem) { if (!lastItem) {
return false; return false;
} }
@ -106,7 +97,8 @@ module.exports = function(context) {
* @returns {void} * @returns {void}
*/ */
function forbidTrailingComma(node) { function forbidTrailingComma(node) {
var lastItem = getLast(node.properties || node.elements || node.specifiers); var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
return; return;
} }
@ -142,7 +134,8 @@ module.exports = function(context) {
* @returns {void} * @returns {void}
*/ */
function forceTrailingComma(node) { function forceTrailingComma(node) {
var lastItem = getLast(node.properties || node.elements || node.specifiers); var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
return; return;
} }
@ -205,6 +198,7 @@ module.exports = function(context) {
// Chooses a checking function. // Chooses a checking function.
var checkForTrailingComma; var checkForTrailingComma;
if (mode === "always") { if (mode === "always") {
checkForTrailingComma = forceTrailingComma; checkForTrailingComma = forceTrailingComma;
} else if (mode === "always-multiline") { } else if (mode === "always-multiline") {

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

@ -11,8 +11,18 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = function(context) {
var option = context.options[0],
THRESHOLD = 20;
var THRESHOLD = (typeof context.options[0] !== "undefined") ? context.options[0] : 20; if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
THRESHOLD = option.maximum;
}
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
THRESHOLD = option.max;
}
if (typeof option === "number") {
THRESHOLD = option;
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Helpers // Helpers
@ -69,6 +79,7 @@ module.exports = function(context) {
* @private * @private
*/ */
function increaseSwitchComplexity(node) { function increaseSwitchComplexity(node) {
// Avoiding `default` // Avoiding `default`
if (node.test) { if (node.test) {
increaseComplexity(node); increaseComplexity(node);
@ -82,6 +93,7 @@ module.exports = function(context) {
* @private * @private
*/ */
function increaseLogicalComplexity(node) { function increaseLogicalComplexity(node) {
// Avoiding && // Avoiding &&
if (node.operator === "||") { if (node.operator === "||") {
increaseComplexity(node); increaseComplexity(node);
@ -115,8 +127,26 @@ module.exports = function(context) {
}; };
module.exports.schema = [ module.exports.schema = [
{
"oneOf": [
{ {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false
}
]
} }
]; ];

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

@ -4,6 +4,12 @@
*/ */
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -34,20 +40,25 @@ module.exports = function(context) {
function checkLastSegment(node) { function checkLastSegment(node) {
var loc, type; var loc, type;
// Skip if it expected no return value or unreachable. /*
// When unreachable, all paths are returned or thrown. * Skip if it expected no return value or unreachable.
* When unreachable, all paths are returned or thrown.
*/
if (!funcInfo.hasReturnValue || if (!funcInfo.hasReturnValue ||
funcInfo.codePath.currentSegments.every(isUnreachable) funcInfo.codePath.currentSegments.every(isUnreachable) ||
astUtils.isES5Constructor(node)
) { ) {
return; return;
} }
// Adjust a location and a message. // Adjust a location and a message.
if (node.type === "Program") { if (node.type === "Program") {
// The head of program. // The head of program.
loc = {line: 1, column: 0}; loc = {line: 1, column: 0};
type = "program"; type = "program";
} else if (node.type === "ArrowFunctionExpression") { } else if (node.type === "ArrowFunctionExpression") {
// `=>` token // `=>` token
loc = context.getSourceCode().getTokenBefore(node.body).loc.start; loc = context.getSourceCode().getTokenBefore(node.body).loc.start;
type = "function"; type = "function";
@ -55,10 +66,12 @@ module.exports = function(context) {
node.parent.type === "MethodDefinition" || node.parent.type === "MethodDefinition" ||
(node.parent.type === "Property" && node.parent.method) (node.parent.type === "Property" && node.parent.method)
) { ) {
// Method name. // Method name.
loc = node.parent.key.loc.start; loc = node.parent.key.loc.start;
type = "method"; type = "method";
} else { } else {
// Function name or `function` keyword. // Function name or `function` keyword.
loc = (node.id || node).loc.start; loc = (node.id || node).loc.start;
type = "function"; type = "function";
@ -74,6 +87,7 @@ module.exports = function(context) {
} }
return { return {
// Initializes/Disposes state of each code path. // Initializes/Disposes state of each code path.
"onCodePathStart": function(codePath) { "onCodePathStart": function(codePath) {
funcInfo = { funcInfo = {

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

@ -79,6 +79,7 @@ module.exports = function(context) {
// assigned later in the same scope. // assigned later in the same scope.
if (!variable.references.some(function(reference) { if (!variable.references.some(function(reference) {
var write = reference.writeExpr; var write = reference.writeExpr;
return ( return (
reference.from === scope && reference.from === scope &&
write && write.type === "ThisExpression" && write && write.type === "ThisExpression" &&

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

@ -36,19 +36,25 @@ function isConstructorFunction(node) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = function(context) {
// {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
// Information for each constructor. /*
// - upper: Information of the upper constructor. * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
// - hasExtends: A flag which shows whether own class has a valid `extends` * Information for each constructor.
// part. * - upper: Information of the upper constructor.
// - scope: The scope of own class. * - hasExtends: A flag which shows whether own class has a valid `extends`
// - codePath: The code path object of the constructor. * part.
* - scope: The scope of own class.
* - codePath: The code path object of the constructor.
*/
var funcInfo = null; var funcInfo = null;
// {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>} /*
// Information for each code path segment. * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
// - calledInSomePaths: A flag of be called `super()` in some code paths. * Information for each code path segment.
// - calledInEveryPaths: A flag of be called `super()` in all code paths. * - calledInSomePaths: A flag of be called `super()` in some code paths.
* - calledInEveryPaths: A flag of be called `super()` in all code paths.
* - validNodes:
*/
var segInfoMap = Object.create(null); var segInfoMap = Object.create(null);
/** /**
@ -66,10 +72,22 @@ module.exports = function(context) {
* @returns {boolean} The flag which shows `super()` is called in all paths. * @returns {boolean} The flag which shows `super()` is called in all paths.
*/ */
function isCalledInEveryPath(segment) { function isCalledInEveryPath(segment) {
/*
* If specific segment is the looped segment of the current segment,
* skip the segment.
* If not skipped, this never becomes true after a loop.
*/
if (segment.nextSegments.length === 1 &&
segment.nextSegments[0].isLoopedPrevSegment(segment)
) {
return true;
}
return segInfoMap[segment.id].calledInEveryPaths; return segInfoMap[segment.id].calledInEveryPaths;
} }
return { return {
/** /**
* Stacks a constructor information. * Stacks a constructor information.
* @param {CodePath} codePath - A code path which was started. * @param {CodePath} codePath - A code path which was started.
@ -77,21 +95,28 @@ module.exports = function(context) {
* @returns {void} * @returns {void}
*/ */
"onCodePathStart": function(codePath, node) { "onCodePathStart": function(codePath, node) {
if (!isConstructorFunction(node)) { if (isConstructorFunction(node)) {
return;
}
// Class > ClassBody > MethodDefinition > FunctionExpression // Class > ClassBody > MethodDefinition > FunctionExpression
var classNode = node.parent.parent.parent; var classNode = node.parent.parent.parent;
funcInfo = { funcInfo = {
upper: funcInfo, upper: funcInfo,
isConstructor: true,
hasExtends: Boolean( hasExtends: Boolean(
classNode.superClass && classNode.superClass &&
!astUtils.isNullOrUndefined(classNode.superClass) !astUtils.isNullOrUndefined(classNode.superClass)
), ),
scope: context.getScope(),
codePath: codePath codePath: codePath
}; };
} else {
funcInfo = {
upper: funcInfo,
isConstructor: false,
hasExtends: false,
codePath: codePath
};
}
}, },
/** /**
@ -102,13 +127,12 @@ module.exports = function(context) {
* @returns {void} * @returns {void}
*/ */
"onCodePathEnd": function(codePath, node) { "onCodePathEnd": function(codePath, node) {
if (!isConstructorFunction(node)) {
return;
}
// Skip if own class which has a valid `extends` part. // Skip if own class which has a valid `extends` part.
var hasExtends = funcInfo.hasExtends; var hasExtends = funcInfo.hasExtends;
funcInfo = funcInfo.upper; funcInfo = funcInfo.upper;
if (!hasExtends) { if (!hasExtends) {
return; return;
} }
@ -117,6 +141,7 @@ module.exports = function(context) {
var segments = codePath.returnedSegments; var segments = codePath.returnedSegments;
var calledInEveryPaths = segments.every(isCalledInEveryPath); var calledInEveryPaths = segments.every(isCalledInEveryPath);
var calledInSomePaths = segments.some(isCalledInSomePath); var calledInSomePaths = segments.some(isCalledInSomePath);
if (!calledInEveryPaths) { if (!calledInEveryPaths) {
context.report({ context.report({
message: calledInSomePaths ? message: calledInSomePaths ?
@ -133,52 +158,110 @@ module.exports = function(context) {
* @returns {void} * @returns {void}
*/ */
"onCodePathSegmentStart": function(segment) { "onCodePathSegmentStart": function(segment) {
// Skip if this is not in a constructor of a class which has a valid
// `extends` part. /*
if (!( * Skip if this is not in a constructor of a class which has a
funcInfo && * valid `extends` part.
funcInfo.hasExtends && */
funcInfo.scope === context.getScope().variableScope if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
)) {
return; return;
} }
// Initialize info. // Initialize info.
var info = segInfoMap[segment.id] = { var info = segInfoMap[segment.id] = {
calledInSomePaths: false, calledInSomePaths: false,
calledInEveryPaths: false calledInEveryPaths: false,
validNodes: []
}; };
// When there are previous segments, aggregates these. // When there are previous segments, aggregates these.
var prevSegments = segment.prevSegments; var prevSegments = segment.prevSegments;
if (prevSegments.length > 0) { if (prevSegments.length > 0) {
info.calledInSomePaths = prevSegments.some(isCalledInSomePath); info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
} }
}, },
/**
* Update information of the code path segment when a code path was
* looped.
* @param {CodePathSegment} fromSegment - The code path segment of the
* end of a loop.
* @param {CodePathSegment} toSegment - A code path segment of the head
* of a loop.
* @returns {void}
*/
"onCodePathSegmentLoop": function(fromSegment, toSegment) {
/*
* Skip if this is not in a constructor of a class which has a
* valid `extends` part.
*/
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
return;
}
// Update information inside of the loop.
var isRealLoop = toSegment.prevSegments.length >= 2;
funcInfo.codePath.traverseSegments(
{first: toSegment, last: fromSegment},
function(segment) {
var info = segInfoMap[segment.id];
// Updates flags.
var prevSegments = segment.prevSegments;
info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
// If flags become true anew, reports the valid nodes.
if (info.calledInSomePaths || isRealLoop) {
var nodes = info.validNodes;
info.validNodes = [];
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
context.report({
message: "Unexpected duplicate 'super()'.",
node: node
});
}
}
}
);
},
/** /**
* Checks for a call of `super()`. * Checks for a call of `super()`.
* @param {ASTNode} node - A CallExpression node to check. * @param {ASTNode} node - A CallExpression node to check.
* @returns {void} * @returns {void}
*/ */
"CallExpression:exit": function(node) { "CallExpression:exit": function(node) {
// Skip if the node is not `super()`. // Skip if the node is not `super()`.
if (node.callee.type !== "Super") { if (node.callee.type !== "Super") {
return; return;
} }
// Skip if this is not in a constructor. // Skip if this is not in a constructor.
if (!(funcInfo && funcInfo.scope === context.getScope().variableScope)) { if (!(funcInfo && funcInfo.isConstructor)) {
return; return;
} }
// Reports if needed. // Reports if needed.
if (funcInfo.hasExtends) { if (funcInfo.hasExtends) {
// This class has a valid `extends` part.
// Checks duplicate `super()`; /*
* This class has a valid `extends` part.
* Checks duplicate `super()`;
*/
var segments = funcInfo.codePath.currentSegments; var segments = funcInfo.codePath.currentSegments;
var duplicate = false; var duplicate = false;
for (var i = 0; i < segments.length; ++i) { for (var i = 0; i < segments.length; ++i) {
var info = segInfoMap[segments[i].id]; var info = segInfoMap[segments[i].id];
@ -191,10 +274,15 @@ module.exports = function(context) {
message: "Unexpected duplicate 'super()'.", message: "Unexpected duplicate 'super()'.",
node: node node: node
}); });
} else {
info.validNodes.push(node);
} }
} else { } else {
// This class does not have a valid `extends` part.
// Disallow `super()`. /*
* This class does not have a valid `extends` part.
* Disallow `super()`.
*/
context.report({ context.report({
message: "Unexpected 'super()'.", message: "Unexpected 'super()'.",
node: node node: node

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

@ -35,6 +35,7 @@ module.exports = function(context) {
function isCollapsedOneLiner(node) { function isCollapsedOneLiner(node) {
var before = context.getTokenBefore(node), var before = context.getTokenBefore(node),
last = context.getLastToken(node); last = context.getLastToken(node);
return before.loc.start.line === last.loc.end.line; return before.loc.start.line === last.loc.end.line;
} }
@ -197,6 +198,7 @@ module.exports = function(context) {
*/ */
function prepareIfChecks(node) { function prepareIfChecks(node) {
var preparedChecks = []; var preparedChecks = [];
do { do {
preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition")); preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition"));
if (node.alternate && node.alternate.type !== "IfStatement") { if (node.alternate && node.alternate.type !== "IfStatement") {
@ -207,8 +209,12 @@ module.exports = function(context) {
} while (node); } while (node);
if (consistent) { if (consistent) {
// If any node should have or already have braces, make sure they all have braces.
// If all nodes shouldn't have braces, make sure they don't. /*
* If any node should have or already have braces, make sure they
* all have braces.
* If all nodes shouldn't have braces, make sure they don't.
*/
var expected = preparedChecks.some(function(preparedCheck) { var expected = preparedChecks.some(function(preparedCheck) {
if (preparedCheck.expected !== null) { if (preparedCheck.expected !== null) {
return preparedCheck.expected; return preparedCheck.expected;

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

@ -34,8 +34,11 @@ module.exports = function(context) {
"SwitchStatement": function(node) { "SwitchStatement": function(node) {
if (!node.cases.length) { if (!node.cases.length) {
// skip check of empty switch because there is no easy way
// to extract comments inside it now /*
* skip check of empty switch because there is no easy way
* to extract comments inside it now
*/
return; return;
} }
@ -49,6 +52,7 @@ module.exports = function(context) {
var comments; var comments;
var lastCase = last(node.cases); var lastCase = last(node.cases);
comments = context.getComments(lastCase).trailing; comments = context.getComments(lastCase).trailing;
if (comments.length) { if (comments.length) {

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

@ -15,6 +15,8 @@ var astUtils = require("../ast-utils");
module.exports = function(context) { module.exports = 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; onObject = config === "object" || !config;

5
tools/eslint/lib/rules/dot-notation.js

@ -16,6 +16,7 @@ module.exports = function(context) {
var allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords; var allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords;
var allowPattern; var allowPattern;
if (options.allowPattern) { if (options.allowPattern) {
allowPattern = new RegExp(options.allowPattern); allowPattern = new RegExp(options.allowPattern);
} }
@ -29,7 +30,7 @@ module.exports = function(context) {
(allowKeywords || keywords.indexOf("" + node.property.value) === -1) (allowKeywords || keywords.indexOf("" + node.property.value) === -1)
) { ) {
if (!(allowPattern && allowPattern.test(node.property.value))) { if (!(allowPattern && allowPattern.test(node.property.value))) {
context.report(node, "[" + JSON.stringify(node.property.value) + "] is better written in dot notation."); context.report(node.property, "[" + JSON.stringify(node.property.value) + "] is better written in dot notation.");
} }
} }
if ( if (
@ -37,7 +38,7 @@ module.exports = function(context) {
!node.computed && !node.computed &&
keywords.indexOf("" + node.property.name) !== -1 keywords.indexOf("" + node.property.name) !== -1
) { ) {
context.report(node, "." + node.property.name + " is a syntax error."); context.report(node.property, "." + node.property.name + " is a syntax error.");
} }
} }
}; };

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

@ -17,6 +17,7 @@ module.exports = function(context) {
return { return {
"Program": function checkBadEOF(node) { "Program": function checkBadEOF(node) {
// Get the whole source code, not for node only. // Get the whole source code, not for node only.
var src = context.getSource(), var src = context.getSource(),
location = {column: 1}, location = {column: 1},
@ -24,6 +25,7 @@ module.exports = function(context) {
linebreak = linebreakStyle === "unix" ? "\n" : "\r\n"; linebreak = linebreakStyle === "unix" ? "\n" : "\r\n";
if (src[src.length - 1] !== "\n") { if (src[src.length - 1] !== "\n") {
// file is not newline-terminated // file is not newline-terminated
location.line = src.split(/\n/g).length; location.line = src.split(/\n/g).length;
context.report({ context.report({

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

@ -63,6 +63,7 @@ module.exports = function(context) {
*/ */
function getOperatorLocation(node) { function getOperatorLocation(node) {
var opToken = context.getTokenAfter(node.left); var opToken = context.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};
} }

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

@ -41,6 +41,7 @@ module.exports = function(context) {
var node = after ? leftToken : rightToken; var node = after ? leftToken : rightToken;
var type = spaceRequired ? "Missing" : "Unexpected"; var type = spaceRequired ? "Missing" : "Unexpected";
var message = type + " space " + side + " *."; var message = type + " space " + side + " *.";
context.report({ context.report({
node: node, node: node,
message: message, message: message,
@ -81,12 +82,9 @@ module.exports = function(context) {
checkSpacing("before", prevToken, starToken); checkSpacing("before", prevToken, starToken);
} }
// Only check after when followed by an identifier
nextToken = context.getTokenAfter(starToken); nextToken = context.getTokenAfter(starToken);
if (nextToken.type === "Identifier") {
checkSpacing("after", starToken, nextToken); checkSpacing("after", starToken, nextToken);
} }
}
return { return {
"FunctionDeclaration": checkFunction, "FunctionDeclaration": checkFunction,

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

@ -45,6 +45,7 @@ function findReference(scope, node) {
*/ */
function isShadowed(scope, node) { function isShadowed(scope, node) {
var reference = findReference(scope, node); var reference = findReference(scope, node);
return reference && reference.resolved && reference.resolved.defs.length > 0; return reference && reference.resolved && reference.resolved.defs.length > 0;
} }

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

@ -22,6 +22,7 @@ module.exports = function(context) {
*/ */
function isPattern(stringToCheck) { function isPattern(stringToCheck) {
var firstChar = stringToCheck[0]; var firstChar = stringToCheck[0];
return firstChar === "^"; return firstChar === "^";
} }
@ -33,6 +34,7 @@ module.exports = function(context) {
function matchesConfiguredErrorName(name) { function matchesConfiguredErrorName(name) {
if (isPattern(errorArgument)) { if (isPattern(errorArgument)) {
var regexp = new RegExp(errorArgument); var regexp = new RegExp(errorArgument);
return regexp.test(name); return regexp.test(name);
} }
return name === errorArgument; return name === errorArgument;

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

@ -27,8 +27,10 @@ module.exports = function(context) {
var SUPPORTED_EXPRESSIONS = { var SUPPORTED_EXPRESSIONS = {
"MemberExpression": properties && function(parent) { "MemberExpression": properties && function(parent) {
return !parent.computed && ( return !parent.computed && (
// regular property assignment // regular property assignment
parent.parent.left === parent || ( parent.parent.left === parent || (
// or the last identifier in an ObjectPattern destructuring // or the last identifier in an ObjectPattern destructuring
parent.parent.type === "Property" && parent.parent.value === parent && parent.parent.type === "Property" && parent.parent.value === parent &&
parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent
@ -61,6 +63,7 @@ module.exports = function(context) {
var isShort = name.length < minLength; var isShort = name.length < minLength;
var isLong = name.length > maxLength; var isLong = name.length > maxLength;
if (!(isShort || isLong) || exceptions[name]) { if (!(isShort || isLong) || exceptions[name]) {
return; // Nothing to report return; // Nothing to report
} }

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

@ -71,6 +71,7 @@ module.exports = function(context) {
// MemberExpressions get special rules // MemberExpressions get special rules
if (node.parent.type === "MemberExpression") { if (node.parent.type === "MemberExpression") {
// return early if properties is false // return early if properties is false
if (!properties) { if (!properties) {
return; return;
@ -95,6 +96,7 @@ module.exports = function(context) {
// Properties have their own rules // Properties have their own rules
} else if (node.parent.type === "Property") { } else if (node.parent.type === "Property") {
// return early if properties is false // return early if properties is false
if (!properties) { if (!properties) {
return; return;

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

@ -26,6 +26,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
*/ */
"use strict"; "use strict";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -61,8 +62,10 @@ module.exports = function(context) {
if (context.options[1]) { if (context.options[1]) {
var opts = context.options[1]; var opts = context.options[1];
options.SwitchCase = opts.SwitchCase || 0; options.SwitchCase = opts.SwitchCase || 0;
var variableDeclaratorRules = opts.VariableDeclarator; var variableDeclaratorRules = opts.VariableDeclarator;
if (typeof variableDeclaratorRules === "number") { if (typeof variableDeclaratorRules === "number") {
options.VariableDeclarator = { options.VariableDeclarator = {
var: variableDeclaratorRules, var: variableDeclaratorRules,
@ -194,21 +197,38 @@ module.exports = function(context) {
} }
/** /**
* Check indent for nodes list * Check indent for node
* @param {ASTNode[]} nodes list of node objects * @param {ASTNode} node Node to check
* @param {int} indent needed indent * @param {int} indent needed indent
* @param {boolean} [excludeCommas=false] skip comma on start of line * @param {boolean} [excludeCommas=false] skip comma on start of line
* @returns {void} * @returns {void}
*/ */
function checkNodesIndent(nodes, indent, excludeCommas) { function checkNodeIndent(node, indent, excludeCommas) {
nodes.forEach(function(node) {
var nodeIndent = getNodeIndent(node, false, excludeCommas); var nodeIndent = getNodeIndent(node, false, excludeCommas);
if ( if (
node.type !== "ArrayExpression" && node.type !== "ObjectExpression" && node.type !== "ArrayExpression" && node.type !== "ObjectExpression" &&
nodeIndent !== indent && isNodeFirstInLine(node) nodeIndent !== indent && isNodeFirstInLine(node)
) { ) {
report(node, indent, nodeIndent); report(node, indent, nodeIndent);
} }
}
/**
* Check indent for nodes list
* @param {ASTNode[]} nodes list of node objects
* @param {int} indent needed indent
* @param {boolean} [excludeCommas=false] skip comma on start of line
* @returns {void}
*/
function checkNodesIndent(nodes, indent, excludeCommas) {
nodes.forEach(function(node) {
if (node.type === "IfStatement" && node.alternate) {
var elseToken = context.getTokenBefore(node.alternate);
checkNodeIndent(elseToken, indent, excludeCommas);
}
checkNodeIndent(node, indent, excludeCommas);
}); });
} }
@ -241,6 +261,7 @@ module.exports = function(context) {
*/ */
function checkFirstNodeLineIndent(node, firstLineIndent) { function checkFirstNodeLineIndent(node, firstLineIndent) {
var startIndent = getNodeIndent(node, false); var startIndent = getNodeIndent(node, false);
if (startIndent !== firstLineIndent && isNodeFirstInLine(node)) { if (startIndent !== firstLineIndent && isNodeFirstInLine(node)) {
report( report(
node, node,
@ -303,26 +324,30 @@ module.exports = function(context) {
*/ */
function checkIndentInFunctionBlock(node) { function checkIndentInFunctionBlock(node) {
// Search first caller in chain. /*
// Ex.: * Search first caller in chain.
// * Ex.:
// Models <- Identifier *
// .User * Models <- Identifier
// .find() * .User
// .exec(function() { * .find()
// // function body * .exec(function() {
// }); * // function body
// * });
// Looks for 'Models' *
* Looks for 'Models'
*/
var calleeNode = node.parent; // FunctionExpression var calleeNode = node.parent; // FunctionExpression
var indent; var indent;
if (calleeNode.parent && if (calleeNode.parent &&
(calleeNode.parent.type === "Property" || (calleeNode.parent.type === "Property" ||
calleeNode.parent.type === "ArrayExpression")) { calleeNode.parent.type === "ArrayExpression")) {
// If function is part of array or object, comma can be put at left // If function is part of array or object, comma can be put at left
indent = getNodeIndent(calleeNode, false, false); indent = getNodeIndent(calleeNode, false, false);
} else { } else {
// If function is standalone, simple calculate indent // If function is standalone, simple calculate indent
indent = getNodeIndent(calleeNode); indent = getNodeIndent(calleeNode);
} }
@ -348,6 +373,7 @@ module.exports = function(context) {
// check if the node is inside a variable // check if the node is inside a variable
var parentVarNode = getVariableDeclaratorNode(node); var parentVarNode = getVariableDeclaratorNode(node);
if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) { if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) {
indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
} }
@ -393,6 +419,7 @@ module.exports = function(context) {
* @returns {void} * @returns {void}
*/ */
function checkIndentInArrayOrObjectBlock(node) { function checkIndentInArrayOrObjectBlock(node) {
// Skip inline // Skip inline
if (isSingleLineNode(node)) { if (isSingleLineNode(node)) {
return; return;
@ -429,9 +456,15 @@ module.exports = function(context) {
nodeIndent = getNodeIndent(effectiveParent); nodeIndent = getNodeIndent(effectiveParent);
if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) { if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) {
if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) { if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
if (parentVarNode.loc.start.line === effectiveParent.loc.start.line) { if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === effectiveParent.loc.start.line) {
nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]); nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
} else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") { } else if (
parent.type === "ObjectExpression" ||
parent.type === "ArrayExpression" ||
parent.type === "CallExpression" ||
parent.type === "ArrowFunctionExpression" ||
parent.type === "NewExpression"
) {
nodeIndent = nodeIndent + indentSize; nodeIndent = nodeIndent + indentSize;
} }
} }
@ -447,8 +480,10 @@ module.exports = function(context) {
elementsIndent = nodeIndent + indentSize; elementsIndent = nodeIndent + indentSize;
} }
// check if the node is a multiple variable declaration, if yes then make sure indentation takes into account /*
// variable indentation concept * Check if the node is a multiple variable declaration; if so, then
* make sure indentation takes that into account.
*/
if (isNodeInVarOnTop(node, parentVarNode)) { if (isNodeInVarOnTop(node, parentVarNode)) {
elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
} }
@ -457,6 +492,7 @@ module.exports = function(context) {
checkNodesIndent(elements, elementsIndent, true); checkNodesIndent(elements, elementsIndent, true);
if (elements.length > 0) { if (elements.length > 0) {
// Skip last block line check if last item in same line // Skip last block line check if last item in same line
if (elements[elements.length - 1].loc.end.line === node.loc.end.line) { if (elements[elements.length - 1].loc.end.line === node.loc.end.line) {
return; return;
@ -472,7 +508,7 @@ module.exports = function(context) {
* @returns {boolean} True if it or its body is a block statement * @returns {boolean} True if it or its body is a block statement
*/ */
function isNodeBodyBlock(node) { function isNodeBodyBlock(node) {
return node.type === "BlockStatement" || (node.body && node.body.type === "BlockStatement") || return node.type === "BlockStatement" || node.type === "ClassBody" || (node.body && node.body.type === "BlockStatement") ||
(node.consequent && node.consequent.type === "BlockStatement"); (node.consequent && node.consequent.type === "BlockStatement");
} }
@ -482,6 +518,7 @@ module.exports = function(context) {
* @returns {void} * @returns {void}
*/ */
function blockIndentationCheck(node) { function blockIndentationCheck(node) {
// Skip inline blocks // Skip inline blocks
if (isSingleLineNode(node)) { if (isSingleLineNode(node)) {
return; return;
@ -499,10 +536,12 @@ module.exports = function(context) {
var indent; var indent;
var nodesToCheck = []; var nodesToCheck = [];
// For this statements we should check indent from statement begin /*
// (not from block begin) * For this statements we should check indent from statement beginning,
* not from the beginning of the block.
*/
var statementsWithProperties = [ var statementsWithProperties = [
"IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement" "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration"
]; ];
if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) { if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) {
@ -570,6 +609,7 @@ module.exports = function(context) {
var tokenBeforeLastElement = context.getTokenBefore(lastElement); var tokenBeforeLastElement = context.getTokenBefore(lastElement);
if (tokenBeforeLastElement.value === ",") { if (tokenBeforeLastElement.value === ",") {
// Special case for comma-first syntax where the semicolon is indented // Special case for comma-first syntax where the semicolon is indented
checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement)); checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement));
} else { } else {
@ -620,6 +660,7 @@ module.exports = function(context) {
return { return {
"Program": function(node) { "Program": function(node) {
if (node.body.length > 0) { if (node.body.length > 0) {
// Root nodes should have no indent // Root nodes should have no indent
checkNodesIndent(node.body, getNodeIndent(node)); checkNodesIndent(node.body, getNodeIndent(node));
} }
@ -660,9 +701,11 @@ module.exports = function(context) {
}, },
"SwitchStatement": function(node) { "SwitchStatement": function(node) {
// Switch is not a 'BlockStatement' // Switch is not a 'BlockStatement'
var switchIndent = getNodeIndent(node); var switchIndent = getNodeIndent(node);
var caseIndent = expectedCaseIndent(node, switchIndent); var caseIndent = expectedCaseIndent(node, switchIndent);
checkNodesIndent(node.cases, caseIndent); checkNodesIndent(node.cases, caseIndent);
@ -670,11 +713,13 @@ module.exports = function(context) {
}, },
"SwitchCase": function(node) { "SwitchCase": function(node) {
// Skip inline cases // Skip inline cases
if (isSingleLineNode(node)) { if (isSingleLineNode(node)) {
return; return;
} }
var caseIndent = expectedCaseIndent(node); var caseIndent = expectedCaseIndent(node);
checkNodesIndent(node.consequent, caseIndent + indentSize); checkNodesIndent(node.consequent, caseIndent + indentSize);
} }
}; };

2
tools/eslint/lib/rules/init-declarations.js

@ -50,6 +50,7 @@ module.exports = function(context) {
var mode = context.options[0] || MODE_ALWAYS; var mode = context.options[0] || MODE_ALWAYS;
var params = context.options[1] || {}; var params = context.options[1] || {};
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public API // Public API
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -65,6 +66,7 @@ module.exports = function(context) {
id = declaration.id, id = declaration.id,
initialized = isInitialized(declaration), initialized = isInitialized(declaration),
isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent); isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent);
if (id.type !== "Identifier") { if (id.type !== "Identifier") {
continue; continue;
} }

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

@ -331,6 +331,7 @@ module.exports = function(context) {
*/ */
function verifySpacing(node, lineOptions) { function verifySpacing(node, lineOptions) {
var actual = getPropertyWhitespace(node); var actual = getPropertyWhitespace(node);
if (actual) { // Object literal getters/setters lack colons if (actual) { // Object literal getters/setters lack colons
report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode); report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode);
report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode); report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode);
@ -359,7 +360,7 @@ module.exports = function(context) {
return { return {
"ObjectExpression": function(node) { "ObjectExpression": function(node) {
if (isSingleLine(node)) { if (isSingleLine(node)) {
verifyListSpacing(node.properties); verifyListSpacing(node.properties.filter(isKeyValueProperty));
} else { } else {
verifyAlignment(node); verifyAlignment(node);
} }
@ -370,7 +371,7 @@ module.exports = function(context) {
return { return {
"Property": function(node) { "Property": function(node) {
verifySpacing(node, isSingleLine(node) ? singleLineOptions : multiLineOptions); verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
} }
}; };

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

@ -80,6 +80,7 @@ module.exports = function(context) {
pattern = pattern || PREV_TOKEN; pattern = pattern || PREV_TOKEN;
var prevToken = sourceCode.getTokenBefore(token); var prevToken = sourceCode.getTokenBefore(token);
if (prevToken && if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) && !isOpenParenOfTemplate(prevToken) &&
@ -109,6 +110,7 @@ module.exports = function(context) {
pattern = pattern || PREV_TOKEN; pattern = pattern || PREV_TOKEN;
var prevToken = sourceCode.getTokenBefore(token); var prevToken = sourceCode.getTokenBefore(token);
if (prevToken && if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) && !isOpenParenOfTemplate(prevToken) &&
@ -138,6 +140,7 @@ module.exports = function(context) {
pattern = pattern || NEXT_TOKEN; pattern = pattern || NEXT_TOKEN;
var nextToken = sourceCode.getTokenAfter(token); var nextToken = sourceCode.getTokenAfter(token);
if (nextToken && if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) && !isCloseParenOfTemplate(nextToken) &&
@ -167,6 +170,7 @@ module.exports = function(context) {
pattern = pattern || NEXT_TOKEN; pattern = pattern || NEXT_TOKEN;
var nextToken = sourceCode.getTokenAfter(token); var nextToken = sourceCode.getTokenAfter(token);
if (nextToken && if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) && !isCloseParenOfTemplate(nextToken) &&
@ -209,6 +213,7 @@ module.exports = function(context) {
if (override) { if (override) {
var thisBefore = ("before" in override) ? override.before : before; var thisBefore = ("before" in override) ? override.before : before;
var thisAfter = ("after" in override) ? override.after : after; var thisAfter = ("after" in override) ? override.after : after;
retv[key] = { retv[key] = {
before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore, before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
@ -269,6 +274,7 @@ module.exports = function(context) {
*/ */
function checkSpacingAroundFirstToken(node) { function checkSpacingAroundFirstToken(node) {
var firstToken = node && sourceCode.getFirstToken(node); var firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && firstToken.type === "Keyword") { if (firstToken && firstToken.type === "Keyword") {
checkSpacingAround(firstToken); checkSpacingAround(firstToken);
} }
@ -286,6 +292,7 @@ module.exports = function(context) {
*/ */
function checkSpacingBeforeFirstToken(node) { function checkSpacingBeforeFirstToken(node) {
var firstToken = node && sourceCode.getFirstToken(node); var firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && firstToken.type === "Keyword") { if (firstToken && firstToken.type === "Keyword") {
checkSpacingBefore(firstToken); checkSpacingBefore(firstToken);
} }
@ -301,6 +308,7 @@ module.exports = function(context) {
function checkSpacingAroundTokenBefore(node) { function checkSpacingAroundTokenBefore(node) {
if (node) { if (node) {
var token = sourceCode.getTokenBefore(node); var token = sourceCode.getTokenBefore(node);
while (token.type !== "Keyword") { while (token.type !== "Keyword") {
token = sourceCode.getTokenBefore(token); token = sourceCode.getTokenBefore(token);
} }
@ -382,6 +390,7 @@ module.exports = function(context) {
// `of` is not a keyword token. // `of` is not a keyword token.
var token = sourceCode.getTokenBefore(node.right); var token = sourceCode.getTokenBefore(node.right);
while (token.value !== "of") { while (token.value !== "of") {
token = sourceCode.getTokenBefore(token); token = sourceCode.getTokenBefore(token);
} }
@ -402,11 +411,13 @@ module.exports = function(context) {
*/ */
function checkSpacingForModuleDeclaration(node) { function checkSpacingForModuleDeclaration(node) {
var firstToken = sourceCode.getFirstToken(node); var firstToken = sourceCode.getFirstToken(node);
checkSpacingBefore(firstToken, PREV_TOKEN_M); checkSpacingBefore(firstToken, PREV_TOKEN_M);
checkSpacingAfter(firstToken, NEXT_TOKEN_M); checkSpacingAfter(firstToken, NEXT_TOKEN_M);
if (node.source) { if (node.source) {
var fromToken = sourceCode.getTokenBefore(node.source); var fromToken = sourceCode.getTokenBefore(node.source);
checkSpacingBefore(fromToken, PREV_TOKEN_M); checkSpacingBefore(fromToken, PREV_TOKEN_M);
checkSpacingAfter(fromToken, NEXT_TOKEN_M); checkSpacingAfter(fromToken, NEXT_TOKEN_M);
} }
@ -421,6 +432,7 @@ module.exports = function(context) {
*/ */
function checkSpacingForImportNamespaceSpecifier(node) { function checkSpacingForImportNamespaceSpecifier(node) {
var asToken = sourceCode.getFirstToken(node, 1); var asToken = sourceCode.getFirstToken(node, 1);
checkSpacingBefore(asToken, PREV_TOKEN_M); checkSpacingBefore(asToken, PREV_TOKEN_M);
} }
@ -440,11 +452,13 @@ module.exports = function(context) {
node, node,
node.static ? 1 : 0 node.static ? 1 : 0
); );
checkSpacingAround(token); checkSpacingAround(token);
} }
} }
return { return {
// Statements // Statements
DebuggerStatement: checkSpacingAroundFirstToken, DebuggerStatement: checkSpacingAroundFirstToken,
WithStatement: checkSpacingAroundFirstToken, WithStatement: checkSpacingAroundFirstToken,

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

@ -1,5 +1,5 @@
/** /**
* @fileoverview Rule to forbid mixing LF and LFCR line breaks. * @fileoverview Rule to enforce a single linebreak style.
* @author Erik Mueller * @author Erik Mueller
* @copyright 2015 Varun Verma. All rights reserverd. * @copyright 2015 Varun Verma. All rights reserverd.
* @copyright 2015 James Whitney. All rights reserved. * @copyright 2015 James Whitney. All rights reserved.
@ -50,6 +50,7 @@ module.exports = function(context) {
range; range;
var i = 0; var i = 0;
while ((match = pattern.exec(source)) !== null) { while ((match = pattern.exec(source)) !== null) {
i++; i++;
if (match[0] === expectedLFChars) { if (match[0] === expectedLFChars) {

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

@ -33,6 +33,7 @@ function getEmptyLineNums(lines) {
}).map(function(line) { }).map(function(line) {
return line.num; return line.num;
}); });
return emptyLines; return emptyLines;
} }
@ -43,9 +44,11 @@ function getEmptyLineNums(lines) {
*/ */
function getCommentLineNums(comments) { function getCommentLineNums(comments) {
var lines = []; var lines = [];
comments.forEach(function(token) { comments.forEach(function(token) {
var start = token.loc.start.line; var start = token.loc.start.line;
var end = token.loc.end.line; var end = token.loc.end.line;
lines.push(start, end); lines.push(start, end);
}); });
return lines; return lines;
@ -68,6 +71,7 @@ function contains(val, array) {
module.exports = function(context) { module.exports = function(context) {
var options = context.options[0] ? lodash.assign({}, context.options[0]) : {}; var options = context.options[0] ? lodash.assign({}, context.options[0]) : {};
options.beforeLineComment = options.beforeLineComment || false; options.beforeLineComment = options.beforeLineComment || false;
options.afterLineComment = options.afterLineComment || false; options.afterLineComment = options.afterLineComment || false;
options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true; options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
@ -76,6 +80,7 @@ module.exports = function(context) {
options.allowBlockEnd = options.allowBlockEnd || false; options.allowBlockEnd = options.allowBlockEnd || false;
var sourceCode = context.getSourceCode(); var sourceCode = context.getSourceCode();
/** /**
* 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.
@ -160,7 +165,7 @@ module.exports = function(context) {
* @returns {boolean} True if the comment is at block start. * @returns {boolean} True if the comment is at block start.
*/ */
function isCommentAtBlockStart(node) { function isCommentAtBlockStart(node) {
return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement"); return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement") || isCommentAtParentStart(node, "SwitchCase");
} }
/** /**
@ -169,7 +174,7 @@ module.exports = function(context) {
* @returns {boolean} True if the comment is at block end. * @returns {boolean} True if the comment is at block end.
*/ */
function isCommentAtBlockEnd(node) { function isCommentAtBlockEnd(node) {
return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement"); return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement") || isCommentAtParentEnd(node, "SwitchCase") || isCommentAtParentEnd(node, "SwitchStatement");
} }
/** /**

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

@ -17,7 +17,18 @@ module.exports = function(context) {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
var functionStack = [], var functionStack = [],
maxDepth = context.options[0] || 4; option = context.options[0],
maxDepth = 4;
if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
maxDepth = option.maximum;
}
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
maxDepth = option.max;
}
if (typeof option === "number") {
maxDepth = option;
}
/** /**
* When parsing a new function, store it in our function stack * When parsing a new function, store it in our function stack
@ -104,8 +115,26 @@ module.exports = function(context) {
}; };
module.exports.schema = [ module.exports.schema = [
{
"oneOf": [
{ {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false
}
]
} }
]; ];

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

@ -11,11 +11,15 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = function(context) {
// takes some ideas from http://tools.ietf.org/html/rfc3986#appendix-B, however:
// - They're matching an entire string that we know is a URI /*
// - We're matching part of a string where we think there *might* be a URL * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however:
// - We're only concerned about URLs, as picking out any URI would cause too many false positives * - They're matching an entire string that we know is a URI
// - We don't care about matching the entire URL, any small segment is fine * - We're matching part of a string where we think there *might* be a URL
* - We're only concerned about URLs, as picking out any URI would cause
* too many false positives
* - We don't care about matching the entire URL, any small segment is fine
*/
var URL_REGEXP = /[^:/?#]:\/\/[^?#]/; var URL_REGEXP = /[^:/?#]:\/\/[^?#]/;
/** /**
@ -28,10 +32,12 @@ module.exports = function(context) {
*/ */
function computeLineLength(line, tabWidth) { function computeLineLength(line, tabWidth) {
var extraCharacterCount = 0; var extraCharacterCount = 0;
line.replace(/\t/g, function(match, offset) { line.replace(/\t/g, function(match, offset) {
var totalOffset = offset + extraCharacterCount, var totalOffset = offset + extraCharacterCount,
previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0, previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0,
spaceCount = tabWidth - previousTabStopOffset; spaceCount = tabWidth - previousTabStopOffset;
extraCharacterCount += spaceCount - 1; // -1 for the replaced tab extraCharacterCount += spaceCount - 1; // -1 for the replaced tab
}); });
return line.length + extraCharacterCount; return line.length + extraCharacterCount;
@ -40,10 +46,12 @@ module.exports = function(context) {
// The options object must be the last option specified… // The options object must be the last option specified…
var lastOption = context.options[context.options.length - 1]; var lastOption = context.options[context.options.length - 1];
var options = typeof lastOption === "object" ? Object.create(lastOption) : {}; var options = typeof lastOption === "object" ? Object.create(lastOption) : {};
// …but max code length… // …but max code length…
if (typeof context.options[0] === "number") { if (typeof context.options[0] === "number") {
options.code = context.options[0]; options.code = context.options[0];
} }
// …and tabWidth can be optionally specified directly as integers. // …and tabWidth can be optionally specified directly as integers.
if (typeof context.options[1] === "number") { if (typeof context.options[1] === "number") {
options.tabWidth = context.options[1]; options.tabWidth = context.options[1];
@ -104,6 +112,7 @@ module.exports = function(context) {
* @returns {string} Line without comment and trailing whitepace * @returns {string} Line without comment and trailing whitepace
*/ */
function stripTrailingComment(line, lineNumber, comment) { function stripTrailingComment(line, lineNumber, comment) {
// loc.column is zero-indexed // loc.column is zero-indexed
return line.slice(0, comment.loc.start.column).replace(/\s+$/, ""); return line.slice(0, comment.loc.start.column).replace(/\s+$/, "");
} }
@ -115,26 +124,38 @@ module.exports = function(context) {
* @private * @private
*/ */
function checkProgramForMaxLength(node) { function checkProgramForMaxLength(node) {
// split (honors line-ending) // split (honors line-ending)
var lines = context.getSourceLines(), var lines = context.getSourceLines(),
// list of comments to ignore // list of comments to ignore
comments = ignoreComments || maxCommentLength ? context.getAllComments() : [], comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? context.getAllComments() : [],
// we iterate over comments in parallel with the lines // we iterate over comments in parallel with the lines
commentsIndex = 0; commentsIndex = 0;
lines.forEach(function(line, i) { lines.forEach(function(line, i) {
// i is zero-indexed, line numbers are one-indexed // i is zero-indexed, line numbers are one-indexed
var lineNumber = i + 1; var lineNumber = i + 1;
// if we're checking comment length; we need to know whether this
// line is a comment /*
* if we're checking comment length; we need to know whether this
* line is a comment
*/
var lineIsComment = false; var lineIsComment = false;
// we can short-circuit the comment checks if we're already out of comments to check /*
* We can short-circuit the comment checks if we're already out of
* comments to check.
*/
if (commentsIndex < comments.length) { if (commentsIndex < comments.length) {
// iterate over comments until we find one past the current line // iterate over comments until we find one past the current line
do { do {
var comment = comments[++commentsIndex]; var comment = comments[++commentsIndex];
} while (comment && comment.loc.start.line <= lineNumber); } while (comment && comment.loc.start.line <= lineNumber);
// and step back by one // and step back by one
comment = comments[--commentsIndex]; comment = comments[--commentsIndex];
@ -146,6 +167,7 @@ module.exports = function(context) {
} }
if (ignorePattern && ignorePattern.test(line) || if (ignorePattern && ignorePattern.test(line) ||
ignoreUrls && URL_REGEXP.test(line)) { ignoreUrls && URL_REGEXP.test(line)) {
// ignore this line // ignore this line
return; return;
} }

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

@ -15,8 +15,18 @@ module.exports = function(context) {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Constants // Constants
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
var option = context.options[0],
THRESHOLD = 10;
var THRESHOLD = context.options[0] || 10; if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
THRESHOLD = option.maximum;
}
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
THRESHOLD = option.max;
}
if (typeof option === "number") {
THRESHOLD = option;
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Helpers // Helpers
@ -39,6 +49,7 @@ module.exports = function(context) {
if (callbackStack.length > THRESHOLD) { if (callbackStack.length > THRESHOLD) {
var opts = {num: callbackStack.length, max: THRESHOLD}; var opts = {num: callbackStack.length, max: THRESHOLD};
context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts); context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts);
} }
} }
@ -68,7 +79,25 @@ module.exports = function(context) {
module.exports.schema = [ module.exports.schema = [
{ {
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0
} }
},
"additionalProperties": false
}
]
}
]; ];

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

@ -13,7 +13,18 @@
module.exports = function(context) { module.exports = function(context) {
var numParams = context.options[0] || 3; var option = context.options[0],
numParams = 3;
if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
numParams = option.maximum;
}
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
numParams = option.max;
}
if (typeof option === "number") {
numParams = option;
}
/** /**
* Checks a function to see if it has too many parameters. * Checks a function to see if it has too many parameters.
@ -40,7 +51,25 @@ module.exports = function(context) {
module.exports.schema = [ module.exports.schema = [
{ {
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0
} }
},
"additionalProperties": false
}
]
}
]; ];

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

@ -0,0 +1,106 @@
/**
* @fileoverview Specify the maximum number of statements allowed per line.
* @author Kenneth Williams
* @copyright 2016 Kenneth Williams. All rights reserved.
* @copyright 2016 Michael Ficarra. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var options = context.options[0] || {},
lastStatementLine = 0,
numberOfStatementsOnThisLine = 0,
maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Reports a node
* @param {ASTNode} node The node to report
* @returns {void}
* @private
*/
function report(node) {
context.report(
node,
"This line has too many statements. Maximum allowed is {{max}}.",
{ max: maxStatementsPerLine });
}
/**
* Enforce a maximum number of statements per line
* @param {ASTNode} nodes Array of nodes to evaluate
* @returns {void}
* @private
*/
function enforceMaxStatementsPerLine(nodes) {
if (nodes.length < 1) {
return;
}
for (var i = 0, l = nodes.length; i < l; ++i) {
var currentStatement = nodes[i];
if (currentStatement.loc.start.line === lastStatementLine) {
++numberOfStatementsOnThisLine;
} else {
numberOfStatementsOnThisLine = 1;
lastStatementLine = currentStatement.loc.end.line;
}
if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
report(currentStatement);
}
}
}
/**
* Check each line in the body of a node
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkLinesInBody(node) {
enforceMaxStatementsPerLine(node.body);
}
/**
* Check each line in the consequent of a switch case
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkLinesInConsequent(node) {
enforceMaxStatementsPerLine(node.consequent);
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
"Program": checkLinesInBody,
"BlockStatement": checkLinesInBody,
"SwitchCase": checkLinesInConsequent
};
};
module.exports.schema = [
{
"type": "object",
"properties": {
"max": {
"type": "integer"
}
},
"additionalProperties": false
}
];

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

@ -17,10 +17,21 @@ module.exports = function(context) {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
var functionStack = [], var functionStack = [],
maxStatements = context.options[0] || 10, option = context.options[0],
maxStatements = 10,
ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false, ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false,
topLevelFunctions = []; topLevelFunctions = [];
if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
maxStatements = option.maximum;
}
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
maxStatements = option.max;
}
if (typeof option === "number") {
maxStatements = option;
}
/** /**
* Reports a node if it has too many statements * Reports a node if it has too many statements
* @param {ASTNode} node node to evaluate * @param {ASTNode} node node to evaluate
@ -55,6 +66,7 @@ module.exports = function(context) {
*/ */
function endFunction(node) { function endFunction(node) {
var count = functionStack.pop(); var count = functionStack.pop();
if (ignoreTopLevelFunctions && functionStack.length === 0) { if (ignoreTopLevelFunctions && functionStack.length === 0) {
topLevelFunctions.push({ node: node, count: count}); topLevelFunctions.push({ node: node, count: count});
} else { } else {
@ -95,6 +107,7 @@ module.exports = function(context) {
topLevelFunctions.forEach(function(element) { topLevelFunctions.forEach(function(element) {
var count = element.count; var count = element.count;
var node = element.node; var node = element.node;
reportIfTooManyStatements(node, count, maxStatements); reportIfTooManyStatements(node, count, maxStatements);
}); });
} }
@ -104,8 +117,26 @@ module.exports = function(context) {
module.exports.schema = [ module.exports.schema = [
{ {
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0
}
},
"additionalProperties": false
}
]
}, },
{ {
"type": "object", "type": "object",

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

@ -38,6 +38,7 @@ var CAPS_ALLOWED = [
* @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback` * @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback`
*/ */
function checkArray(obj, key, fallback) { function checkArray(obj, key, fallback) {
/* istanbul ignore if */ /* istanbul ignore if */
if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) { if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) {
throw new TypeError(key + ", if provided, must be an Array"); throw new TypeError(key + ", if provided, must be an Array");
@ -78,6 +79,7 @@ function calculateCapIsNewExceptions(config) {
module.exports = function(context) { module.exports = function(context) {
var config = context.options[0] ? lodash.assign({}, context.options[0]) : {}; var config = context.options[0] ? lodash.assign({}, context.options[0]) : {};
config.newIsCap = config.newIsCap !== false; config.newIsCap = config.newIsCap !== false;
config.capIsNew = config.capIsNew !== false; config.capIsNew = config.capIsNew !== false;
var skipProperties = config.properties === false; var skipProperties = config.properties === false;
@ -129,6 +131,7 @@ module.exports = function(context) {
var firstCharUpper = firstChar.toUpperCase(); var firstCharUpper = firstChar.toUpperCase();
if (firstCharLower === firstCharUpper) { if (firstCharLower === firstCharUpper) {
// char has no uppercase variant, so it's non-alphabetic // char has no uppercase variant, so it's non-alphabetic
return "non-alpha"; return "non-alpha";
} else if (firstChar === firstCharLower) { } else if (firstChar === firstCharLower) {
@ -151,6 +154,7 @@ module.exports = function(context) {
} }
if (calleeName === "UTC" && node.callee.type === "MemberExpression") { if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
// allow if callee is Date.UTC // allow if callee is Date.UTC
return node.callee.object.type === "Identifier" && return node.callee.object.type === "Identifier" &&
node.callee.object.name === "Date"; node.callee.object.name === "Date";
@ -183,9 +187,11 @@ module.exports = function(context) {
listeners.NewExpression = function(node) { listeners.NewExpression = function(node) {
var constructorName = extractNameFromExpression(node); var constructorName = extractNameFromExpression(node);
if (constructorName) { if (constructorName) {
var capitalization = getCap(constructorName); var capitalization = getCap(constructorName);
var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName); var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName);
if (!isAllowed) { if (!isAllowed) {
report(node, "A constructor name should not start with a lowercase letter."); report(node, "A constructor name should not start with a lowercase letter.");
} }
@ -197,9 +203,11 @@ module.exports = function(context) {
listeners.CallExpression = function(node) { listeners.CallExpression = function(node) {
var calleeName = extractNameFromExpression(node); var calleeName = extractNameFromExpression(node);
if (calleeName) { if (calleeName) {
var capitalization = getCap(calleeName); var capitalization = getCap(calleeName);
var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName); var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName);
if (!isAllowed) { if (!isAllowed) {
report(node, "A function with a name starting with an uppercase letter should only be used as a constructor."); report(node, "A function with a name starting with an uppercase letter should only be used as a constructor.");
} }

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

@ -18,6 +18,7 @@ module.exports = function(context) {
var prenticesTokens = tokens.filter(function(token) { var prenticesTokens = tokens.filter(function(token) {
return token.value === "(" || token.value === ")"; return token.value === "(" || token.value === ")";
}); });
if (prenticesTokens.length < 2) { if (prenticesTokens.length < 2) {
context.report(node, "Missing '()' invoking a constructor"); context.report(node, "Missing '()' invoking a constructor");
} }

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

@ -72,6 +72,7 @@ module.exports = function(context) {
*/ */
function isLastNode(node) { function isLastNode(node) {
var token = sourceCode.getTokenAfter(node); var token = sourceCode.getTokenAfter(node);
return !token || (token.type === "Punctuator" && token.value === "}"); return !token || (token.type === "Punctuator" && token.value === "}");
} }
@ -83,10 +84,12 @@ module.exports = function(context) {
*/ */
function hasBlankLineAfterComment(token, commentStartLine) { function hasBlankLineAfterComment(token, commentStartLine) {
var commentEnd = commentEndLine[commentStartLine]; var commentEnd = commentEndLine[commentStartLine];
// If there's another comment, repeat check for blank line // If there's another comment, repeat check for blank line
if (commentEndLine[commentEnd + 1]) { if (commentEndLine[commentEnd + 1]) {
return hasBlankLineAfterComment(token, commentEnd + 1); return hasBlankLineAfterComment(token, commentEnd + 1);
} }
return (token.loc.start.line > commentEndLine[commentStartLine] + 1); return (token.loc.start.line > commentEndLine[commentStartLine] + 1);
} }

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

@ -0,0 +1,143 @@
/**
* @fileoverview Rule to require newlines before `return` statement
* @author Kai Cataldo
* @copyright 2016 Kai Cataldo. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Tests whether node is preceded by supplied tokens
* @param {ASTNode} node - node to check
* @param {array} testTokens - array of tokens to test against
* @returns {boolean} Whether or not the node is preceded by one of the supplied tokens
* @private
*/
function isPrecededByTokens(node, testTokens) {
var tokenBefore = sourceCode.getTokenBefore(node);
return testTokens.some(function(token) {
return tokenBefore.value === token;
});
}
/**
* Checks whether node is the first node after statement or in block
* @param {ASTNode} node - node to check
* @returns {boolean} Whether or not the node is the first node after statement or in block
* @private
*/
function isFirstNode(node) {
var parentType = node.parent.type;
if (node.parent.body) {
return Array.isArray(node.parent.body)
? node.parent.body[0] === node
: node.parent.body === node;
}
if (parentType === "IfStatement") {
return isPrecededByTokens(node, ["else", ")"]);
} else if (parentType === "DoWhileStatement") {
return isPrecededByTokens(node, ["do"]);
} else if (parentType === "SwitchCase") {
return isPrecededByTokens(node, [":"]);
} else {
return isPrecededByTokens(node, [")"]);
}
}
/**
* Returns the number of lines of comments that precede the node
* @param {ASTNode} node - node to check for overlapping comments
* @param {token} tokenBefore - previous token to check for overlapping comments
* @returns {number} Number of lines of comments that precede the node
* @private
*/
function calcCommentLines(node, tokenBefore) {
var comments = sourceCode.getComments(node).leading;
var numLinesComments = 0;
if (!comments.length) {
return numLinesComments;
}
comments.forEach(function(comment) {
numLinesComments++;
if (comment.type === "Block") {
numLinesComments += comment.loc.end.line - comment.loc.start.line;
}
// avoid counting lines with inline comments twice
if (comment.loc.start.line === tokenBefore.loc.end.line) {
numLinesComments--;
}
if (comment.loc.end.line === node.loc.start.line) {
numLinesComments--;
}
});
return numLinesComments;
}
/**
* Checks whether node is preceded by a newline
* @param {ASTNode} node - node to check
* @returns {boolean} Whether or not the node is preceded by a newline
* @private
*/
function hasNewlineBefore(node) {
var tokenBefore = sourceCode.getTokenBefore(node);
var lineNumTokenBefore = tokenBefore.loc.end.line;
var lineNumNode = node.loc.start.line;
var commentLines = calcCommentLines(node, tokenBefore);
return (lineNumNode - lineNumTokenBefore - commentLines) > 1;
}
/**
* Reports expected/unexpected newline before return statement
* @param {ASTNode} node - the node to report in the event of an error
* @param {boolean} isExpected - whether the newline is expected or not
* @returns {void}
* @private
*/
function reportError(node, isExpected) {
var expected = isExpected ? "Expected" : "Unexpected";
context.report({
node: node,
message: expected + " newline before return statement."
});
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
ReturnStatement: function(node) {
if (isFirstNode(node) && hasNewlineBefore(node)) {
reportError(node, false);
} else if (!isFirstNode(node) && !hasNewlineBefore(node)) {
reportError(node, true);
}
}
};
};
module.exports.schema = [];

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

@ -1,7 +1,9 @@
/** /**
* @fileoverview Rule to ensure newline per method call when chaining calls * @fileoverview Rule to ensure newline per method call when chaining calls
* @author Rajendra Patil * @author Rajendra Patil
* @author Burak Yigit Kaya
* @copyright 2016 Rajendra Patil. All rights reserved. * @copyright 2016 Rajendra Patil. All rights reserved.
* @copyright 2016 Burak Yigit Kaya. All rights reserved.
*/ */
"use strict"; "use strict";
@ -13,90 +15,31 @@
module.exports = function(context) { module.exports = function(context) {
var options = context.options[0] || {}, var options = context.options[0] || {},
codeStateMap = {},
ignoreChainWithDepth = options.ignoreChainWithDepth || 2; ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
/** return {
* Check and Capture State if the chained calls/members "CallExpression:exit": function(node) {
* @param {ASTNode} node The node to check if (!node.callee || node.callee.type !== "MemberExpression") {
* @param {object} codeState The state of the code current code to be filled return;
* @returns {void}
*/
function checkAndCaptureStateRecursively(node, codeState) {
var valid = false,
objectLineNumber,
propertyLineNumber;
if (node.callee) {
node = node.callee;
codeState.hasFunctionCall = true;
} }
if (node.object) { var callee = node.callee;
codeState.depth++; var parent = callee.object;
var depth = 1;
objectLineNumber = node.object.loc.end.line; while (parent && parent.callee) {
propertyLineNumber = node.property.loc.end.line; depth += 1;
valid = node.computed || propertyLineNumber > objectLineNumber; parent = parent.callee.object;
if (!valid) {
codeState.reports.push({
node: node,
text: "Expected line break after `{{code}}`.",
depth: codeState.depth
});
}
// Recurse
checkAndCaptureStateRecursively(node.object, codeState);
} }
} if (depth > ignoreChainWithDepth && callee.property.loc.start.line === callee.object.loc.end.line) {
/** context.report(
* Verify and report the captured state report callee.property,
* @param {object} codeState contains the captured state with `hasFunctionCall, reports and depth` callee.property.loc.start,
* @returns {void} "Expected line break after `" + context.getSource(callee.object).replace(/\r\n|\r|\n/g, "\\n") + "`."
*/ );
function reportState(codeState) {
var report;
if (codeState.hasFunctionCall && codeState.depth > ignoreChainWithDepth && codeState.reports) {
while (codeState.reports.length) {
report = codeState.reports.shift();
context.report(report.node, report.text, {
code: context.getSourceCode().getText(report.node.object).replace(/\r\n|\r|\n/g, "\\n") // Not to print newlines in error report
});
}
} }
} }
/**
* Initialize the node state object with default values.
* @returns {void}
*/
function initializeState() {
return {
visited: false,
hasFunctionCall: false,
depth: 1,
reports: []
};
}
/**
* Process the said node and recurse internally
* @param {ASTNode} node The node to check
* @returns {void}
*/
function processNode(node) {
var stateKey = [node.loc.start.line, node.loc.start.column].join("@"),
codeState = codeStateMap[stateKey] = (codeStateMap[stateKey] || initializeState());
if (!codeState.visited) {
codeState.visited = true;
checkAndCaptureStateRecursively(node, codeState);
}
reportState(codeState);
}
return {
"CallExpression": processNode,
"MemberExpression": processNode
}; };
}; };

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

@ -73,6 +73,7 @@ function findReference(scope, node) {
*/ */
function isShadowed(scope, globalScope, node) { function isShadowed(scope, globalScope, node) {
var reference = findReference(scope, node); var reference = findReference(scope, node);
return reference && reference.resolved && reference.resolved.defs.length > 0; return reference && reference.resolved && reference.resolved.defs.length > 0;
} }

1
tools/eslint/lib/rules/no-case-declarations.js

@ -32,6 +32,7 @@ module.exports = function(context) {
"SwitchCase": function(node) { "SwitchCase": function(node) {
for (var i = 0; i < node.consequent.length; i++) { for (var i = 0; i < node.consequent.length; i++) {
var statement = node.consequent[i]; var statement = node.consequent[i];
if (isLexicalDeclaration(statement)) { if (isLexicalDeclaration(statement)) {
context.report({ context.report({
node: node, node: node,

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

@ -87,6 +87,7 @@ module.exports = function(context) {
!isParenthesisedTwice(node.test) !isParenthesisedTwice(node.test)
) )
) { ) {
// must match JSHint's error message // must match JSHint's error message
context.report({ context.report({
node: node, node: node,

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

@ -28,6 +28,8 @@
"use strict"; "use strict";
var astUtils = require("../ast-utils.js");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -38,7 +40,7 @@
* @returns {boolean} `true` if the node is a conditional expression. * @returns {boolean} `true` if the node is a conditional expression.
*/ */
function isConditional(node) { function isConditional(node) {
return node.body && node.body.type === "ConditionalExpression"; return node && node.type === "ConditionalExpression";
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -46,13 +48,17 @@ function isConditional(node) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = function(context) {
var config = context.options[0] || {};
/** /**
* Reports if an arrow function contains an ambiguous conditional. * Reports if an arrow function contains an ambiguous conditional.
* @param {ASTNode} node - A node to check and report. * @param {ASTNode} node - A node to check and report.
* @returns {void} * @returns {void}
*/ */
function checkArrowFunc(node) { function checkArrowFunc(node) {
if (isConditional(node)) { var body = node.body;
if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(context, body))) {
context.report(node, "Arrow function used ambiguously with a conditional expression."); context.report(node, "Arrow function used ambiguously with a conditional expression.");
} }
} }
@ -62,4 +68,10 @@ module.exports = function(context) {
}; };
}; };
module.exports.schema = []; module.exports.schema = [{
type: "object",
properties: {
allowParens: {type: "boolean"}
},
additionalProperties: false
}];

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

@ -30,15 +30,20 @@ module.exports = function(context) {
case "ObjectExpression": case "ObjectExpression":
case "ArrayExpression": case "ArrayExpression":
return true; return true;
case "UnaryExpression": case "UnaryExpression":
return isConstant(node.argument); return isConstant(node.argument);
case "BinaryExpression": case "BinaryExpression":
case "LogicalExpression": case "LogicalExpression":
return isConstant(node.left) && isConstant(node.right) && node.operator !== "in"; return isConstant(node.left) && isConstant(node.right) && node.operator !== "in";
case "AssignmentExpression": case "AssignmentExpression":
return (node.operator === "=") && isConstant(node.right); return (node.operator === "=") && isConstant(node.right);
case "SequenceExpression": case "SequenceExpression":
return isConstant(node.expressions[node.expressions.length - 1]); return isConstant(node.expressions[node.expressions.length - 1]);
// no default // no default
} }
return false; return false;

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

@ -23,9 +23,11 @@ module.exports = function(context) {
} else if (typeof node.value === "string") { } else if (typeof node.value === "string") {
var parent = context.getAncestors().pop(); var parent = context.getAncestors().pop();
if ((parent.type === "NewExpression" || parent.type === "CallExpression") && if ((parent.type === "NewExpression" || parent.type === "CallExpression") &&
parent.callee.type === "Identifier" && parent.callee.name === "RegExp" parent.callee.type === "Identifier" && parent.callee.name === "RegExp"
) { ) {
// there could be an invalid regular expression string // there could be an invalid regular expression string
try { try {
return new RegExp(node.value); return new RegExp(node.value);

2
tools/eslint/lib/rules/no-dupe-args.js

@ -42,6 +42,7 @@ module.exports = function(context) {
// TODO(nagashima): Remove this duplication check after https://github.com/estools/escope/pull/79 // TODO(nagashima): Remove this duplication check after https://github.com/estools/escope/pull/79
var key = "$" + variable.name; // to avoid __proto__. var key = "$" + variable.name; // to avoid __proto__.
if (!isParameter(variable.defs[0]) || keyMap[key]) { if (!isParameter(variable.defs[0]) || keyMap[key]) {
continue; continue;
} }
@ -49,6 +50,7 @@ module.exports = function(context) {
// Checks and reports duplications. // Checks and reports duplications.
var defs = variable.defs.filter(isParameter); var defs = variable.defs.filter(isParameter);
if (defs.length >= 2) { if (defs.length >= 2) {
context.report({ context.report({
node: node, node: node,

2
tools/eslint/lib/rules/no-dupe-class-members.js

@ -53,6 +53,7 @@ module.exports = function(context) {
} }
return { return {
// Initializes the stack of state of member declarations. // Initializes the stack of state of member declarations.
"Program": function() { "Program": function() {
stack = []; stack = [];
@ -77,6 +78,7 @@ module.exports = function(context) {
var name = getName(node.key); var name = getName(node.key);
var state = getState(name, node.static); var state = getState(name, node.static);
var isDuplicate = false; var isDuplicate = false;
if (node.kind === "get") { if (node.kind === "get") {
isDuplicate = (state.init || state.get); isDuplicate = (state.init || state.get);
state.get = true; state.get = true;

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

@ -20,6 +20,7 @@ module.exports = function(context) {
node.cases.forEach(function(switchCase) { node.cases.forEach(function(switchCase) {
var key = context.getSource(switchCase.test); var key = context.getSource(switchCase.test);
if (mapping[key]) { if (mapping[key]) {
context.report(switchCase, "Duplicate case label."); context.report(switchCase, "Duplicate case label.");
} else { } else {

126
tools/eslint/lib/rules/no-duplicate-imports.js

@ -0,0 +1,126 @@
/**
* @fileoverview Restrict usage of duplicate imports.
* @author Simen Bekkhus
* @copyright 2016 Simen Bekkhus. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/**
* Returns the name of the module imported or re-exported.
*
* @param {ASTNode} node - A node to get.
* @returns {string} the name of the module, or empty string if no name.
*/
function getValue(node) {
if (node && node.source && node.source.value) {
return node.source.value.trim();
}
return "";
}
/**
* Checks if the name of the import or export exists in the given array, and reports if so.
*
* @param {RuleContext} context - The ESLint rule context object.
* @param {ASTNode} node - A node to get.
* @param {string} value - The name of the imported or exported module.
* @param {string[]} array - The array containing other imports or exports in the file.
* @param {string} message - A message to be reported after the name of the module
*
* @returns {void} No return value
*/
function checkAndReport(context, node, value, array, message) {
if (array.indexOf(value) !== -1) {
context.report({
node: node,
message: "'{{module}}' " + message,
data: {module: value}
});
}
}
/**
* @callback nodeCallback
* @param {ASTNode} node - A node to handle.
*/
/**
* Returns a function handling the imports of a given file
*
* @param {RuleContext} context - The ESLint rule context object.
* @param {boolean} includeExports - Whether or not to check for exports in addition to imports.
* @param {string[]} importsInFile - The array containing other imports in the file.
* @param {string[]} exportsInFile - The array containing other exports in the file.
*
* @returns {nodeCallback} A function passed to ESLint to handle the statement.
*/
function handleImports(context, includeExports, importsInFile, exportsInFile) {
return function(node) {
var value = getValue(node);
if (value) {
checkAndReport(context, node, value, importsInFile, "import is duplicated.");
if (includeExports) {
checkAndReport(context, node, value, exportsInFile, "import is duplicated as export.");
}
importsInFile.push(value);
}
};
}
/**
* Returns a function handling the exports of a given file
*
* @param {RuleContext} context - The ESLint rule context object.
* @param {string[]} importsInFile - The array containing other imports in the file.
* @param {string[]} exportsInFile - The array containing other exports in the file.
*
* @returns {nodeCallback} A function passed to ESLint to handle the statement.
*/
function handleExports(context, importsInFile, exportsInFile) {
return function(node) {
var value = getValue(node);
if (value) {
checkAndReport(context, node, value, exportsInFile, "export is duplicated.");
checkAndReport(context, node, value, importsInFile, "export is duplicated as import.");
exportsInFile.push(value);
}
};
}
module.exports = function(context) {
var includeExports = (context.options[0] || {}).includeExports,
importsInFile = [],
exportsInFile = [];
var handlers = {
"ImportDeclaration": handleImports(context, includeExports, importsInFile, exportsInFile)
};
if (includeExports) {
handlers.ExportNamedDeclaration = handleExports(context, importsInFile, exportsInFile);
handlers.ExportAllDeclaration = handleExports(context, importsInFile, exportsInFile);
}
return handlers;
};
module.exports.schema = [{
"type": "object",
"properties": {
"includeExports": {
"type": "boolean"
}
},
"additionalProperties": false
}];

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

@ -97,12 +97,16 @@ module.exports = function(context) {
* @returns {boolean} `true` if it returns on every codepath. * @returns {boolean} `true` if it returns on every codepath.
*/ */
function alwaysReturns(node) { function alwaysReturns(node) {
// If we have a BlockStatement, check each consequent body node.
if (node.type === "BlockStatement") { if (node.type === "BlockStatement") {
// If we have a BlockStatement, check each consequent body node.
return node.body.some(checkForReturnOrIf); return node.body.some(checkForReturnOrIf);
// If not a block statement, make sure the consequent isn't a ReturnStatement
// or an IfStatement with returns on both paths
} else { } 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); return checkForReturnOrIf(node);
} }
} }

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

@ -33,6 +33,7 @@ module.exports = function(context) {
"Literal": function(node) { "Literal": function(node) {
var token = context.getFirstToken(node); var token = context.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.");
} }

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

@ -81,6 +81,7 @@ function getKind(node) {
// Detects prefix. // Detects prefix.
var prefix = ""; var prefix = "";
if (node.generator) { if (node.generator) {
prefix = "generator"; prefix = "generator";
} else if (node.async) { } else if (node.async) {

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

@ -15,6 +15,7 @@ var FUNCTION_TYPE = /^(?:ArrowFunctionExpression|Function(?:Declaration|Expressi
module.exports = function(context) { module.exports = function(context) {
return { return {
"BlockStatement": function(node) { "BlockStatement": function(node) {
// if the body is not empty, we can just return immediately // if the body is not empty, we can just return immediately
if (node.body.length !== 0) { if (node.body.length !== 0) {
return; return;

19
tools/eslint/lib/rules/no-eval.js

@ -157,11 +157,13 @@ module.exports = function(context) {
for (var i = 0; i < candidatesOfGlobalObject.length; ++i) { for (var i = 0; i < candidatesOfGlobalObject.length; ++i) {
var name = candidatesOfGlobalObject[i]; var name = candidatesOfGlobalObject[i];
var variable = astUtils.getVariableByName(globalScope, name); var variable = astUtils.getVariableByName(globalScope, name);
if (!variable) { if (!variable) {
continue; continue;
} }
var references = variable.references; var references = variable.references;
for (var j = 0; j < references.length; ++j) { for (var j = 0; j < references.length; ++j) {
var identifier = references[j].identifier; var identifier = references[j].identifier;
var node = identifier.parent; var node = identifier.parent;
@ -187,16 +189,19 @@ module.exports = function(context) {
*/ */
function reportAccessingEval(globalScope) { function reportAccessingEval(globalScope) {
var variable = astUtils.getVariableByName(globalScope, "eval"); var variable = astUtils.getVariableByName(globalScope, "eval");
if (!variable) { if (!variable) {
return; return;
} }
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];
var id = reference.identifier; var id = reference.identifier;
if (id.name === "eval" && !astUtils.isCallee(id)) { if (id.name === "eval" && !astUtils.isCallee(id)) {
// Is accessing to eval (excludes direct calls to eval) // Is accessing to eval (excludes direct calls to eval)
report(id); report(id);
} }
@ -204,11 +209,12 @@ module.exports = function(context) {
} }
if (allowIndirect) { if (allowIndirect) {
// Checks only direct calls to eval.
// It's simple! // Checks only direct calls to eval. It's simple!
return { return {
"CallExpression:exit": function(node) { "CallExpression:exit": function(node) {
var callee = node.callee; var callee = node.callee;
if (isIdentifier(callee, "eval")) { if (isIdentifier(callee, "eval")) {
report(callee); report(callee);
} }
@ -219,6 +225,7 @@ module.exports = function(context) {
return { return {
"CallExpression:exit": function(node) { "CallExpression:exit": function(node) {
var callee = node.callee; var callee = node.callee;
if (isIdentifier(callee, "eval")) { if (isIdentifier(callee, "eval")) {
report(callee); report(callee);
} }
@ -261,8 +268,10 @@ module.exports = function(context) {
return; return;
} }
// `this.eval` is found. /*
// Checks whether or not the value of `this` is the global object. * `this.eval` is found.
* Checks whether or not the value of `this` is the global object.
*/
if (!funcInfo.initialized) { if (!funcInfo.initialized) {
funcInfo.initialized = true; funcInfo.initialized = true;
funcInfo.defaultThis = astUtils.isDefaultThisBinding( funcInfo.defaultThis = astUtils.isDefaultThisBinding(
@ -270,7 +279,9 @@ module.exports = function(context) {
sourceCode sourceCode
); );
} }
if (!funcInfo.strict && funcInfo.defaultThis) { if (!funcInfo.strict && funcInfo.defaultThis) {
// `this.eval` is possible built-in `eval`. // `this.eval` is possible built-in `eval`.
report(node.parent); report(node.parent);
} }

3
tools/eslint/lib/rules/no-extend-native.js

@ -33,7 +33,8 @@ module.exports = function(context) {
// handle the Array.prototype.extra style case // handle the Array.prototype.extra style case
"AssignmentExpression": function(node) { "AssignmentExpression": function(node) {
var lhs = node.left, affectsProto; var lhs = node.left,
affectsProto;
if (lhs.type !== "MemberExpression" || lhs.object.type !== "MemberExpression") { if (lhs.type !== "MemberExpression" || lhs.object.type !== "MemberExpression") {
return; return;

2
tools/eslint/lib/rules/no-extra-bind.js

@ -45,6 +45,7 @@ module.exports = function(context) {
if (node.property.expressions.length === 0) { if (node.property.expressions.length === 0) {
return node.property.quasis[0].value.cooked; return node.property.quasis[0].value.cooked;
} }
// fallthrough // fallthrough
default: default:
return false; return false;
@ -66,6 +67,7 @@ module.exports = function(context) {
function isCalleeOfBindMethod(node) { function isCalleeOfBindMethod(node) {
var parent = node.parent; var parent = node.parent;
var grandparent = parent.parent; var grandparent = parent.parent;
return ( return (
grandparent && grandparent &&
grandparent.type === "CallExpression" && grandparent.type === "CallExpression" &&

2
tools/eslint/lib/rules/no-extra-boolean-cast.js

@ -31,6 +31,7 @@ module.exports = function(context) {
return ( return (
(BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 && (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 &&
node === parent.test) || node === parent.test) ||
// !<bool> // !<bool>
(parent.type === "UnaryExpression" && (parent.type === "UnaryExpression" &&
parent.operator === "!") parent.operator === "!")
@ -52,6 +53,7 @@ module.exports = function(context) {
} }
if (isInBooleanContext(parent, grandparent) || if (isInBooleanContext(parent, grandparent) ||
// Boolean(<bool>) and new Boolean(<bool>) // Boolean(<bool>) and new Boolean(<bool>)
((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") && ((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") &&
grandparent.callee.type === "Identifier" && grandparent.callee.type === "Identifier" &&

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

@ -10,8 +10,10 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { var astUtils = require("../ast-utils.js");
module.exports = function(context) {
var isParenthesised = astUtils.isParenthesised.bind(astUtils, context);
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 sourceCode = context.getSourceCode(); var sourceCode = context.getSourceCode();
@ -26,21 +28,6 @@ module.exports = function(context) {
return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression"; return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
} }
/**
* Determines if a node is surrounded by parentheses.
* @param {ASTNode} node - The node to be checked.
* @returns {boolean} True if the node is parenthesised.
* @private
*/
function isParenthesised(node) {
var previousToken = context.getTokenBefore(node),
nextToken = context.getTokenAfter(node);
return previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
}
/** /**
* Determines if a node is surrounded by parentheses twice. * Determines if a node is surrounded by parentheses twice.
* @param {ASTNode} node - The node to be checked. * @param {ASTNode} node - The node to be checked.
@ -110,6 +97,7 @@ module.exports = function(context) {
*/ */
function isHeadOfExpressionStatement(node) { function isHeadOfExpressionStatement(node) {
var parent = node.parent; var parent = node.parent;
while (parent) { while (parent) {
switch (parent.type) { switch (parent.type) {
case "SequenceExpression": case "SequenceExpression":
@ -191,11 +179,14 @@ module.exports = function(context) {
return 4; return 4;
case "&&": case "&&":
return 5; return 5;
// no default // no default
} }
/* falls through */ /* falls through */
case "BinaryExpression": case "BinaryExpression":
switch (node.operator) { switch (node.operator) {
case "|": case "|":
return 6; return 6;
@ -226,21 +217,29 @@ module.exports = function(context) {
case "/": case "/":
case "%": case "%":
return 13; return 13;
// no default // no default
} }
/* falls through */ /* falls through */
case "UnaryExpression": case "UnaryExpression":
return 14; return 14;
case "UpdateExpression": case "UpdateExpression":
return 15; return 15;
case "CallExpression": case "CallExpression":
// IIFE is allowed to have parens in any position (#655) // IIFE is allowed to have parens in any position (#655)
if (node.callee.type === "FunctionExpression") { if (node.callee.type === "FunctionExpression") {
return -1; return -1;
} }
return 16; return 16;
case "NewExpression": case "NewExpression":
return 17; return 17;
// no default // no default
} }
return 18; return 18;
@ -254,6 +253,7 @@ module.exports = function(context) {
*/ */
function report(node) { function report(node) {
var previousToken = context.getTokenBefore(node); var previousToken = context.getTokenBefore(node);
context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression."); context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression.");
} }
@ -279,6 +279,7 @@ module.exports = function(context) {
if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !( if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !(
node.type === "CallExpression" && node.type === "CallExpression" &&
node.callee.type === "FunctionExpression" && node.callee.type === "FunctionExpression" &&
// One set of parentheses are allowed for a function expression // One set of parentheses are allowed for a function expression
!hasDoubleExcessParens(node.callee) !hasDoubleExcessParens(node.callee)
)) { )) {
@ -305,6 +306,7 @@ module.exports = function(context) {
*/ */
function dryBinaryLogical(node) { function dryBinaryLogical(node) {
var prec = precedence(node); var prec = precedence(node);
if (hasExcessParens(node.left) && precedence(node.left) >= prec) { if (hasExcessParens(node.left) && precedence(node.left) >= prec) {
report(node.left); report(node.left);
} }
@ -321,6 +323,7 @@ module.exports = function(context) {
} }
}); });
}, },
"ArrowFunctionExpression": function(node) { "ArrowFunctionExpression": function(node) {
if (node.body.type !== "BlockStatement") { if (node.body.type !== "BlockStatement") {
if (node.body.type !== "ObjectExpression" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) { if (node.body.type !== "ObjectExpression" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) {
@ -335,13 +338,16 @@ module.exports = function(context) {
} }
} }
}, },
"AssignmentExpression": function(node) { "AssignmentExpression": function(node) {
if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) { if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) {
report(node.right); report(node.right);
} }
}, },
"BinaryExpression": dryBinaryLogical, "BinaryExpression": dryBinaryLogical,
"CallExpression": dryCallNew, "CallExpression": dryCallNew,
"ConditionalExpression": function(node) { "ConditionalExpression": function(node) {
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);
@ -353,13 +359,16 @@ module.exports = function(context) {
report(node.alternate); report(node.alternate);
} }
}, },
"DoWhileStatement": function(node) { "DoWhileStatement": function(node) {
if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
report(node.test); report(node.test);
} }
}, },
"ExpressionStatement": function(node) { "ExpressionStatement": function(node) {
var firstToken, secondToken, firstTokens; var firstToken, secondToken, firstTokens;
if (hasExcessParens(node.expression)) { if (hasExcessParens(node.expression)) {
firstTokens = context.getFirstTokens(node.expression, 2); firstTokens = context.getFirstTokens(node.expression, 2);
firstToken = firstTokens[0]; firstToken = firstTokens[0];
@ -380,16 +389,19 @@ module.exports = function(context) {
} }
} }
}, },
"ForInStatement": function(node) { "ForInStatement": function(node) {
if (hasExcessParens(node.right)) { if (hasExcessParens(node.right)) {
report(node.right); report(node.right);
} }
}, },
"ForOfStatement": function(node) { "ForOfStatement": function(node) {
if (hasExcessParens(node.right)) { if (hasExcessParens(node.right)) {
report(node.right); report(node.right);
} }
}, },
"ForStatement": function(node) { "ForStatement": function(node) {
if (node.init && hasExcessParens(node.init)) { if (node.init && hasExcessParens(node.init)) {
report(node.init); report(node.init);
@ -403,12 +415,15 @@ module.exports = function(context) {
report(node.update); report(node.update);
} }
}, },
"IfStatement": function(node) { "IfStatement": function(node) {
if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
report(node.test); report(node.test);
} }
}, },
"LogicalExpression": dryBinaryLogical, "LogicalExpression": dryBinaryLogical,
"MemberExpression": function(node) { "MemberExpression": function(node) {
if ( if (
hasExcessParens(node.object) && hasExcessParens(node.object) &&
@ -420,6 +435,7 @@ module.exports = function(context) {
typeof node.object.value === "number" && typeof node.object.value === "number" &&
/^[0-9]+$/.test(context.getFirstToken(node.object).value)) /^[0-9]+$/.test(context.getFirstToken(node.object).value))
|| ||
// RegExp literal is allowed to have parens (#1589) // RegExp literal is allowed to have parens (#1589)
(node.object.type === "Literal" && node.object.regex) (node.object.type === "Literal" && node.object.regex)
) )
@ -436,25 +452,31 @@ module.exports = function(context) {
report(node.property); report(node.property);
} }
}, },
"NewExpression": dryCallNew, "NewExpression": dryCallNew,
"ObjectExpression": function(node) { "ObjectExpression": function(node) {
[].forEach.call(node.properties, function(e) { [].forEach.call(node.properties, function(e) {
var v = e.value; var v = e.value;
if (v && hasExcessParens(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) { if (v && hasExcessParens(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) {
report(v); report(v);
} }
}); });
}, },
"ReturnStatement": function(node) { "ReturnStatement": function(node) {
var returnToken = sourceCode.getFirstToken(node); var returnToken = sourceCode.getFirstToken(node);
if (node.argument && if (node.argument &&
hasExcessParensNoLineTerminator(returnToken, node.argument) && hasExcessParensNoLineTerminator(returnToken, node.argument) &&
// RegExp literal is allowed to have parens (#1589) // RegExp literal is allowed to have parens (#1589)
!(node.argument.type === "Literal" && node.argument.regex)) { !(node.argument.type === "Literal" && node.argument.regex)) {
report(node.argument); report(node.argument);
} }
}, },
"SequenceExpression": function(node) { "SequenceExpression": function(node) {
[].forEach.call(node.expressions, function(e) { [].forEach.call(node.expressions, function(e) {
if (hasExcessParens(e) && precedence(e) >= precedence(node)) { if (hasExcessParens(e) && precedence(e) >= precedence(node)) {
@ -462,16 +484,19 @@ module.exports = function(context) {
} }
}); });
}, },
"SwitchCase": function(node) { "SwitchCase": function(node) {
if (node.test && hasExcessParens(node.test)) { if (node.test && hasExcessParens(node.test)) {
report(node.test); report(node.test);
} }
}, },
"SwitchStatement": function(node) { "SwitchStatement": function(node) {
if (hasDoubleExcessParens(node.discriminant)) { if (hasDoubleExcessParens(node.discriminant)) {
report(node.discriminant); report(node.discriminant);
} }
}, },
"ThrowStatement": function(node) { "ThrowStatement": function(node) {
var throwToken = sourceCode.getFirstToken(node); var throwToken = sourceCode.getFirstToken(node);
@ -479,26 +504,32 @@ module.exports = function(context) {
report(node.argument); report(node.argument);
} }
}, },
"UnaryExpression": dryUnaryUpdate, "UnaryExpression": dryUnaryUpdate,
"UpdateExpression": dryUnaryUpdate, "UpdateExpression": dryUnaryUpdate,
"VariableDeclarator": function(node) { "VariableDeclarator": function(node) {
if (node.init && hasExcessParens(node.init) && if (node.init && hasExcessParens(node.init) &&
precedence(node.init) >= precedence({type: "AssignmentExpression"}) && precedence(node.init) >= precedence({type: "AssignmentExpression"}) &&
// RegExp literal is allowed to have parens (#1589) // RegExp literal is allowed to have parens (#1589)
!(node.init.type === "Literal" && node.init.regex)) { !(node.init.type === "Literal" && node.init.regex)) {
report(node.init); report(node.init);
} }
}, },
"WhileStatement": function(node) { "WhileStatement": function(node) {
if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
report(node.test); report(node.test);
} }
}, },
"WithStatement": function(node) { "WithStatement": function(node) {
if (hasDoubleExcessParens(node.object)) { if (hasDoubleExcessParens(node.object)) {
report(node.object); report(node.object);
} }
}, },
"YieldExpression": function(node) { "YieldExpression": function(node) {
var yieldToken; var yieldToken;

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

@ -45,6 +45,7 @@ module.exports = function(context) {
} }
return { return {
/** /**
* Reports this empty statement, except if the parent node is a loop. * Reports this empty statement, except if the parent node is a loop.
* @param {Node} node - A EmptyStatement node to be reported. * @param {Node} node - A EmptyStatement node to be reported.

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

Loading…
Cancel
Save