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. 197
      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. 109
      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. 25
      tools/eslint/lib/rules/brace-style.js
  46. 3
      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. 34
      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. 2
      tools/eslint/lib/rules/no-console.js
  86. 5
      tools/eslint/lib/rules/no-constant-condition.js
  87. 2
      tools/eslint/lib/rules/no-control-regex.js
  88. 2
      tools/eslint/lib/rules/no-dupe-args.js
  89. 2
      tools/eslint/lib/rules/no-dupe-class-members.js
  90. 1
      tools/eslint/lib/rules/no-duplicate-case.js
  91. 126
      tools/eslint/lib/rules/no-duplicate-imports.js
  92. 10
      tools/eslint/lib/rules/no-else-return.js
  93. 1
      tools/eslint/lib/rules/no-empty-character-class.js
  94. 1
      tools/eslint/lib/rules/no-empty-function.js
  95. 1
      tools/eslint/lib/rules/no-empty.js
  96. 19
      tools/eslint/lib/rules/no-eval.js
  97. 3
      tools/eslint/lib/rules/no-extend-native.js
  98. 2
      tools/eslint/lib/rules/no-extra-bind.js
  99. 2
      tools/eslint/lib/rules/no-extra-boolean-cast.js
  100. 63
      tools/eslint/lib/rules/no-extra-parens.js

2892
tools/eslint/CHANGELOG.md

File diff suppressed because it is too large

32
tools/eslint/README.md

@ -8,7 +8,7 @@
# 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:
@ -39,17 +39,17 @@ After running `eslint --init`, you'll have a `.eslintrc` file in your directory.
```json
{
"rules": {
"semi": [2, "always"],
"quotes": [2, "double"]
"semi": ["error", "always"],
"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:
* `0` - turn the rule off
* `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)
* `"off"` or `0` - turn the rule off
* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
* `"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)).
@ -66,12 +66,14 @@ These folks keep the project moving and are resources for help:
* Ilya Volodin ([@ilyavolodin](https://github.com/ilyavolodin)) - reviewer
* Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - reviewer
* Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - committer
* Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - 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
* 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
@ -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)
* [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)
## 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.
### 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?
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.
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?
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
var concat = require("concat-stream"),
cli = require("../lib/cli");
cli = require("../lib/cli"),
path = require("path"),
fs = require("fs");
//------------------------------------------------------------------------------
// 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) {
process.stdin.pipe(concat({ encoding: "string" }, function(text) {
try {

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

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

414
tools/eslint/conf/eslint.json

@ -2,210 +2,214 @@
"parser": "espree",
"ecmaFeatures": {},
"rules": {
"no-alert": 0,
"no-array-constructor": 0,
"no-bitwise": 0,
"no-caller": 0,
"no-case-declarations": 2,
"no-catch-shadow": 0,
"no-class-assign": 2,
"no-cond-assign": 2,
"no-confusing-arrow": 0,
"no-console": 2,
"no-const-assign": 2,
"no-constant-condition": 2,
"no-continue": 0,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 0,
"no-dupe-class-members": 2,
"no-dupe-keys": 2,
"no-dupe-args": 2,
"no-duplicate-case": 2,
"no-else-return": 0,
"no-empty": 2,
"no-empty-character-class": 2,
"no-empty-function": 0,
"no-empty-pattern": 2,
"no-eq-null": 0,
"no-eval": 0,
"no-ex-assign": 2,
"no-extend-native": 0,
"no-extra-bind": 0,
"no-extra-boolean-cast": 2,
"no-extra-label": 0,
"no-extra-parens": 0,
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-floating-decimal": 0,
"no-func-assign": 2,
"no-implicit-coercion": 0,
"no-implicit-globals": 0,
"no-implied-eval": 0,
"no-inline-comments": 0,
"no-inner-declarations": 2,
"no-invalid-regexp": 2,
"no-invalid-this": 0,
"no-irregular-whitespace": 2,
"no-iterator": 0,
"no-label-var": 0,
"no-labels": 0,
"no-lone-blocks": 0,
"no-lonely-if": 0,
"no-loop-func": 0,
"no-mixed-requires": 0,
"no-mixed-spaces-and-tabs": 2,
"linebreak-style": 0,
"no-multi-spaces": 0,
"no-multi-str": 0,
"no-multiple-empty-lines": 0,
"no-native-reassign": 0,
"no-negated-condition": 0,
"no-negated-in-lhs": 2,
"no-nested-ternary": 0,
"no-new": 0,
"no-new-func": 0,
"no-new-object": 0,
"no-new-require": 0,
"no-new-symbol": 2,
"no-new-wrappers": 0,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 0,
"no-param-reassign": 0,
"no-path-concat": 0,
"no-plusplus": 0,
"no-process-env": 0,
"no-process-exit": 0,
"no-proto": 0,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-restricted-imports": 0,
"no-restricted-modules": 0,
"no-restricted-syntax": 0,
"no-return-assign": 0,
"no-script-url": 0,
"no-self-assign": 2,
"no-self-compare": 0,
"no-sequences": 0,
"no-shadow": 0,
"no-shadow-restricted-names": 0,
"no-whitespace-before-property": 0,
"no-spaced-func": 0,
"no-sparse-arrays": 2,
"no-sync": 0,
"no-ternary": 0,
"no-trailing-spaces": 0,
"no-this-before-super": 2,
"no-throw-literal": 0,
"no-undef": 2,
"no-undef-init": 0,
"no-undefined": 0,
"no-unexpected-multiline": 2,
"no-underscore-dangle": 0,
"no-unmodified-loop-condition": 0,
"no-unneeded-ternary": 0,
"no-unreachable": 2,
"no-unused-expressions": 0,
"no-unused-labels": 2,
"no-unused-vars": 2,
"no-use-before-define": 0,
"no-useless-call": 0,
"no-useless-concat": 0,
"no-useless-constructor": 0,
"no-void": 0,
"no-var": 0,
"no-warning-comments": 0,
"no-with": 0,
"no-magic-numbers": 0,
"array-bracket-spacing": 0,
"array-callback-return": 0,
"arrow-body-style": 0,
"arrow-parens": 0,
"arrow-spacing": 0,
"accessor-pairs": 0,
"block-scoped-var": 0,
"block-spacing": 0,
"brace-style": 0,
"callback-return": 0,
"camelcase": 0,
"comma-dangle": 2,
"comma-spacing": 0,
"comma-style": 0,
"complexity": [0, 11],
"computed-property-spacing": 0,
"consistent-return": 0,
"consistent-this": 0,
"constructor-super": 2,
"curly": 0,
"default-case": 0,
"dot-location": 0,
"dot-notation": 0,
"eol-last": 0,
"eqeqeq": 0,
"func-names": 0,
"func-style": 0,
"generator-star-spacing": 0,
"global-require": 0,
"guard-for-in": 0,
"handle-callback-err": 0,
"id-length": 0,
"indent": 0,
"init-declarations": 0,
"jsx-quotes": 0,
"key-spacing": 0,
"keyword-spacing": 0,
"lines-around-comment": 0,
"max-depth": 0,
"max-len": 0,
"max-nested-callbacks": 0,
"max-params": 0,
"max-statements": 0,
"new-cap": 0,
"new-parens": 0,
"newline-after-var": 0,
"newline-per-chained-call": 0,
"object-curly-spacing": [0, "never"],
"object-shorthand": 0,
"one-var": 0,
"one-var-declaration-per-line": 0,
"operator-assignment": 0,
"operator-linebreak": 0,
"padded-blocks": 0,
"prefer-arrow-callback": 0,
"prefer-const": 0,
"prefer-reflect": 0,
"prefer-rest-params": 0,
"prefer-spread": 0,
"prefer-template": 0,
"quote-props": 0,
"quotes": 0,
"radix": 0,
"id-match": 0,
"id-blacklist": 0,
"require-jsdoc": 0,
"require-yield": 0,
"semi": 0,
"semi-spacing": 0,
"sort-vars": 0,
"sort-imports": 0,
"space-before-blocks": 0,
"space-before-function-paren": 0,
"space-in-parens": 0,
"space-infix-ops": 0,
"space-unary-ops": 0,
"spaced-comment": 0,
"strict": 0,
"template-curly-spacing": 0,
"use-isnan": 2,
"valid-jsdoc": 0,
"valid-typeof": 2,
"vars-on-top": 0,
"wrap-iife": 0,
"wrap-regex": 0,
"yield-star-spacing": 0,
"yoda": 0
"no-alert": "off",
"no-array-constructor": "off",
"no-bitwise": "off",
"no-caller": "off",
"no-case-declarations": "error",
"no-catch-shadow": "off",
"no-class-assign": "error",
"no-cond-assign": "error",
"no-confusing-arrow": "off",
"no-console": "error",
"no-const-assign": "error",
"no-constant-condition": "error",
"no-continue": "off",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-div-regex": "off",
"no-dupe-class-members": "error",
"no-dupe-keys": "error",
"no-dupe-args": "error",
"no-duplicate-case": "error",
"no-duplicate-imports": "off",
"no-else-return": "off",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-function": "off",
"no-empty-pattern": "error",
"no-eq-null": "off",
"no-eval": "off",
"no-ex-assign": "error",
"no-extend-native": "off",
"no-extra-bind": "off",
"no-extra-boolean-cast": "error",
"no-extra-label": "off",
"no-extra-parens": "off",
"no-extra-semi": "error",
"no-fallthrough": "error",
"no-floating-decimal": "off",
"no-func-assign": "error",
"no-implicit-coercion": "off",
"no-implicit-globals": "off",
"no-implied-eval": "off",
"no-inline-comments": "off",
"no-inner-declarations": "error",
"no-invalid-regexp": "error",
"no-invalid-this": "off",
"no-irregular-whitespace": "error",
"no-iterator": "off",
"no-label-var": "off",
"no-labels": "off",
"no-lone-blocks": "off",
"no-lonely-if": "off",
"no-loop-func": "off",
"no-mixed-requires": "off",
"no-mixed-spaces-and-tabs": "error",
"linebreak-style": "off",
"no-multi-spaces": "off",
"no-multi-str": "off",
"no-multiple-empty-lines": "off",
"no-native-reassign": "off",
"no-negated-condition": "off",
"no-negated-in-lhs": "error",
"no-nested-ternary": "off",
"no-new": "off",
"no-new-func": "off",
"no-new-object": "off",
"no-new-require": "off",
"no-new-symbol": "error",
"no-new-wrappers": "off",
"no-obj-calls": "error",
"no-octal": "error",
"no-octal-escape": "off",
"no-param-reassign": "off",
"no-path-concat": "off",
"no-plusplus": "off",
"no-process-env": "off",
"no-process-exit": "off",
"no-proto": "off",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-restricted-globals": "off",
"no-restricted-imports": "off",
"no-restricted-modules": "off",
"no-restricted-syntax": "off",
"no-return-assign": "off",
"no-script-url": "off",
"no-self-assign": "error",
"no-self-compare": "off",
"no-sequences": "off",
"no-shadow": "off",
"no-shadow-restricted-names": "off",
"no-whitespace-before-property": "off",
"no-spaced-func": "off",
"no-sparse-arrays": "error",
"no-sync": "off",
"no-ternary": "off",
"no-trailing-spaces": "off",
"no-this-before-super": "error",
"no-throw-literal": "off",
"no-undef": "error",
"no-undef-init": "off",
"no-undefined": "off",
"no-unexpected-multiline": "error",
"no-underscore-dangle": "off",
"no-unmodified-loop-condition": "off",
"no-unneeded-ternary": "off",
"no-unreachable": "error",
"no-unused-expressions": "off",
"no-unused-labels": "error",
"no-unused-vars": "error",
"no-use-before-define": "off",
"no-useless-call": "off",
"no-useless-concat": "off",
"no-useless-constructor": "off",
"no-useless-escape": "off",
"no-void": "off",
"no-var": "off",
"no-warning-comments": "off",
"no-with": "off",
"no-magic-numbers": "off",
"array-bracket-spacing": "off",
"array-callback-return": "off",
"arrow-body-style": "off",
"arrow-parens": "off",
"arrow-spacing": "off",
"accessor-pairs": "off",
"block-scoped-var": "off",
"block-spacing": "off",
"brace-style": "off",
"callback-return": "off",
"camelcase": "off",
"comma-dangle": "error",
"comma-spacing": "off",
"comma-style": "off",
"complexity": ["off", 11],
"computed-property-spacing": "off",
"consistent-return": "off",
"consistent-this": "off",
"constructor-super": "error",
"curly": "off",
"default-case": "off",
"dot-location": "off",
"dot-notation": "off",
"eol-last": "off",
"eqeqeq": "off",
"func-names": "off",
"func-style": "off",
"generator-star-spacing": "off",
"global-require": "off",
"guard-for-in": "off",
"handle-callback-err": "off",
"id-length": "off",
"indent": "off",
"init-declarations": "off",
"jsx-quotes": "off",
"key-spacing": "off",
"keyword-spacing": "off",
"lines-around-comment": "off",
"max-depth": "off",
"max-len": "off",
"max-nested-callbacks": "off",
"max-params": "off",
"max-statements": "off",
"max-statements-per-line": "off",
"new-cap": "off",
"new-parens": "off",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"object-curly-spacing": ["off", "never"],
"object-shorthand": "off",
"one-var": "off",
"one-var-declaration-per-line": "off",
"operator-assignment": "off",
"operator-linebreak": "off",
"padded-blocks": "off",
"prefer-arrow-callback": "off",
"prefer-const": "off",
"prefer-reflect": "off",
"prefer-rest-params": "off",
"prefer-spread": "off",
"prefer-template": "off",
"quote-props": "off",
"quotes": "off",
"radix": "off",
"id-match": "off",
"id-blacklist": "off",
"require-jsdoc": "off",
"require-yield": "off",
"semi": "off",
"semi-spacing": "off",
"sort-vars": "off",
"sort-imports": "off",
"space-before-blocks": "off",
"space-before-function-paren": "off",
"space-in-parens": "off",
"space-infix-ops": "off",
"space-unary-ops": "off",
"spaced-comment": "off",
"strict": "off",
"template-curly-spacing": "off",
"use-isnan": "error",
"valid-jsdoc": "off",
"valid-typeof": "error",
"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
*/
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 &&
reference.init === false &&
reference.isWrite() &&
// Destructuring assignments can have multiple default value,
// so possibly there are multiple writeable references for the same identifier.
(index === 0 || references[index - 1].identifier !== identifier)
modifyingDifferentIdentifier
);
}
@ -155,6 +162,7 @@ function isMethodWhichHasThisArg(node) {
*/
function hasJSDocThisTag(node, sourceCode) {
var jsdocComment = sourceCode.getJSDocComment(node);
if (jsdocComment && thisTagPattern.test(jsdocComment.value)) {
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
//------------------------------------------------------------------------------
@ -187,8 +211,10 @@ module.exports = {
isNullOrUndefined: isNullOrUndefined,
isCallee: isCallee,
isES5Constructor: isES5Constructor,
getUpperFunction: getUpperFunction,
isArrayFromMethod: isArrayFromMethod,
isParenthesised: isParenthesised,
/**
* Checks whether or not a given node is a string literal.
@ -261,6 +287,7 @@ module.exports = {
*/
isDirectiveComment: function(node) {
var comment = node.value.trim();
return (
node.type === "Line" && comment.indexOf("eslint-") === 0 ||
node.type === "Block" && (
@ -293,8 +320,10 @@ module.exports = {
*/
getVariableByName: function(initScope, name) {
var scope = initScope;
while (scope) {
var variable = scope.set.get(name);
if (variable) {
return variable;
}
@ -333,10 +362,13 @@ module.exports = {
while (node) {
var parent = node.parent;
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 "ConditionalExpression":
node = parent;
@ -350,6 +382,7 @@ module.exports = {
// })();
case "ReturnStatement":
var func = getUpperFunction(parent);
if (func === null || !isCallee(func)) {
return true;
}

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

@ -22,8 +22,7 @@ var fs = require("fs"),
lodash = require("lodash"),
debug = require("debug"),
glob = require("glob"),
shell = require("shelljs"),
isAbsolute = require("path-is-absolute"),
rules = require("./rules"),
eslint = require("./eslint"),
@ -36,8 +35,8 @@ var fs = require("fs"),
SourceCodeFixer = require("./util/source-code-fixer"),
validator = require("./config/config-validator"),
stringify = require("json-stable-stringify"),
hash = require("./util/hash"),
crypto = require( "crypto" ),
pkg = require("../package.json");
@ -73,12 +72,6 @@ var fs = require("fs"),
* @property {LintMessage[]} messages All of the messages for the result.
*/
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
defaultOptions = lodash.assign({}, defaultOptions, {cwd: process.cwd()});
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
@ -141,13 +134,14 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
config,
messages,
stats,
fileExtension = path.extname(filename),
fileExtension,
processor,
loadedPlugins,
fixedResult;
if (filename) {
filePath = path.resolve(filename);
fileExtension = path.extname(filename);
}
filename = filename || "<text>";
@ -171,6 +165,7 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
debug("Using processor");
var parsedBlocks = processor.preprocess(text, filename);
var unprocessedMessages = [];
parsedBlocks.forEach(function(block) {
unprocessedMessages.push(eslint.verify(block, config, {
filename: filename,
@ -262,17 +257,6 @@ function isErrorMessage(message) {
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
@ -286,8 +270,11 @@ function md5Hash(str) {
* @returns {string} the resolved path to the cache file
*/
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);
var resolvedCacheFile = path.resolve(cwd, cacheFile);
@ -298,7 +285,7 @@ function getCacheFile(cacheFile, cwd) {
* @returns {string} the resolved path to the cacheFile
*/
function getCacheFileForDirectory() {
return path.join(resolvedCacheFile, ".cache_" + md5Hash(cwd));
return path.join(resolvedCacheFile, ".cache_" + hash(cwd));
}
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
// inside that directory
/*
* 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
* inside that directory
*/
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) {
return getCacheFileForDirectory();
}
// is file so just use that file
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
// for the current operating system.
/*
* 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
* for the current operating system.
*/
// if the last character passed is a path separator we assume is a directory
if (looksLikeADirectory) {
@ -347,7 +342,12 @@ function getCacheFile(cacheFile, cwd) {
*/
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
@ -358,8 +358,9 @@ function CLIEngine(options) {
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
* execution (e.g. file passed with no errors and no warnings
* Cache used to avoid operating on files that haven't changed since the
* last successful execution (e.g., file passed linting with no errors and
* no warnings).
* @type {Object}
*/
this._fileCache = fileEntryCache.create(cacheFile);
@ -371,6 +372,7 @@ function CLIEngine(options) {
// load in additional rules
if (this.options.rulePaths) {
var cwd = this.options.cwd;
this.options.rulePaths.forEach(function(rulesdir) {
debug("Loading rules from " + rulesdir);
rules.load(rulesdir, cwd);
@ -404,7 +406,9 @@ CLIEngine.getFormatter = function(format) {
// if there's a slash, then it's a file
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 {
formatterPath = "./formatters/" + format;
}
@ -476,7 +480,7 @@ CLIEngine.prototype = {
* @returns {string[]} The equivalent glob 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,
fileCache = this._fileCache,
configHelper = new Config(options),
ignoredPaths = new IgnoredPaths(options),
globOptions,
fileList,
stats,
startTime,
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
* @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
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;
var eslintVersion = pkg.version;
prevConfig.hash = md5Hash(eslintVersion + "_" + stringify(config));
prevConfig.hash = hash(eslintVersion + "_" + stringify(config));
}
return prevConfig.hash;
@ -546,48 +538,41 @@ CLIEngine.prototype = {
function executeOnFile(filename, warnIgnored) {
var hashOfConfig;
if (options.ignore !== false) {
if (ignoredPaths.contains(filename, "custom")) {
if (warnIgnored) {
results.push(createIgnoreResult(filename));
}
return;
}
if (ignoredPaths.contains(filename, "default")) {
return;
}
}
filename = path.resolve(filename);
if (processed[filename]) {
return;
}
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 meta = descriptor.meta || {};
hashOfConfig = hashOfConfigFor(filename);
var changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig;
if (!changed) {
debug("Skipping file since hasn't changed: " + filename);
// Adding the filename to the processed hashmap
// so the reporting is not affected (showing a warning about .eslintignore being used
// when it is not really used)
/*
* Adding the filename to the processed hashmap
* so the reporting is not affected (showing a warning about .eslintignore being used
* when it is not really used)
*/
processed[filename] = true;
// Add the the cached results (always will be 0 error and 0 warnings)
// cause we don't save to cache files that failed
// to guarantee that next execution will process those files as well
/*
* Add the the cached results (always will be 0 error and
* 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);
// move to the next file
@ -602,17 +587,25 @@ CLIEngine.prototype = {
var res = processFile(filename, configHelper, options);
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 ( res.errorCount > 0 || res.warningCount > 0 ) {
/*
* 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) {
debug("File has problems, skipping it: " + filename);
// remove the entry from the cache
fileCache.removeEntry( filename );
fileCache.removeEntry(filename);
} 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.results = res;
}
@ -621,23 +614,20 @@ CLIEngine.prototype = {
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);
if (options.cache) {
// persist the cache to disk
fileCache.reconcile();
}
@ -665,7 +655,12 @@ CLIEngine.prototype = {
configHelper = new Config(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)) {
results.push(createIgnoreResult(filename));
} else {
results.push(processText(text, configHelper, filename, options.fix, options.allowInlineConfig));
@ -689,6 +684,7 @@ CLIEngine.prototype = {
*/
getConfigForFile: function(filePath) {
var configHelper = new Config(this.options);
return configHelper.getConfig(filePath);
},
@ -699,10 +695,11 @@ CLIEngine.prototype = {
*/
isPathIgnored: function(filePath) {
var ignoredPaths;
var resolvedPath = path.resolve(this.options.cwd, filePath);
if (this.options.ignore) {
ignoredPaths = new IgnoredPaths(this.options);
return ignoredPaths.contains(filePath);
return ignoredPaths.contains(resolvedPath);
}
return false;

2
tools/eslint/lib/cli.js

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

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

@ -41,6 +41,7 @@ function isCaseNode(node) {
*/
function isForkingByTrueOrFalse(node) {
var parent = node.parent;
switch (parent.type) {
case "ConditionalExpression":
case "IfStatement":
@ -128,8 +129,8 @@ function isIdentifierReference(node) {
*
* To separate the current and the head is in order to not make useless segments.
*
* In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd" events
* are fired.
* In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd"
* events are fired.
*
* @param {CodePathAnalyzer} analyzer - The instance.
* @param {ASTNode} node - The current AST node.
@ -206,6 +207,7 @@ function leaveFromCurrentSegment(analyzer, node) {
node);
}
}
state.currentSegments = [];
}
@ -234,9 +236,12 @@ function preprocess(analyzer, node) {
case "ConditionalExpression":
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) {
state.makeIfConsequent();
} else if (parent.alternate === node) {
@ -299,9 +304,12 @@ function preprocess(analyzer, node) {
break;
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) {
state.pushForkContext();
state.forkBypassPath();
@ -332,6 +340,7 @@ function processCodePathToEnter(analyzer, node) {
case "FunctionExpression":
case "ArrowFunctionExpression":
if (codePath) {
// Emits onCodePathSegmentStart events if updated.
forwardCurrentToHead(analyzer, node);
debug.dumpState(node, state, false);
@ -370,9 +379,12 @@ function processCodePathToEnter(analyzer, node) {
break;
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) {
state.forkPath();
}
@ -425,9 +437,12 @@ function processCodePathToExit(analyzer, node) {
break;
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) {
state.makeSwitchCaseBody(true, !node.test);
}
@ -499,9 +514,12 @@ function processCodePathToExit(analyzer, node) {
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)) {
// Emits onCodePathSegmentStart events if updated.
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.
*/
function CodePathSegment(id, allPrevSegments, reachable) {
/**
* The identifier of this code path.
* Rules use it to store additional information of each rule.
@ -120,7 +121,8 @@ function CodePathSegment(id, allPrevSegments, reachable) {
// Internal data.
Object.defineProperty(this, "internal", {value: {
used: false
used: false,
loopedPrevSegments: []
}});
/* 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.
*
@ -191,6 +207,7 @@ CodePathSegment.markUsed = function(segment) {
segment.internal.used = true;
var i;
if (segment.reachable) {
for (i = 0; i < segment.allPrevSegments.length; ++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;

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

@ -55,6 +55,7 @@ function getContinueContext(state, label) {
}
var context = state.loopContext;
while (context) {
if (context.label === label) {
return context;
@ -75,6 +76,7 @@ function getContinueContext(state, label) {
*/
function getBreakContext(state, label) {
var context = state.breakContext;
while (context) {
if (label ? context.label === label : context.breakable) {
return context;
@ -94,6 +96,7 @@ function getBreakContext(state, label) {
*/
function getReturnContext(state) {
var context = state.tryContext;
while (context) {
if (context.hasFinalizer && context.position !== "finally") {
return context;
@ -112,6 +115,7 @@ function getReturnContext(state) {
*/
function getThrowContext(state) {
var context = state.tryContext;
while (context) {
if (context.position === "try" ||
(context.hasFinalizer && context.position === "catch")
@ -168,6 +172,7 @@ function removeConnection(prevSegments, nextSegments) {
*/
function makeLooped(state, fromSegments, toSegments) {
var end = Math.min(fromSegments.length, toSegments.length);
for (var i = 0; i < end; ++i) {
var fromSegment = fromSegments[i];
var toSegment = toSegments[i];
@ -181,6 +186,10 @@ function makeLooped(state, fromSegments, toSegments) {
fromSegment.allNextSegments.push(toSegment);
toSegment.allPrevSegments.push(fromSegment);
if (toSegment.allPrevSegments.length >= 2) {
CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment);
}
state.notifyLooped(fromSegment, toSegment);
}
}
@ -237,6 +246,7 @@ function CodePathState(idGenerator, onLooped) {
var final = this.finalSegments = [];
var returned = this.returnedForkContext = [];
var thrown = this.thrownForkContext = [];
returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
}
@ -259,6 +269,7 @@ CodePathState.prototype = {
*/
get parentForkContext() {
var current = this.forkContext;
return current && current.upper;
},
@ -362,6 +373,7 @@ CodePathState.prototype = {
*/
popChoiceContext: function() {
var context = this.choiceContext;
this.choiceContext = context.upper;
var forkContext = this.forkContext;
@ -370,43 +382,61 @@ CodePathState.prototype = {
switch (context.kind) {
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) {
context.trueForkContext.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) {
var parentContext = this.choiceContext;
parentContext.trueForkContext.addAll(context.trueForkContext);
parentContext.falseForkContext.addAll(context.falseForkContext);
parentContext.processed = true;
return context;
}
break;
case "test":
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.add(headSegments);
} 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.add(headSegments);
}
break;
case "loop":
// Loops are addressed in popLoopContext().
// This is called from popLoopContext().
/*
* Loops are addressed in popLoopContext().
* This is called from popLoopContext().
*/
return context;
/* istanbul ignore next */
@ -416,6 +446,7 @@ CodePathState.prototype = {
// Merges all paths.
var prevForkContext = context.trueForkContext;
prevForkContext.addAll(context.falseForkContext);
forkContext.replaceHead(prevForkContext.makeNext(0, -1));
@ -433,8 +464,11 @@ CodePathState.prototype = {
var forkContext = this.forkContext;
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 =
context.kind === "&&" ? context.trueForkContext :
/* kind === "||" */ context.falseForkContext;
@ -444,13 +478,18 @@ CodePathState.prototype = {
context.processed = false;
} 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 === "&&") {
// The path does short-circuit if false.
context.falseForkContext.add(forkContext.head);
} else {
// The path does short-circuit if true.
context.trueForkContext.add(forkContext.head);
}
@ -468,13 +507,16 @@ CodePathState.prototype = {
var context = this.choiceContext;
var forkContext = this.forkContext;
// 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 test expression.
/*
* 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 test expression.
*/
if (!context.processed) {
context.trueForkContext.add(forkContext.head);
context.falseForkContext.add(forkContext.head);
}
context.processed = false;
// Creates new path from the `true` case.
@ -492,8 +534,10 @@ CodePathState.prototype = {
var context = this.choiceContext;
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.add(forkContext.head);
context.processed = true;
@ -526,6 +570,7 @@ CodePathState.prototype = {
lastIsDefault: false,
countForks: 0
};
this.pushBreakContext(true, label);
},
@ -541,41 +586,57 @@ CodePathState.prototype = {
*/
popSwitchContext: function() {
var context = this.switchContext;
this.switchContext = context.upper;
var forkContext = this.forkContext;
var brokenForkContext = this.popBreakContext().brokenForkContext;
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) {
brokenForkContext.add(forkContext.makeNext(-1, -1));
forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
}
return;
}
var lastSegments = forkContext.head;
this.forkBypassPath();
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);
// 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.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);
makeLooped(this, lastCaseSegments, context.defaultBodySegments);
} 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);
}
}
@ -584,8 +645,11 @@ CodePathState.prototype = {
for (var i = 0; i < context.countForks; ++i) {
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));
},
@ -598,20 +662,26 @@ CodePathState.prototype = {
*/
makeSwitchCaseBody: function(isEmpty, isDefault) {
var context = this.switchContext;
if (!context.hasCase) {
return;
}
// Merge forks.
// The parent fork context has two segments.
// Those are from the current case and the body of the previous case.
/*
* Merge forks.
* 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 forkContext = this.pushForkContext();
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
// the last `case` to the `default` chunk.
/*
* Save `default` chunk info.
* If the `default` label is not at the last, we must make a path from
* the last `case` to the `default` chunk.
*/
if (isDefault) {
context.defaultSegments = parentForkContext.head;
if (isEmpty) {
@ -625,6 +695,7 @@ CodePathState.prototype = {
context.defaultBodySegments = forkContext.head;
}
}
context.lastIsDefault = isDefault;
context.countForks += 1;
},
@ -645,9 +716,11 @@ CodePathState.prototype = {
upper: this.tryContext,
position: "try",
hasFinalizer: hasFinalizer,
returnedForkContext: hasFinalizer
? ForkContext.newEmpty(this.forkContext)
: null,
thrownForkContext: ForkContext.newEmpty(this.forkContext),
lastOfTryIsReachable: false,
lastOfCatchIsReachable: false
@ -661,24 +734,31 @@ CodePathState.prototype = {
*/
popTryContext: function() {
var context = this.tryContext;
this.tryContext = context.upper;
if (context.position === "catch") {
// Merges two paths from the `try` block and `catch` block merely.
this.popForkContext();
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 thrown = context.thrownForkContext;
if (returned.empty && thrown.empty) {
return;
}
// Separate head to normal paths and leaving paths.
var headSegments = this.forkContext.head;
this.forkContext = this.forkContext.upper;
var normalSegments = headSegments.slice(0, headSegments.length / 2 | 0);
var leavingSegments = headSegments.slice(headSegments.length / 2 | 0);
@ -744,6 +824,7 @@ CodePathState.prototype = {
// Update state.
if (context.position === "catch") {
// Merges two paths from the `try` block and `catch` block.
this.popForkContext();
forkContext = this.forkContext;
@ -755,14 +836,18 @@ CodePathState.prototype = {
context.position = "finally";
if (returned.empty && thrown.empty) {
// This path does not leave.
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 j;
for (var i = 0; i < forkContext.count; ++i) {
var prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
@ -790,11 +875,13 @@ CodePathState.prototype = {
*/
makeFirstThrowablePathInTryBlock: function() {
var forkContext = this.forkContext;
if (!forkContext.reachable) {
return;
}
var context = getThrowContext(this);
if (context === this ||
context.position !== "try" ||
!context.thrownForkContext.empty
@ -893,6 +980,7 @@ CodePathState.prototype = {
*/
popLoopContext: function() {
var context = this.loopContext;
this.loopContext = context.upper;
var forkContext = this.forkContext;
@ -923,6 +1011,7 @@ CodePathState.prototype = {
// `true` paths go to looping.
var segmentsList = choiceContext.trueForkContext.segmentsList;
for (var i = 0; i < segmentsList.length; ++i) {
makeLooped(
this,
@ -1069,6 +1158,7 @@ CodePathState.prototype = {
// Update state.
var updateSegments = forkContext.makeDisconnected(-1, -1);
context.continueDestSegments = context.updateSegments = updateSegments;
forkContext.replaceHead(updateSegments);
},
@ -1104,10 +1194,15 @@ CodePathState.prototype = {
}
var bodySegments = context.endOfTestSegments;
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);
prevForkContext.add(context.endOfInitSegments);
if (context.endOfUpdateSegments) {
prevForkContext.add(context.endOfUpdateSegments);
@ -1146,6 +1241,7 @@ CodePathState.prototype = {
var context = this.loopContext;
var forkContext = this.forkContext;
var temp = ForkContext.newEmpty(forkContext);
temp.add(context.prevSegments);
var rightSegments = temp.makeNext(-1, -1);
@ -1164,6 +1260,7 @@ CodePathState.prototype = {
var context = this.loopContext;
var forkContext = this.forkContext;
var temp = ForkContext.newEmpty(forkContext);
temp.add(context.endOfLeftSegments);
var bodySegments = temp.makeNext(-1, -1);
@ -1205,11 +1302,13 @@ CodePathState.prototype = {
popBreakContext: function() {
var context = this.breakContext;
var forkContext = this.forkContext;
this.breakContext = context.upper;
// Process this context here for other than switches and loops.
if (!context.breakable) {
var brokenForkContext = context.brokenForkContext;
if (!brokenForkContext.empty) {
brokenForkContext.add(forkContext.head);
forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
@ -1230,15 +1329,18 @@ CodePathState.prototype = {
*/
makeBreak: function(label) {
var forkContext = this.forkContext;
if (!forkContext.reachable) {
return;
}
var context = getBreakContext(this, label);
/* istanbul ignore else: foolproof (syntax error) */
if (context) {
context.brokenForkContext.add(forkContext.head);
}
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
},
@ -1253,11 +1355,13 @@ CodePathState.prototype = {
*/
makeContinue: function(label) {
var forkContext = this.forkContext;
if (!forkContext.reachable) {
return;
}
var context = getContinueContext(this, label);
/* istanbul ignore else: foolproof (syntax error) */
if (context) {
if (context.continueDestSegments) {
@ -1286,6 +1390,7 @@ CodePathState.prototype = {
*/
makeReturn: function() {
var forkContext = this.forkContext;
if (forkContext.reachable) {
getReturnContext(this).returnedForkContext.add(forkContext.head);
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
@ -1302,6 +1407,7 @@ CodePathState.prototype = {
*/
makeThrow: function() {
var forkContext = this.forkContext;
if (forkContext.reachable) {
getThrowContext(this).thrownForkContext.add(forkContext.head);
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
@ -1314,6 +1420,7 @@ CodePathState.prototype = {
*/
makeFinal: function() {
var segments = this.currentSegments;
if (segments.length > 0 && segments[0].reachable) {
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.
*/
function CodePath(id, upper, onLooped) {
/**
* The identifier of this code path.
* Rules use it to store additional information of each rule.
@ -102,6 +103,123 @@ CodePath.prototype = {
*/
get 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 = {
/**
* A flag that debug dumping is enabled or not.
* @type {boolean}
@ -57,6 +58,7 @@ module.exports = {
dumpState: !debug.enabled ? debug : /* istanbul ignore next */ function(node, state, leaving) {
for (var i = 0; i < state.currentSegments.length; ++i) {
var segInternal = state.currentSegments[i].internal;
if (leaving) {
segInternal.exitNodes.push(node);
} else {
@ -153,12 +155,14 @@ module.exports = {
var item = stack.pop();
var segment = item[0];
var index = item[1];
if (done[segment.id] && index === 0) {
continue;
}
done[segment.id] = segment;
var nextSegment = segment.allNextSegments[index];
if (!nextSegment) {
continue;
}

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

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

17
tools/eslint/lib/config.js

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

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

@ -15,6 +15,7 @@ var lodash = require("lodash"),
debug = require("debug"),
eslint = require("../eslint"),
configRule = require("./config-rule"),
ConfigOps = require("./config-ops"),
recConfig = require("../../conf/eslint.json");
//------------------------------------------------------------------------------
@ -91,6 +92,7 @@ Registry.prototype = {
*/
populateFromCoreRules: function() {
var rulesConfig = configRule.createCoreRuleConfigs();
this.rules = makeRegistryItems(rulesConfig);
},
@ -123,18 +125,32 @@ Registry.prototype = {
* @returns {void}
*/
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);
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") {
return;
}
ruleSets[idx] = ruleSets[idx] || {};
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;
}
}.bind(this);
@ -164,6 +180,7 @@ Registry.prototype = {
var errorFreeItems = newRegistry.rules[ruleId].filter(function(registryItem) {
return (registryItem.errorCount === 0);
});
if (errorFreeItems.length > 0) {
newRegistry.rules[ruleId] = errorFreeItems;
} else {
@ -208,6 +225,7 @@ Registry.prototype = {
var failingConfigs = this.rules[ruleId].filter(function(registryItem) {
return (registryItem.errorCount > 0);
});
if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) {
failingRegistry.rules[ruleId] = failingConfigs;
}
@ -273,26 +291,37 @@ Registry.prototype = {
lintedRegistry = new Registry();
lintedRegistry.rules = lodash.assign({}, this.rules);
ruleSets = lintedRegistry.buildRuleSets();
lintedRegistry = lintedRegistry.stripExtraConfigs();
debug("Linting with all possible rule combinations");
filenames = Object.keys(sourceCodes);
totalFilesLinting = filenames.length * ruleSets.length;
filenames.forEach(function(filename) {
debug("Linting file: " + filename);
ruleSetIdx = 0;
ruleSets.forEach(function(ruleSet) {
lintConfig = lodash.assign({}, config, {rules: ruleSet});
var lintResults = eslint.verify(sourceCodes[filename], lintConfig);
lintResults.forEach(function(result) {
lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
});
ruleSetIdx += 1;
if (cb) {
cb(totalFilesLinting); // eslint-disable-line callback-return
}
});
// Deallocate for GC
sourceCodes[filename] = null;
});
@ -312,8 +341,11 @@ Registry.prototype = {
*/
function extendFromRecommended(config) {
var newConfig = lodash.assign({}, config);
ConfigOps.normalizeToStrings(newConfig);
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) {

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

@ -4,7 +4,9 @@
* @copyright 2015 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
/* eslint no-use-before-define: 0 */
"use strict";
//------------------------------------------------------------------------------
@ -17,11 +19,14 @@ var debug = require("debug"),
ConfigOps = require("./config-ops"),
validator = require("./config-validator"),
Plugins = require("./plugins"),
resolveModule = require("resolve"),
pathUtil = require("../util/path-util"),
ModuleResolver = require("../util/module-resolver"),
pathIsInside = require("path-is-inside"),
stripComments = require("strip-json-comments"),
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"
];
var resolver = new ModuleResolver();
debug = debug("eslint:config-file");
/**
@ -92,6 +99,7 @@ function loadYAMLConfigFile(filePath) {
var yaml = require("js-yaml");
try {
// empty YAML file can be null, so always use
return yaml.safeLoad(readFile(filePath)) || {};
} catch (e) {
@ -152,7 +160,7 @@ function loadLegacyConfigFile(filePath) {
function loadJSConfigFile(filePath) {
debug("Loading JS config file: " + filePath);
try {
return require(filePath);
return requireUncached(filePath);
} catch (e) {
debug("Error reading JavaScript file: " + filePath);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
@ -186,9 +194,8 @@ function loadPackageJSONConfigFile(filePath) {
* @private
*/
function loadConfigFile(file) {
var config;
var filePath = file.filePath;
var config,
filePath = file.filePath;
switch (path.extname(filePath)) {
case ".js":
@ -232,6 +239,7 @@ function writeJSONConfigFile(config, filePath) {
debug("Writing JSON config file: " + filePath);
var content = stringify(config, {cmp: sortByKey, space: 4});
fs.writeFileSync(filePath, content, "utf8");
}
@ -249,6 +257,7 @@ function writeYAMLConfigFile(config, filePath) {
var yaml = require("js-yaml");
var content = yaml.safeDump(config, {sortKeys: true});
fs.writeFileSync(filePath, content, "utf8");
}
@ -263,6 +272,7 @@ function writeJSConfigFile(config, filePath) {
debug("Writing JS config file: " + filePath);
var content = "module.exports = " + stringify(config, {cmp: sortByKey, space: 4}) + ";";
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.
* If the config
* Determines the base directory for node packages referenced in a config file.
* 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.
* @returns {string} The lookup path for the file path.
* @returns {string} The base directory for the file path.
* @private
*/
function getLookupPath(configFilePath) {
function getBaseDir(configFilePath) {
// calculates the path of the project including ESLint as dependency
var projectPath = path.resolve(__dirname, "../../../");
if (configFilePath && pathIsInside(configFilePath, projectPath)) {
// 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
return projectPath;
/*
* default to ESLint project path since it's unlikely that plugins will be
* 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 {string} filePath The file path from which the configuration information
* was loaded.
* @param {string} [relativeTo] The path to resolve relative to.
* @returns {Object} A new configuration object with all of the "extends" fields
* loaded and merged.
* @private
*/
function applyExtends(config, filePath) {
function applyExtends(config, filePath, relativeTo) {
var configExtends = config.extends;
// normalize into an array for easier handling
@ -336,12 +365,18 @@ function applyExtends(config, filePath) {
config = configExtends.reduceRight(function(previousValue, parentPath) {
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");
} 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) ?
path.join(path.dirname(filePath), parentPath) :
parentPath
@ -350,11 +385,15 @@ function applyExtends(config, filePath) {
try {
debug("Loading " + parentPath);
return ConfigOps.merge(load(parentPath), previousValue);
return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue);
} 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;
throw e;
}
@ -372,16 +411,33 @@ function applyExtends(config, filePath) {
* @private
*/
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) === "@") {
// 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 + ")?)?$"),
scopedPackageNameRegex = new RegExp("^" + prefix + "(-|$)");
if (scopedPackageShortcutRegex.test(name)) {
name = name.replace(scopedPackageShortcutRegex, "$1/" + prefix);
} 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");
}
} else if (name.indexOf(prefix + "-") !== 0) {
@ -400,21 +456,23 @@ function normalizePackageName(name, prefix) {
* @private
*/
function resolve(filePath, relativeTo) {
if (isFilePath(filePath)) {
return { filePath: path.resolve(relativeTo || "", filePath) };
} else {
var normalizedPackageName;
if (filePath.indexOf("plugin:") === 0) {
var packagePath = filePath.substr(7, filePath.lastIndexOf("/") - 7);
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 };
} else {
filePath = resolveModule.sync(normalizePackageName(filePath, "eslint-config"), {
basedir: getLookupPath(relativeTo)
});
normalizedPackageName = normalizePackageName(filePath, "eslint-config");
debug("Attempting to resolve " + normalizedPackageName);
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
return { filePath: filePath };
}
}
@ -426,12 +484,15 @@ function resolve(filePath, relativeTo) {
* @param {string} filePath The filename or package name to load the configuration
* information from.
* @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.
* @private
*/
function load(filePath, applyEnvironments) {
var resolvedPath = resolve(filePath),
function load(filePath, applyEnvironments, relativeTo) {
var resolvedPath = resolve(filePath, relativeTo),
dirname = path.dirname(resolvedPath.filePath),
basedir = getBaseDir(dirname),
lookupPath = getLookupPath(dirname),
config = loadConfigFile(resolvedPath);
if (config) {
@ -441,23 +502,33 @@ function load(filePath, applyEnvironments) {
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
if (config.parser) {
config.parser = resolveModule.sync(config.parser, {
basedir: getLookupPath(path.dirname(path.resolve(filePath)))
});
if (isFilePath(config.parser)) {
config.parser = path.resolve(basedir || "", config.parser);
} else {
config.parser = resolver.resolve(config.parser, lookupPath);
}
}
// validate the configuration before continuing
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) {
config = applyExtends(config, filePath);
config = applyExtends(config, filePath, basedir);
}
if (config.env && applyEnvironments) {
// Merge in environment-specific globals and parserOptions.
config = ConfigOps.applyEnvironments(config);
}
@ -473,11 +544,13 @@ function load(filePath, applyEnvironments) {
module.exports = {
getBaseDir: getBaseDir,
getLookupPath: getLookupPath,
load: load,
resolve: resolve,
write: write,
applyExtends: applyExtends,
normalizePackageName: normalizePackageName,
CONFIG_FILES: CONFIG_FILES,
/**

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

@ -17,6 +17,7 @@ var util = require("util"),
ProgressBar = require("progress"),
autoconfig = require("./autoconfig.js"),
ConfigFile = require("./config-file"),
ConfigOps = require("./config-ops"),
getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles,
npmUtil = require("../util/npm-util"),
recConfig = require("../../conf/eslint.json"),
@ -39,6 +40,7 @@ function writeFile(config, format) {
// default is .js
var extname = ".js";
if (format === "YAML") {
extname = ".yml";
} 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
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
failingRegistry = registry.getFailingRulesRegistry();
Object.keys(failingRegistry.rules).forEach(function(ruleId) {
// If the rule is recommended, set it to error, otherwise disable it
disabledConfigs[ruleId] = (recRules.indexOf(ruleId) !== -1) ? 2 : 0;
});
@ -184,8 +188,9 @@ function configureRules(answers, config) {
bar.update(BAR_TOTAL);
// Log out some stats to let the user know what happened
var totalRules = Object.keys(newConfig.rules).length;
var enabledRules = Object.keys(newConfig.rules).filter(function(ruleId) {
var finalRuleIds = Object.keys(newConfig.rules),
totalRules = finalRuleIds.length;
var enabledRules = finalRuleIds.filter(function(ruleId) {
return (newConfig.rules[ruleId] !== 0);
}).length;
var resultMessage = [
@ -193,7 +198,10 @@ function configureRules(answers, config) {
"rules based on " + fileQty,
"file" + ((fileQty === 1) ? "." : "s.")
].join(" ");
log.info(resultMessage);
ConfigOps.normalizeToStrings(newConfig);
return newConfig;
}
@ -230,10 +238,10 @@ function processAnswers(answers) {
if (answers.source === "prompt") {
config.extends = "eslint:recommended";
config.rules.indent = [2, answers.indent];
config.rules.quotes = [2, answers.quotes];
config.rules["linebreak-style"] = [2, answers.linebreak];
config.rules.semi = [2, answers.semi ? "always" : "never"];
config.rules.indent = ["error", answers.indent];
config.rules.quotes = ["error", answers.quotes];
config.rules["linebreak-style"] = ["error", answers.linebreak];
config.rules.semi = ["error", answers.semi ? "always" : "never"];
}
installModules(config);
@ -242,6 +250,8 @@ function processAnswers(answers) {
config = configureRules(answers, config);
config = autoconfig.extendFromRecommended(config);
}
ConfigOps.normalizeToStrings(config);
return config;
}
@ -256,6 +266,7 @@ function getConfigForStyleGuide(guide) {
airbnb: {extends: "airbnb", plugins: ["react"]},
standard: {extends: "standard", plugins: ["standard"]}
};
if (!guides[guide]) {
throw new Error("You referenced an unsupported guide.");
}
@ -273,6 +284,7 @@ function getConfigForStyleGuide(guide) {
*/
function promptUser(callback) {
var config;
inquirer.prompt([
{
type: "list",
@ -387,11 +399,8 @@ function promptUser(callback) {
// early exit if you are using automatic style generation
if (earlyAnswers.source === "auto") {
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);
config = processAnswers(combinedAnswers);
installModules(config);
writeFile(config, earlyAnswers.format);
@ -408,7 +417,7 @@ function promptUser(callback) {
type: "list",
name: "indent",
message: "What style of indentation do you use?",
default: "tabs",
default: "tab",
choices: [{name: "Tabs", value: "tab"}, {name: "Spaces", value: 4}]
},
{
@ -441,6 +450,7 @@ function promptUser(callback) {
], function(answers) {
try {
var totalAnswers = lodash.assign({}, earlyAnswers, secondAnswers, answers);
config = processAnswers(totalAnswers);
installModules(config);
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");
var RULE_SEVERITY_STRINGS = ["off", "warn", "error"],
RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce(function(map, value, index) {
map[value] = index;
return map;
}, {});
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@ -99,6 +105,7 @@ module.exports = {
* @returns {Object} merged config object.
*/
merge: function deepmerge(target, src, combine, isRule) {
/*
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
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 dst = array && [] || {};
@ -130,7 +142,9 @@ module.exports = {
isRule = !!isRule;
if (array) {
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);
} else {
dst = dst.concat(target);
@ -176,7 +190,66 @@ module.exports = {
}
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) {
var res = [];
if (arr1.length === 0) {
return explodeArray(arr2);
}
@ -81,9 +82,11 @@ function combineArrays(arr1, arr2) {
function groupByProperty(objects) {
var groupedObj = objects.reduce(function(accumulator, obj) {
var prop = Object.keys(obj)[0];
accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj];
return accumulator;
}, {});
return Object.keys(groupedObj).map(function(prop) {
return groupedObj[prop];
});
@ -144,6 +147,7 @@ function groupByProperty(objects) {
*/
function combinePropertyObjects(objArr1, objArr2) {
var res = [];
if (objArr1.length === 0) {
return objArr2;
}
@ -155,6 +159,7 @@ function combinePropertyObjects(objArr1, objArr2) {
var combinedObj = {};
var obj1Props = Object.keys(obj1);
var obj2Props = Object.keys(obj2);
obj1Props.forEach(function(prop1) {
combinedObj[prop1] = obj1[prop1];
});
@ -201,10 +206,12 @@ RuleConfigSet.prototype = {
*/
addErrorSeverity: function(severity) {
severity = severity || 2;
this.ruleConfigs = this.ruleConfigs.map(function(config) {
config.unshift(severity);
return config;
});
// Add a single config at the beginning consisting of only the severity
this.ruleConfigs.unshift(severity);
},
@ -228,6 +235,7 @@ RuleConfigSet.prototype = {
objectConfigs: [],
add: function(property, values) {
var optionObj;
for (var idx = 0; idx < values.length; idx++) {
optionObj = {};
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) {
if (obj.properties[prop].enum) {
objectConfigSet.add(prop, obj.properties[prop].enum);
@ -266,15 +277,19 @@ RuleConfigSet.prototype = {
*/
function generateConfigsFromSchema(schema) {
var configSet = new RuleConfigSet();
if (Array.isArray(schema)) {
schema.forEach(function(opt) {
if (opt.enum) {
configSet.addEnums(opt.enum);
}
if (opt.type && opt.type === "object") {
configSet.addObject(opt);
}
if (opt.oneOf) {
// TODO (IanVS): not yet implemented
}
});
@ -289,9 +304,11 @@ function generateConfigsFromSchema(schema) {
*/
function createCoreRuleConfigs() {
var ruleList = loadRules();
return Object.keys(ruleList).reduce(function(accumulator, id) {
var rule = rules.get(id);
var schema = (typeof rule === "function") ? rule.schema : rule.meta.schema;
accumulator[id] = generateConfigsFromSchema(schema);
return accumulator;
}, {});

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

@ -13,7 +13,8 @@
var rules = require("../rules"),
Environments = require("./environments"),
schemaValidator = require("is-my-json-valid");
schemaValidator = require("is-my-json-valid"),
util = require("util");
var validators = {
rules: Object.create(null)
@ -83,7 +84,10 @@ function validateRuleOptions(id, options, source) {
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) {
validateRule(localOptions);
@ -97,7 +101,10 @@ function validateRuleOptions(id, options, source) {
if (!validSeverity) {
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) {
@ -136,6 +143,7 @@ function validateEnvironment(environment, source) {
source, ":\n",
"\tEnvironment key \"", env, "\" is unknown\n"
];
throw new Error(message.join(""));
}
});

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

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

109
tools/eslint/lib/eslint.js

@ -11,7 +11,7 @@
//------------------------------------------------------------------------------
var lodash = require("lodash"),
estraverse = require("./util/estraverse"),
Traverser = require("./util/traverser"),
escope = require("escope"),
Environments = require("./config/environments"),
blankScriptAST = require("../conf/blank-script.json"),
@ -43,16 +43,20 @@ var DEFAULT_PARSER = require("../conf/eslint.json").parser;
*/
function parseBooleanConfig(string, comment) {
var items = {};
// Collapse whitespace around : to make parsing easier
string = string.replace(/\s*:\s*/g, ":");
// Collapse whitespace around ,
string = string.replace(/\s*,\s*/g, ",");
string.split(/\s|,+/).forEach(function(name) {
if (!name) {
return;
}
var pos = name.indexOf(":"),
value;
if (pos !== -1) {
value = name.substring(pos + 1, name.length);
name = name.substring(0, pos);
@ -76,6 +80,7 @@ function parseBooleanConfig(string, comment) {
*/
function parseJsonConfig(string, location, messages) {
var items = {};
string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,");
try {
items = JSON.parse("{" + string + "}");
@ -103,8 +108,10 @@ function parseJsonConfig(string, location, messages) {
*/
function parseListConfig(string) {
var items = {};
// Collapse whitespace around ,
string = string.replace(/\s*,\s*/g, ",");
string.split(/,+/).forEach(function(name) {
name = name.trim();
if (!name) {
@ -136,6 +143,7 @@ function addDeclaredGlobals(program, globalScope, config) {
if (config.env[name]) {
var env = Environments.get(name),
environmentGlobals = env && env.globals;
if (environmentGlobals) {
lodash.assign(declaredGlobals, environmentGlobals);
}
@ -148,6 +156,7 @@ function addDeclaredGlobals(program, globalScope, config) {
Object.keys(declaredGlobals).forEach(function(name) {
var variable = globalScope.set.get(name);
if (!variable) {
variable = new escope.Variable(name, globalScope);
variable.eslintExplicitGlobal = false;
@ -159,6 +168,7 @@ function addDeclaredGlobals(program, globalScope, config) {
Object.keys(explicitGlobals).forEach(function(name) {
var variable = globalScope.set.get(name);
if (!variable) {
variable = new escope.Variable(name, globalScope);
variable.eslintExplicitGlobal = true;
@ -172,23 +182,33 @@ function addDeclaredGlobals(program, globalScope, config) {
// mark all exported variables as such
Object.keys(exportedGlobals).forEach(function(name) {
var variable = globalScope.set.get(name);
if (variable) {
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) {
var name = reference.identifier.name;
var variable = globalScope.set.get(name);
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;
variable.references.push(reference);
return false;
}
return true;
});
}
@ -234,15 +254,17 @@ function enableReporting(reportingConfig, start, rulesToEnable) {
if (rulesToEnable.length) {
rulesToEnable.forEach(function(rule) {
for (i = reportingConfig.length - 1; i >= 0; i--) {
if (!reportingConfig[i].end && reportingConfig[i].rule === rule ) {
if (!reportingConfig[i].end && reportingConfig[i].rule === rule) {
reportingConfig[i].end = start;
break;
}
}
});
} else {
// find all previous disabled locations if they was started as list of rules
var prevStart;
for (i = reportingConfig.length - 1; i >= 0; i--) {
if (prevStart && prevStart !== reportingConfig[i].start) {
break;
@ -280,7 +302,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
ast.comments.forEach(function(comment) {
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) {
value = value.substring(match.index + match[1].length);
@ -310,8 +332,10 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
case "eslint":
var items = parseJsonConfig(value, comment.loc, messages);
Object.keys(items).forEach(function(name) {
var ruleValue = items[name];
validator.validateRuleOptions(name, ruleValue, filename + " line " + comment.loc.start.line);
commentRules[name] = ruleValue;
});
@ -319,11 +343,13 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
// no default
}
} else {
// comment.type === "Line"
} else { // comment.type === "Line"
if (match[1] === "eslint-disable-line") {
disableReporting(reportingConfig, { "line": comment.loc.start.line, "column": 0 }, 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
Object.keys(commentConfig.env).forEach(function(name) {
var env = Environments.get(name);
if (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++) {
var ignore = reportingConfig[i];
if ((!ignore.rule || ignore.rule === ruleId) &&
(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)))) {
@ -380,6 +408,7 @@ function prepareConfig(config) {
if (typeof config.rules === "object") {
Object.keys(config.rules).forEach(function(k) {
var rule = config.rules[k];
if (rule === null) {
throw new Error("Invalid config for rule '" + k + "'\.");
}
@ -395,6 +424,7 @@ function prepareConfig(config) {
if (typeof config.env === "object") {
Object.keys(config.env).forEach(function(envName) {
var env = Environments.get(envName);
if (config.env[envName] && env && env.parserOptions) {
parserOptions = ConfigOps.merge(parserOptions, env.parserOptions);
}
@ -418,7 +448,7 @@ function prepareConfig(config) {
// can't have global return inside of modules
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) {
preparedConfig.parserOptions.ecmaVersion = 6;
}
@ -462,8 +492,10 @@ function createStubRule(message) {
function getRuleReplacementMessage(ruleId) {
if (ruleId in replacements.rules) {
var newRules = replacements.rules[ruleId];
return "Rule \'" + ruleId + "\' was removed and replaced by: " + newRules.join(", ");
}
return null;
}
@ -478,6 +510,7 @@ function findEslintEnv(text) {
var match, retv;
eslintEnvPattern.lastIndex = 0;
while ((match = eslintEnvPattern.exec(text))) {
retv = lodash.assign(retv || {}, parseListConfig(match[1]));
}
@ -492,9 +525,12 @@ function findEslintEnv(text) {
* @returns {string} The stripped 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) {
return text.slice(1);
}
@ -518,7 +554,7 @@ module.exports = (function() {
scopeMap = null,
scopeManager = null,
currentFilename = null,
controller = null,
traverser = null,
reportingConfig = [],
sourceCode = null;
@ -636,7 +672,7 @@ module.exports = (function() {
currentScopes = null;
scopeMap = null;
scopeManager = null;
controller = null;
traverser = null;
reportingConfig = [];
sourceCode = null;
};
@ -689,6 +725,7 @@ module.exports = (function() {
// search and apply "eslint-env *".
var envInFile = findEslintEnv(text || textOrSourceCode.text);
if (envInFile) {
if (!config || !config.env) {
config = lodash.assign({}, config || {}, {env: envInFile});
@ -703,6 +740,7 @@ module.exports = (function() {
// only do this for text
if (text !== null) {
// there's no input, just exit here
if (text.trim().length === 0) {
sourceCode = new SourceCode(text, blankScriptAST);
@ -734,6 +772,9 @@ module.exports = (function() {
config = modifyConfigsFromComments(currentFilename, ast, config, reportingConfig, messages);
}
// ensure that severities are normalized in the config
ConfigOps.normalize(config);
// enable appropriate rules
Object.keys(config.rules).filter(function(key) {
return getRuleSeverity(config.rules[key]) > 0;
@ -744,8 +785,10 @@ module.exports = (function() {
rule;
ruleCreator = rules.get(key);
if (!ruleCreator) {
var replacementMsg = getRuleReplacementMessage(key);
if (replacementMsg) {
ruleCreator = createStubRule(replacementMsg);
} else {
@ -761,6 +804,7 @@ module.exports = (function() {
var ruleContext = new RuleContext(
key, api, severity, options,
config.settings, config.parserOptions, config.parser, ruleCreator.meta);
rule = ruleCreator.create ? ruleCreator.create(ruleContext) :
ruleCreator(ruleContext);
@ -779,19 +823,21 @@ module.exports = (function() {
// save config so rules can access as necessary
currentConfig = config;
controller = new estraverse.Controller();
traverser = new Traverser();
ecmaFeatures = currentConfig.parserOptions.ecmaFeatures || {};
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, {
ignoreEval: true,
nodejsScope: ecmaFeatures.globalReturn,
impliedStrict: ecmaFeatures.impliedStrict,
ecmaVersion: ecmaVersion,
sourceType: currentConfig.parserOptions.sourceType || "script"
sourceType: currentConfig.parserOptions.sourceType || "script",
fallback: Traverser.getKeys
});
currentScopes = scopeManager.scopes;
/*
@ -799,11 +845,14 @@ module.exports = (function() {
* lookup in getScope.
*/
scopeMap = [];
currentScopes.forEach(function(scope, index) {
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]) {
scopeMap[range] = index;
}
@ -822,15 +871,17 @@ module.exports = (function() {
}
var eventGenerator = new NodeEventGenerator(api);
eventGenerator = new CodePathAnalyzer(eventGenerator);
eventGenerator = new CommentEventGenerator(eventGenerator, sourceCode);
/*
* Each node has a type property. Whenever a particular type of node is found,
* an event is fired. This allows any listeners to automatically be informed
* that this type of node has been found and react accordingly.
* Each node has a type property. Whenever a particular type of
* node is found, an event is fired. This allows any listeners to
* automatically be informed that this type of node has been found
* and react accordingly.
*/
controller.traverse(ast, {
traverser.traverse(ast, {
enter: function(node, parent) {
node.parent = parent;
eventGenerator.enterNode(node);
@ -884,6 +935,7 @@ module.exports = (function() {
message = location;
location = node.loc.start;
}
// else, assume location was provided, so node may be omitted
if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) {
@ -966,7 +1018,7 @@ module.exports = (function() {
* @returns {ASTNode[]} Array of objects representing ancestors.
*/
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.
*/
api.getScope = function() {
var parents = controller.parents(),
var parents = traverser.parents(),
scope = currentScopes[0];
// Don't do this for Program nodes - they have no parents
if (parents.length) {
// 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 (["BlockStatement", "SwitchStatement", "CatchClause", "FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
parents.push(current);
@ -1039,7 +1092,7 @@ module.exports = (function() {
return true;
}
}
} while ( (scope = scope.upper) );
} while ((scope = scope.upper));
return false;
};

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

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

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

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

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

@ -36,6 +36,7 @@ function pluralize(word, count) {
function renderSummary(totalErrors, totalWarnings) {
var totalProblems = totalErrors + totalWarnings;
var renderedText = totalProblems + " " + pluralize("problem", totalProblems);
if (totalProblems !== 0) {
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.
*/
function renderMessages(messages, parentIndex) {
/**
* Get HTML (table row) describing a message.
* @param {Object} message Message.

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

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

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

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

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

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

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

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

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

@ -12,7 +12,8 @@ var lodash = require("lodash"),
fs = require("fs"),
path = require("path"),
debug = require("debug"),
ignore = require("ignore");
ignore = require("ignore"),
pathUtil = require("./util/path-util");
debug = debug("eslint:ignored-paths");
@ -23,8 +24,8 @@ debug = debug("eslint:ignored-paths");
var ESLINT_IGNORE_FILENAME = ".eslintignore";
var DEFAULT_IGNORE_PATTERNS = [
"/node_modules/",
"/bower_components/"
"/node_modules/*",
"/bower_components/*"
];
var DEFAULT_OPTIONS = {
dotfiles: false,
@ -46,59 +47,8 @@ function findIgnoreFile(cwd) {
cwd = cwd || DEFAULT_OPTIONS.cwd;
var ignoreFilePath = path.resolve(cwd, ESLINT_IGNORE_FILENAME);
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);
return fs.existsSync(ignoreFilePath) ? ignoreFilePath : "";
}
/**
@ -142,35 +92,40 @@ function IgnoredPaths(options) {
* @returns {array} raw ignore rules
*/
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.baseDir = ".";
this.baseDir = options.cwd;
this.ig = {
custom: new ignore.Ignore({
twoGlobstars: true,
ignore: []
}),
default: new ignore.Ignore({
twoGlobstars: true,
ignore: []
})
custom: ignore(),
default: 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) {
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);
if (options.ignore !== false) {
var ignorePath;
if (options.ignorePattern) {
addPattern(this.ig.custom, options.ignorePattern);
addPattern(this.ig.default, options.ignorePattern);
}
if (options.ignorePath) {
@ -198,8 +153,9 @@ function IgnoredPaths(options) {
if (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.default, ignorePath);
}
}
@ -217,14 +173,15 @@ function IgnoredPaths(options) {
IgnoredPaths.prototype.contains = function(filepath, category) {
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")) {
result = result || (this.ig.default.filter([filepath]).length === 0);
result = result || (this.ig.default.filter([relativePath]).length === 0);
}
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;

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

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

3
tools/eslint/lib/logging.js

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

7
tools/eslint/lib/options.js

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

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

@ -73,6 +73,7 @@ var PASSTHROUGHS = [
* @param {Object} meta The metadata of the rule
*/
function RuleContext(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta) {
// public.
this.id = ruleId;
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) {
// All functions expected to have less arguments than 5.
this[name] = function(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) {
var newRules = loadRules(rulesDir, cwd);
Object.keys(newRules).forEach(function(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) {
var parent = node.parent;
return (
parent.type === "CallExpression" &&
parent.callee.type === "MemberExpression" &&
@ -46,6 +47,7 @@ function isArgumentOfMethodCall(node, index, object, property) {
* @returns {boolean} `true` if the node is a property descriptor.
*/
function isPropertyDescriptor(node) {
// Object.defineProperty(obj, "foo", {set: ...})
if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") ||
isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty")
@ -53,9 +55,12 @@ function isPropertyDescriptor(node) {
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;
return node.type === "ObjectExpression" && (
isArgumentOfMethodCall(node, 1, "Object", "create") ||
isArgumentOfMethodCall(node, 1, "Object", "defineProperties")
@ -106,6 +111,7 @@ module.exports = {
var property = node.properties[i];
var propToCheck = "";
if (property.kind === "init") {
if (isDescriptor && !property.computed) {
propToCheck = property.key.name;
@ -124,6 +130,7 @@ module.exports = {
break;
default:
// 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 + "'",
fix: function(fixer) {
var nextToken = context.getSourceCode().getTokenAfter(token);
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
});
@ -100,6 +101,7 @@ module.exports = {
message: "There should be no space before '" + token.value + "'",
fix: function(fixer) {
var previousToken = context.getSourceCode().getTokenBefore(token);
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) {
while (node) {
var parent = node.parent;
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 "ConditionalExpression":
node = parent;
@ -124,6 +127,7 @@ function isCallbackOfArrayMethod(node) {
// })());
case "ReturnStatement":
var func = astUtils.getUpperFunction(parent);
if (func === null || !astUtils.isCallee(func)) {
return false;
}
@ -195,6 +199,7 @@ module.exports = function(context) {
}
return {
// Stacks this function's information.
"onCodePathStart": function(codePath, node) {
funcInfo = {

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

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

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

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

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

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

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

@ -56,6 +56,7 @@ module.exports = function(context) {
* @returns {void}
*/
function checkSpacingInsideBraces(node) {
// Gets braces and the first/last token of content.
var openBrace = getOpenBrace(node);
var closeBrace = context.getLastToken(node);

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

@ -57,8 +57,12 @@ module.exports = function(context) {
var blockProperties = arguments;
return function(node) {
[].forEach.call(blockProperties, function(blockProp) {
var block = node[blockProp], previousToken, curlyToken, curlyTokenEnd, curlyTokensOnSameLine;
Array.prototype.forEach.call(blockProperties, function(blockProp) {
var block = node[blockProp],
previousToken,
curlyToken,
curlyTokenEnd,
allOnSameLine;
if (!isBlock(block)) {
return;
@ -67,21 +71,27 @@ module.exports = function(context) {
previousToken = sourceCode.getTokenBefore(block);
curlyToken = sourceCode.getFirstToken(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) {
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);
}
if (!block.body.length || curlyTokensOnSameLine && params.allowSingleLine) {
if (!block.body.length) {
return;
}
if (curlyToken.loc.start.line === block.body[0].loc.start.line) {
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);
}
});
@ -106,7 +116,7 @@ module.exports = function(context) {
if (style === "1tbs") {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line &&
node.consequent.type === "BlockStatement" &&
isCurlyPunctuator(tokens[0]) ) {
isCurlyPunctuator(tokens[0])) {
context.report(node.alternate, CLOSE_MESSAGE);
}
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
@ -172,6 +182,7 @@ module.exports = function(context) {
*/
function checkSwitchStatement(node) {
var tokens;
if (node.cases && node.cases.length) {
tokens = sourceCode.getTokensBefore(node.cases[0], 2);
} else {

3
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
*/
function isCallbackExpression(node, parentNode) {
// ensure the parent node exists and is an expression
if (!parentNode || parentNode.type !== "ExpressionStatement") {
return false;
@ -86,7 +87,7 @@ module.exports = function(context) {
lastItem, parentType;
// if our parent is a return we know we're ok
if (closestBlock.type === "ReturnStatement" ) {
if (closestBlock.type === "ReturnStatement") {
return;
}

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

@ -54,7 +54,11 @@ module.exports = function(context) {
return {
"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, ""),
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
@ -83,6 +87,7 @@ module.exports = function(context) {
// Properties have their own rules
} else if (node.parent.type === "Property") {
// "never" check properties
if (properties === "never") {
return;

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

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

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

@ -11,8 +11,18 @@
//------------------------------------------------------------------------------
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
@ -58,7 +68,7 @@ module.exports = function(context) {
*/
function increaseComplexity() {
if (fns.length) {
fns[fns.length - 1] ++;
fns[fns.length - 1]++;
}
}
@ -69,6 +79,7 @@ module.exports = function(context) {
* @private
*/
function increaseSwitchComplexity(node) {
// Avoiding `default`
if (node.test) {
increaseComplexity(node);
@ -82,6 +93,7 @@ module.exports = function(context) {
* @private
*/
function increaseLogicalComplexity(node) {
// Avoiding &&
if (node.operator === "||") {
increaseComplexity(node);
@ -115,8 +127,26 @@ module.exports = function(context) {
};
module.exports.schema = [
{
"oneOf": [
{
"type": "integer",
"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";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
@ -34,20 +40,25 @@ module.exports = function(context) {
function checkLastSegment(node) {
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 ||
funcInfo.codePath.currentSegments.every(isUnreachable)
funcInfo.codePath.currentSegments.every(isUnreachable) ||
astUtils.isES5Constructor(node)
) {
return;
}
// Adjust a location and a message.
if (node.type === "Program") {
// The head of program.
loc = {line: 1, column: 0};
type = "program";
} else if (node.type === "ArrowFunctionExpression") {
// `=>` token
loc = context.getSourceCode().getTokenBefore(node.body).loc.start;
type = "function";
@ -55,10 +66,12 @@ module.exports = function(context) {
node.parent.type === "MethodDefinition" ||
(node.parent.type === "Property" && node.parent.method)
) {
// Method name.
loc = node.parent.key.loc.start;
type = "method";
} else {
// Function name or `function` keyword.
loc = (node.id || node).loc.start;
type = "function";
@ -74,6 +87,7 @@ module.exports = function(context) {
}
return {
// Initializes/Disposes state of each code path.
"onCodePathStart": function(codePath) {
funcInfo = {

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

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

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

@ -36,19 +36,25 @@ function isConstructorFunction(node) {
//------------------------------------------------------------------------------
module.exports = function(context) {
// {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
// Information for each constructor.
// - upper: Information of the upper constructor.
// - hasExtends: A flag which shows whether own class has a valid `extends`
// part.
// - scope: The scope of own class.
// - codePath: The code path object of the constructor.
/*
* {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
* Information for each constructor.
* - upper: Information of the upper constructor.
* - hasExtends: A flag which shows whether own class has a valid `extends`
* part.
* - scope: The scope of own class.
* - codePath: The code path object of the constructor.
*/
var funcInfo = null;
// {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
// Information for each code path segment.
// - calledInSomePaths: A flag of be called `super()` in some code paths.
// - calledInEveryPaths: A flag of be called `super()` in all code paths.
/*
* {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
* Information for each code path segment.
* - 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);
/**
@ -66,10 +72,22 @@ module.exports = function(context) {
* @returns {boolean} The flag which shows `super()` is called in all paths.
*/
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 {
/**
* Stacks a constructor information.
* @param {CodePath} codePath - A code path which was started.
@ -77,21 +95,28 @@ module.exports = function(context) {
* @returns {void}
*/
"onCodePathStart": function(codePath, node) {
if (!isConstructorFunction(node)) {
return;
}
if (isConstructorFunction(node)) {
// Class > ClassBody > MethodDefinition > FunctionExpression
var classNode = node.parent.parent.parent;
funcInfo = {
upper: funcInfo,
isConstructor: true,
hasExtends: Boolean(
classNode.superClass &&
!astUtils.isNullOrUndefined(classNode.superClass)
),
scope: context.getScope(),
codePath: codePath
};
} else {
funcInfo = {
upper: funcInfo,
isConstructor: false,
hasExtends: false,
codePath: codePath
};
}
},
/**
@ -102,13 +127,12 @@ module.exports = function(context) {
* @returns {void}
*/
"onCodePathEnd": function(codePath, node) {
if (!isConstructorFunction(node)) {
return;
}
// Skip if own class which has a valid `extends` part.
var hasExtends = funcInfo.hasExtends;
funcInfo = funcInfo.upper;
if (!hasExtends) {
return;
}
@ -117,6 +141,7 @@ module.exports = function(context) {
var segments = codePath.returnedSegments;
var calledInEveryPaths = segments.every(isCalledInEveryPath);
var calledInSomePaths = segments.some(isCalledInSomePath);
if (!calledInEveryPaths) {
context.report({
message: calledInSomePaths ?
@ -133,52 +158,110 @@ module.exports = function(context) {
* @returns {void}
*/
"onCodePathSegmentStart": function(segment) {
// Skip if this is not in a constructor of a class which has a valid
// `extends` part.
if (!(
funcInfo &&
funcInfo.hasExtends &&
funcInfo.scope === context.getScope().variableScope
)) {
/*
* Skip if this is not in a constructor of a class which has a
* valid `extends` part.
*/
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
return;
}
// Initialize info.
var info = segInfoMap[segment.id] = {
calledInSomePaths: false,
calledInEveryPaths: false
calledInEveryPaths: false,
validNodes: []
};
// When there are previous segments, aggregates these.
var prevSegments = segment.prevSegments;
if (prevSegments.length > 0) {
info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
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()`.
* @param {ASTNode} node - A CallExpression node to check.
* @returns {void}
*/
"CallExpression:exit": function(node) {
// Skip if the node is not `super()`.
if (node.callee.type !== "Super") {
return;
}
// Skip if this is not in a constructor.
if (!(funcInfo && funcInfo.scope === context.getScope().variableScope)) {
if (!(funcInfo && funcInfo.isConstructor)) {
return;
}
// Reports if needed.
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 duplicate = false;
for (var i = 0; i < segments.length; ++i) {
var info = segInfoMap[segments[i].id];
@ -191,10 +274,15 @@ module.exports = function(context) {
message: "Unexpected duplicate 'super()'.",
node: node
});
} else {
info.validNodes.push(node);
}
} 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({
message: "Unexpected 'super()'.",
node: node

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

@ -35,6 +35,7 @@ module.exports = function(context) {
function isCollapsedOneLiner(node) {
var before = context.getTokenBefore(node),
last = context.getLastToken(node);
return before.loc.start.line === last.loc.end.line;
}
@ -197,6 +198,7 @@ module.exports = function(context) {
*/
function prepareIfChecks(node) {
var preparedChecks = [];
do {
preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition"));
if (node.alternate && node.alternate.type !== "IfStatement") {
@ -207,8 +209,12 @@ module.exports = function(context) {
} while (node);
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) {
if (preparedCheck.expected !== null) {
return preparedCheck.expected;

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

@ -34,8 +34,11 @@ module.exports = function(context) {
"SwitchStatement": function(node) {
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;
}
@ -49,6 +52,7 @@ module.exports = function(context) {
var comments;
var lastCase = last(node.cases);
comments = context.getComments(lastCase).trailing;
if (comments.length) {

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

@ -15,6 +15,8 @@ var astUtils = require("../ast-utils");
module.exports = function(context) {
var config = context.options[0],
onObject;
// default to onObject if no preference is passed
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 allowPattern;
if (options.allowPattern) {
allowPattern = new RegExp(options.allowPattern);
}
@ -29,7 +30,7 @@ module.exports = function(context) {
(allowKeywords || keywords.indexOf("" + node.property.value) === -1)
) {
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 (
@ -37,7 +38,7 @@ module.exports = function(context) {
!node.computed &&
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 {
"Program": function checkBadEOF(node) {
// Get the whole source code, not for node only.
var src = context.getSource(),
location = {column: 1},
@ -24,6 +25,7 @@ module.exports = function(context) {
linebreak = linebreakStyle === "unix" ? "\n" : "\r\n";
if (src[src.length - 1] !== "\n") {
// file is not newline-terminated
location.line = src.split(/\n/g).length;
context.report({

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

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

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

@ -45,6 +45,7 @@ function findReference(scope, node) {
*/
function isShadowed(scope, node) {
var reference = findReference(scope, node);
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) {
var firstChar = stringToCheck[0];
return firstChar === "^";
}
@ -33,6 +34,7 @@ module.exports = function(context) {
function matchesConfiguredErrorName(name) {
if (isPattern(errorArgument)) {
var regexp = new RegExp(errorArgument);
return regexp.test(name);
}
return name === errorArgument;

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

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

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

@ -71,6 +71,7 @@ module.exports = function(context) {
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
// return early if properties is false
if (!properties) {
return;
@ -95,6 +96,7 @@ module.exports = function(context) {
// Properties have their own rules
} else if (node.parent.type === "Property") {
// return early if properties is false
if (!properties) {
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
THE SOFTWARE.
*/
"use strict";
//------------------------------------------------------------------------------
@ -61,8 +62,10 @@ module.exports = function(context) {
if (context.options[1]) {
var opts = context.options[1];
options.SwitchCase = opts.SwitchCase || 0;
var variableDeclaratorRules = opts.VariableDeclarator;
if (typeof variableDeclaratorRules === "number") {
options.VariableDeclarator = {
var: variableDeclaratorRules,
@ -194,21 +197,38 @@ module.exports = function(context) {
}
/**
* Check indent for nodes list
* @param {ASTNode[]} nodes list of node objects
* Check indent for node
* @param {ASTNode} node Node to check
* @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) {
function checkNodeIndent(node, indent, excludeCommas) {
var nodeIndent = getNodeIndent(node, false, excludeCommas);
if (
node.type !== "ArrayExpression" && node.type !== "ObjectExpression" &&
nodeIndent !== indent && isNodeFirstInLine(node)
) {
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) {
var startIndent = getNodeIndent(node, false);
if (startIndent !== firstLineIndent && isNodeFirstInLine(node)) {
report(
node,
@ -303,26 +324,30 @@ module.exports = function(context) {
*/
function checkIndentInFunctionBlock(node) {
// Search first caller in chain.
// Ex.:
//
// Models <- Identifier
// .User
// .find()
// .exec(function() {
// // function body
// });
//
// Looks for 'Models'
/*
* Search first caller in chain.
* Ex.:
*
* Models <- Identifier
* .User
* .find()
* .exec(function() {
* // function body
* });
*
* Looks for 'Models'
*/
var calleeNode = node.parent; // FunctionExpression
var indent;
if (calleeNode.parent &&
(calleeNode.parent.type === "Property" ||
calleeNode.parent.type === "ArrayExpression")) {
// If function is part of array or object, comma can be put at left
indent = getNodeIndent(calleeNode, false, false);
} else {
// If function is standalone, simple calculate indent
indent = getNodeIndent(calleeNode);
}
@ -348,6 +373,7 @@ module.exports = function(context) {
// check if the node is inside a variable
var parentVarNode = getVariableDeclaratorNode(node);
if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) {
indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
}
@ -393,6 +419,7 @@ module.exports = function(context) {
* @returns {void}
*/
function checkIndentInArrayOrObjectBlock(node) {
// Skip inline
if (isSingleLineNode(node)) {
return;
@ -429,9 +456,15 @@ module.exports = function(context) {
nodeIndent = getNodeIndent(effectiveParent);
if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) {
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]);
} 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;
}
}
@ -447,8 +480,10 @@ module.exports = function(context) {
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)) {
elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
}
@ -457,6 +492,7 @@ module.exports = function(context) {
checkNodesIndent(elements, elementsIndent, true);
if (elements.length > 0) {
// Skip last block line check if last item in same line
if (elements[elements.length - 1].loc.end.line === node.loc.end.line) {
return;
@ -472,7 +508,7 @@ module.exports = function(context) {
* @returns {boolean} True if it or its body is a block statement
*/
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");
}
@ -482,6 +518,7 @@ module.exports = function(context) {
* @returns {void}
*/
function blockIndentationCheck(node) {
// Skip inline blocks
if (isSingleLineNode(node)) {
return;
@ -499,10 +536,12 @@ module.exports = function(context) {
var indent;
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 = [
"IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement"
"IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration"
];
if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) {
@ -570,6 +609,7 @@ module.exports = function(context) {
var tokenBeforeLastElement = context.getTokenBefore(lastElement);
if (tokenBeforeLastElement.value === ",") {
// Special case for comma-first syntax where the semicolon is indented
checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement));
} else {
@ -620,6 +660,7 @@ module.exports = function(context) {
return {
"Program": function(node) {
if (node.body.length > 0) {
// Root nodes should have no indent
checkNodesIndent(node.body, getNodeIndent(node));
}
@ -660,9 +701,11 @@ module.exports = function(context) {
},
"SwitchStatement": function(node) {
// Switch is not a 'BlockStatement'
var switchIndent = getNodeIndent(node);
var caseIndent = expectedCaseIndent(node, switchIndent);
checkNodesIndent(node.cases, caseIndent);
@ -670,11 +713,13 @@ module.exports = function(context) {
},
"SwitchCase": function(node) {
// Skip inline cases
if (isSingleLineNode(node)) {
return;
}
var caseIndent = expectedCaseIndent(node);
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 params = context.options[1] || {};
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
@ -65,6 +66,7 @@ module.exports = function(context) {
id = declaration.id,
initialized = isInitialized(declaration),
isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent);
if (id.type !== "Identifier") {
continue;
}

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

@ -331,6 +331,7 @@ module.exports = function(context) {
*/
function verifySpacing(node, lineOptions) {
var actual = getPropertyWhitespace(node);
if (actual) { // Object literal getters/setters lack colons
report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode);
report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode);
@ -359,7 +360,7 @@ module.exports = function(context) {
return {
"ObjectExpression": function(node) {
if (isSingleLine(node)) {
verifyListSpacing(node.properties);
verifyListSpacing(node.properties.filter(isKeyValueProperty));
} else {
verifyAlignment(node);
}
@ -370,7 +371,7 @@ module.exports = function(context) {
return {
"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;
var prevToken = sourceCode.getTokenBefore(token);
if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) &&
@ -109,6 +110,7 @@ module.exports = function(context) {
pattern = pattern || PREV_TOKEN;
var prevToken = sourceCode.getTokenBefore(token);
if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) &&
@ -138,6 +140,7 @@ module.exports = function(context) {
pattern = pattern || NEXT_TOKEN;
var nextToken = sourceCode.getTokenAfter(token);
if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) &&
@ -167,6 +170,7 @@ module.exports = function(context) {
pattern = pattern || NEXT_TOKEN;
var nextToken = sourceCode.getTokenAfter(token);
if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) &&
@ -209,6 +213,7 @@ module.exports = function(context) {
if (override) {
var thisBefore = ("before" in override) ? override.before : before;
var thisAfter = ("after" in override) ? override.after : after;
retv[key] = {
before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
@ -269,6 +274,7 @@ module.exports = function(context) {
*/
function checkSpacingAroundFirstToken(node) {
var firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && firstToken.type === "Keyword") {
checkSpacingAround(firstToken);
}
@ -286,6 +292,7 @@ module.exports = function(context) {
*/
function checkSpacingBeforeFirstToken(node) {
var firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && firstToken.type === "Keyword") {
checkSpacingBefore(firstToken);
}
@ -301,6 +308,7 @@ module.exports = function(context) {
function checkSpacingAroundTokenBefore(node) {
if (node) {
var token = sourceCode.getTokenBefore(node);
while (token.type !== "Keyword") {
token = sourceCode.getTokenBefore(token);
}
@ -382,6 +390,7 @@ module.exports = function(context) {
// `of` is not a keyword token.
var token = sourceCode.getTokenBefore(node.right);
while (token.value !== "of") {
token = sourceCode.getTokenBefore(token);
}
@ -402,11 +411,13 @@ module.exports = function(context) {
*/
function checkSpacingForModuleDeclaration(node) {
var firstToken = sourceCode.getFirstToken(node);
checkSpacingBefore(firstToken, PREV_TOKEN_M);
checkSpacingAfter(firstToken, NEXT_TOKEN_M);
if (node.source) {
var fromToken = sourceCode.getTokenBefore(node.source);
checkSpacingBefore(fromToken, PREV_TOKEN_M);
checkSpacingAfter(fromToken, NEXT_TOKEN_M);
}
@ -421,6 +432,7 @@ module.exports = function(context) {
*/
function checkSpacingForImportNamespaceSpecifier(node) {
var asToken = sourceCode.getFirstToken(node, 1);
checkSpacingBefore(asToken, PREV_TOKEN_M);
}
@ -440,11 +452,13 @@ module.exports = function(context) {
node,
node.static ? 1 : 0
);
checkSpacingAround(token);
}
}
return {
// Statements
DebuggerStatement: 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
* @copyright 2015 Varun Verma. All rights reserverd.
* @copyright 2015 James Whitney. All rights reserved.
@ -50,6 +50,7 @@ module.exports = function(context) {
range;
var i = 0;
while ((match = pattern.exec(source)) !== null) {
i++;
if (match[0] === expectedLFChars) {

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

@ -33,6 +33,7 @@ function getEmptyLineNums(lines) {
}).map(function(line) {
return line.num;
});
return emptyLines;
}
@ -43,9 +44,11 @@ function getEmptyLineNums(lines) {
*/
function getCommentLineNums(comments) {
var lines = [];
comments.forEach(function(token) {
var start = token.loc.start.line;
var end = token.loc.end.line;
lines.push(start, end);
});
return lines;
@ -68,6 +71,7 @@ function contains(val, array) {
module.exports = function(context) {
var options = context.options[0] ? lodash.assign({}, context.options[0]) : {};
options.beforeLineComment = options.beforeLineComment || false;
options.afterLineComment = options.afterLineComment || false;
options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
@ -76,6 +80,7 @@ module.exports = function(context) {
options.allowBlockEnd = options.allowBlockEnd || false;
var sourceCode = context.getSourceCode();
/**
* Returns whether or not comments are on lines starting with or ending with code
* @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.
*/
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.
*/
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 = [],
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
@ -104,8 +115,26 @@ module.exports = function(context) {
};
module.exports.schema = [
{
"oneOf": [
{
"type": "integer",
"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) {
// 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
// - 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
/*
* Inspired by 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
* - 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 = /[^:/?#]:\/\/[^?#]/;
/**
@ -28,10 +32,12 @@ module.exports = function(context) {
*/
function computeLineLength(line, tabWidth) {
var extraCharacterCount = 0;
line.replace(/\t/g, function(match, offset) {
var totalOffset = offset + extraCharacterCount,
previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0,
spaceCount = tabWidth - previousTabStopOffset;
extraCharacterCount += spaceCount - 1; // -1 for the replaced tab
});
return line.length + extraCharacterCount;
@ -40,10 +46,12 @@ module.exports = function(context) {
// The options object must be the last option specified…
var lastOption = context.options[context.options.length - 1];
var options = typeof lastOption === "object" ? Object.create(lastOption) : {};
// …but max code length…
if (typeof context.options[0] === "number") {
options.code = context.options[0];
}
// …and tabWidth can be optionally specified directly as integers.
if (typeof context.options[1] === "number") {
options.tabWidth = context.options[1];
@ -104,6 +112,7 @@ module.exports = function(context) {
* @returns {string} Line without comment and trailing whitepace
*/
function stripTrailingComment(line, lineNumber, comment) {
// loc.column is zero-indexed
return line.slice(0, comment.loc.start.column).replace(/\s+$/, "");
}
@ -115,26 +124,38 @@ module.exports = function(context) {
* @private
*/
function checkProgramForMaxLength(node) {
// split (honors line-ending)
var lines = context.getSourceLines(),
// 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
commentsIndex = 0;
lines.forEach(function(line, i) {
// i is zero-indexed, line numbers are one-indexed
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;
// 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) {
// iterate over comments until we find one past the current line
do {
var comment = comments[++commentsIndex];
} while (comment && comment.loc.start.line <= lineNumber);
// and step back by one
comment = comments[--commentsIndex];
@ -146,6 +167,7 @@ module.exports = function(context) {
}
if (ignorePattern && ignorePattern.test(line) ||
ignoreUrls && URL_REGEXP.test(line)) {
// ignore this line
return;
}

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

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

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

@ -13,7 +13,18 @@
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.
@ -40,7 +51,25 @@ module.exports = function(context) {
module.exports.schema = [
{
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer",
"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 = [],
maxStatements = context.options[0] || 10,
option = context.options[0],
maxStatements = 10,
ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false,
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
* @param {ASTNode} node node to evaluate
@ -55,6 +66,7 @@ module.exports = function(context) {
*/
function endFunction(node) {
var count = functionStack.pop();
if (ignoreTopLevelFunctions && functionStack.length === 0) {
topLevelFunctions.push({ node: node, count: count});
} else {
@ -95,6 +107,7 @@ module.exports = function(context) {
topLevelFunctions.forEach(function(element) {
var count = element.count;
var node = element.node;
reportIfTooManyStatements(node, count, maxStatements);
});
}
@ -104,8 +117,26 @@ module.exports = function(context) {
module.exports.schema = [
{
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false
}
]
},
{
"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`
*/
function checkArray(obj, key, fallback) {
/* istanbul ignore if */
if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) {
throw new TypeError(key + ", if provided, must be an Array");
@ -78,6 +79,7 @@ function calculateCapIsNewExceptions(config) {
module.exports = function(context) {
var config = context.options[0] ? lodash.assign({}, context.options[0]) : {};
config.newIsCap = config.newIsCap !== false;
config.capIsNew = config.capIsNew !== false;
var skipProperties = config.properties === false;
@ -129,6 +131,7 @@ module.exports = function(context) {
var firstCharUpper = firstChar.toUpperCase();
if (firstCharLower === firstCharUpper) {
// char has no uppercase variant, so it's non-alphabetic
return "non-alpha";
} else if (firstChar === firstCharLower) {
@ -151,6 +154,7 @@ module.exports = function(context) {
}
if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
// allow if callee is Date.UTC
return node.callee.object.type === "Identifier" &&
node.callee.object.name === "Date";
@ -183,9 +187,11 @@ module.exports = function(context) {
listeners.NewExpression = function(node) {
var constructorName = extractNameFromExpression(node);
if (constructorName) {
var capitalization = getCap(constructorName);
var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName);
if (!isAllowed) {
report(node, "A constructor name should not start with a lowercase letter.");
}
@ -197,9 +203,11 @@ module.exports = function(context) {
listeners.CallExpression = function(node) {
var calleeName = extractNameFromExpression(node);
if (calleeName) {
var capitalization = getCap(calleeName);
var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName);
if (!isAllowed) {
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) {
return token.value === "(" || token.value === ")";
});
if (prenticesTokens.length < 2) {
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) {
var token = sourceCode.getTokenAfter(node);
return !token || (token.type === "Punctuator" && token.value === "}");
}
@ -83,10 +84,12 @@ module.exports = function(context) {
*/
function hasBlankLineAfterComment(token, commentStartLine) {
var commentEnd = commentEndLine[commentStartLine];
// If there's another comment, repeat check for blank line
if (commentEndLine[commentEnd + 1]) {
return hasBlankLineAfterComment(token, commentEnd + 1);
}
return (token.loc.start.line > commentEndLine[commentStartLine] + 1);
}

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
* @author Rajendra Patil
* @author Burak Yigit Kaya
* @copyright 2016 Rajendra Patil. All rights reserved.
* @copyright 2016 Burak Yigit Kaya. All rights reserved.
*/
"use strict";
@ -13,90 +15,31 @@
module.exports = function(context) {
var options = context.options[0] || {},
codeStateMap = {},
ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
/**
* Check and Capture State if the chained calls/members
* @param {ASTNode} node The node to check
* @param {object} codeState The state of the code current code to be filled
* @returns {void}
*/
function checkAndCaptureStateRecursively(node, codeState) {
var valid = false,
objectLineNumber,
propertyLineNumber;
if (node.callee) {
node = node.callee;
codeState.hasFunctionCall = true;
return {
"CallExpression:exit": function(node) {
if (!node.callee || node.callee.type !== "MemberExpression") {
return;
}
if (node.object) {
codeState.depth++;
objectLineNumber = node.object.loc.end.line;
propertyLineNumber = node.property.loc.end.line;
valid = node.computed || propertyLineNumber > objectLineNumber;
var callee = node.callee;
var parent = callee.object;
var depth = 1;
if (!valid) {
codeState.reports.push({
node: node,
text: "Expected line break after `{{code}}`.",
depth: codeState.depth
});
}
// Recurse
checkAndCaptureStateRecursively(node.object, codeState);
while (parent && parent.callee) {
depth += 1;
parent = parent.callee.object;
}
if (depth > ignoreChainWithDepth && callee.property.loc.start.line === callee.object.loc.end.line) {
context.report(
callee.property,
callee.property.loc.start,
"Expected line break after `" + context.getSource(callee.object).replace(/\r\n|\r|\n/g, "\\n") + "`."
);
}
/**
* Verify and report the captured state report
* @param {object} codeState contains the captured state with `hasFunctionCall, reports and depth`
* @returns {void}
*/
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) {
var reference = findReference(scope, node);
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) {
for (var i = 0; i < node.consequent.length; i++) {
var statement = node.consequent[i];
if (isLexicalDeclaration(statement)) {
context.report({
node: node,

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

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

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

@ -28,6 +28,8 @@
"use strict";
var astUtils = require("../ast-utils.js");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
@ -38,7 +40,7 @@
* @returns {boolean} `true` if the node is a conditional expression.
*/
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) {
var config = context.options[0] || {};
/**
* Reports if an arrow function contains an ambiguous conditional.
* @param {ASTNode} node - A node to check and report.
* @returns {void}
*/
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.");
}
}
@ -62,4 +68,10 @@ module.exports = function(context) {
};
};
module.exports.schema = [];
module.exports.schema = [{
type: "object",
properties: {
allowParens: {type: "boolean"}
},
additionalProperties: false
}];

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

@ -19,7 +19,7 @@ module.exports = function(context) {
if (node.object.name === "console") {
var blockConsole = true;
if ( context.options.length > 0 ) {
if (context.options.length > 0) {
var allowedProperties = context.options[0].allow;
var passedProperty = node.property.name;
var propertyIsAllowed = (allowedProperties.indexOf(passedProperty) > -1);

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

@ -30,15 +30,20 @@ module.exports = function(context) {
case "ObjectExpression":
case "ArrayExpression":
return true;
case "UnaryExpression":
return isConstant(node.argument);
case "BinaryExpression":
case "LogicalExpression":
return isConstant(node.left) && isConstant(node.right) && node.operator !== "in";
case "AssignmentExpression":
return (node.operator === "=") && isConstant(node.right);
case "SequenceExpression":
return isConstant(node.expressions[node.expressions.length - 1]);
// no default
}
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") {
var parent = context.getAncestors().pop();
if ((parent.type === "NewExpression" || parent.type === "CallExpression") &&
parent.callee.type === "Identifier" && parent.callee.name === "RegExp"
) {
// there could be an invalid regular expression string
try {
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
var key = "$" + variable.name; // to avoid __proto__.
if (!isParameter(variable.defs[0]) || keyMap[key]) {
continue;
}
@ -49,6 +50,7 @@ module.exports = function(context) {
// Checks and reports duplications.
var defs = variable.defs.filter(isParameter);
if (defs.length >= 2) {
context.report({
node: node,

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

@ -53,6 +53,7 @@ module.exports = function(context) {
}
return {
// Initializes the stack of state of member declarations.
"Program": function() {
stack = [];
@ -77,6 +78,7 @@ module.exports = function(context) {
var name = getName(node.key);
var state = getState(name, node.static);
var isDuplicate = false;
if (node.kind === "get") {
isDuplicate = (state.init || state.get);
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) {
var key = context.getSource(switchCase.test);
if (mapping[key]) {
context.report(switchCase, "Duplicate case label.");
} 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.
*/
function alwaysReturns(node) {
// If we have a BlockStatement, check each consequent body node.
if (node.type === "BlockStatement") {
// If we have a BlockStatement, check each consequent body node.
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 {
/*
* If not a block statement, make sure the consequent isn't a
* ReturnStatement or an IfStatement with returns on both paths.
*/
return checkForReturnOrIf(node);
}
}

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

@ -33,6 +33,7 @@ module.exports = function(context) {
"Literal": function(node) {
var token = context.getFirstToken(node);
if (token.type === "RegularExpression" && !regex.test(token.value)) {
context.report(node, "Empty class.");
}

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save