Browse Source

tools: update ESLint to 2.9.0

ESLint 2.9.0 fixes some minor bugs that we have been experiencing and
introduces some new rules that we may wish to consider.

PR-URL: https://github.com/nodejs/node/pull/6498
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Roman Reiss <me@silverwind.io>
v6.x
Rich Trott 9 years ago
committed by Evan Lucas
parent
commit
d629f265fe
  1. 92
      tools/eslint/CHANGELOG.md
  2. 2
      tools/eslint/LICENSE
  3. 30
      tools/eslint/README.md
  4. 2
      tools/eslint/bin/eslint.js
  5. 2
      tools/eslint/conf/cli-options.js
  6. 1
      tools/eslint/conf/environments.js
  7. 2
      tools/eslint/conf/eslint.json
  8. 2
      tools/eslint/lib/ast-utils.js
  9. 91
      tools/eslint/lib/cli-engine.js
  10. 2
      tools/eslint/lib/code-path-analysis/code-path-analyzer.js
  11. 17
      tools/eslint/lib/code-path-analysis/code-path-segment.js
  12. 2
      tools/eslint/lib/code-path-analysis/code-path-state.js
  13. 2
      tools/eslint/lib/code-path-analysis/code-path.js
  14. 2
      tools/eslint/lib/code-path-analysis/debug-helpers.js
  15. 2
      tools/eslint/lib/code-path-analysis/fork-context.js
  16. 2
      tools/eslint/lib/code-path-analysis/id-generator.js
  17. 5
      tools/eslint/lib/config.js
  18. 10
      tools/eslint/lib/config/autoconfig.js
  19. 2
      tools/eslint/lib/config/config-file.js
  20. 21
      tools/eslint/lib/config/config-initializer.js
  21. 2
      tools/eslint/lib/config/config-ops.js
  22. 2
      tools/eslint/lib/config/config-rule.js
  23. 16
      tools/eslint/lib/config/config-validator.js
  24. 2
      tools/eslint/lib/config/environments.js
  25. 2
      tools/eslint/lib/config/plugins.js
  26. 9
      tools/eslint/lib/eslint.js
  27. 4
      tools/eslint/lib/file-finder.js
  28. 1
      tools/eslint/lib/formatters/html.js
  29. 1
      tools/eslint/lib/formatters/json.js
  30. 1
      tools/eslint/lib/formatters/table.js
  31. 1
      tools/eslint/lib/formatters/unix.js
  32. 2
      tools/eslint/lib/formatters/visualstudio.js
  33. 1
      tools/eslint/lib/ignored-paths.js
  34. 4
      tools/eslint/lib/logging.js
  35. 8
      tools/eslint/lib/options.js
  36. 2
      tools/eslint/lib/rule-context.js
  37. 17
      tools/eslint/lib/rules/accessor-pairs.js
  38. 28
      tools/eslint/lib/rules/array-bracket-spacing.js
  39. 146
      tools/eslint/lib/rules/array-callback-return.js
  40. 90
      tools/eslint/lib/rules/arrow-body-style.js
  41. 79
      tools/eslint/lib/rules/arrow-parens.js
  42. 233
      tools/eslint/lib/rules/arrow-spacing.js
  43. 178
      tools/eslint/lib/rules/block-scoped-var.js
  44. 201
      tools/eslint/lib/rules/block-spacing.js
  45. 410
      tools/eslint/lib/rules/brace-style.js
  46. 217
      tools/eslint/lib/rules/callback-return.js
  47. 205
      tools/eslint/lib/rules/camelcase.js
  48. 361
      tools/eslint/lib/rules/comma-dangle.js
  49. 301
      tools/eslint/lib/rules/comma-spacing.js
  50. 302
      tools/eslint/lib/rules/comma-style.js
  51. 256
      tools/eslint/lib/rules/complexity.js
  52. 265
      tools/eslint/lib/rules/computed-property-spacing.js
  53. 190
      tools/eslint/lib/rules/consistent-return.js
  54. 234
      tools/eslint/lib/rules/consistent-this.js
  55. 530
      tools/eslint/lib/rules/constructor-super.js
  56. 503
      tools/eslint/lib/rules/curly.js
  57. 110
      tools/eslint/lib/rules/default-case.js
  58. 87
      tools/eslint/lib/rules/dot-location.js
  59. 90
      tools/eslint/lib/rules/dot-notation.js
  60. 84
      tools/eslint/lib/rules/eol-last.js
  61. 166
      tools/eslint/lib/rules/eqeqeq.js
  62. 66
      tools/eslint/lib/rules/func-names.js
  63. 125
      tools/eslint/lib/rules/func-style.js
  64. 186
      tools/eslint/lib/rules/generator-star-spacing.js
  65. 41
      tools/eslint/lib/rules/global-require.js
  66. 38
      tools/eslint/lib/rules/guard-for-in.js
  67. 124
      tools/eslint/lib/rules/handle-callback-err.js
  68. 165
      tools/eslint/lib/rules/id-blacklist.js
  69. 175
      tools/eslint/lib/rules/id-length.js
  70. 199
      tools/eslint/lib/rules/id-match.js
  71. 1257
      tools/eslint/lib/rules/indent.js
  72. 131
      tools/eslint/lib/rules/init-declarations.js
  73. 73
      tools/eslint/lib/rules/jsx-quotes.js
  74. 586
      tools/eslint/lib/rules/key-spacing.js
  75. 870
      tools/eslint/lib/rules/keyword-spacing.js
  76. 127
      tools/eslint/lib/rules/linebreak-style.js
  77. 471
      tools/eslint/lib/rules/lines-around-comment.js
  78. 237
      tools/eslint/lib/rules/max-depth.js
  79. 435
      tools/eslint/lib/rules/max-len.js
  80. 163
      tools/eslint/lib/rules/max-nested-callbacks.js
  81. 120
      tools/eslint/lib/rules/max-params.js
  82. 169
      tools/eslint/lib/rules/max-statements-per-line.js
  83. 249
      tools/eslint/lib/rules/max-statements.js
  84. 286
      tools/eslint/lib/rules/new-cap.js
  85. 42
      tools/eslint/lib/rules/new-parens.js
  86. 293
      tools/eslint/lib/rules/newline-after-var.js
  87. 259
      tools/eslint/lib/rules/newline-before-return.js
  88. 82
      tools/eslint/lib/rules/newline-per-chained-call.js
  89. 62
      tools/eslint/lib/rules/no-alert.js
  90. 54
      tools/eslint/lib/rules/no-array-constructor.js
  91. 148
      tools/eslint/lib/rules/no-bitwise.js
  92. 34
      tools/eslint/lib/rules/no-caller.js
  93. 71
      tools/eslint/lib/rules/no-case-declarations.js
  94. 76
      tools/eslint/lib/rules/no-catch-shadow.js
  95. 73
      tools/eslint/lib/rules/no-class-assign.js
  96. 226
      tools/eslint/lib/rules/no-cond-assign.js
  97. 78
      tools/eslint/lib/rules/no-confusing-arrow.js
  98. 77
      tools/eslint/lib/rules/no-console.js
  99. 57
      tools/eslint/lib/rules/no-const-assign.js
  100. 160
      tools/eslint/lib/rules/no-constant-condition.js

92
tools/eslint/CHANGELOG.md

@ -1,3 +1,95 @@
v2.9.0 - April 29, 2016
* a8a2cd8 Fix: Avoid autoconfig crashes from inline comments (fixes #5992) (#5999) (Ian VanSchooten)
* 23b00e0 Upgrade: npm-license to 0.3.2 (fixes #5996) (#5998) (alberto)
* 377167d Upgrade: ignore to 3.1.2 (fixes #5979) (#5988) (alberto)
* 141b778 Fix: no-control-regex literal handling fixed. (fixes #5737) (#5943) (Efe Gürkan YALAMAN)
* 577757d Fix: Clarify color option (fixes #5928) (#5974) (Grant Snodgrass)
* e7e6581 Docs: Update CLA link (#5980) (Gustav Nikolaj)
* 0be26bc Build: Add nodejs 6 to travis (fixes #5971) (#5973) (Gyandeep Singh)
* e606523 New: Rule `no-unsafe-finally` (fixes #5808) (#5932) (Onur Temizkan)
* 42d1ecc Chore: Add metadata to existing rules - Batch 7 (refs #5417) (#5969) (Vitor Balocco)
* e2ad1ec Update: object-shorthand lints computed methods (fixes #5871) (#5963) (Chris Sauvé)
* d24516a Chore: Add metadata to existing rules - Batch 6 (refs #5417) (#5966) (Vitor Balocco)
* 1e7a3ef Fix: `id-match` false positive in property values (fixes #5885) (#5960) (Mike Sherov)
* 51ddd4b Update: Use process @abstract when processing @return (fixes #5941) (#5945) (Simon Schick)
* 52a4bea Update: Add autofix for `no-whitespace-before-property` (fixes #5927) (#5951) (alberto)
* 46e058d Docs: Correct typo in configuring.md (#5957) (Nick S. Plekhanov)
* 5f8abab Chore: Add metadata to existing rules - Batch 5 (refs #5417) (#5944) (Vitor Balocco)
* 0562f77 Chore: Add missing newlines to test cases (fixes #5947) (Rich Trott)
* fc78e78 Chore: Enable quote-props rule in eslint-config-eslint (refs #5188) (#5938) (Gyandeep Singh)
* 43f6d05 Docs: Update docs to refer to column (#5937) (Sashko Stubailo)
* 586478e Update: Add autofix for `comma-dangle` (fixes #3805) (#5925) (alberto)
* a4f9c5a Docs: Distinguish examples in rules under Stylistic Issues part 3 (Kenneth Williams)
* e7c0737 Chore: Enable no-console rule in eslint-config-eslint (refs #5188) (Kevin Partington)
* 0023fe6 Build: Add “chore” to commit tags (fixes #5880) (#5929) (Mike Sherov)
* 25d626a Upgrade: espree 3.1.4 (fixes #5923, fixes #5756) (Kai Cataldo)
* a01b412 New: Add `no-useless-computed-key` rule (fixes #5402) (Burak Yigit Kaya)
* 9afb9cb Chore: Remove workaround for espree and escope bugs (fixes #5852) (alberto)
* 3ffc582 Chore: Update copyright and license info (alberto)
* 249eb40 Docs: Clarify init sets up local installation (fixes #5874) (Kai Cataldo)
* 6cd8c86 Docs: Describe options in rules under Possible Errors part 1 (Mark Pedrotti)
* f842d18 Fix: `no-this-before-super` crash on unreachable paths (fixes #5894) (Toru Nagashima)
* a02960b Docs: Fix missing delimiter in README links (Kevin Partington)
* 3a9e72c Docs: Update developer guide with new standards (Nicholas C. Zakas)
* cb78585 Update: Add `allowUnboundThis` to `prefer-arrow-callback` (fixes #4668) (Burak Yigit Kaya)
* 02be29f Chore: Remove CLA check from bot (Nicholas C. Zakas)
* 220713e Chore: Add metadata to existing rules - Batch 4 (refs #5417) (Vitor Balocco)
* df53414 Chore: Include jQuery Foundation info (Nicholas C. Zakas)
* f1b2992 Fix: `no-useless-escape` false positive in JSXAttribute (fixes #5882) (Toru Nagashima)
* 74674ad Docs: Move `sort-imports` to 'ECMAScript 6' (Kenneth Williams)
* ae69ddb Docs: Fix severity type in example (Kenneth Williams)
* 19f6fff Update: Autofixing does multiple passes (refs #5329) (Nicholas C. Zakas)
* 1e4b0ca Docs: Reduce length of paragraphs in rules index (Mark Pedrotti)
* 8cfe1eb Docs: Fix a wrong option (Zach Orlovsky)
* 8f6739f Docs: Add alberto as reviewer (alberto)
* 2ae4938 Docs: Fix message for `inline-config` option (alberto)
* 089900b Docs: Fix a wrong rule name in an example (Toru Nagashima)
* c032b41 Docs: Fix emphasis (Toru Nagashima)
* ae606f0 Docs: Update JSCS info in README (alberto)
* a9c5323 Fix: Install ESLint on init if not installed (fixes #5833) (Kai Cataldo)
* ed38358 Docs: Removed incorrect example (James M. Greene)
* af3113c Docs: Fix config comments in indent docs (Brandon Mills)
* 2b39461 Update: `commentPattern` option for `default-case` rule (fixes #5803) (Artyom Lvov)
v2.8.0 - April 15, 2016
* a8821a5 Docs: Distinguish examples in rules under Stylistic Issues part 2 (Kenneth Williams)
* 76913b6 Update: Add metadata to existing rules - Batch 3 (refs #5417) (Vitor Balocco)
* 34ad8d2 Fix: Check that module.paths exists (fixes #5791) (Nicholas C. Zakas)
* 37239b1 Docs: Add new members of the team (Ilya Volodin)
* fb3c2eb Update: allow template literals (fixes #5234) (Jonathan Haines)
* 5a4a935 Update: Add metadata to existing rules - Batch 2 (refs #5417) (Vitor Balocco)
* ea2e625 Fix: newline-before-return handles return as first token (fixes #5816) (Kevin Partington)
* f8db9c9 Update: add nestedBinaryExpressions to no-extra-parens (fixes #3065) (Ilya Volodin)
* 0045d57 Update: `allowNamedFunctions` in `prefer-arrow-callback` (fixes #5675) (alberto)
* 19da72a Update: Add metadata to existing rules - Batch 1 (refs #5417) (Vitor Balocco)
* cc14e43 Fix: `no-fallthrough` empty case with comment (fixes #5799) (alberto)
* 13c8b14 Fix: LogicalExpression checks for short circuit (fixes #5693) (Vamshi krishna)
* 73b225e Fix: Document and fix metadata (refs #5417) (Ilya Volodin)
* 882d199 Docs: Improve options description in `no-redeclare` (alberto)
* 6a71ceb Docs: Improve options description in `no-params-reassign` (alberto)
* 24b6215 Update: Include 'typeof' in rule 'no-constant-condition' (fixes #5228) (Vamshi krishna)
* a959063 Docs: Remove link to deprecated ESLintTester project (refs #3110) (Trey Thomas)
* 6fd7d82 Update: Change order in `eslint --init` env options (fixes #5742) (alberto)
* c59d909 Fix: Extra paren check around object arrow bodies (fixes #5789) (Brandon Mills)
* 6f88546 Docs: Use double quotes for better Win compatibility (fixes #5796) (alberto)
* 02743d5 Fix: catch self-assignment operators in `no-magic-number` (fixes #4400) (alberto)
* c94e74e Docs: Make rule descriptions more consistent (Kenneth Williams)
* 6028252 Docs: Distinguish examples in rules under Stylistic Issues part 1 (Mark Pedrotti)
* ccd8ca9 Fix: Added property onlyDeclaration to id-match rule (fixes #3488) (Gajus Kuizinas)
* 6703c02 Update: no-useless-escape / exact locations of errors (fixes #5751) (Onur Temizkan)
* 3d84b91 Fix: ignore trailing whitespace in template literal (fixes #5786) (Kai Cataldo)
* b0e6bc4 Update: add allowEmptyCatch option to no-empty (fixes #5800) (Kai Cataldo)
* f1f1dd7 Docs: Add @pedrottimark as a committer (Brandon Mills)
* 228f201 Update: `commentPattern` option for `no-fallthrough` rule (fixes #5757) (Artyom Lvov)
* 41db670 Docs: Clarify disable inline comments (Kai Cataldo)
* 9c9a295 Docs: Add note about shell vs node glob parameters in cli (alberto)
* 5308ff9 Docs: Add code backticks to sentence in fixable rules (Mark Pedrotti)
* 965ec06 Docs: fix the examples for space-before-function-paren. (Craig Silverstein)
* 2b202fc Update: Add ignore option to space-before-function-parens (fixes #4127) (Craig Silverstein)
* 24c12ba Fix: improve `constructor-super` errors for literals (fixes #5449) (Toru Nagashima)
v2.7.0 - April 4, 2016
* 134cb1f Revert "Update: adds nestedBinaryExpressions for no-extra-parens rule (fixes #3065)" (Ilya Volodin)

2
tools/eslint/LICENSE

@ -1,5 +1,5 @@
ESLint
Copyright (c) 2013 Nicholas C. Zakas. All rights reserved.
Copyright jQuery Foundation and other contributors, https://jquery.org/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

30
tools/eslint/README.md

@ -8,7 +8,15 @@
# 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)
[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) |
[Code of Conduct](https://jquery.org/conduct/) |
[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:
@ -32,6 +40,8 @@ After that, you can run ESLint on any JavaScript file:
eslint test.js test2.js
**Note:** `eslint --init` is intended for setting up and configuring ESLint on a per-project basis and will perform a local installation of ESLint and its plugins in the directory in which it is run. If you prefer using a global installation of ESLint, any plugins used in your configuration must also be installed globally.
## Configuration
After running `eslint --init`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this:
@ -67,13 +77,19 @@ These folks keep the project moving and are resources for help:
* Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - reviewer
* Alberto Rodríguez ([@alberto](https://github.com/alberto)) - reviewer
* Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - committer
* Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - committer
* Ian VanSchooten ([@ianvs](https://github.com/ianvs)) - committer
* Burak Yiğit Kaya ([@byk](https://github.com/byk)) - committer
* 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
* Mark Pedrotti ([@pedrottimark](https://github.com/pedrottimark)) - committer
* Oleg Gaidarenko ([@markelog](https://github.com/markelog)) - committer
* Mike Sherov [@mikesherov](https://github.com/mikesherov)) - committer
* Henry Zhu ([@hzoo](https://github.com/hzoo)) - committer
* Marat Dulin ([@mdevils](https://github.com/mdevils)) - committer
* Alexej Yaroshevich ([@zxqfox](https://github.com/zxqfox)) - committer
## Releases
@ -98,15 +114,19 @@ I do like JSHint. And I like Anton and Rick. Neither of those were deciding fact
That's not really a question, but I got it. I'm not trying to convince you that ESLint is better than JSHint. The only thing I know is that ESLint is better than JSHint for what I'm doing. In the off chance you're doing something similar, it might be better for you. Otherwise, keep using JSHint, I'm certainly not going to tell you to stop using it.
### How does ESLint performance compare to JSHint and JSCS?
### How does ESLint performance compare to JSHint?
ESLint is slower than JSHint, usually 2-3x slower on a single file. This is because ESLint uses Espree to construct an AST before it can evaluate your code whereas JSHint evaluates your code as it's being parsed. The speed is also based on the number of rules you enable; the more rules you enable, the slower the process.
Despite being slower, we believe that ESLint is fast enough to replace JSHint without causing significant pain.
ESLint is faster than JSCS, as ESLint uses a single-pass traversal for analysis whereas JSCS using a querying model.
### I heard ESLint is going to replace JSCS?
Yes. Since we are solving the same problems, ESLint and JSCS teams have decided to join forces and work together in the development of ESLint instead of competing with each other. You can read more about this in both [ESLint](http://eslint.org/blog/2016/04/welcoming-jscs-to-eslint) and [JSCS](https://medium.com/@markelog/jscs-end-of-the-line-bc9bf0b3fdb2#.u76sx334n) announcements.
### So, should I stop using JSCS and start using ESLint?
If you are using both JSHint and JSCS on your files, then using just ESLint will be faster.
Not yet. We are still working to smooth the transition. You can see our progress [here](https://github.com/eslint/eslint/milestones/JSCS%20Compatibility). We’ll announce when all of the changes necessary to support JSCS users in ESLint are complete and will start encouraging JSCS users to switch to ESLint at that time. Meanwhile, we recommend you to upgrade to JSCS 3.0 and provide feedback to the team.
### Is ESLint just linting or does it also check style?

2
tools/eslint/bin/eslint.js

@ -3,8 +3,6 @@
/**
* @fileoverview Main CLI that is run via the eslint command.
* @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -1,8 +1,6 @@
/**
* @fileoverview Default CLIEngineOptions.
* @author Ian VanSchooten
* @copyright 2016 Ian VanSchooten. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";

1
tools/eslint/conf/environments.js

@ -1,7 +1,6 @@
/**
* @fileoverview Defines environment settings and globals.
* @author Elan Shanker
* @copyright 2014 Elan Shanker. All rights reserved.
*/
"use strict";

2
tools/eslint/conf/eslint.json

@ -109,11 +109,13 @@
"no-unmodified-loop-condition": "off",
"no-unneeded-ternary": "off",
"no-unreachable": "error",
"no-unsafe-finally": "off",
"no-unused-expressions": "off",
"no-unused-labels": "error",
"no-unused-vars": "error",
"no-use-before-define": "off",
"no-useless-call": "off",
"no-useless-computed-key": "off",
"no-useless-concat": "off",
"no-useless-constructor": "off",
"no-useless-escape": "off",

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

@ -1,8 +1,6 @@
/**
* @fileoverview Common utils for AST.
* @author Gyandeep Singh
* @copyright 2015 Gyandeep Singh. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -1,8 +1,6 @@
/**
* @fileoverview Main CLI object.
* @author Nicholas C. Zakas
* @copyright 2014 Nicholas C. Zakas. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
@ -115,6 +113,79 @@ function calculateStatsPerRun(results) {
});
}
/**
* Performs multiple autofix passes over the text until as many fixes as possible
* have been applied.
* @param {string} text The source text to apply fixes to.
* @param {Object} config The ESLint config object to use.
* @param {Object} options The ESLint options object to use.
* @param {string} options.filename The filename from which the text was read.
* @param {boolean} options.allowInlineConfig Flag indicating if inline comments
* should be allowed.
* @returns {Object} The result of the fix operation as returned from the
* SourceCodeFixer.
* @private
*/
function multipassFix(text, config, options) {
var messages = [],
fixedResult,
fixed = false,
passNumber = 0,
lastMessageCount,
MAX_PASSES = 10;
/**
* This loop continues until one of the following is true:
*
* 1. No more fixes have been applied.
* 2. There are no more linting errors reported.
* 3. The number of linting errors is no different between two passes.
* 4. Ten passes have been made.
*
* That means anytime a fix is successfully applied, there will be another pass.
* Essentially, guaranteeing a minimum of two passes.
*/
do {
passNumber++;
lastMessageCount = messages.length;
debug("Linting code for " + options.filename + " (pass " + passNumber + ")");
messages = eslint.verify(text, config, options);
debug("Generating fixed text for " + options.filename + " (pass " + passNumber + ")");
fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages);
// keep track if any fixes were ever applied - important for return value
fixed = fixed || fixedResult.fixed;
// update to use the fixed output instead of the original text
text = fixedResult.output;
} while (
fixedResult.fixed && fixedResult.messages.length > 0 &&
fixedResult.messages.length !== lastMessageCount &&
passNumber < MAX_PASSES
);
/*
* If the last result had fixes, we need to lint again to me sure we have
* the most up-to-date information.
*/
if (fixedResult.fixed) {
fixedResult.messages = eslint.verify(text, config, options);
}
// ensure the last result properly reflects if fixes were done
fixedResult.fixed = fixed;
fixedResult.output = text;
return fixedResult;
}
/**
* Processes an source code using ESLint.
* @param {string} text The source code to check.
@ -179,15 +250,17 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
} else {
messages = eslint.verify(text, config, {
filename: filename,
allowInlineConfig: allowInlineConfig
});
if (fix) {
debug("Generating fixed text for " + filename);
fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages);
fixedResult = multipassFix(text, config, {
filename: filename,
allowInlineConfig: allowInlineConfig
});
messages = fixedResult.messages;
} else {
messages = eslint.verify(text, config, {
filename: filename,
allowInlineConfig: allowInlineConfig
});
}
}

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

@ -1,8 +1,6 @@
/**
* @fileoverview A class of the code path analyzer.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -1,8 +1,6 @@
/**
* @fileoverview A class of the code path segment.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -11,8 +9,7 @@
// Requirements
//------------------------------------------------------------------------------
var assert = require("assert"),
debug = require("./debug-helpers");
var debug = require("./debug-helpers");
//------------------------------------------------------------------------------
// Helpers
@ -178,7 +175,13 @@ CodePathSegment.newNext = function(id, allPrevSegments) {
* @returns {CodePathSegment} The created segment.
*/
CodePathSegment.newUnreachable = function(id, allPrevSegments) {
return new CodePathSegment(id, flattenUnusedSegments(allPrevSegments), false);
var segment = new CodePathSegment(id, flattenUnusedSegments(allPrevSegments), false);
// In `if (a) return a; foo();` case, the unreachable segment preceded by
// the return statement is not used but must not be remove.
CodePathSegment.markUsed(segment);
return segment;
};
/**
@ -203,7 +206,9 @@ CodePathSegment.newDisconnected = function(id, allPrevSegments) {
* @returns {void}
*/
CodePathSegment.markUsed = function(segment) {
assert(!segment.internal.used, segment.id + " is marked twice.");
if (segment.internal.used) {
return;
}
segment.internal.used = true;
var i;

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

@ -1,8 +1,6 @@
/**
* @fileoverview A class to manage state of generating a code path.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -1,8 +1,6 @@
/**
* @fileoverview A class of the code path.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -1,8 +1,6 @@
/**
* @fileoverview Helpers to debug for code path analysis.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -5,8 +5,6 @@
* This has a fork list and manages it.
*
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -5,8 +5,6 @@
* information of the code path.
*
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

5
tools/eslint/lib/config.js

@ -1,11 +1,8 @@
/**
* @fileoverview Responsible for loading config files
* @author Seth McLaughlin
* @copyright 2014-2016 Nicholas C. Zakas. All rights reserved.
* @copyright 2014 Michael McLaughlin. All rights reserved.
* @copyright 2013 Seth McLaughlin. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------

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

@ -1,8 +1,6 @@
/**
* @fileoverview Used for creating a suggested configuration based on project code.
* @author Ian VanSchooten
* @copyright 2015 Ian VanSchooten. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
@ -312,7 +310,13 @@ Registry.prototype = {
var lintResults = eslint.verify(sourceCodes[filename], lintConfig);
lintResults.forEach(function(result) {
lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
// It is possible that the error is from a configuration comment
// in a linted file, in which case there may not be a config
// set in this ruleSetIdx. (https://github.com/eslint/eslint/issues/5992)
if (lintedRegistry.rules[result.ruleId][ruleSetIdx]) {
lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
}
});
ruleSetIdx += 1;

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

@ -1,8 +1,6 @@
/**
* @fileoverview Helper to locate and load configuration files.
* @author Nicholas C. Zakas
* @copyright 2015 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
/* eslint no-use-before-define: 0 */

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

@ -1,7 +1,6 @@
/**
* @fileoverview Config initialization wizard.
* @author Ilya Volodin
* @copyright 2015 Ilya Volodin. All rights reserved.
*/
"use strict";
@ -50,6 +49,10 @@ function writeFile(config, format) {
ConfigFile.write(config, "./.eslintrc" + extname);
log.info("Successfully created .eslintrc" + extname + " file in " + process.cwd());
if (config.installedESLint) {
log.info("ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy.");
}
}
/**
@ -76,12 +79,24 @@ function installModules(config) {
if (modules.length === 0) {
return;
}
// Add eslint to list in case user does not have it installed locally
modules.unshift("eslint");
installStatus = npmUtil.checkDevDeps(modules);
// Install packages which aren't already installed
modulesToInstall = Object.keys(installStatus).filter(function(module) {
return installStatus[module] === false;
var notInstalled = installStatus[module] === false;
if (module === "eslint" && notInstalled) {
log.info("Local ESLint installation not found.");
config.installedESLint = true;
}
return notInstalled;
});
if (modulesToInstall.length > 0) {
log.info("Installing " + modulesToInstall.join(", "));
npmUtil.installSyncSaveDev(modulesToInstall);
@ -366,7 +381,7 @@ function promptUser(callback) {
name: "env",
message: "Where will your code run?",
default: ["browser"],
choices: [{name: "Node", value: "node"}, {name: "Browser", value: "browser"}]
choices: [{name: "Browser", value: "browser"}, {name: "Node", value: "node"}]
},
{
type: "confirm",

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

@ -2,8 +2,6 @@
* @fileoverview Config file operations. This file must be usable in the browser,
* so no Node-specific code can be here.
* @author Nicholas C. Zakas
* @copyright 2015 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -1,8 +1,6 @@
/**
* @fileoverview Create configurations for a rule
* @author Ian VanSchooten
* @copyright 2016 Ian VanSchooten. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";

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

@ -1,8 +1,6 @@
/**
* @fileoverview Validates configs.
* @author Brandon Mills
* @copyright 2015 Brandon Mills
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -37,16 +35,16 @@ function getRuleOptionsSchema(id) {
if (Array.isArray(schema)) {
if (schema.length) {
return {
"type": "array",
"items": schema,
"minItems": 0,
"maxItems": schema.length
type: "array",
items: schema,
minItems: 0,
maxItems: schema.length
};
} else {
return {
"type": "array",
"minItems": 0,
"maxItems": 0
type: "array",
minItems: 0,
maxItems: 0
};
}
}

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

@ -1,8 +1,6 @@
/**
* @fileoverview Environments manager
* @author Nicholas C. Zakas
* @copyright 2016 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -1,8 +1,6 @@
/**
* @fileoverview Plugins manager
* @author Nicholas C. Zakas
* @copyright 2016 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

9
tools/eslint/lib/eslint.js

@ -1,9 +1,8 @@
/**
* @fileoverview Main ESLint object.
* @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
@ -345,11 +344,11 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
}
} else { // comment.type === "Line"
if (match[1] === "eslint-disable-line") {
disableReporting(reportingConfig, { "line": comment.loc.start.line, "column": 0 }, Object.keys(parseListConfig(value)));
disableReporting(reportingConfig, { line: comment.loc.start.line, column: 0 }, Object.keys(parseListConfig(value)));
enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value)));
} 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)));
enableReporting(reportingConfig, { line: comment.loc.start.line + 2 }, Object.keys(parseListConfig(value)));
}
}
}
@ -964,7 +963,7 @@ module.exports = (function() {
};
// ensure there's range and text properties as well as metadata switch, otherwise it's not a valid fix
if (fix && Array.isArray(fix.range) && (typeof fix.text === "string") && (!meta || !meta.docs || meta.docs.fixable)) {
if (fix && Array.isArray(fix.range) && (typeof fix.text === "string") && (!meta || meta.fixable)) {
problem.fix = fix;
}

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

@ -1,10 +1,8 @@
/**
* @fileoverview Util class to find config files.
* @author Aliaksei Shytkin
* @copyright 2014 Michael McLaughlin. All rights reserved.
* @copyright 2014 Aliaksei Shytkin. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------

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

@ -1,7 +1,6 @@
/**
* @fileoverview HTML reporter
* @author Julian Laval
* @copyright 2015 Julian Laval. All rights reserved.
*/
"use strict";

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

@ -1,7 +1,6 @@
/**
* @fileoverview JSON reporter
* @author Burak Yigit Kaya aka BYK
* @copyright 2015 Burak Yigit Kaya. All rights reserved.
*/
"use strict";

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

@ -1,7 +1,6 @@
/**
* @fileoverview "table reporter.
* @author Gajus Kuizinas <gajus@gajus.com>
* @copyright 2016 Gajus Kuizinas <gajus@gajus.com>. All rights reserved.
*/
"use strict";

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

@ -1,7 +1,6 @@
/**
* @fileoverview unix-style formatter.
* @author oshi-shinobu
* @copyright 2015 oshi-shinobu. All rights reserved.
*/
"use strict";

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

@ -1,8 +1,6 @@
/**
* @fileoverview Visual Studio compatible formatter
* @author Ronald Pijnacker
* @copyright 2015 Ronald Pijnacker. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -2,6 +2,7 @@
* @fileoverview Responsible for loading ignore config files and managing ignore patterns
* @author Jonathan Rajavuori
*/
"use strict";
//------------------------------------------------------------------------------

4
tools/eslint/lib/logging.js

@ -1,10 +1,12 @@
/**
* @fileoverview Handle logging for ESLint
* @author Gyandeep Singh
* @copyright 2015 Gyandeep Singh. All rights reserved.
*/
"use strict";
/* eslint no-console: "off" */
/* istanbul ignore next */
module.exports = {

8
tools/eslint/lib/options.js

@ -1,8 +1,8 @@
/**
* @fileoverview Options configuration for optionator.
* @author George Zahariev
* See LICENSE in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
@ -173,8 +173,8 @@ module.exports = optionator({
{
option: "color",
type: "Boolean",
default: "true",
description: "Disable color in piped output"
alias: "no-color",
description: "Force enabling/disabling of color"
},
{
heading: "Miscellaneous"
@ -213,7 +213,7 @@ module.exports = optionator({
option: "inline-config",
type: "Boolean",
default: "true",
description: "Allow comments to change eslint config/rules"
description: "Prevent comments from changing config or rules"
},
{
option: "print-config",

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

@ -1,8 +1,6 @@
/**
* @fileoverview RuleContext utility for rules
* @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to flag wrapping non-iife in parens
* @author Gyandeep Singh
* @copyright 2015 Gyandeep Singh. All rights reserved.
*/
"use strict";
@ -79,16 +78,16 @@ module.exports = {
recommended: false
},
schema: [{
"type": "object",
"properties": {
"getWithoutSet": {
"type": "boolean"
type: "object",
properties: {
getWithoutSet: {
type: "boolean"
},
"setWithoutGet": {
"type": "boolean"
setWithoutGet: {
type: "boolean"
}
},
"additionalProperties": false
additionalProperties: false
}]
},
create: function(context) {
@ -147,7 +146,7 @@ module.exports = {
}
return {
"ObjectExpression": function(node) {
ObjectExpression: function(node) {
if (checkSetWithoutGet || checkGetWithoutSet) {
checkLonelySetGet(node);
}

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

@ -1,10 +1,6 @@
/**
* @fileoverview Disallows or enforces spaces inside of array brackets.
* @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
* @copyright 2014 Brandyn Bennett. All rights reserved.
* @copyright 2014 Michael Ficarra. No rights reserved.
* @copyright 2014 Vignesh Anand. All rights reserved.
*/
"use strict";
@ -19,27 +15,27 @@ module.exports = {
docs: {
description: "Enforce spacing inside array brackets",
category: "Stylistic Issues",
recommended: false,
fixable: "whitespace"
recommended: false
},
fixable: "whitespace",
schema: [
{
"enum": ["always", "never"]
enum: ["always", "never"]
},
{
"type": "object",
"properties": {
"singleValue": {
"type": "boolean"
type: "object",
properties: {
singleValue: {
type: "boolean"
},
"objectsInArrays": {
"type": "boolean"
objectsInArrays: {
type: "boolean"
},
"arraysInArrays": {
"type": "boolean"
arraysInArrays: {
type: "boolean"
}
},
"additionalProperties": false
additionalProperties: false
}
]
},

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to enforce return statements in callbacks of array's methods
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -166,76 +164,86 @@ function isCallbackOfArrayMethod(node) {
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var funcInfo = {
upper: null,
codePath: null,
hasReturn: false,
shouldCheck: false
};
/**
* Checks whether or not the last code path segment is reachable.
* Then reports this function if the segment is reachable.
*
* If the last code path segment is reachable, there are paths which are not
* returned or thrown.
*
* @param {ASTNode} node - A node to check.
* @returns {void}
*/
function checkLastSegment(node) {
if (funcInfo.shouldCheck &&
funcInfo.codePath.currentSegments.some(isReachable)
) {
context.report({
node: node,
loc: getLocation(node, context.getSourceCode()).loc.start,
message: funcInfo.hasReturn
? "Expected to return a value at the end of this function."
: "Expected to return a value in this function."
});
}
}
return {
// Stacks this function's information.
"onCodePathStart": function(codePath, node) {
funcInfo = {
upper: funcInfo,
codePath: codePath,
hasReturn: false,
shouldCheck:
TARGET_NODE_TYPE.test(node.type) &&
node.body.type === "BlockStatement" &&
isCallbackOfArrayMethod(node)
};
module.exports = {
meta: {
docs: {
description: "enforce `return` statements in callbacks of array methods",
category: "Best Practices",
recommended: false
},
// Pops this function's information.
"onCodePathEnd": function() {
funcInfo = funcInfo.upper;
},
// Checks the return statement is valid.
"ReturnStatement": function(node) {
if (funcInfo.shouldCheck) {
funcInfo.hasReturn = true;
schema: []
},
create: function(context) {
var funcInfo = {
upper: null,
codePath: null,
hasReturn: false,
shouldCheck: false
};
/**
* Checks whether or not the last code path segment is reachable.
* Then reports this function if the segment is reachable.
*
* If the last code path segment is reachable, there are paths which are not
* returned or thrown.
*
* @param {ASTNode} node - A node to check.
* @returns {void}
*/
function checkLastSegment(node) {
if (funcInfo.shouldCheck &&
funcInfo.codePath.currentSegments.some(isReachable)
) {
context.report({
node: node,
loc: getLocation(node, context.getSourceCode()).loc.start,
message: funcInfo.hasReturn
? "Expected to return a value at the end of this function."
: "Expected to return a value in this function."
});
}
}
if (!node.argument) {
context.report({
node: node,
message: "Expected a return value."
});
return {
// Stacks this function's information.
onCodePathStart: function(codePath, node) {
funcInfo = {
upper: funcInfo,
codePath: codePath,
hasReturn: false,
shouldCheck:
TARGET_NODE_TYPE.test(node.type) &&
node.body.type === "BlockStatement" &&
isCallbackOfArrayMethod(node)
};
},
// Pops this function's information.
onCodePathEnd: function() {
funcInfo = funcInfo.upper;
},
// Checks the return statement is valid.
ReturnStatement: function(node) {
if (funcInfo.shouldCheck) {
funcInfo.hasReturn = true;
if (!node.argument) {
context.report({
node: node,
message: "Expected a return value."
});
}
}
}
},
},
// Reports a given function if the last path is reachable.
"FunctionExpression:exit": checkLastSegment,
"ArrowFunctionExpression:exit": checkLastSegment
};
// Reports a given function if the last path is reachable.
"FunctionExpression:exit": checkLastSegment,
"ArrowFunctionExpression:exit": checkLastSegment
};
}
};
module.exports.schema = [];

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to require braces in arrow function body.
* @author Alberto Rodríguez
* @copyright 2015 Alberto Rodríguez. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -10,50 +8,60 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var always = context.options[0] === "always";
var asNeeded = !context.options[0] || context.options[0] === "as-needed";
module.exports = {
meta: {
docs: {
description: "require braces around arrow function bodies",
category: "ECMAScript 6",
recommended: false
},
/**
* Determines whether a arrow function body needs braces
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function validate(node) {
var arrowBody = node.body;
schema: [
{
enum: ["always", "as-needed"]
}
]
},
if (arrowBody.type === "BlockStatement") {
var blockBody = arrowBody.body;
create: function(context) {
var always = context.options[0] === "always";
var asNeeded = !context.options[0] || context.options[0] === "as-needed";
if (blockBody.length !== 1) {
return;
}
/**
* Determines whether a arrow function body needs braces
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function validate(node) {
var arrowBody = node.body;
if (asNeeded && blockBody[0].type === "ReturnStatement") {
context.report({
node: node,
loc: arrowBody.loc.start,
message: "Unexpected block statement surrounding arrow body."
});
}
} else {
if (always) {
context.report({
node: node,
loc: arrowBody.loc.start,
message: "Expected block statement surrounding arrow body."
});
if (arrowBody.type === "BlockStatement") {
var blockBody = arrowBody.body;
if (blockBody.length !== 1) {
return;
}
if (asNeeded && blockBody[0].type === "ReturnStatement") {
context.report({
node: node,
loc: arrowBody.loc.start,
message: "Unexpected block statement surrounding arrow body."
});
}
} else {
if (always) {
context.report({
node: node,
loc: arrowBody.loc.start,
message: "Expected block statement surrounding arrow body."
});
}
}
}
}
return {
"ArrowFunctionExpression": validate
};
};
module.exports.schema = [
{
"enum": ["always", "as-needed"]
return {
ArrowFunctionExpression: validate
};
}
];
};

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to require parens in arrow function arguments.
* @author Jxck
* @copyright 2015 Jxck. All rights reserved.
*/
"use strict";
@ -9,44 +8,54 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var message = "Expected parentheses around arrow function argument.";
var asNeededMessage = "Unexpected parentheses around single function argument";
var asNeeded = context.options[0] === "as-needed";
/**
* Determines whether a arrow function argument end with `)`
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function parens(node) {
var token = context.getFirstToken(node);
// as-needed: x => x
if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") {
if (token.type === "Punctuator" && token.value === "(") {
context.report(node, asNeededMessage);
module.exports = {
meta: {
docs: {
description: "require parentheses around arrow function arguments",
category: "ECMAScript 6",
recommended: false
},
schema: [
{
enum: ["always", "as-needed"]
}
]
},
create: function(context) {
var message = "Expected parentheses around arrow function argument.";
var asNeededMessage = "Unexpected parentheses around single function argument";
var asNeeded = context.options[0] === "as-needed";
/**
* Determines whether a arrow function argument end with `)`
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function parens(node) {
var token = context.getFirstToken(node);
// as-needed: x => x
if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") {
if (token.type === "Punctuator" && token.value === "(") {
context.report(node, asNeededMessage);
}
return;
}
return;
}
if (token.type === "Identifier") {
var after = context.getTokenAfter(token);
if (token.type === "Identifier") {
var after = context.getTokenAfter(token);
// (x) => x
if (after.value !== ")") {
context.report(node, message);
// (x) => x
if (after.value !== ")") {
context.report(node, message);
}
}
}
}
return {
"ArrowFunctionExpression": parens
};
};
module.exports.schema = [
{
"enum": ["always", "as-needed"]
return {
ArrowFunctionExpression: parens
};
}
];
};

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to define spacing before/after arrow function's arrow.
* @author Jxck
* @copyright 2015 Jxck. All rights reserved.
*/
"use strict";
@ -9,125 +8,137 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
// merge rules with default
var rule = { before: true, after: true },
option = context.options[0] || {};
rule.before = option.before !== false;
rule.after = option.after !== false;
/**
* Get tokens of arrow(`=>`) and before/after arrow.
* @param {ASTNode} node The arrow function node.
* @returns {Object} Tokens of arrow and before/after arrow.
*/
function getTokens(node) {
var t = context.getFirstToken(node);
var 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 };
}
/**
* Count spaces before/after arrow(`=>`) token.
* @param {Object} tokens Tokens before/after arrow.
* @returns {Object} count of space before/after arrow.
*/
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 };
}
module.exports = {
meta: {
docs: {
description: "enforce consistent spacing before and after the arrow in arrow functions",
category: "ECMAScript 6",
recommended: false
},
/**
* Determines whether space(s) before after arrow(`=>`) is satisfy rule.
* if before/after value is `true`, there should be space(s).
* if before/after value is `false`, there should be no space.
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function spaces(node) {
var tokens = getTokens(node);
var countSpace = countSpaces(tokens);
if (rule.before) {
// should be space(s) before arrow
if (countSpace.before === 0) {
context.report({
node: tokens.before,
message: "Missing space before =>",
fix: function(fixer) {
return fixer.insertTextBefore(tokens.arrow, " ");
fixable: "whitespace",
schema: [
{
type: "object",
properties: {
before: {
type: "boolean"
},
after: {
type: "boolean"
}
});
},
additionalProperties: false
}
} else {
// should be no space before arrow
if (countSpace.before > 0) {
context.report({
node: tokens.before,
message: "Unexpected space before =>",
fix: function(fixer) {
return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]);
}
});
]
},
create: function(context) {
// merge rules with default
var rule = { before: true, after: true },
option = context.options[0] || {};
rule.before = option.before !== false;
rule.after = option.after !== false;
/**
* Get tokens of arrow(`=>`) and before/after arrow.
* @param {ASTNode} node The arrow function node.
* @returns {Object} Tokens of arrow and before/after arrow.
*/
function getTokens(node) {
var t = context.getFirstToken(node);
var 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 };
}
if (rule.after) {
/**
* Count spaces before/after arrow(`=>`) token.
* @param {Object} tokens Tokens before/after arrow.
* @returns {Object} count of space before/after arrow.
*/
function countSpaces(tokens) {
var before = tokens.arrow.range[0] - tokens.before.range[1];
var after = tokens.after.range[0] - tokens.arrow.range[1];
// should be space(s) after arrow
if (countSpace.after === 0) {
context.report({
node: tokens.after,
message: "Missing space after =>",
fix: function(fixer) {
return fixer.insertTextAfter(tokens.arrow, " ");
}
});
}
} else {
// should be no space after arrow
if (countSpace.after > 0) {
context.report({
node: tokens.after,
message: "Unexpected space after =>",
fix: function(fixer) {
return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]);
}
});
}
return { before: before, after: after };
}
}
return {
"ArrowFunctionExpression": spaces
};
};
/**
* Determines whether space(s) before after arrow(`=>`) is satisfy rule.
* if before/after value is `true`, there should be space(s).
* if before/after value is `false`, there should be no space.
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function spaces(node) {
var tokens = getTokens(node);
var countSpace = countSpaces(tokens);
if (rule.before) {
// should be space(s) before arrow
if (countSpace.before === 0) {
context.report({
node: tokens.before,
message: "Missing space before =>",
fix: function(fixer) {
return fixer.insertTextBefore(tokens.arrow, " ");
}
});
}
} else {
// should be no space before arrow
if (countSpace.before > 0) {
context.report({
node: tokens.before,
message: "Unexpected space before =>",
fix: function(fixer) {
return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]);
}
});
}
}
module.exports.schema = [
{
"type": "object",
"properties": {
"before": {
"type": "boolean"
},
"after": {
"type": "boolean"
if (rule.after) {
// should be space(s) after arrow
if (countSpace.after === 0) {
context.report({
node: tokens.after,
message: "Missing space after =>",
fix: function(fixer) {
return fixer.insertTextAfter(tokens.arrow, " ");
}
});
}
} else {
// should be no space after arrow
if (countSpace.after > 0) {
context.report({
node: tokens.after,
message: "Unexpected space after =>",
fix: function(fixer) {
return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]);
}
});
}
}
},
"additionalProperties": false
}
return {
ArrowFunctionExpression: spaces
};
}
];
};

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to check for "block scoped" variables by binding context
* @author Matt DuVall <http://www.mattduvall.com>
* @copyright 2015 Toru Nagashima. All rights reserved.
* @copyright 2015 Mathieu M-Gosselin. All rights reserved.
*/
"use strict";
@ -10,101 +8,111 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var stack = [];
module.exports = {
meta: {
docs: {
description: "enforce the use of variables within the scope they are defined",
category: "Best Practices",
recommended: false
},
/**
* Makes a block scope.
* @param {ASTNode} node - A node of a scope.
* @returns {void}
*/
function enterScope(node) {
stack.push(node.range);
}
schema: []
},
/**
* Pops the last block scope.
* @returns {void}
*/
function exitScope() {
stack.pop();
}
/**
* Reports a given reference.
* @param {escope.Reference} reference - A reference to report.
* @returns {void}
*/
function report(reference) {
var identifier = reference.identifier;
context.report(
identifier,
"'{{name}}' used outside of binding context.",
{name: identifier.name});
}
create: function(context) {
var stack = [];
/**
* Finds and reports references which are outside of valid scopes.
* @param {ASTNode} node - A node to get variables.
* @returns {void}
*/
function checkForVariables(node) {
if (node.kind !== "var") {
return;
/**
* Makes a block scope.
* @param {ASTNode} node - A node of a scope.
* @returns {void}
*/
function enterScope(node) {
stack.push(node.range);
}
// Defines a predicate to check whether or not a given reference is outside of valid scope.
var scopeRange = stack[stack.length - 1];
/**
* Check if a reference is out of scope
* @param {ASTNode} reference node to examine
* @returns {boolean} True is its outside the scope
* @private
* Pops the last block scope.
* @returns {void}
*/
function isOutsideOfScope(reference) {
var idRange = reference.identifier.range;
return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1];
function exitScope() {
stack.pop();
}
// Gets declared variables, and checks its references.
var variables = context.getDeclaredVariables(node);
for (var i = 0; i < variables.length; ++i) {
/**
* Reports a given reference.
* @param {escope.Reference} reference - A reference to report.
* @returns {void}
*/
function report(reference) {
var identifier = reference.identifier;
// Reports.
variables[i]
.references
.filter(isOutsideOfScope)
.forEach(report);
context.report(
identifier,
"'{{name}}' used outside of binding context.",
{name: identifier.name});
}
}
return {
"Program": function(node) {
stack = [node.range];
},
/**
* Finds and reports references which are outside of valid scopes.
* @param {ASTNode} node - A node to get variables.
* @returns {void}
*/
function checkForVariables(node) {
if (node.kind !== "var") {
return;
}
// Defines a predicate to check whether or not a given reference is outside of valid scope.
var scopeRange = stack[stack.length - 1];
/**
* Check if a reference is out of scope
* @param {ASTNode} reference node to examine
* @returns {boolean} True is its outside the scope
* @private
*/
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
.filter(isOutsideOfScope)
.forEach(report);
}
}
// Manages scopes.
"BlockStatement": enterScope,
"BlockStatement:exit": exitScope,
"ForStatement": enterScope,
"ForStatement:exit": exitScope,
"ForInStatement": enterScope,
"ForInStatement:exit": exitScope,
"ForOfStatement": enterScope,
"ForOfStatement:exit": exitScope,
"SwitchStatement": enterScope,
"SwitchStatement:exit": exitScope,
"CatchClause": enterScope,
"CatchClause:exit": exitScope,
// Finds and reports references which are outside of valid scope.
"VariableDeclaration": checkForVariables
};
return {
Program: function(node) {
stack = [node.range];
},
// Manages scopes.
BlockStatement: enterScope,
"BlockStatement:exit": exitScope,
ForStatement: enterScope,
"ForStatement:exit": exitScope,
ForInStatement: enterScope,
"ForInStatement:exit": exitScope,
ForOfStatement: enterScope,
"ForOfStatement:exit": exitScope,
SwitchStatement: enterScope,
"SwitchStatement:exit": exitScope,
CatchClause: enterScope,
"CatchClause:exit": exitScope,
// Finds and reports references which are outside of valid scope.
VariableDeclaration: checkForVariables
};
}
};
module.exports.schema = [];

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

@ -1,7 +1,6 @@
/**
* @fileoverview A rule to disallow or enforce spaces inside of single line blocks.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/
"use strict";
@ -12,109 +11,121 @@ var util = require("../ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var always = (context.options[0] !== "never"),
message = always ? "Requires a space" : "Unexpected space(s)",
sourceCode = context.getSourceCode();
/**
* Gets the open brace token from a given node.
* @param {ASTNode} node - A BlockStatement/SwitchStatement node to get.
* @returns {Token} The token of the open brace.
*/
function getOpenBrace(node) {
if (node.type === "SwitchStatement") {
if (node.cases.length > 0) {
return context.getTokenBefore(node.cases[0]);
}
return context.getLastToken(node, 1);
}
return context.getFirstToken(node);
}
module.exports = {
meta: {
docs: {
description: "enforce consistent spacing inside single-line blocks",
category: "Stylistic Issues",
recommended: false
},
/**
* Checks whether or not:
* - given tokens are on same line.
* - there is/isn't a space between given tokens.
* @param {Token} left - A token to check.
* @param {Token} right - The token which is next to `left`.
* @returns {boolean}
* When the option is `"always"`, `true` if there are one or more spaces between given tokens.
* When the option is `"never"`, `true` if there are not any spaces between given tokens.
* If given tokens are not on same line, it's always `true`.
*/
function isValid(left, right) {
return (
!util.isTokenOnSameLine(left, right) ||
sourceCode.isSpaceBetweenTokens(left, right) === always
);
}
fixable: "whitespace",
schema: [
{enum: ["always", "never"]}
]
},
create: function(context) {
var always = (context.options[0] !== "never"),
message = always ? "Requires a space" : "Unexpected space(s)",
sourceCode = context.getSourceCode();
/**
* Reports invalid spacing style inside braces.
* @param {ASTNode} node - A BlockStatement/SwitchStatement node to get.
* @returns {void}
*/
function checkSpacingInsideBraces(node) {
// Gets braces and the first/last token of content.
var openBrace = getOpenBrace(node);
var closeBrace = context.getLastToken(node);
var firstToken = sourceCode.getTokenOrCommentAfter(openBrace);
var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace);
// Skip if the node is invalid or empty.
if (openBrace.type !== "Punctuator" ||
openBrace.value !== "{" ||
closeBrace.type !== "Punctuator" ||
closeBrace.value !== "}" ||
firstToken === closeBrace
) {
return;
/**
* Gets the open brace token from a given node.
* @param {ASTNode} node - A BlockStatement/SwitchStatement node to get.
* @returns {Token} The token of the open brace.
*/
function getOpenBrace(node) {
if (node.type === "SwitchStatement") {
if (node.cases.length > 0) {
return context.getTokenBefore(node.cases[0]);
}
return context.getLastToken(node, 1);
}
return context.getFirstToken(node);
}
// Skip line comments for option never
if (!always && firstToken.type === "Line") {
return;
/**
* Checks whether or not:
* - given tokens are on same line.
* - there is/isn't a space between given tokens.
* @param {Token} left - A token to check.
* @param {Token} right - The token which is next to `left`.
* @returns {boolean}
* When the option is `"always"`, `true` if there are one or more spaces between given tokens.
* When the option is `"never"`, `true` if there are not any spaces between given tokens.
* If given tokens are not on same line, it's always `true`.
*/
function isValid(left, right) {
return (
!util.isTokenOnSameLine(left, right) ||
sourceCode.isSpaceBetweenTokens(left, right) === always
);
}
// Check.
if (!isValid(openBrace, firstToken)) {
context.report({
node: node,
loc: openBrace.loc.start,
message: message + " after '{'.",
fix: function(fixer) {
if (always) {
return fixer.insertTextBefore(firstToken, " ");
}
/**
* Reports invalid spacing style inside braces.
* @param {ASTNode} node - A BlockStatement/SwitchStatement node to get.
* @returns {void}
*/
function checkSpacingInsideBraces(node) {
return fixer.removeRange([openBrace.range[1], firstToken.range[0]]);
}
});
}
if (!isValid(lastToken, closeBrace)) {
context.report({
node: node,
loc: closeBrace.loc.start,
message: message + " before '}'.",
fix: function(fixer) {
if (always) {
return fixer.insertTextAfter(lastToken, " ");
// Gets braces and the first/last token of content.
var openBrace = getOpenBrace(node);
var closeBrace = context.getLastToken(node);
var firstToken = sourceCode.getTokenOrCommentAfter(openBrace);
var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace);
// Skip if the node is invalid or empty.
if (openBrace.type !== "Punctuator" ||
openBrace.value !== "{" ||
closeBrace.type !== "Punctuator" ||
closeBrace.value !== "}" ||
firstToken === closeBrace
) {
return;
}
// Skip line comments for option never
if (!always && firstToken.type === "Line") {
return;
}
// Check.
if (!isValid(openBrace, firstToken)) {
context.report({
node: node,
loc: openBrace.loc.start,
message: message + " after '{'.",
fix: function(fixer) {
if (always) {
return fixer.insertTextBefore(firstToken, " ");
}
return fixer.removeRange([openBrace.range[1], firstToken.range[0]]);
}
});
}
if (!isValid(lastToken, closeBrace)) {
context.report({
node: node,
loc: closeBrace.loc.start,
message: message + " before '}'.",
fix: function(fixer) {
if (always) {
return fixer.insertTextAfter(lastToken, " ");
}
return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]);
}
});
return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]);
}
});
}
}
}
return {
BlockStatement: checkSpacingInsideBraces,
SwitchStatement: checkSpacingInsideBraces
};
return {
BlockStatement: checkSpacingInsideBraces,
SwitchStatement: checkSpacingInsideBraces
};
}
};
module.exports.schema = [
{enum: ["always", "never"]}
];

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

@ -9,226 +9,236 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var style = context.options[0] || "1tbs",
params = context.options[1] || {},
sourceCode = context.getSourceCode();
var OPEN_MESSAGE = "Opening curly brace does not appear on the same line as controlling statement.",
OPEN_MESSAGE_ALLMAN = "Opening curly brace appears on the same line as controlling statement.",
BODY_MESSAGE = "Statement inside of curly braces should be on next line.",
CLOSE_MESSAGE = "Closing curly brace does not appear on the same line as the subsequent block.",
CLOSE_MESSAGE_SINGLE = "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.",
CLOSE_MESSAGE_STROUSTRUP_ALLMAN = "Closing curly brace appears on the same line as the subsequent block.";
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determines if a given node is a block statement.
* @param {ASTNode} node The node to check.
* @returns {boolean} True if the node is a block statement, false if not.
* @private
*/
function isBlock(node) {
return node && node.type === "BlockStatement";
}
/**
* Check if the token is an punctuator with a value of curly brace
* @param {object} token - Token to check
* @returns {boolean} true if its a curly punctuator
* @private
*/
function isCurlyPunctuator(token) {
return token.value === "{" || token.value === "}";
}
/**
* Binds a list of properties to a function that verifies that the opening
* curly brace is on the same line as its controlling statement of a given
* node.
* @param {...string} The properties to check on the node.
* @returns {Function} A function that will perform the check on a node
* @private
*/
function checkBlock() {
var blockProperties = arguments;
return function(node) {
Array.prototype.forEach.call(blockProperties, function(blockProp) {
var block = node[blockProp],
previousToken,
curlyToken,
curlyTokenEnd,
allOnSameLine;
if (!isBlock(block)) {
return;
}
previousToken = sourceCode.getTokenBefore(block);
curlyToken = sourceCode.getFirstToken(block);
curlyTokenEnd = sourceCode.getLastToken(block);
allOnSameLine = previousToken.loc.start.line === curlyTokenEnd.loc.start.line;
module.exports = {
meta: {
docs: {
description: "enforce consistent brace style for blocks",
category: "Stylistic Issues",
recommended: false
},
if (allOnSameLine && params.allowSingleLine) {
return;
}
schema: [
{
enum: ["1tbs", "stroustrup", "allman"]
},
{
type: "object",
properties: {
allowSingleLine: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
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) {
context.report(node, OPEN_MESSAGE_ALLMAN);
}
create: function(context) {
var style = context.options[0] || "1tbs",
params = context.options[1] || {},
sourceCode = context.getSourceCode();
var OPEN_MESSAGE = "Opening curly brace does not appear on the same line as controlling statement.",
OPEN_MESSAGE_ALLMAN = "Opening curly brace appears on the same line as controlling statement.",
BODY_MESSAGE = "Statement inside of curly braces should be on next line.",
CLOSE_MESSAGE = "Closing curly brace does not appear on the same line as the subsequent block.",
CLOSE_MESSAGE_SINGLE = "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.",
CLOSE_MESSAGE_STROUSTRUP_ALLMAN = "Closing curly brace appears on the same line as the subsequent block.";
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determines if a given node is a block statement.
* @param {ASTNode} node The node to check.
* @returns {boolean} True if the node is a block statement, false if not.
* @private
*/
function isBlock(node) {
return node && node.type === "BlockStatement";
}
if (!block.body.length) {
return;
}
/**
* Check if the token is an punctuator with a value of curly brace
* @param {object} token - Token to check
* @returns {boolean} true if its a curly punctuator
* @private
*/
function isCurlyPunctuator(token) {
return token.value === "{" || token.value === "}";
}
if (curlyToken.loc.start.line === block.body[0].loc.start.line) {
context.report(block.body[0], BODY_MESSAGE);
}
/**
* Binds a list of properties to a function that verifies that the opening
* curly brace is on the same line as its controlling statement of a given
* node.
* @param {...string} The properties to check on the node.
* @returns {Function} A function that will perform the check on a node
* @private
*/
function checkBlock() {
var blockProperties = arguments;
return function(node) {
Array.prototype.forEach.call(blockProperties, function(blockProp) {
var block = node[blockProp],
previousToken,
curlyToken,
curlyTokenEnd,
allOnSameLine;
if (!isBlock(block)) {
return;
}
previousToken = sourceCode.getTokenBefore(block);
curlyToken = sourceCode.getFirstToken(block);
curlyTokenEnd = sourceCode.getLastToken(block);
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) {
context.report(node, OPEN_MESSAGE_ALLMAN);
}
if (!block.body.length) {
return;
}
if (curlyToken.loc.start.line === block.body[0].loc.start.line) {
context.report(block.body[0], BODY_MESSAGE);
}
if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.start.line) {
context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE);
}
});
};
}
if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.start.line) {
context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE);
/**
* Enforces the configured brace style on IfStatements
* @param {ASTNode} node An IfStatement node.
* @returns {void}
* @private
*/
function checkIfStatement(node) {
var tokens;
checkBlock("consequent", "alternate")(node);
if (node.alternate) {
tokens = sourceCode.getTokensBefore(node.alternate, 2);
if (style === "1tbs") {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line &&
node.consequent.type === "BlockStatement" &&
isCurlyPunctuator(tokens[0])) {
context.report(node.alternate, CLOSE_MESSAGE);
}
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node.alternate, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
}
});
};
}
/**
* Enforces the configured brace style on IfStatements
* @param {ASTNode} node An IfStatement node.
* @returns {void}
* @private
*/
function checkIfStatement(node) {
var tokens;
checkBlock("consequent", "alternate")(node);
if (node.alternate) {
tokens = sourceCode.getTokensBefore(node.alternate, 2);
if (style === "1tbs") {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line &&
node.consequent.type === "BlockStatement" &&
isCurlyPunctuator(tokens[0])) {
context.report(node.alternate, CLOSE_MESSAGE);
}
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node.alternate, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
}
}
}
/**
* Enforces the configured brace style on TryStatements
* @param {ASTNode} node A TryStatement node.
* @returns {void}
* @private
*/
function checkTryStatement(node) {
var tokens;
checkBlock("block", "finalizer")(node);
if (isBlock(node.finalizer)) {
tokens = sourceCode.getTokensBefore(node.finalizer, 2);
if (style === "1tbs") {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node.finalizer, CLOSE_MESSAGE);
/**
* Enforces the configured brace style on TryStatements
* @param {ASTNode} node A TryStatement node.
* @returns {void}
* @private
*/
function checkTryStatement(node) {
var tokens;
checkBlock("block", "finalizer")(node);
if (isBlock(node.finalizer)) {
tokens = sourceCode.getTokensBefore(node.finalizer, 2);
if (style === "1tbs") {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node.finalizer, CLOSE_MESSAGE);
}
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node.finalizer, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
}
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node.finalizer, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
}
}
}
/**
* Enforces the configured brace style on CatchClauses
* @param {ASTNode} node A CatchClause node.
* @returns {void}
* @private
*/
function checkCatchClause(node) {
var previousToken = sourceCode.getTokenBefore(node),
firstToken = sourceCode.getFirstToken(node);
checkBlock("body")(node);
if (isBlock(node.body)) {
if (style === "1tbs") {
if (previousToken.loc.start.line !== firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE);
}
} else {
if (previousToken.loc.start.line === firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
/**
* Enforces the configured brace style on CatchClauses
* @param {ASTNode} node A CatchClause node.
* @returns {void}
* @private
*/
function checkCatchClause(node) {
var previousToken = sourceCode.getTokenBefore(node),
firstToken = sourceCode.getFirstToken(node);
checkBlock("body")(node);
if (isBlock(node.body)) {
if (style === "1tbs") {
if (previousToken.loc.start.line !== firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE);
}
} else {
if (previousToken.loc.start.line === firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
}
}
}
}
}
/**
* Enforces the configured brace style on SwitchStatements
* @param {ASTNode} node A SwitchStatement node.
* @returns {void}
* @private
*/
function checkSwitchStatement(node) {
var tokens;
if (node.cases && node.cases.length) {
tokens = sourceCode.getTokensBefore(node.cases[0], 2);
} else {
tokens = sourceCode.getLastTokens(node, 3);
}
/**
* Enforces the configured brace style on SwitchStatements
* @param {ASTNode} node A SwitchStatement node.
* @returns {void}
* @private
*/
function checkSwitchStatement(node) {
var tokens;
if (node.cases && node.cases.length) {
tokens = sourceCode.getTokensBefore(node.cases[0], 2);
} else {
tokens = sourceCode.getLastTokens(node, 3);
}
if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE);
} else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE_ALLMAN);
if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE);
} else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE_ALLMAN);
}
}
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
"FunctionDeclaration": checkBlock("body"),
"FunctionExpression": checkBlock("body"),
"ArrowFunctionExpression": checkBlock("body"),
"IfStatement": checkIfStatement,
"TryStatement": checkTryStatement,
"CatchClause": checkCatchClause,
"DoWhileStatement": checkBlock("body"),
"WhileStatement": checkBlock("body"),
"WithStatement": checkBlock("body"),
"ForStatement": checkBlock("body"),
"ForInStatement": checkBlock("body"),
"ForOfStatement": checkBlock("body"),
"SwitchStatement": checkSwitchStatement
};
};
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
FunctionDeclaration: checkBlock("body"),
FunctionExpression: checkBlock("body"),
ArrowFunctionExpression: checkBlock("body"),
IfStatement: checkIfStatement,
TryStatement: checkTryStatement,
CatchClause: checkCatchClause,
DoWhileStatement: checkBlock("body"),
WhileStatement: checkBlock("body"),
WithStatement: checkBlock("body"),
ForStatement: checkBlock("body"),
ForInStatement: checkBlock("body"),
ForOfStatement: checkBlock("body"),
SwitchStatement: checkSwitchStatement
};
module.exports.schema = [
{
"enum": ["1tbs", "stroustrup", "allman"]
},
{
"type": "object",
"properties": {
"allowSingleLine": {
"type": "boolean"
}
},
"additionalProperties": false
}
];
};

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

@ -1,7 +1,6 @@
/**
* @fileoverview Enforce return after a callback.
* @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
*/
"use strict";
@ -9,136 +8,146 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var callbacks = context.options[0] || ["callback", "cb", "next"];
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Find the closest parent matching a list of types.
* @param {ASTNode} node The node whose parents we are searching
* @param {Array} types The node types to match
* @returns {ASTNode} The matched node or undefined.
*/
function findClosestParentOfType(node, types) {
if (!node.parent) {
return null;
}
if (types.indexOf(node.parent.type) === -1) {
return findClosestParentOfType(node.parent, types);
module.exports = {
meta: {
docs: {
description: "require `return` statements after callbacks",
category: "Node.js and CommonJS",
recommended: false
},
schema: [{
type: "array",
items: { type: "string" }
}]
},
create: function(context) {
var callbacks = context.options[0] || ["callback", "cb", "next"];
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Find the closest parent matching a list of types.
* @param {ASTNode} node The node whose parents we are searching
* @param {Array} types The node types to match
* @returns {ASTNode} The matched node or undefined.
*/
function findClosestParentOfType(node, types) {
if (!node.parent) {
return null;
}
if (types.indexOf(node.parent.type) === -1) {
return findClosestParentOfType(node.parent, types);
}
return node.parent;
}
return node.parent;
}
/**
* Check to see if a CallExpression is in our callback list.
* @param {ASTNode} node The node to check against our callback names list.
* @returns {Boolean} Whether or not this function matches our callback name.
*/
function isCallback(node) {
return node.callee.type === "Identifier" && callbacks.indexOf(node.callee.name) > -1;
}
/**
* Determines whether or not the callback is part of a callback expression.
* @param {ASTNode} node The callback node
* @param {ASTNode} parentNode The expression node
* @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;
/**
* Check to see if a CallExpression is in our callback list.
* @param {ASTNode} node The node to check against our callback names list.
* @returns {Boolean} Whether or not this function matches our callback name.
*/
function isCallback(node) {
return node.callee.type === "Identifier" && callbacks.indexOf(node.callee.name) > -1;
}
// cb()
if (parentNode.expression === node) {
return true;
}
/**
* Determines whether or not the callback is part of a callback expression.
* @param {ASTNode} node The callback node
* @param {ASTNode} parentNode The expression node
* @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;
}
// special case for cb && cb() and similar
if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") {
if (parentNode.expression.right === node) {
// cb()
if (parentNode.expression === node) {
return true;
}
// special case for cb && cb() and similar
if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") {
if (parentNode.expression.right === node) {
return true;
}
}
return false;
}
return false;
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
CallExpression: function(node) {
return {
"CallExpression": function(node) {
// if we"re not a callback we can return
if (!isCallback(node)) {
return;
}
// if we"re not a callback we can return
if (!isCallback(node)) {
return;
}
// find the closest block, return or loop
var closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {},
lastItem, parentType;
// find the closest block, return or loop
var closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {},
lastItem, parentType;
// if our parent is a return we know we're ok
if (closestBlock.type === "ReturnStatement") {
return;
}
// if our parent is a return we know we're ok
if (closestBlock.type === "ReturnStatement") {
return;
}
// arrow functions don't always have blocks and implicitly return
if (closestBlock.type === "ArrowFunctionExpression") {
return;
}
// arrow functions don't always have blocks and implicitly return
if (closestBlock.type === "ArrowFunctionExpression") {
return;
}
// block statements are part of functions and most if statements
if (closestBlock.type === "BlockStatement") {
// block statements are part of functions and most if statements
if (closestBlock.type === "BlockStatement") {
// find the last item in the block
lastItem = closestBlock.body[closestBlock.body.length - 1];
// find the last item in the block
lastItem = closestBlock.body[closestBlock.body.length - 1];
// if the callback is the last thing in a block that might be ok
if (isCallbackExpression(node, lastItem)) {
// if the callback is the last thing in a block that might be ok
if (isCallbackExpression(node, lastItem)) {
parentType = closestBlock.parent.type;
parentType = closestBlock.parent.type;
// but only if the block is part of a function
if (parentType === "FunctionExpression" ||
parentType === "FunctionDeclaration" ||
parentType === "ArrowFunctionExpression"
) {
return;
}
// but only if the block is part of a function
if (parentType === "FunctionExpression" ||
parentType === "FunctionDeclaration" ||
parentType === "ArrowFunctionExpression"
) {
return;
}
}
// ending a block with a return is also ok
if (lastItem.type === "ReturnStatement") {
// ending a block with a return is also ok
if (lastItem.type === "ReturnStatement") {
// but only if the callback is immediately before
if (isCallbackExpression(node, closestBlock.body[closestBlock.body.length - 2])) {
return;
// but only if the callback is immediately before
if (isCallbackExpression(node, closestBlock.body[closestBlock.body.length - 2])) {
return;
}
}
}
}
// as long as you're the child of a function at this point you should be asked to return
if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) {
context.report(node, "Expected return with your callback function.");
}
// as long as you're the child of a function at this point you should be asked to return
if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) {
context.report(node, "Expected return with your callback function.");
}
}
};
};
}
};
module.exports.schema = [{
type: "array",
items: { type: "string" }
}];

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to flag non-camelcased identifiers
* @author Nicholas C. Zakas
* @copyright 2015 Dieter Oberkofler. All rights reserved.
*/
"use strict";
@ -10,116 +9,126 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
var reported = [];
/**
* Checks if a string contains an underscore and isn't all upper-case
* @param {String} name The string to check.
* @returns {boolean} if the string is underscored
* @private
*/
function isUnderscored(name) {
// if there's an underscore, it might be A_CONSTANT, which is okay
return name.indexOf("_") > -1 && name !== name.toUpperCase();
}
/**
* Reports an AST node as a rule violation.
* @param {ASTNode} node The node to report.
* @returns {void}
* @private
*/
function report(node) {
if (reported.indexOf(node) < 0) {
reported.push(node);
context.report(node, "Identifier '{{name}}' is not in camel case.", { name: node.name });
}
}
var options = context.options[0] || {},
properties = options.properties || "";
if (properties !== "always" && properties !== "never") {
properties = "always";
}
return {
module.exports = {
meta: {
docs: {
description: "enforce camelcase naming convention",
category: "Stylistic Issues",
recommended: false
},
"Identifier": function(node) {
schema: [
{
type: "object",
properties: {
properties: {
enum: ["always", "never"]
}
},
additionalProperties: false
}
]
},
/*
* 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;
create: function(context) {
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// "never" check properties
if (properties === "never") {
return;
}
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
var reported = [];
// Always report underscored object names
if (node.parent.object.type === "Identifier" &&
node.parent.object.name === node.name &&
isUnderscored(name)) {
report(node);
/**
* Checks if a string contains an underscore and isn't all upper-case
* @param {String} name The string to check.
* @returns {boolean} if the string is underscored
* @private
*/
function isUnderscored(name) {
// Report AssignmentExpressions only if they are the left side of the assignment
} else if (effectiveParent.type === "AssignmentExpression" &&
isUnderscored(name) &&
(effectiveParent.right.type !== "MemberExpression" ||
effectiveParent.left.type === "MemberExpression" &&
effectiveParent.left.property.name === node.name)) {
report(node);
}
// if there's an underscore, it might be A_CONSTANT, which is okay
return name.indexOf("_") > -1 && name !== name.toUpperCase();
}
// Properties have their own rules
} else if (node.parent.type === "Property") {
/**
* Reports an AST node as a rule violation.
* @param {ASTNode} node The node to report.
* @returns {void}
* @private
*/
function report(node) {
if (reported.indexOf(node) < 0) {
reported.push(node);
context.report(node, "Identifier '{{name}}' is not in camel case.", { name: node.name });
}
}
// "never" check properties
if (properties === "never") {
return;
}
var options = context.options[0] || {},
properties = options.properties || "";
if (node.parent.parent && node.parent.parent.type === "ObjectPattern" &&
node.parent.key === node && node.parent.value !== node) {
return;
}
if (properties !== "always" && properties !== "never") {
properties = "always";
}
if (isUnderscored(name) && effectiveParent.type !== "CallExpression") {
return {
Identifier: function(node) {
/*
* 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;
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
// "never" check properties
if (properties === "never") {
return;
}
// Always report underscored object names
if (node.parent.object.type === "Identifier" &&
node.parent.object.name === node.name &&
isUnderscored(name)) {
report(node);
// Report AssignmentExpressions only if they are the left side of the assignment
} else if (effectiveParent.type === "AssignmentExpression" &&
isUnderscored(name) &&
(effectiveParent.right.type !== "MemberExpression" ||
effectiveParent.left.type === "MemberExpression" &&
effectiveParent.left.property.name === node.name)) {
report(node);
}
// Properties have their own rules
} else if (node.parent.type === "Property") {
// "never" check properties
if (properties === "never") {
return;
}
if (node.parent.parent && node.parent.parent.type === "ObjectPattern" &&
node.parent.key === node && node.parent.value !== node) {
return;
}
if (isUnderscored(name) && effectiveParent.type !== "CallExpression") {
report(node);
}
// Report anything that is underscored that isn't a CallExpression
} else if (isUnderscored(name) && effectiveParent.type !== "CallExpression") {
report(node);
}
// Report anything that is underscored that isn't a CallExpression
} else if (isUnderscored(name) && effectiveParent.type !== "CallExpression") {
report(node);
}
}
};
};
};
module.exports.schema = [
{
"type": "object",
"properties": {
"properties": {
"enum": ["always", "never"]
}
},
"additionalProperties": false
}
];
};

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

@ -1,10 +1,6 @@
/**
* @fileoverview Rule to forbid or enforce dangling commas.
* @author Ian Christian Myers
* @copyright 2015 Toru Nagashima
* @copyright 2015 Mathias Schreck
* @copyright 2013 Ian Christian Myers
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -24,203 +20,204 @@ var lodash = require("lodash");
* @returns {boolean} `true` if a trailing comma is allowed.
*/
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" &&
lastItem.type !== "SpreadElement"
);
// TODO(t-nagashima): Remove this case after https://github.com/eslint/espree/issues/195 was fixed.
case "ArrayExpression":
return (
node.parent.type !== "ForOfStatement" ||
node.parent.left !== node ||
lastItem.type !== "SpreadElement"
);
default:
return true;
}
return node.type !== "ArrayPattern" || lastItem.type !== "RestElement";
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var mode = context.options[0];
var UNEXPECTED_MESSAGE = "Unexpected trailing comma.";
var MISSING_MESSAGE = "Missing trailing comma.";
/**
* Checks whether or not a given node is multiline.
* This rule handles a given node as multiline when the closing parenthesis
* and the last element are not on the same line.
*
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is multiline.
*/
function isMultiline(node) {
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
if (!lastItem) {
return false;
module.exports = {
meta: {
docs: {
description: "require or disallow trailing commas",
category: "Possible Errors",
recommended: true
},
fixable: "code",
schema: [
{
enum: ["always", "always-multiline", "only-multiline", "never"]
}
]
},
create: function(context) {
var mode = context.options[0];
var UNEXPECTED_MESSAGE = "Unexpected trailing comma.";
var MISSING_MESSAGE = "Missing trailing comma.";
/**
* Checks whether or not a given node is multiline.
* This rule handles a given node as multiline when the closing parenthesis
* and the last element are not on the same line.
*
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is multiline.
*/
function isMultiline(node) {
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
if (!lastItem) {
return false;
}
var sourceCode = context.getSourceCode(),
penultimateToken = sourceCode.getLastToken(lastItem),
lastToken = sourceCode.getTokenAfter(penultimateToken);
// parentheses are a pain
while (lastToken.value === ")") {
penultimateToken = lastToken;
lastToken = sourceCode.getTokenAfter(lastToken);
}
if (lastToken.value === ",") {
penultimateToken = lastToken;
lastToken = sourceCode.getTokenAfter(lastToken);
}
return lastToken.loc.end.line !== penultimateToken.loc.end.line;
}
var sourceCode = context.getSourceCode(),
penultimateToken = sourceCode.getLastToken(lastItem),
lastToken = sourceCode.getTokenAfter(penultimateToken);
// parentheses are a pain
while (lastToken.value === ")") {
penultimateToken = lastToken;
lastToken = sourceCode.getTokenAfter(lastToken);
/**
* Reports a trailing comma if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forbidTrailingComma(node) {
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
return;
}
var sourceCode = context.getSourceCode(),
trailingToken;
// last item can be surrounded by parentheses for object and array literals
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
} else {
trailingToken = sourceCode.getTokenAfter(lastItem);
}
if (trailingToken.value === ",") {
context.report({
node: lastItem,
loc: trailingToken.loc.start,
message: UNEXPECTED_MESSAGE,
fix: function(fixer) {
return fixer.remove(trailingToken);
}
});
}
}
if (lastToken.value === ",") {
penultimateToken = lastToken;
lastToken = sourceCode.getTokenAfter(lastToken);
/**
* Reports the last element of a given node if it does not have a trailing
* comma.
*
* If a given node is `ArrayPattern` which has `RestElement`, the trailing
* comma is disallowed, so report if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forceTrailingComma(node) {
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
return;
}
if (!isTrailingCommaAllowed(node, lastItem)) {
forbidTrailingComma(node);
return;
}
var sourceCode = context.getSourceCode(),
trailingToken;
// last item can be surrounded by parentheses for object and array literals
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
} else {
trailingToken = sourceCode.getTokenAfter(lastItem);
}
if (trailingToken.value !== ",") {
context.report({
node: lastItem,
loc: lastItem.loc.end,
message: MISSING_MESSAGE,
fix: function(fixer) {
return fixer.insertTextAfter(lastItem, ",");
}
});
}
}
return lastToken.loc.end.line !== penultimateToken.loc.end.line;
}
/**
* Reports a trailing comma if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forbidTrailingComma(node) {
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
return;
/**
* If a given node is multiline, reports the last element of a given node
* when it does not have a trailing comma.
* Otherwise, reports a trailing comma if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forceTrailingCommaIfMultiline(node) {
if (isMultiline(node)) {
forceTrailingComma(node);
} else {
forbidTrailingComma(node);
}
}
var sourceCode = context.getSourceCode(),
trailingToken;
// last item can be surrounded by parentheses for object and array literals
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
} else {
trailingToken = sourceCode.getTokenAfter(lastItem);
/**
* Only if a given node is not multiline, reports the last element of a given node
* when it does not have a trailing comma.
* Otherwise, reports a trailing comma if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function allowTrailingCommaIfMultiline(node) {
if (!isMultiline(node)) {
forbidTrailingComma(node);
}
}
if (trailingToken.value === ",") {
context.report(
lastItem,
trailingToken.loc.start,
UNEXPECTED_MESSAGE);
}
}
/**
* Reports the last element of a given node if it does not have a trailing
* comma.
*
* If a given node is `ArrayPattern` which has `RestElement`, the trailing
* comma is disallowed, so report if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forceTrailingComma(node) {
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
return;
}
if (!isTrailingCommaAllowed(node, lastItem)) {
forbidTrailingComma(node);
return;
}
var sourceCode = context.getSourceCode(),
trailingToken;
// Chooses a checking function.
var checkForTrailingComma;
// last item can be surrounded by parentheses for object and array literals
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
if (mode === "always") {
checkForTrailingComma = forceTrailingComma;
} else if (mode === "always-multiline") {
checkForTrailingComma = forceTrailingCommaIfMultiline;
} else if (mode === "only-multiline") {
checkForTrailingComma = allowTrailingCommaIfMultiline;
} else {
trailingToken = sourceCode.getTokenAfter(lastItem);
checkForTrailingComma = forbidTrailingComma;
}
if (trailingToken.value !== ",") {
context.report(
lastItem,
lastItem.loc.end,
MISSING_MESSAGE);
}
return {
ObjectExpression: checkForTrailingComma,
ObjectPattern: checkForTrailingComma,
ArrayExpression: checkForTrailingComma,
ArrayPattern: checkForTrailingComma,
ImportDeclaration: checkForTrailingComma,
ExportNamedDeclaration: checkForTrailingComma
};
}
/**
* If a given node is multiline, reports the last element of a given node
* when it does not have a trailing comma.
* Otherwise, reports a trailing comma if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forceTrailingCommaIfMultiline(node) {
if (isMultiline(node)) {
forceTrailingComma(node);
} else {
forbidTrailingComma(node);
}
}
/**
* Only if a given node is not multiline, reports the last element of a given node
* when it does not have a trailing comma.
* Otherwise, reports a trailing comma if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function allowTrailingCommaIfMultiline(node) {
if (!isMultiline(node)) {
forbidTrailingComma(node);
}
}
// Chooses a checking function.
var checkForTrailingComma;
if (mode === "always") {
checkForTrailingComma = forceTrailingComma;
} else if (mode === "always-multiline") {
checkForTrailingComma = forceTrailingCommaIfMultiline;
} else if (mode === "only-multiline") {
checkForTrailingComma = allowTrailingCommaIfMultiline;
} else {
checkForTrailingComma = forbidTrailingComma;
}
return {
"ObjectExpression": checkForTrailingComma,
"ObjectPattern": checkForTrailingComma,
"ArrayExpression": checkForTrailingComma,
"ArrayPattern": checkForTrailingComma,
"ImportDeclaration": checkForTrailingComma,
"ExportNamedDeclaration": checkForTrailingComma
};
};
module.exports.schema = [
{
"enum": ["always", "always-multiline", "only-multiline", "never"]
}
];

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

@ -1,7 +1,6 @@
/**
* @fileoverview Comma spacing - validates spacing before and after comma
* @author Vignesh Anand aka vegetableman.
* @copyright 2014 Vignesh Anand. All rights reserved.
*/
"use strict";
@ -11,173 +10,185 @@ var astUtils = require("../ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var sourceCode = context.getSourceCode();
var tokensAndComments = sourceCode.tokensAndComments;
var options = {
before: context.options[0] ? !!context.options[0].before : false,
after: context.options[0] ? !!context.options[0].after : true
};
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// list of comma tokens to ignore for the check of leading whitespace
var commaTokensToIgnore = [];
/**
* Determines if a given token is a comma operator.
* @param {ASTNode} token The token to check.
* @returns {boolean} True if the token is a comma, false if not.
* @private
*/
function isComma(token) {
return !!token && (token.type === "Punctuator") && (token.value === ",");
}
module.exports = {
meta: {
docs: {
description: "enforce consistent spacing before and after commas",
category: "Stylistic Issues",
recommended: false
},
/**
* Reports a spacing error with an appropriate message.
* @param {ASTNode} node The binary expression node to report.
* @param {string} dir Is the error "before" or "after" the comma?
* @param {ASTNode} otherNode The node at the left or right of `node`
* @returns {void}
* @private
*/
function report(node, dir, otherNode) {
context.report({
node: node,
fix: function(fixer) {
if (options[dir]) {
if (dir === "before") {
return fixer.insertTextBefore(node, " ");
} else {
return fixer.insertTextAfter(node, " ");
fixable: "whitespace",
schema: [
{
type: "object",
properties: {
before: {
type: "boolean"
},
after: {
type: "boolean"
}
} else {
var start, end;
var newText = "";
},
additionalProperties: false
}
]
},
create: function(context) {
var sourceCode = context.getSourceCode();
var tokensAndComments = sourceCode.tokensAndComments;
var options = {
before: context.options[0] ? !!context.options[0].before : false,
after: context.options[0] ? !!context.options[0].after : true
};
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// list of comma tokens to ignore for the check of leading whitespace
var commaTokensToIgnore = [];
/**
* Determines if a given token is a comma operator.
* @param {ASTNode} token The token to check.
* @returns {boolean} True if the token is a comma, false if not.
* @private
*/
function isComma(token) {
return !!token && (token.type === "Punctuator") && (token.value === ",");
}
if (dir === "before") {
start = otherNode.range[1];
end = node.range[0];
/**
* Reports a spacing error with an appropriate message.
* @param {ASTNode} node The binary expression node to report.
* @param {string} dir Is the error "before" or "after" the comma?
* @param {ASTNode} otherNode The node at the left or right of `node`
* @returns {void}
* @private
*/
function report(node, dir, otherNode) {
context.report({
node: node,
fix: function(fixer) {
if (options[dir]) {
if (dir === "before") {
return fixer.insertTextBefore(node, " ");
} else {
return fixer.insertTextAfter(node, " ");
}
} else {
start = node.range[1];
end = otherNode.range[0];
var start, end;
var newText = "";
if (dir === "before") {
start = otherNode.range[1];
end = node.range[0];
} else {
start = node.range[1];
end = otherNode.range[0];
}
return fixer.replaceTextRange([start, end], newText);
}
return fixer.replaceTextRange([start, end], newText);
}
},
message: options[dir] ?
"A space is required " + dir + " ','." :
"There should be no space " + dir + " ','."
});
}
/**
* Validates the spacing around a comma token.
* @param {Object} tokens - The tokens to be validated.
* @param {Token} tokens.comma The token representing the comma.
* @param {Token} [tokens.left] The last token before the comma.
* @param {Token} [tokens.right] The first token after the comma.
* @param {Token|ASTNode} reportItem The item to use when reporting an error.
* @returns {void}
* @private
*/
function validateCommaItemSpacing(tokens, reportItem) {
if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) &&
(options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma))
) {
report(reportItem, "before", tokens.left);
},
message: options[dir] ?
"A space is required " + dir + " ','." :
"There should be no space " + dir + " ','."
});
}
if (tokens.right && !options.after && tokens.right.type === "Line") {
return;
}
/**
* Validates the spacing around a comma token.
* @param {Object} tokens - The tokens to be validated.
* @param {Token} tokens.comma The token representing the comma.
* @param {Token} [tokens.left] The last token before the comma.
* @param {Token} [tokens.right] The first token after the comma.
* @param {Token|ASTNode} reportItem The item to use when reporting an error.
* @returns {void}
* @private
*/
function validateCommaItemSpacing(tokens, reportItem) {
if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) &&
(options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma))
) {
report(reportItem, "before", tokens.left);
}
if (tokens.right && !options.after && tokens.right.type === "Line") {
return;
}
if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) &&
(options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right))
) {
report(reportItem, "after", tokens.right);
if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) &&
(options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right))
) {
report(reportItem, "after", tokens.right);
}
}
}
/**
* Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
* @param {ASTNode} node An ArrayExpression or ArrayPattern node.
* @returns {void}
*/
function addNullElementsToIgnoreList(node) {
var previousToken = context.getFirstToken(node);
/**
* Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
* @param {ASTNode} node An ArrayExpression or ArrayPattern node.
* @returns {void}
*/
function addNullElementsToIgnoreList(node) {
var previousToken = context.getFirstToken(node);
node.elements.forEach(function(element) {
var token;
node.elements.forEach(function(element) {
var token;
if (element === null) {
token = context.getTokenAfter(previousToken);
if (element === null) {
token = context.getTokenAfter(previousToken);
if (isComma(token)) {
commaTokensToIgnore.push(token);
if (isComma(token)) {
commaTokensToIgnore.push(token);
}
} else {
token = context.getTokenAfter(element);
}
} else {
token = context.getTokenAfter(element);
}
previousToken = token;
});
}
previousToken = token;
});
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
"Program:exit": function() {
return {
"Program:exit": function() {
var previousToken,
nextToken;
var previousToken,
nextToken;
tokensAndComments.forEach(function(token, i) {
tokensAndComments.forEach(function(token, i) {
if (!isComma(token)) {
return;
}
if (token && token.type === "JSXText") {
return;
}
if (!isComma(token)) {
return;
}
previousToken = tokensAndComments[i - 1];
nextToken = tokensAndComments[i + 1];
if (token && token.type === "JSXText") {
return;
}
validateCommaItemSpacing({
comma: token,
left: isComma(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
right: isComma(nextToken) ? null : nextToken
}, token);
});
},
"ArrayExpression": addNullElementsToIgnoreList,
"ArrayPattern": addNullElementsToIgnoreList
previousToken = tokensAndComments[i - 1];
nextToken = tokensAndComments[i + 1];
};
validateCommaItemSpacing({
comma: token,
left: isComma(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
right: isComma(nextToken) ? null : nextToken
}, token);
});
},
ArrayExpression: addNullElementsToIgnoreList,
ArrayPattern: addNullElementsToIgnoreList
};
};
module.exports.schema = [
{
"type": "object",
"properties": {
"before": {
"type": "boolean"
},
"after": {
"type": "boolean"
}
},
"additionalProperties": false
}
];
};

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

@ -1,8 +1,6 @@
/**
* @fileoverview Comma style - enforces comma styles of two types: last and first
* @author Vignesh Anand aka vegetableman
* @copyright 2014 Vignesh Anand. All rights reserved.
* @copyright 2015 Evan Simmons. All rights reserved.
*/
"use strict";
@ -13,174 +11,184 @@ var astUtils = require("../ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var style = context.options[0] || "last",
exceptions = {};
module.exports = {
meta: {
docs: {
description: "enforce consistent comma style",
category: "Stylistic Issues",
recommended: false
},
if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
exceptions = context.options[1].exceptions;
}
schema: [
{
enum: ["first", "last"]
},
{
type: "object",
properties: {
exceptions: {
type: "object",
additionalProperties: {
type: "boolean"
}
}
},
additionalProperties: false
}
]
},
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determines if a given token is a comma operator.
* @param {ASTNode} token The token to check.
* @returns {boolean} True if the token is a comma, false if not.
* @private
*/
function isComma(token) {
return !!token && (token.type === "Punctuator") && (token.value === ",");
}
create: function(context) {
/**
* Validates the spacing around single items in lists.
* @param {Token} previousItemToken The last token from the previous item.
* @param {Token} commaToken The token representing the comma.
* @param {Token} currentItemToken The first token of the current item.
* @param {Token} reportItem The item to use when reporting an error.
* @returns {void}
* @private
*/
function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) {
var style = context.options[0] || "last",
exceptions = {};
// if single line
if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
exceptions = context.options[1].exceptions;
}
return;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determines if a given token is a comma operator.
* @param {ASTNode} token The token to check.
* @returns {boolean} True if the token is a comma, false if not.
* @private
*/
function isComma(token) {
return !!token && (token.type === "Punctuator") && (token.value === ",");
}
} else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
!astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
/**
* Validates the spacing around single items in lists.
* @param {Token} previousItemToken The last token from the previous item.
* @param {Token} commaToken The token representing the comma.
* @param {Token} currentItemToken The first token of the current item.
* @param {Token} reportItem The item to use when reporting an error.
* @returns {void}
* @private
*/
function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) {
// lone comma
context.report(reportItem, {
line: commaToken.loc.end.line,
column: commaToken.loc.start.column
}, "Bad line breaking before and after ','.");
// if single line
if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
} else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
return;
context.report(reportItem, "',' should be placed first.");
} else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
!astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
} else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
// lone comma
context.report(reportItem, {
line: commaToken.loc.end.line,
column: commaToken.loc.start.column
}, "Bad line breaking before and after ','.");
context.report(reportItem, {
line: commaToken.loc.end.line,
column: commaToken.loc.end.column
}, "',' should be placed last.");
}
}
} else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
/**
* Checks the comma placement with regards to a declaration/property/element
* @param {ASTNode} node The binary expression node to check
* @param {string} property The property of the node containing child nodes.
* @private
* @returns {void}
*/
function validateComma(node, property) {
var items = node[property],
arrayLiteral = (node.type === "ArrayExpression"),
previousItemToken;
context.report(reportItem, "',' should be placed first.");
if (items.length > 1 || arrayLiteral) {
} else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
// seed as opening [
previousItemToken = context.getFirstToken(node);
context.report(reportItem, {
line: commaToken.loc.end.line,
column: commaToken.loc.end.column
}, "',' should be placed last.");
}
}
items.forEach(function(item) {
var commaToken = item ? context.getTokenBefore(item) : previousItemToken,
currentItemToken = item ? context.getFirstToken(item) : context.getTokenAfter(commaToken),
reportItem = item || currentItemToken;
/**
* Checks the comma placement with regards to a declaration/property/element
* @param {ASTNode} node The binary expression node to check
* @param {string} property The property of the node containing child nodes.
* @private
* @returns {void}
*/
function validateComma(node, property) {
var items = node[property],
arrayLiteral = (node.type === "ArrayExpression"),
previousItemToken;
if (items.length > 1 || arrayLiteral) {
// seed as opening [
previousItemToken = context.getFirstToken(node);
items.forEach(function(item) {
var commaToken = item ? context.getTokenBefore(item) : previousItemToken,
currentItemToken = item ? context.getFirstToken(item) : context.getTokenAfter(commaToken),
reportItem = item || currentItemToken;
/*
* This works by comparing three token locations:
* - previousItemToken is the last token of the previous item
* - commaToken is the location of the comma before the current item
* - currentItemToken is the first token of the current item
*
* These values get switched around if item is undefined.
* previousItemToken will refer to the last token not belonging
* to the current item, which could be a comma or an opening
* square bracket. currentItemToken could be a comma.
*
* All comparisons are done based on these tokens directly, so
* they are always valid regardless of an undefined item.
*/
if (isComma(commaToken)) {
validateCommaItemSpacing(previousItemToken, commaToken,
currentItemToken, reportItem);
}
previousItemToken = item ? context.getLastToken(item) : previousItemToken;
});
/*
* This works by comparing three token locations:
* - previousItemToken is the last token of the previous item
* - commaToken is the location of the comma before the current item
* - currentItemToken is the first token of the current item
*
* These values get switched around if item is undefined.
* previousItemToken will refer to the last token not belonging
* to the current item, which could be a comma or an opening
* square bracket. currentItemToken could be a comma.
*
* All comparisons are done based on these tokens directly, so
* they are always valid regardless of an undefined item.
* Special case for array literals that have empty last items, such
* as [ 1, 2, ]. These arrays only have two items show up in the
* AST, so we need to look at the token to verify that there's no
* dangling comma.
*/
if (isComma(commaToken)) {
validateCommaItemSpacing(previousItemToken, commaToken,
currentItemToken, reportItem);
}
previousItemToken = item ? context.getLastToken(item) : previousItemToken;
});
/*
* Special case for array literals that have empty last items, such
* as [ 1, 2, ]. These arrays only have two items show up in the
* AST, so we need to look at the token to verify that there's no
* dangling comma.
*/
if (arrayLiteral) {
var lastToken = context.getLastToken(node),
nextToLastToken = context.getTokenBefore(lastToken);
if (isComma(nextToLastToken)) {
validateCommaItemSpacing(
context.getTokenBefore(nextToLastToken),
nextToLastToken,
lastToken,
lastToken
);
if (arrayLiteral) {
var lastToken = context.getLastToken(node),
nextToLastToken = context.getTokenBefore(lastToken);
if (isComma(nextToLastToken)) {
validateCommaItemSpacing(
context.getTokenBefore(nextToLastToken),
nextToLastToken,
lastToken,
lastToken
);
}
}
}
}
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
var nodes = {};
var nodes = {};
if (!exceptions.VariableDeclaration) {
nodes.VariableDeclaration = function(node) {
validateComma(node, "declarations");
};
}
if (!exceptions.ObjectExpression) {
nodes.ObjectExpression = function(node) {
validateComma(node, "properties");
};
}
if (!exceptions.ArrayExpression) {
nodes.ArrayExpression = function(node) {
validateComma(node, "elements");
};
}
return nodes;
};
if (!exceptions.VariableDeclaration) {
nodes.VariableDeclaration = function(node) {
validateComma(node, "declarations");
};
}
if (!exceptions.ObjectExpression) {
nodes.ObjectExpression = function(node) {
validateComma(node, "properties");
};
}
if (!exceptions.ArrayExpression) {
nodes.ArrayExpression = function(node) {
validateComma(node, "elements");
};
}
module.exports.schema = [
{
"enum": ["first", "last"]
},
{
"type": "object",
"properties": {
"exceptions": {
"type": "object",
"additionalProperties": {
"type": "boolean"
}
}
},
"additionalProperties": false
return nodes;
}
];
};

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

@ -10,143 +10,153 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var option = context.options[0],
THRESHOLD = 20;
module.exports = {
meta: {
docs: {
description: "enforce a maximum cyclomatic complexity allowed in a program",
category: "Best Practices",
recommended: false
},
schema: [
{
oneOf: [
{
type: "integer",
minimum: 0
},
{
type: "object",
properties: {
maximum: {
type: "integer",
minimum: 0
},
max: {
type: "integer",
minimum: 0
}
},
additionalProperties: false
}
]
}
]
},
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;
}
create: function(context) {
var option = context.options[0],
THRESHOLD = 20;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
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;
}
// Using a stack to store complexity (handling nested functions)
var fns = [];
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
fns.push(1);
}
// Using a stack to store complexity (handling nested functions)
var fns = [];
/**
* Evaluate the node at the end of function
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function endFunction(node) {
var complexity = fns.pop(),
name = "anonymous";
if (node.id) {
name = node.id.name;
} else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") {
name = node.parent.key.name;
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
fns.push(1);
}
if (complexity > THRESHOLD) {
context.report(node, "Function '{{name}}' has a complexity of {{complexity}}.", { name: name, complexity: complexity });
}
}
/**
* Evaluate the node at the end of function
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function endFunction(node) {
var complexity = fns.pop(),
name = "anonymous";
if (node.id) {
name = node.id.name;
} else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") {
name = node.parent.key.name;
}
/**
* Increase the complexity of the function in context
* @returns {void}
* @private
*/
function increaseComplexity() {
if (fns.length) {
fns[fns.length - 1]++;
if (complexity > THRESHOLD) {
context.report(node, "Function '{{name}}' has a complexity of {{complexity}}.", { name: name, complexity: complexity });
}
}
}
/**
* Increase the switch complexity in context
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function increaseSwitchComplexity(node) {
// Avoiding `default`
if (node.test) {
increaseComplexity(node);
/**
* Increase the complexity of the function in context
* @returns {void}
* @private
*/
function increaseComplexity() {
if (fns.length) {
fns[fns.length - 1]++;
}
}
}
/**
* Increase the logical path complexity in context
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function increaseLogicalComplexity(node) {
// Avoiding &&
if (node.operator === "||") {
increaseComplexity(node);
/**
* Increase the switch complexity in context
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function increaseSwitchComplexity(node) {
// Avoiding `default`
if (node.test) {
increaseComplexity(node);
}
}
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
"FunctionDeclaration": startFunction,
"FunctionExpression": startFunction,
"ArrowFunctionExpression": startFunction,
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
"CatchClause": increaseComplexity,
"ConditionalExpression": increaseComplexity,
"LogicalExpression": increaseLogicalComplexity,
"ForStatement": increaseComplexity,
"ForInStatement": increaseComplexity,
"ForOfStatement": increaseComplexity,
"IfStatement": increaseComplexity,
"SwitchCase": increaseSwitchComplexity,
"WhileStatement": increaseComplexity,
"DoWhileStatement": increaseComplexity
};
/**
* Increase the logical path complexity in context
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function increaseLogicalComplexity(node) {
// Avoiding &&
if (node.operator === "||") {
increaseComplexity(node);
}
}
};
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
CatchClause: increaseComplexity,
ConditionalExpression: increaseComplexity,
LogicalExpression: increaseLogicalComplexity,
ForStatement: increaseComplexity,
ForInStatement: increaseComplexity,
ForOfStatement: increaseComplexity,
IfStatement: increaseComplexity,
SwitchCase: increaseSwitchComplexity,
WhileStatement: increaseComplexity,
DoWhileStatement: increaseComplexity
};
module.exports.schema = [
{
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false
}
]
}
];
};

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

@ -1,7 +1,6 @@
/**
* @fileoverview Disallows or enforces spaces inside computed properties.
* @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
*/
"use strict";
@ -11,143 +10,155 @@ var astUtils = require("../ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var sourceCode = context.getSourceCode();
var propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never"
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Reports that there shouldn't be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @param {Token} tokenAfter - The token after `token`.
* @returns {void}
*/
function reportNoBeginningSpace(node, token, tokenAfter) {
context.report({
node: node,
loc: token.loc.start,
message: "There should be no space after '" + token.value + "'",
fix: function(fixer) {
return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
}
});
}
/**
* Reports that there shouldn't be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @param {Token} tokenBefore - The token before `token`.
* @returns {void}
*/
function reportNoEndingSpace(node, token, tokenBefore) {
context.report({
node: node,
loc: token.loc.start,
message: "There should be no space before '" + token.value + "'",
fix: function(fixer) {
return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
}
});
}
/**
* Reports that there should be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredBeginningSpace(node, token) {
context.report({
node: node,
loc: token.loc.start,
message: "A space is required after '" + token.value + "'",
fix: function(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
module.exports = {
meta: {
docs: {
description: "enforce consistent spacing inside computed property brackets",
category: "Stylistic Issues",
recommended: false
},
/**
* Reports that there should be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredEndingSpace(node, token) {
context.report({
node: node,
loc: token.loc.start,
message: "A space is required before '" + token.value + "'",
fix: function(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
fixable: "whitespace",
/**
* Returns a function that checks the spacing of a node on the property name
* that was passed in.
* @param {String} propertyName The property on the node to check for spacing
* @returns {Function} A function that will check spacing on a node
*/
function checkSpacing(propertyName) {
return function(node) {
if (!node.computed) {
return;
schema: [
{
enum: ["always", "never"]
}
]
},
create: function(context) {
var sourceCode = context.getSourceCode();
var propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never"
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Reports that there shouldn't be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @param {Token} tokenAfter - The token after `token`.
* @returns {void}
*/
function reportNoBeginningSpace(node, token, tokenAfter) {
context.report({
node: node,
loc: token.loc.start,
message: "There should be no space after '" + token.value + "'",
fix: function(fixer) {
return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
}
});
}
/**
* Reports that there shouldn't be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @param {Token} tokenBefore - The token before `token`.
* @returns {void}
*/
function reportNoEndingSpace(node, token, tokenBefore) {
context.report({
node: node,
loc: token.loc.start,
message: "There should be no space before '" + token.value + "'",
fix: function(fixer) {
return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
}
});
}
/**
* Reports that there should be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredBeginningSpace(node, token) {
context.report({
node: node,
loc: token.loc.start,
message: "A space is required after '" + token.value + "'",
fix: function(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
/**
* Reports that there should be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredEndingSpace(node, token) {
context.report({
node: node,
loc: token.loc.start,
message: "A space is required before '" + token.value + "'",
fix: function(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
/**
* Returns a function that checks the spacing of a node on the property name
* that was passed in.
* @param {String} propertyName The property on the node to check for spacing
* @returns {Function} A function that will check spacing on a node
*/
function checkSpacing(propertyName) {
return function(node) {
if (!node.computed) {
return;
}
var property = node[propertyName];
var before = context.getTokenBefore(property),
first = context.getFirstToken(property),
last = context.getLastToken(property),
after = context.getTokenAfter(property);
if (astUtils.isTokenOnSameLine(before, first)) {
if (propertyNameMustBeSpaced) {
if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) {
reportRequiredBeginningSpace(node, before);
}
} else {
if (sourceCode.isSpaceBetweenTokens(before, first)) {
reportNoBeginningSpace(node, before, first);
var property = node[propertyName];
var before = context.getTokenBefore(property),
first = context.getFirstToken(property),
last = context.getLastToken(property),
after = context.getTokenAfter(property);
if (astUtils.isTokenOnSameLine(before, first)) {
if (propertyNameMustBeSpaced) {
if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) {
reportRequiredBeginningSpace(node, before);
}
} else {
if (sourceCode.isSpaceBetweenTokens(before, first)) {
reportNoBeginningSpace(node, before, first);
}
}
}
}
if (astUtils.isTokenOnSameLine(last, after)) {
if (propertyNameMustBeSpaced) {
if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) {
reportRequiredEndingSpace(node, after);
}
} else {
if (sourceCode.isSpaceBetweenTokens(last, after)) {
reportNoEndingSpace(node, after, last);
if (astUtils.isTokenOnSameLine(last, after)) {
if (propertyNameMustBeSpaced) {
if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) {
reportRequiredEndingSpace(node, after);
}
} else {
if (sourceCode.isSpaceBetweenTokens(last, after)) {
reportNoEndingSpace(node, after, last);
}
}
}
}
};
}
};
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
Property: checkSpacing("key"),
MemberExpression: checkSpacing("property")
};
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
};
return {
Property: checkSpacing("key"),
MemberExpression: checkSpacing("property")
};
module.exports.schema = [
{
"enum": ["always", "never"]
}
];
};

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

@ -27,100 +27,110 @@ function isUnreachable(segment) {
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var funcInfo = null;
/**
* Checks whether of not the implicit returning is consistent if the last
* code path segment is reachable.
*
* @param {ASTNode} node - A program/function node to check.
* @returns {void}
*/
function checkLastSegment(node) {
var loc, type;
/*
* 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) ||
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";
} else if (
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";
}
module.exports = {
meta: {
docs: {
description: "require `return` statements to either always or never specify values",
category: "Best Practices",
recommended: false
},
// Reports.
context.report({
node: node,
loc: loc,
message: "Expected to return a value at the end of this {{type}}.",
data: {type: type}
});
}
schema: []
},
return {
// Initializes/Disposes state of each code path.
"onCodePathStart": function(codePath) {
funcInfo = {
upper: funcInfo,
codePath: codePath,
hasReturn: false,
hasReturnValue: false,
message: ""
};
},
"onCodePathEnd": function() {
funcInfo = funcInfo.upper;
},
create: function(context) {
var funcInfo = null;
// Reports a given return statement if it's inconsistent.
"ReturnStatement": function(node) {
var hasReturnValue = Boolean(node.argument);
/**
* Checks whether of not the implicit returning is consistent if the last
* code path segment is reachable.
*
* @param {ASTNode} node - A program/function node to check.
* @returns {void}
*/
function checkLastSegment(node) {
var loc, type;
/*
* 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) ||
astUtils.isES5Constructor(node)
) {
return;
}
if (!funcInfo.hasReturn) {
funcInfo.hasReturn = true;
funcInfo.hasReturnValue = hasReturnValue;
funcInfo.message = "Expected " + (hasReturnValue ? "a" : "no") + " return value.";
} else if (funcInfo.hasReturnValue !== hasReturnValue) {
context.report({node: node, message: funcInfo.message});
// 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";
} else if (
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";
}
},
// Reports a given program/function if the implicit returning is not consistent.
"Program:exit": checkLastSegment,
"FunctionDeclaration:exit": checkLastSegment,
"FunctionExpression:exit": checkLastSegment,
"ArrowFunctionExpression:exit": checkLastSegment
};
};
// Reports.
context.report({
node: node,
loc: loc,
message: "Expected to return a value at the end of this {{type}}.",
data: {type: type}
});
}
module.exports.schema = [];
return {
// Initializes/Disposes state of each code path.
onCodePathStart: function(codePath) {
funcInfo = {
upper: funcInfo,
codePath: codePath,
hasReturn: false,
hasReturnValue: false,
message: ""
};
},
onCodePathEnd: function() {
funcInfo = funcInfo.upper;
},
// Reports a given return statement if it's inconsistent.
ReturnStatement: function(node) {
var hasReturnValue = Boolean(node.argument);
if (!funcInfo.hasReturn) {
funcInfo.hasReturn = true;
funcInfo.hasReturnValue = hasReturnValue;
funcInfo.message = "Expected " + (hasReturnValue ? "a" : "no") + " return value.";
} else if (funcInfo.hasReturnValue !== hasReturnValue) {
context.report({node: node, message: funcInfo.message});
}
},
// Reports a given program/function if the implicit returning is not consistent.
"Program:exit": checkLastSegment,
"FunctionDeclaration:exit": checkLastSegment,
"FunctionExpression:exit": checkLastSegment,
"ArrowFunctionExpression:exit": checkLastSegment
};
}
};

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to enforce consistent naming of "this" context variables
* @author Raphael Pigulla
* @copyright 2015 Timothy Jones. All rights reserved.
* @copyright 2015 David Aurelio. All rights reserved.
*/
"use strict";
@ -10,131 +8,141 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var aliases = [];
if (context.options.length === 0) {
aliases.push("that");
} else {
aliases = context.options;
}
/**
* Reports that a variable declarator or assignment expression is assigning
* a non-'this' value to the specified alias.
* @param {ASTNode} node - The assigning node.
* @param {string} alias - the name of the alias that was incorrectly used.
* @returns {void}
*/
function reportBadAssignment(node, alias) {
context.report(node,
"Designated alias '{{alias}}' is not assigned to 'this'.",
{ alias: alias });
}
module.exports = {
meta: {
docs: {
description: "enforce consistent naming when capturing the current execution context",
category: "Stylistic Issues",
recommended: false
},
/**
* Checks that an assignment to an identifier only assigns 'this' to the
* appropriate alias, and the alias is only assigned to 'this'.
* @param {ASTNode} node - The assigning node.
* @param {Identifier} name - The name of the variable assigned to.
* @param {Expression} value - The value of the assignment.
* @returns {void}
*/
function checkAssignment(node, name, value) {
var isThis = value.type === "ThisExpression";
if (aliases.indexOf(name) !== -1) {
if (!isThis || node.operator && node.operator !== "=") {
reportBadAssignment(node, name);
}
} else if (isThis) {
context.report(node,
"Unexpected alias '{{name}}' for 'this'.", { name: name });
schema: {
type: "array",
items: {
type: "string",
minLength: 1
},
uniqueItems: true
}
}
},
/**
* Ensures that a variable declaration of the alias in a program or function
* is assigned to the correct value.
* @param {string} alias alias the check the assignment of.
* @param {object} scope scope of the current code we are checking.
* @private
* @returns {void}
*/
function checkWasAssigned(alias, scope) {
var variable = scope.set.get(alias);
if (!variable) {
return;
}
create: function(context) {
var aliases = [];
if (variable.defs.some(function(def) {
return def.node.type === "VariableDeclarator" &&
def.node.init !== null;
})) {
return;
if (context.options.length === 0) {
aliases.push("that");
} else {
aliases = context.options;
}
// The alias has been declared and not assigned: check it was
// assigned later in the same scope.
if (!variable.references.some(function(reference) {
var write = reference.writeExpr;
return (
reference.from === scope &&
write && write.type === "ThisExpression" &&
write.parent.operator === "="
);
})) {
variable.defs.map(function(def) {
return def.node;
}).forEach(function(node) {
reportBadAssignment(node, alias);
});
/**
* Reports that a variable declarator or assignment expression is assigning
* a non-'this' value to the specified alias.
* @param {ASTNode} node - The assigning node.
* @param {string} alias - the name of the alias that was incorrectly used.
* @returns {void}
*/
function reportBadAssignment(node, alias) {
context.report(node,
"Designated alias '{{alias}}' is not assigned to 'this'.",
{ alias: alias });
}
}
/**
* Check each alias to ensure that is was assinged to the correct value.
* @returns {void}
*/
function ensureWasAssigned() {
var scope = context.getScope();
aliases.forEach(function(alias) {
checkWasAssigned(alias, scope);
});
}
return {
"Program:exit": ensureWasAssigned,
"FunctionExpression:exit": ensureWasAssigned,
"FunctionDeclaration:exit": ensureWasAssigned,
/**
* Checks that an assignment to an identifier only assigns 'this' to the
* appropriate alias, and the alias is only assigned to 'this'.
* @param {ASTNode} node - The assigning node.
* @param {Identifier} name - The name of the variable assigned to.
* @param {Expression} value - The value of the assignment.
* @returns {void}
*/
function checkAssignment(node, name, value) {
var isThis = value.type === "ThisExpression";
if (aliases.indexOf(name) !== -1) {
if (!isThis || node.operator && node.operator !== "=") {
reportBadAssignment(node, name);
}
} else if (isThis) {
context.report(node,
"Unexpected alias '{{name}}' for 'this'.", { name: name });
}
}
"VariableDeclarator": function(node) {
var id = node.id;
var isDestructuring =
id.type === "ArrayPattern" || id.type === "ObjectPattern";
/**
* Ensures that a variable declaration of the alias in a program or function
* is assigned to the correct value.
* @param {string} alias alias the check the assignment of.
* @param {object} scope scope of the current code we are checking.
* @private
* @returns {void}
*/
function checkWasAssigned(alias, scope) {
var variable = scope.set.get(alias);
if (!variable) {
return;
}
if (node.init !== null && !isDestructuring) {
checkAssignment(node, id.name, node.init);
if (variable.defs.some(function(def) {
return def.node.type === "VariableDeclarator" &&
def.node.init !== null;
})) {
return;
}
},
"AssignmentExpression": function(node) {
if (node.left.type === "Identifier") {
checkAssignment(node, node.left.name, node.right);
// The alias has been declared and not assigned: check it was
// assigned later in the same scope.
if (!variable.references.some(function(reference) {
var write = reference.writeExpr;
return (
reference.from === scope &&
write && write.type === "ThisExpression" &&
write.parent.operator === "="
);
})) {
variable.defs.map(function(def) {
return def.node;
}).forEach(function(node) {
reportBadAssignment(node, alias);
});
}
}
};
};
/**
* Check each alias to ensure that is was assinged to the correct value.
* @returns {void}
*/
function ensureWasAssigned() {
var scope = context.getScope();
module.exports.schema = {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"uniqueItems": true
aliases.forEach(function(alias) {
checkWasAssigned(alias, scope);
});
}
return {
"Program:exit": ensureWasAssigned,
"FunctionExpression:exit": ensureWasAssigned,
"FunctionDeclaration:exit": ensureWasAssigned,
VariableDeclarator: function(node) {
var id = node.id;
var isDestructuring =
id.type === "ArrayPattern" || id.type === "ObjectPattern";
if (node.init !== null && !isDestructuring) {
checkAssignment(node, id.name, node.init);
}
},
AssignmentExpression: function(node) {
if (node.left.type === "Identifier") {
checkAssignment(node, node.left.name, node.right);
}
}
};
}
};

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

@ -1,21 +1,24 @@
/**
* @fileoverview A rule to verify `super()` callings in constructor.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks whether a given code path segment is reachable or not.
*
* @param {CodePathSegment} segment - A code path segment to check.
* @returns {boolean} `true` if the segment is reachable.
*/
function isReachable(segment) {
return segment.reachable;
}
/**
* Checks whether or not a given node is a constructor.
* @param {ASTNode} node - A node to check. This node type is one of
@ -31,273 +34,352 @@ function isConstructorFunction(node) {
);
}
/**
* Checks whether a given node can be a constructor or not.
*
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node can be a constructor.
*/
function isPossibleConstructor(node) {
if (!node) {
return false;
}
switch (node.type) {
case "ClassExpression":
case "FunctionExpression":
case "ThisExpression":
case "MemberExpression":
case "CallExpression":
case "NewExpression":
case "YieldExpression":
case "TaggedTemplateExpression":
case "MetaProperty":
return true;
case "Identifier":
return node.name !== "undefined";
case "AssignmentExpression":
return isPossibleConstructor(node.right);
case "LogicalExpression":
return (
isPossibleConstructor(node.left) ||
isPossibleConstructor(node.right)
);
case "ConditionalExpression":
return (
isPossibleConstructor(node.alternate) ||
isPossibleConstructor(node.consequent)
);
case "SequenceExpression":
var lastExpression = node.expressions[node.expressions.length - 1];
return isPossibleConstructor(lastExpression);
default:
return false;
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
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.
*/
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.
* - validNodes:
*/
var segInfoMap = Object.create(null);
/**
* Gets the flag which shows `super()` is called in some paths.
* @param {CodePathSegment} segment - A code path segment to get.
* @returns {boolean} The flag which shows `super()` is called in some paths
*/
function isCalledInSomePath(segment) {
return segInfoMap[segment.id].calledInSomePaths;
}
module.exports = {
meta: {
docs: {
description: "require `super()` calls in constructors",
category: "ECMAScript 6",
recommended: true
},
/**
* Gets the flag which shows `super()` is called in all paths.
* @param {CodePathSegment} segment - A code path segment to get.
* @returns {boolean} The flag which shows `super()` is called in all paths.
*/
function isCalledInEveryPath(segment) {
schema: []
},
create: function(context) {
/*
* If specific segment is the looped segment of the current segment,
* skip the segment.
* If not skipped, this never becomes true after a loop.
* {{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.
*/
if (segment.nextSegments.length === 1 &&
segment.nextSegments[0].isLoopedPrevSegment(segment)
) {
return true;
}
return segInfoMap[segment.id].calledInEveryPaths;
}
return {
var funcInfo = null;
/**
* Stacks a constructor information.
* @param {CodePath} codePath - A code path which was started.
* @param {ASTNode} node - The current node.
* @returns {void}
/*
* {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:
*/
"onCodePathStart": function(codePath, node) {
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)
),
codePath: codePath
};
} else {
funcInfo = {
upper: funcInfo,
isConstructor: false,
hasExtends: false,
codePath: codePath
};
}
},
var segInfoMap = Object.create(null);
/**
* Pops a constructor information.
* And reports if `super()` lacked.
* @param {CodePath} codePath - A code path which was ended.
* @param {ASTNode} node - The current node.
* @returns {void}
* Gets the flag which shows `super()` is called in some paths.
* @param {CodePathSegment} segment - A code path segment to get.
* @returns {boolean} The flag which shows `super()` is called in some paths
*/
"onCodePathEnd": function(codePath, node) {
// Skip if own class which has a valid `extends` part.
var hasExtends = funcInfo.hasExtends;
funcInfo = funcInfo.upper;
if (!hasExtends) {
return;
}
// Reports if `super()` lacked.
var segments = codePath.returnedSegments;
var calledInEveryPaths = segments.every(isCalledInEveryPath);
var calledInSomePaths = segments.some(isCalledInSomePath);
if (!calledInEveryPaths) {
context.report({
message: calledInSomePaths ?
"Lacked a call of 'super()' in some code paths." :
"Expected to call 'super()'.",
node: node.parent
});
}
},
function isCalledInSomePath(segment) {
return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
}
/**
* Initialize information of a given code path segment.
* @param {CodePathSegment} segment - A code path segment to initialize.
* @returns {void}
* Gets the flag which shows `super()` is called in all paths.
* @param {CodePathSegment} segment - A code path segment to get.
* @returns {boolean} The flag which shows `super()` is called in all paths.
*/
"onCodePathSegmentStart": function(segment) {
function isCalledInEveryPath(segment) {
/*
* Skip if this is not in a constructor of a class which has a
* valid `extends` part.
* 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 (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
return;
if (segment.nextSegments.length === 1 &&
segment.nextSegments[0].isLoopedPrevSegment(segment)
) {
return true;
}
return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
}
// Initialize info.
var info = segInfoMap[segment.id] = {
calledInSomePaths: false,
calledInEveryPaths: false,
validNodes: []
};
return {
// When there are previous segments, aggregates these.
var prevSegments = segment.prevSegments;
/**
* Stacks a constructor information.
* @param {CodePath} codePath - A code path which was started.
* @param {ASTNode} node - The current node.
* @returns {void}
*/
onCodePathStart: function(codePath, node) {
if (isConstructorFunction(node)) {
// Class > ClassBody > MethodDefinition > FunctionExpression
var classNode = node.parent.parent.parent;
var superClass = classNode.superClass;
funcInfo = {
upper: funcInfo,
isConstructor: true,
hasExtends: Boolean(superClass),
superIsConstructor: isPossibleConstructor(superClass),
codePath: codePath
};
} else {
funcInfo = {
upper: funcInfo,
isConstructor: false,
hasExtends: false,
superIsConstructor: false,
codePath: codePath
};
}
},
/**
* Pops a constructor information.
* And reports if `super()` lacked.
* @param {CodePath} codePath - A code path which was ended.
* @param {ASTNode} node - The current node.
* @returns {void}
*/
onCodePathEnd: function(codePath, node) {
var hasExtends = funcInfo.hasExtends;
if (prevSegments.length > 0) {
info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
}
},
// Pop.
funcInfo = funcInfo.upper;
/**
* 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) {
if (!hasExtends) {
return;
}
/*
* Skip if this is not in a constructor of a class which has a
* valid `extends` part.
*/
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
return;
}
// Reports if `super()` lacked.
var segments = codePath.returnedSegments;
var calledInEveryPaths = segments.every(isCalledInEveryPath);
var calledInSomePaths = segments.some(isCalledInSomePath);
// Update information inside of the loop.
var isRealLoop = toSegment.prevSegments.length >= 2;
if (!calledInEveryPaths) {
context.report({
message: calledInSomePaths ?
"Lacked a call of 'super()' in some code paths." :
"Expected to call 'super()'.",
node: node.parent
});
}
},
/**
* Initialize information of a given code path segment.
* @param {CodePathSegment} segment - A code path segment to initialize.
* @returns {void}
*/
onCodePathSegmentStart: function(segment) {
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
return;
}
funcInfo.codePath.traverseSegments(
{first: toSegment, last: fromSegment},
function(segment) {
var info = segInfoMap[segment.id];
// Initialize info.
var info = segInfoMap[segment.id] = {
calledInSomePaths: false,
calledInEveryPaths: false,
validNodes: []
};
// Updates flags.
var prevSegments = segment.prevSegments;
// 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) {
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];
var prevSegments = segment.prevSegments;
// Updates flags.
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 = [];
// If flags become true anew, reports the valid nodes.
if (info.calledInSomePaths || isRealLoop) {
var nodes = info.validNodes;
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
info.validNodes = [];
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) {
if (!(funcInfo && funcInfo.isConstructor)) {
return;
}
// Skips except `super()`.
if (node.callee.type !== "Super") {
return;
}
// Reports if needed.
if (funcInfo.hasExtends) {
var segments = funcInfo.codePath.currentSegments;
var reachable = false;
var duplicate = false;
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
for (var i = 0; i < segments.length; ++i) {
var segment = segments[i];
if (segment.reachable) {
var info = segInfoMap[segment.id];
reachable = true;
duplicate = duplicate || info.calledInSomePaths;
info.calledInSomePaths = info.calledInEveryPaths = true;
}
}
if (reachable) {
if (duplicate) {
context.report({
message: "Unexpected duplicate 'super()'.",
node: node
});
} else if (!funcInfo.superIsConstructor) {
context.report({
message: "Unexpected 'super()' because 'super' is not a constructor.",
node: node
});
} else {
info.validNodes.push(node);
}
}
} else if (funcInfo.codePath.currentSegments.some(isReachable)) {
context.report({
message: "Unexpected '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.isConstructor)) {
return;
}
/**
* Set the mark to the returned path as `super()` was called.
* @param {ASTNode} node - A ReturnStatement node to check.
* @returns {void}
*/
ReturnStatement: function(node) {
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
return;
}
// Reports if needed.
if (funcInfo.hasExtends) {
// Skips if no argument.
if (!node.argument) {
return;
}
/*
* This class has a valid `extends` part.
* Checks duplicate `super()`;
*/
// Returning argument is a substitute of 'super()'.
var segments = funcInfo.codePath.currentSegments;
var duplicate = false;
for (var i = 0; i < segments.length; ++i) {
var info = segInfoMap[segments[i].id];
var segment = segments[i];
duplicate = duplicate || info.calledInSomePaths;
info.calledInSomePaths = info.calledInEveryPaths = true;
}
if (segment.reachable) {
var info = segInfoMap[segment.id];
if (duplicate) {
context.report({
message: "Unexpected duplicate 'super()'.",
node: node
});
} else {
info.validNodes.push(node);
info.calledInSomePaths = info.calledInEveryPaths = true;
}
}
} else {
/*
* This class does not have a valid `extends` part.
* Disallow `super()`.
*/
context.report({
message: "Unexpected 'super()'.",
node: node
});
}
},
},
/**
* Resets state.
* @returns {void}
*/
"Program:exit": function() {
segInfoMap = Object.create(null);
}
};
/**
* Resets state.
* @returns {void}
*/
"Program:exit": function() {
segInfoMap = Object.create(null);
}
};
}
};
module.exports.schema = [];

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to flag statements without curly braces
* @author Nicholas C. Zakas
* @copyright 2015 Ivan Nikulin. All rights reserved.
*/
"use strict";
@ -15,280 +14,290 @@ var astUtils = require("../ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var multiOnly = (context.options[0] === "multi");
var multiLine = (context.options[0] === "multi-line");
var multiOrNest = (context.options[0] === "multi-or-nest");
var consistent = (context.options[1] === "consistent");
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determines if a given node is a one-liner that's on the same line as it's preceding code.
* @param {ASTNode} node The node to check.
* @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code.
* @private
*/
function isCollapsedOneLiner(node) {
var before = context.getTokenBefore(node),
last = context.getLastToken(node);
return before.loc.start.line === last.loc.end.line;
}
/**
* Determines if a given node is a one-liner.
* @param {ASTNode} node The node to check.
* @returns {boolean} True if the node is a one-liner.
* @private
*/
function isOneLiner(node) {
var first = context.getFirstToken(node),
last = context.getLastToken(node);
return first.loc.start.line === last.loc.end.line;
}
module.exports = {
meta: {
docs: {
description: "enforce consistent brace style for all control statements",
category: "Best Practices",
recommended: false
},
/**
* Gets the `else` keyword token of a given `IfStatement` node.
* @param {ASTNode} node - A `IfStatement` node to get.
* @returns {Token} The `else` keyword token.
*/
function getElseKeyword(node) {
var sourceCode = context.getSourceCode();
var token = sourceCode.getTokenAfter(node.consequent);
while (token.type !== "Keyword" || token.value !== "else") {
token = sourceCode.getTokenAfter(token);
schema: {
anyOf: [
{
type: "array",
items: [
{
enum: ["all"]
}
],
minItems: 0,
maxItems: 1
},
{
type: "array",
items: [
{
enum: ["multi", "multi-line", "multi-or-nest"]
},
{
enum: ["consistent"]
}
],
minItems: 0,
maxItems: 2
}
]
}
},
create: function(context) {
var multiOnly = (context.options[0] === "multi");
var multiLine = (context.options[0] === "multi-line");
var multiOrNest = (context.options[0] === "multi-or-nest");
var consistent = (context.options[1] === "consistent");
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determines if a given node is a one-liner that's on the same line as it's preceding code.
* @param {ASTNode} node The node to check.
* @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code.
* @private
*/
function isCollapsedOneLiner(node) {
var before = context.getTokenBefore(node),
last = context.getLastToken(node);
return before.loc.start.line === last.loc.end.line;
}
return token;
}
/**
* Determines if a given node is a one-liner.
* @param {ASTNode} node The node to check.
* @returns {boolean} True if the node is a one-liner.
* @private
*/
function isOneLiner(node) {
var first = context.getFirstToken(node),
last = context.getLastToken(node);
return first.loc.start.line === last.loc.end.line;
}
/**
* Checks a given IfStatement node requires braces of the consequent chunk.
* This returns `true` when below:
*
* 1. The given node has the `alternate` node.
* 2. There is a `IfStatement` which doesn't have `alternate` node in the
* trailing statement chain of the `consequent` node.
*
* @param {ASTNode} node - A IfStatement node to check.
* @returns {boolean} `true` if the node requires braces of the consequent chunk.
*/
function requiresBraceOfConsequent(node) {
if (node.alternate && node.consequent.type === "BlockStatement") {
if (node.consequent.body.length >= 2) {
return true;
/**
* Gets the `else` keyword token of a given `IfStatement` node.
* @param {ASTNode} node - A `IfStatement` node to get.
* @returns {Token} The `else` keyword token.
*/
function getElseKeyword(node) {
var sourceCode = context.getSourceCode();
var token = sourceCode.getTokenAfter(node.consequent);
while (token.type !== "Keyword" || token.value !== "else") {
token = sourceCode.getTokenAfter(token);
}
node = node.consequent.body[0];
while (node) {
if (node.type === "IfStatement" && !node.alternate) {
return token;
}
/**
* Checks a given IfStatement node requires braces of the consequent chunk.
* This returns `true` when below:
*
* 1. The given node has the `alternate` node.
* 2. There is a `IfStatement` which doesn't have `alternate` node in the
* trailing statement chain of the `consequent` node.
*
* @param {ASTNode} node - A IfStatement node to check.
* @returns {boolean} `true` if the node requires braces of the consequent chunk.
*/
function requiresBraceOfConsequent(node) {
if (node.alternate && node.consequent.type === "BlockStatement") {
if (node.consequent.body.length >= 2) {
return true;
}
node = astUtils.getTrailingStatement(node);
node = node.consequent.body[0];
while (node) {
if (node.type === "IfStatement" && !node.alternate) {
return true;
}
node = astUtils.getTrailingStatement(node);
}
}
}
return false;
}
return false;
}
/**
* Reports "Expected { after ..." error
* @param {ASTNode} node The node to report.
* @param {string} name The name to report.
* @param {string} suffix Additional string to add to the end of a report.
* @returns {void}
* @private
*/
function reportExpectedBraceError(node, name, suffix) {
context.report({
node: node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
message: "Expected { after '{{name}}'{{suffix}}.",
data: {
name: name,
suffix: (suffix ? " " + suffix : "")
}
});
}
/**
* Reports "Expected { after ..." error
* @param {ASTNode} node The node to report.
* @param {string} name The name to report.
* @param {string} suffix Additional string to add to the end of a report.
* @returns {void}
* @private
*/
function reportExpectedBraceError(node, name, suffix) {
context.report({
node: node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
message: "Expected { after '{{name}}'{{suffix}}.",
data: {
name: name,
suffix: (suffix ? " " + suffix : "")
}
});
}
/**
* Reports "Unnecessary { after ..." error
* @param {ASTNode} node The node to report.
* @param {string} name The name to report.
* @param {string} suffix Additional string to add to the end of a report.
* @returns {void}
* @private
*/
function reportUnnecessaryBraceError(node, name, suffix) {
context.report({
node: node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
message: "Unnecessary { after '{{name}}'{{suffix}}.",
data: {
name: name,
suffix: (suffix ? " " + suffix : "")
}
});
}
/**
* Reports "Unnecessary { after ..." error
* @param {ASTNode} node The node to report.
* @param {string} name The name to report.
* @param {string} suffix Additional string to add to the end of a report.
* @returns {void}
* @private
*/
function reportUnnecessaryBraceError(node, name, suffix) {
context.report({
node: node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
message: "Unnecessary { after '{{name}}'{{suffix}}.",
data: {
name: name,
suffix: (suffix ? " " + suffix : "")
}
});
}
/**
* Prepares to check the body of a node to see if it's a block statement.
* @param {ASTNode} node The node to report if there's a problem.
* @param {ASTNode} body The body node to check for blocks.
* @param {string} name The name to report if there's a problem.
* @param {string} suffix Additional string to add to the end of a report.
* @returns {object} a prepared check object, with "actual", "expected", "check" properties.
* "actual" will be `true` or `false` whether the body is already a block statement.
* "expected" will be `true` or `false` if the body should be a block statement or not, or
* `null` if it doesn't matter, depending on the rule options. It can be modified to change
* the final behavior of "check".
* "check" will be a function reporting appropriate problems depending on the other
* properties.
*/
function prepareCheck(node, body, name, suffix) {
var hasBlock = (body.type === "BlockStatement");
var expected = null;
if (node.type === "IfStatement" && node.consequent === body && requiresBraceOfConsequent(node)) {
expected = true;
} else if (multiOnly) {
if (hasBlock && body.body.length === 1) {
expected = false;
}
} else if (multiLine) {
if (!isCollapsedOneLiner(body)) {
/**
* Prepares to check the body of a node to see if it's a block statement.
* @param {ASTNode} node The node to report if there's a problem.
* @param {ASTNode} body The body node to check for blocks.
* @param {string} name The name to report if there's a problem.
* @param {string} suffix Additional string to add to the end of a report.
* @returns {object} a prepared check object, with "actual", "expected", "check" properties.
* "actual" will be `true` or `false` whether the body is already a block statement.
* "expected" will be `true` or `false` if the body should be a block statement or not, or
* `null` if it doesn't matter, depending on the rule options. It can be modified to change
* the final behavior of "check".
* "check" will be a function reporting appropriate problems depending on the other
* properties.
*/
function prepareCheck(node, body, name, suffix) {
var hasBlock = (body.type === "BlockStatement");
var expected = null;
if (node.type === "IfStatement" && node.consequent === body && requiresBraceOfConsequent(node)) {
expected = true;
}
} else if (multiOrNest) {
if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) {
expected = false;
} else if (!isOneLiner(body)) {
} else if (multiOnly) {
if (hasBlock && body.body.length === 1) {
expected = false;
}
} else if (multiLine) {
if (!isCollapsedOneLiner(body)) {
expected = true;
}
} else if (multiOrNest) {
if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) {
expected = false;
} else if (!isOneLiner(body)) {
expected = true;
}
} else {
expected = true;
}
} else {
expected = true;
}
return {
actual: hasBlock,
expected: expected,
check: function() {
if (this.expected !== null && this.expected !== this.actual) {
if (this.expected) {
reportExpectedBraceError(node, name, suffix);
} else {
reportUnnecessaryBraceError(node, name, suffix);
return {
actual: hasBlock,
expected: expected,
check: function() {
if (this.expected !== null && this.expected !== this.actual) {
if (this.expected) {
reportExpectedBraceError(node, name, suffix);
} else {
reportUnnecessaryBraceError(node, name, suffix);
}
}
}
}
};
}
/**
* Prepares to check the bodies of a "if", "else if" and "else" chain.
* @param {ASTNode} node The first IfStatement node of the chain.
* @returns {object[]} prepared checks for each body of the chain. See `prepareCheck` for more
* information.
*/
function prepareIfChecks(node) {
var preparedChecks = [];
do {
preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition"));
if (node.alternate && node.alternate.type !== "IfStatement") {
preparedChecks.push(prepareCheck(node, node.alternate, "else"));
break;
}
node = node.alternate;
} 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.
*/
var expected = preparedChecks.some(function(preparedCheck) {
if (preparedCheck.expected !== null) {
return preparedCheck.expected;
}
return preparedCheck.actual;
});
preparedChecks.forEach(function(preparedCheck) {
preparedCheck.expected = expected;
});
};
}
return preparedChecks;
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Prepares to check the bodies of a "if", "else if" and "else" chain.
* @param {ASTNode} node The first IfStatement node of the chain.
* @returns {object[]} prepared checks for each body of the chain. See `prepareCheck` for more
* information.
*/
function prepareIfChecks(node) {
var preparedChecks = [];
do {
preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition"));
if (node.alternate && node.alternate.type !== "IfStatement") {
preparedChecks.push(prepareCheck(node, node.alternate, "else"));
break;
}
node = node.alternate;
} 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.
*/
var expected = preparedChecks.some(function(preparedCheck) {
if (preparedCheck.expected !== null) {
return preparedCheck.expected;
}
return preparedCheck.actual;
});
return {
"IfStatement": function(node) {
if (node.parent.type !== "IfStatement") {
prepareIfChecks(node).forEach(function(preparedCheck) {
preparedCheck.check();
preparedChecks.forEach(function(preparedCheck) {
preparedCheck.expected = expected;
});
}
},
"WhileStatement": function(node) {
prepareCheck(node, node.body, "while", "condition").check();
},
return preparedChecks;
}
"DoWhileStatement": function(node) {
prepareCheck(node, node.body, "do").check();
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
"ForStatement": function(node) {
prepareCheck(node, node.body, "for", "condition").check();
},
return {
IfStatement: function(node) {
if (node.parent.type !== "IfStatement") {
prepareIfChecks(node).forEach(function(preparedCheck) {
preparedCheck.check();
});
}
},
"ForInStatement": function(node) {
prepareCheck(node, node.body, "for-in").check();
},
WhileStatement: function(node) {
prepareCheck(node, node.body, "while", "condition").check();
},
"ForOfStatement": function(node) {
prepareCheck(node, node.body, "for-of").check();
}
};
};
DoWhileStatement: function(node) {
prepareCheck(node, node.body, "do").check();
},
module.exports.schema = {
"anyOf": [
{
"type": "array",
"items": [
{
"enum": ["all"]
}
],
"minItems": 0,
"maxItems": 1
},
{
"type": "array",
"items": [
{
"enum": ["multi", "multi-line", "multi-or-nest"]
},
{
"enum": ["consistent"]
}
],
"minItems": 0,
"maxItems": 2
}
]
ForStatement: function(node) {
prepareCheck(node, node.body, "for", "condition").check();
},
ForInStatement: function(node) {
prepareCheck(node, node.body, "for-in").check();
},
ForOfStatement: function(node) {
prepareCheck(node, node.body, "for-of").check();
}
};
}
};

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

@ -4,67 +4,89 @@
*/
"use strict";
var COMMENT_VALUE = "no default";
var DEFAULT_COMMENT_PATTERN = /^no default$/;
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Shortcut to get last element of array
* @param {*[]} collection Array
* @returns {*} Last element
*/
function last(collection) {
return collection[collection.length - 1];
}
module.exports = {
meta: {
docs: {
description: "require `default` cases in <code>switch</code> statements",
category: "Best Practices",
recommended: false
},
schema: [{
type: "object",
properties: {
commentPattern: {
type: "string"
}
},
additionalProperties: false
}]
},
create: function(context) {
var options = context.options[0] || {};
var commentPattern = options.commentPattern ?
new RegExp(options.commentPattern) :
DEFAULT_COMMENT_PATTERN;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Shortcut to get last element of array
* @param {*[]} collection Array
* @returns {*} Last element
*/
function last(collection) {
return collection[collection.length - 1];
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
return {
"SwitchStatement": function(node) {
SwitchStatement: function(node) {
if (!node.cases.length) {
if (!node.cases.length) {
/*
* skip check of empty switch because there is no easy way
* to extract comments inside it now
*/
return;
}
/*
* skip check of empty switch because there is no easy way
* to extract comments inside it now
*/
return;
}
var hasDefault = node.cases.some(function(v) {
return v.test === null;
});
var hasDefault = node.cases.some(function(v) {
return v.test === null;
});
if (!hasDefault) {
if (!hasDefault) {
var comment;
var comments;
var comment;
var comments;
var lastCase = last(node.cases);
var lastCase = last(node.cases);
comments = context.getComments(lastCase).trailing;
comments = context.getComments(lastCase).trailing;
if (comments.length) {
comment = last(comments);
}
if (comments.length) {
comment = last(comments);
}
if (!comment || comment.value.trim() !== COMMENT_VALUE) {
context.report(node, "Expected a default case.");
if (!comment || !commentPattern.test(comment.value.trim())) {
context.report(node, "Expected a default case.");
}
}
}
}
};
};
}
};
module.exports.schema = [];

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

@ -1,7 +1,6 @@
/**
* @fileoverview Validates newlines before and after dots
* @author Greg Cochard
* @copyright 2015 Greg Cochard
*/
"use strict";
@ -12,51 +11,61 @@ var astUtils = require("../ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "enforce consistent newlines before and after dots",
category: "Best Practices",
recommended: false
},
var config = context.options[0],
onObject;
schema: [
{
enum: ["object", "property"]
}
]
},
create: function(context) {
var config = context.options[0],
onObject;
// default to onObject if no preference is passed
onObject = config === "object" || !config;
// default to onObject if no preference is passed
onObject = config === "object" || !config;
/**
* Reports if the dot between object and property is on the correct loccation.
* @param {ASTNode} obj The object owning the property.
* @param {ASTNode} prop The property of the object.
* @param {ASTNode} node The corresponding node of the token.
* @returns {void}
*/
function checkDotLocation(obj, prop, node) {
var dot = context.getTokenBefore(prop);
/**
* Reports if the dot between object and property is on the correct loccation.
* @param {ASTNode} obj The object owning the property.
* @param {ASTNode} prop The property of the object.
* @param {ASTNode} node The corresponding node of the token.
* @returns {void}
*/
function checkDotLocation(obj, prop, node) {
var dot = context.getTokenBefore(prop);
if (dot.type === "Punctuator" && dot.value === ".") {
if (onObject) {
if (!astUtils.isTokenOnSameLine(obj, dot)) {
context.report(node, dot.loc.start, "Expected dot to be on same line as object.");
if (dot.type === "Punctuator" && dot.value === ".") {
if (onObject) {
if (!astUtils.isTokenOnSameLine(obj, dot)) {
context.report(node, dot.loc.start, "Expected dot to be on same line as object.");
}
} else if (!astUtils.isTokenOnSameLine(dot, prop)) {
context.report(node, dot.loc.start, "Expected dot to be on same line as property.");
}
} else if (!astUtils.isTokenOnSameLine(dot, prop)) {
context.report(node, dot.loc.start, "Expected dot to be on same line as property.");
}
}
}
/**
* Checks the spacing of the dot within a member expression.
* @param {ASTNode} node The node to check.
* @returns {void}
*/
function checkNode(node) {
checkDotLocation(node.object, node.property, node);
}
return {
"MemberExpression": checkNode
};
};
/**
* Checks the spacing of the dot within a member expression.
* @param {ASTNode} node The node to check.
* @returns {void}
*/
function checkNode(node) {
checkDotLocation(node.object, node.property, node);
}
module.exports.schema = [
{
"enum": ["object", "property"]
return {
MemberExpression: checkNode
};
}
];
};

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

@ -11,50 +11,60 @@
var validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
var keywords = require("../util/keywords");
module.exports = function(context) {
var options = context.options[0] || {};
var allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords;
module.exports = {
meta: {
docs: {
description: "enforce dot notation whenever possible",
category: "Best Practices",
recommended: false
},
var allowPattern;
schema: [
{
type: "object",
properties: {
allowKeywords: {
type: "boolean"
},
allowPattern: {
type: "string"
}
},
additionalProperties: false
}
]
},
if (options.allowPattern) {
allowPattern = new RegExp(options.allowPattern);
}
create: function(context) {
var options = context.options[0] || {};
var allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords;
return {
"MemberExpression": function(node) {
if (
node.computed &&
node.property.type === "Literal" &&
validIdentifier.test(node.property.value) &&
(allowKeywords || keywords.indexOf("" + node.property.value) === -1)
) {
if (!(allowPattern && allowPattern.test(node.property.value))) {
context.report(node.property, "[" + JSON.stringify(node.property.value) + "] is better written in dot notation.");
}
}
if (
!allowKeywords &&
!node.computed &&
keywords.indexOf("" + node.property.name) !== -1
) {
context.report(node.property, "." + node.property.name + " is a syntax error.");
}
var allowPattern;
if (options.allowPattern) {
allowPattern = new RegExp(options.allowPattern);
}
};
};
module.exports.schema = [
{
"type": "object",
"properties": {
"allowKeywords": {
"type": "boolean"
},
"allowPattern": {
"type": "string"
return {
MemberExpression: function(node) {
if (
node.computed &&
node.property.type === "Literal" &&
validIdentifier.test(node.property.value) &&
(allowKeywords || keywords.indexOf("" + node.property.value) === -1)
) {
if (!(allowPattern && allowPattern.test(node.property.value))) {
context.report(node.property, "[" + JSON.stringify(node.property.value) + "] is better written in dot notation.");
}
}
if (
!allowKeywords &&
!node.computed &&
keywords.indexOf("" + node.property.name) !== -1
) {
context.report(node.property, "." + node.property.name + " is a syntax error.");
}
}
},
"additionalProperties": false
};
}
];
};

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

@ -8,43 +8,55 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
"Program": function checkBadEOF(node) {
// Get the whole source code, not for node only.
var src = context.getSource(),
location = {column: 1},
linebreakStyle = context.options[0] || "unix",
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({
node: node,
loc: location,
message: "Newline required at end of file but not found.",
fix: function(fixer) {
return fixer.insertTextAfterRange([0, src.length], linebreak);
}
});
module.exports = {
meta: {
docs: {
description: "enforce at least one newline at the end of files",
category: "Stylistic Issues",
recommended: false
},
fixable: "whitespace",
schema: [
{
enum: ["unix", "windows"]
}
]
},
create: function(context) {
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
Program: function checkBadEOF(node) {
// Get the whole source code, not for node only.
var src = context.getSource(),
location = {column: 1},
linebreakStyle = context.options[0] || "unix",
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({
node: node,
loc: location,
message: "Newline required at end of file but not found.",
fix: function(fixer) {
return fixer.insertTextAfterRange([0, src.length], linebreak);
}
});
}
}
}
};
};
};
module.exports.schema = [
{
"enum": ["unix", "windows"]
}
];
};

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to flag statements that use != and == instead of !== and ===
* @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -11,91 +9,101 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Checks if an expression is a typeof expression
* @param {ASTNode} node The node to check
* @returns {boolean} if the node is a typeof expression
*/
function isTypeOf(node) {
return node.type === "UnaryExpression" && node.operator === "typeof";
}
/**
* Checks if either operand of a binary expression is a typeof operation
* @param {ASTNode} node The node to check
* @returns {boolean} if one of the operands is typeof
* @private
*/
function isTypeOfBinary(node) {
return isTypeOf(node.left) || isTypeOf(node.right);
}
module.exports = {
meta: {
docs: {
description: "require the use of `===` and `!==`",
category: "Best Practices",
recommended: false
},
schema: [
{
enum: ["smart", "allow-null"]
}
]
},
create: function(context) {
/**
* Checks if an expression is a typeof expression
* @param {ASTNode} node The node to check
* @returns {boolean} if the node is a typeof expression
*/
function isTypeOf(node) {
return node.type === "UnaryExpression" && node.operator === "typeof";
}
/**
* Checks if operands are literals of the same type (via typeof)
* @param {ASTNode} node The node to check
* @returns {boolean} if operands are of same type
* @private
*/
function areLiteralsAndSameType(node) {
return node.left.type === "Literal" && node.right.type === "Literal" &&
typeof node.left.value === typeof node.right.value;
}
/**
* Checks if either operand of a binary expression is a typeof operation
* @param {ASTNode} node The node to check
* @returns {boolean} if one of the operands is typeof
* @private
*/
function isTypeOfBinary(node) {
return isTypeOf(node.left) || isTypeOf(node.right);
}
/**
* Checks if one of the operands is a literal null
* @param {ASTNode} node The node to check
* @returns {boolean} if operands are null
* @private
*/
function isNullCheck(node) {
return (node.right.type === "Literal" && node.right.value === null) ||
(node.left.type === "Literal" && node.left.value === null);
}
/**
* Checks if operands are literals of the same type (via typeof)
* @param {ASTNode} node The node to check
* @returns {boolean} if operands are of same type
* @private
*/
function areLiteralsAndSameType(node) {
return node.left.type === "Literal" && node.right.type === "Literal" &&
typeof node.left.value === typeof node.right.value;
}
/**
* Gets the location (line and column) of the binary expression's operator
* @param {ASTNode} node The binary expression node to check
* @param {String} operator The operator to find
* @returns {Object} { line, column } location of operator
* @private
*/
function getOperatorLocation(node) {
var opToken = context.getTokenAfter(node.left);
return {line: opToken.loc.start.line, column: opToken.loc.start.column};
}
/**
* Checks if one of the operands is a literal null
* @param {ASTNode} node The node to check
* @returns {boolean} if operands are null
* @private
*/
function isNullCheck(node) {
return (node.right.type === "Literal" && node.right.value === null) ||
(node.left.type === "Literal" && node.left.value === null);
}
return {
"BinaryExpression": function(node) {
if (node.operator !== "==" && node.operator !== "!=") {
return;
}
/**
* Gets the location (line and column) of the binary expression's operator
* @param {ASTNode} node The binary expression node to check
* @param {String} operator The operator to find
* @returns {Object} { line, column } location of operator
* @private
*/
function getOperatorLocation(node) {
var opToken = context.getTokenAfter(node.left);
return {line: opToken.loc.start.line, column: opToken.loc.start.column};
}
if (context.options[0] === "smart" && (isTypeOfBinary(node) ||
areLiteralsAndSameType(node) || isNullCheck(node))) {
return;
}
return {
BinaryExpression: function(node) {
if (node.operator !== "==" && node.operator !== "!=") {
return;
}
if (context.options[0] === "allow-null" && isNullCheck(node)) {
return;
}
if (context.options[0] === "smart" && (isTypeOfBinary(node) ||
areLiteralsAndSameType(node) || isNullCheck(node))) {
return;
}
context.report({
node: node,
loc: getOperatorLocation(node),
message: "Expected '{{op}}=' and instead saw '{{op}}'.",
data: { op: node.operator }
});
if (context.options[0] === "allow-null" && isNullCheck(node)) {
return;
}
}
};
context.report({
node: node,
loc: getOperatorLocation(node),
message: "Expected '{{op}}=' and instead saw '{{op}}'.",
data: { op: node.operator }
});
};
}
};
module.exports.schema = [
{
"enum": ["smart", "allow-null"]
}
];
};

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to warn when a function expression does not have a name.
* @author Kyle T. Nunery
* @copyright 2015 Brandon Mills. All rights reserved.
* @copyright 2014 Kyle T. Nunery. All rights reserved.
*/
"use strict";
@ -11,35 +9,45 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Determines whether the current FunctionExpression node is a get, set, or
* shorthand method in an object literal or a class.
* @returns {boolean} True if the node is a get, set, or shorthand method.
*/
function isObjectOrClassMethod() {
var parent = context.getAncestors().pop();
return (parent.type === "MethodDefinition" || (
parent.type === "Property" && (
parent.method ||
parent.kind === "get" ||
parent.kind === "set"
)
));
}
module.exports = {
meta: {
docs: {
description: "enforce named `function` expressions",
category: "Stylistic Issues",
recommended: false
},
schema: []
},
create: function(context) {
/**
* Determines whether the current FunctionExpression node is a get, set, or
* shorthand method in an object literal or a class.
* @returns {boolean} True if the node is a get, set, or shorthand method.
*/
function isObjectOrClassMethod() {
var parent = context.getAncestors().pop();
return (parent.type === "MethodDefinition" || (
parent.type === "Property" && (
parent.method ||
parent.kind === "get" ||
parent.kind === "set"
)
));
}
return {
"FunctionExpression": function(node) {
return {
FunctionExpression: function(node) {
var name = node.id && node.id.name;
var name = node.id && node.id.name;
if (!name && !isObjectOrClassMethod()) {
context.report(node, "Missing function expression name.");
if (!name && !isObjectOrClassMethod()) {
context.report(node, "Missing function expression name.");
}
}
}
};
};
}
};
module.exports.schema = [];

125
tools/eslint/lib/rules/func-style.js

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to enforce a particular function style
* @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
*/
"use strict";
@ -9,76 +8,86 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "enforce the consistent use of either `function` declarations or expressions",
category: "Stylistic Issues",
recommended: false
},
schema: [
{
enum: ["declaration", "expression"]
},
{
type: "object",
properties: {
allowArrowFunctions: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
var style = context.options[0],
allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions === true,
enforceDeclarations = (style === "declaration"),
stack = [];
create: function(context) {
var nodesToCheck = {
"Program": function() {
var style = context.options[0],
allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions === true,
enforceDeclarations = (style === "declaration"),
stack = [];
},
"FunctionDeclaration": function(node) {
stack.push(false);
var nodesToCheck = {
Program: function() {
stack = [];
},
if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") {
context.report(node, "Expected a function expression.");
}
},
"FunctionDeclaration:exit": function() {
stack.pop();
},
FunctionDeclaration: function(node) {
stack.push(false);
"FunctionExpression": function(node) {
stack.push(false);
if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") {
context.report(node, "Expected a function expression.");
}
},
"FunctionDeclaration:exit": function() {
stack.pop();
},
if (enforceDeclarations && node.parent.type === "VariableDeclarator") {
context.report(node.parent, "Expected a function declaration.");
}
},
"FunctionExpression:exit": function() {
stack.pop();
},
FunctionExpression: function(node) {
stack.push(false);
"ThisExpression": function() {
if (stack.length > 0) {
stack[stack.length - 1] = true;
}
}
};
if (enforceDeclarations && node.parent.type === "VariableDeclarator") {
context.report(node.parent, "Expected a function declaration.");
}
},
"FunctionExpression:exit": function() {
stack.pop();
},
if (!allowArrowFunctions) {
nodesToCheck.ArrowFunctionExpression = function() {
stack.push(false);
ThisExpression: function() {
if (stack.length > 0) {
stack[stack.length - 1] = true;
}
}
};
nodesToCheck["ArrowFunctionExpression:exit"] = function(node) {
var hasThisExpr = stack.pop();
if (!allowArrowFunctions) {
nodesToCheck.ArrowFunctionExpression = function() {
stack.push(false);
};
if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") {
context.report(node.parent, "Expected a function declaration.");
}
};
}
nodesToCheck["ArrowFunctionExpression:exit"] = function(node) {
var hasThisExpr = stack.pop();
return nodesToCheck;
if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") {
context.report(node.parent, "Expected a function declaration.");
}
};
}
};
return nodesToCheck;
module.exports.schema = [
{
"enum": ["declaration", "expression"]
},
{
"type": "object",
"properties": {
"allowArrowFunctions": {
"type": "boolean"
}
},
"additionalProperties": false
}
];
};

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to check the spacing around the * in generator functions.
* @author Jamund Ferguson
* @copyright 2015 Brandon Mills. All rights reserved.
* @copyright 2014 Jamund Ferguson. All rights reserved.
*/
"use strict";
@ -11,102 +9,114 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "enforce consistent spacing around `*` operators in generator functions",
category: "ECMAScript 6",
recommended: false
},
var mode = (function(option) {
if (!option || typeof option === "string") {
return {
before: { before: true, after: false },
after: { before: false, after: true },
both: { before: true, after: true },
neither: { before: false, after: false }
}[option || "before"];
}
return option;
}(context.options[0]));
/**
* Checks the spacing between two tokens before or after the star token.
* @param {string} side Either "before" or "after".
* @param {Token} leftToken `function` keyword token if side is "before", or
* star token if side is "after".
* @param {Token} rightToken Star token if side is "before", or identifier
* token if side is "after".
* @returns {void}
*/
function checkSpacing(side, leftToken, rightToken) {
if (!!(rightToken.range[0] - leftToken.range[1]) !== mode[side]) {
var after = leftToken.value === "*";
var spaceRequired = mode[side];
var node = after ? leftToken : rightToken;
var type = spaceRequired ? "Missing" : "Unexpected";
var message = type + " space " + side + " *.";
context.report({
node: node,
message: message,
fix: function(fixer) {
if (spaceRequired) {
if (after) {
return fixer.insertTextAfter(node, " ");
}
return fixer.insertTextBefore(node, " ");
fixable: "whitespace",
schema: [
{
oneOf: [
{
enum: ["before", "after", "both", "neither"]
},
{
type: "object",
properties: {
before: {type: "boolean"},
after: {type: "boolean"}
},
additionalProperties: false
}
return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
}
});
}
}
]
}
]
},
/**
* Enforces the spacing around the star if node is a generator function.
* @param {ASTNode} node A function expression or declaration node.
* @returns {void}
*/
function checkFunction(node) {
var prevToken, starToken, nextToken;
create: function(context) {
if (!node.generator) {
return;
}
var mode = (function(option) {
if (!option || typeof option === "string") {
return {
before: { before: true, after: false },
after: { before: false, after: true },
both: { before: true, after: true },
neither: { before: false, after: false }
}[option || "before"];
}
return option;
}(context.options[0]));
if (node.parent.method || node.parent.type === "MethodDefinition") {
starToken = context.getTokenBefore(node, 1);
} else {
starToken = context.getFirstToken(node, 1);
}
/**
* Checks the spacing between two tokens before or after the star token.
* @param {string} side Either "before" or "after".
* @param {Token} leftToken `function` keyword token if side is "before", or
* star token if side is "after".
* @param {Token} rightToken Star token if side is "before", or identifier
* token if side is "after".
* @returns {void}
*/
function checkSpacing(side, leftToken, rightToken) {
if (!!(rightToken.range[0] - leftToken.range[1]) !== mode[side]) {
var after = leftToken.value === "*";
var spaceRequired = mode[side];
var node = after ? leftToken : rightToken;
var type = spaceRequired ? "Missing" : "Unexpected";
var message = type + " space " + side + " *.";
// Only check before when preceded by `function` keyword
prevToken = context.getTokenBefore(starToken);
if (prevToken.value === "function" || prevToken.value === "static") {
checkSpacing("before", prevToken, starToken);
context.report({
node: node,
message: message,
fix: function(fixer) {
if (spaceRequired) {
if (after) {
return fixer.insertTextAfter(node, " ");
}
return fixer.insertTextBefore(node, " ");
}
return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
}
});
}
}
nextToken = context.getTokenAfter(starToken);
checkSpacing("after", starToken, nextToken);
}
/**
* Enforces the spacing around the star if node is a generator function.
* @param {ASTNode} node A function expression or declaration node.
* @returns {void}
*/
function checkFunction(node) {
var prevToken, starToken, nextToken;
return {
"FunctionDeclaration": checkFunction,
"FunctionExpression": checkFunction
};
if (!node.generator) {
return;
}
};
if (node.parent.method || node.parent.type === "MethodDefinition") {
starToken = context.getTokenBefore(node, 1);
} else {
starToken = context.getFirstToken(node, 1);
}
module.exports.schema = [
{
"oneOf": [
{
"enum": ["before", "after", "both", "neither"]
},
{
"type": "object",
"properties": {
"before": {"type": "boolean"},
"after": {"type": "boolean"}
},
"additionalProperties": false
// Only check before when preceded by `function` keyword
prevToken = context.getTokenBefore(starToken);
if (prevToken.value === "function" || prevToken.value === "static") {
checkSpacing("before", prevToken, starToken);
}
]
nextToken = context.getTokenAfter(starToken);
checkSpacing("after", starToken, nextToken);
}
return {
FunctionDeclaration: checkFunction,
FunctionExpression: checkFunction
};
}
];
};

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule for disallowing require() outside of the top-level module context
* @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
*/
"use strict";
@ -49,22 +48,32 @@ function isShadowed(scope, node) {
return reference && reference.resolved && reference.resolved.defs.length > 0;
}
module.exports = function(context) {
return {
"CallExpression": function(node) {
var currentScope = context.getScope(),
isGoodRequire;
module.exports = {
meta: {
docs: {
description: "require `require()` calls to be placed at top-level module scope",
category: "Node.js and CommonJS",
recommended: false
},
if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) {
isGoodRequire = context.getAncestors().every(function(parent) {
return ACCEPTABLE_PARENTS.indexOf(parent.type) > -1;
});
if (!isGoodRequire) {
context.report(node, "Unexpected require().");
schema: []
},
create: function(context) {
return {
CallExpression: function(node) {
var currentScope = context.getScope(),
isGoodRequire;
if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) {
isGoodRequire = context.getAncestors().every(function(parent) {
return ACCEPTABLE_PARENTS.indexOf(parent.type) > -1;
});
if (!isGoodRequire) {
context.report(node, "Unexpected require().");
}
}
}
}
};
};
}
};
module.exports.schema = [];

38
tools/eslint/lib/rules/guard-for-in.js

@ -9,24 +9,34 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "require `for-in` loops to include an `if` statement",
category: "Best Practices",
recommended: false
},
return {
schema: []
},
"ForInStatement": function(node) {
create: function(context) {
/*
* If the for-in statement has {}, then the real body is the body
* of the BlockStatement. Otherwise, just use body as provided.
*/
var body = node.body.type === "BlockStatement" ? node.body.body[0] : node.body;
return {
if (body && body.type !== "IfStatement") {
context.report(node, "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.");
ForInStatement: function(node) {
/*
* If the for-in statement has {}, then the real body is the body
* of the BlockStatement. Otherwise, just use body as provided.
*/
var body = node.body.type === "BlockStatement" ? node.body.body[0] : node.body;
if (body && body.type !== "IfStatement") {
context.report(node, "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.");
}
}
}
};
};
}
};
module.exports.schema = [];

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

@ -1,8 +1,6 @@
/**
* @fileoverview Ensure handling of errors when we know they exist.
* @author Jamund Ferguson
* @copyright 2015 Mathias Schreck.
* @copyright 2014 Jamund Ferguson. All rights reserved.
*/
"use strict";
@ -11,73 +9,83 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "require error handling in callbacks",
category: "Node.js and CommonJS",
recommended: false
},
var errorArgument = context.options[0] || "err";
schema: [
{
type: "string"
}
]
},
/**
* Checks if the given argument should be interpreted as a regexp pattern.
* @param {string} stringToCheck The string which should be checked.
* @returns {boolean} Whether or not the string should be interpreted as a pattern.
*/
function isPattern(stringToCheck) {
var firstChar = stringToCheck[0];
create: function(context) {
return firstChar === "^";
}
var errorArgument = context.options[0] || "err";
/**
* Checks if the given name matches the configured error argument.
* @param {string} name The name which should be compared.
* @returns {boolean} Whether or not the given name matches the configured error variable name.
*/
function matchesConfiguredErrorName(name) {
if (isPattern(errorArgument)) {
var regexp = new RegExp(errorArgument);
/**
* Checks if the given argument should be interpreted as a regexp pattern.
* @param {string} stringToCheck The string which should be checked.
* @returns {boolean} Whether or not the string should be interpreted as a pattern.
*/
function isPattern(stringToCheck) {
var firstChar = stringToCheck[0];
return regexp.test(name);
return firstChar === "^";
}
return name === errorArgument;
}
/**
* Get the parameters of a given function scope.
* @param {object} scope The function scope.
* @returns {array} All parameters of the given scope.
*/
function getParameters(scope) {
return scope.variables.filter(function(variable) {
return variable.defs[0] && variable.defs[0].type === "Parameter";
});
}
/**
* Checks if the given name matches the configured error argument.
* @param {string} name The name which should be compared.
* @returns {boolean} Whether or not the given name matches the configured error variable name.
*/
function matchesConfiguredErrorName(name) {
if (isPattern(errorArgument)) {
var regexp = new RegExp(errorArgument);
/**
* Check to see if we're handling the error object properly.
* @param {ASTNode} node The AST node to check.
* @returns {void}
*/
function checkForError(node) {
var scope = context.getScope(),
parameters = getParameters(scope),
firstParameter = parameters[0];
if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) {
if (firstParameter.references.length === 0) {
context.report(node, "Expected error to be handled.");
return regexp.test(name);
}
return name === errorArgument;
}
}
return {
"FunctionDeclaration": checkForError,
"FunctionExpression": checkForError,
"ArrowFunctionExpression": checkForError
};
/**
* Get the parameters of a given function scope.
* @param {object} scope The function scope.
* @returns {array} All parameters of the given scope.
*/
function getParameters(scope) {
return scope.variables.filter(function(variable) {
return variable.defs[0] && variable.defs[0].type === "Parameter";
});
}
};
/**
* Check to see if we're handling the error object properly.
* @param {ASTNode} node The AST node to check.
* @returns {void}
*/
function checkForError(node) {
var scope = context.getScope(),
parameters = getParameters(scope),
firstParameter = parameters[0];
if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) {
if (firstParameter.references.length === 0) {
context.report(node, "Expected error to be handled.");
}
}
}
return {
FunctionDeclaration: checkForError,
FunctionExpression: checkForError,
ArrowFunctionExpression: checkForError
};
module.exports.schema = [
{
"type": "string"
}
];
};

165
tools/eslint/lib/rules/id-blacklist.js

@ -1,11 +1,7 @@
/**
* @fileoverview Rule that warns when identifier names that are
blacklisted in the configuration are used.
* blacklisted in the configuration are used.
* @author Keith Cirkel (http://keithcirkel.co.uk)
* Based on id-match rule:
* @author Matthieu Larcher
* @copyright 2015 Matthieu Larcher. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
@ -14,97 +10,108 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "disallow specified identifiers",
category: "Stylistic Issues",
recommended: false
},
schema: {
type: "array",
items: {
type: "string"
},
uniqueItems: true
}
},
var blacklist = context.options;
create: function(context) {
/**
* Checks if a string matches the provided pattern
* @param {String} name The string to check.
* @returns {boolean} if the string is a match
* @private
*/
function isInvalid(name) {
return blacklist.indexOf(name) !== -1;
}
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Verifies if we should report an error or not based on the effective
* parent node and the identifier name.
* @param {ASTNode} effectiveParent The effective parent node of the node to be reported
* @param {String} name The identifier name of the identifier node
* @returns {boolean} whether an error should be reported or not
*/
function shouldReport(effectiveParent, name) {
return effectiveParent.type !== "CallExpression"
&& effectiveParent.type !== "NewExpression" &&
isInvalid(name);
}
var blacklist = context.options;
/**
* Reports an AST node as a rule violation.
* @param {ASTNode} node The node to report.
* @returns {void}
* @private
*/
function report(node) {
context.report(node, "Identifier '{{name}}' is blacklisted", {
name: node.name
});
}
return {
/**
* Checks if a string matches the provided pattern
* @param {String} name The string to check.
* @returns {boolean} if the string is a match
* @private
*/
function isInvalid(name) {
return blacklist.indexOf(name) !== -1;
}
"Identifier": function(node) {
var name = node.name,
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
/**
* Verifies if we should report an error or not based on the effective
* parent node and the identifier name.
* @param {ASTNode} effectiveParent The effective parent node of the node to be reported
* @param {String} name The identifier name of the identifier node
* @returns {boolean} whether an error should be reported or not
*/
function shouldReport(effectiveParent, name) {
return effectiveParent.type !== "CallExpression"
&& effectiveParent.type !== "NewExpression" &&
isInvalid(name);
}
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
/**
* Reports an AST node as a rule violation.
* @param {ASTNode} node The node to report.
* @returns {void}
* @private
*/
function report(node) {
context.report(node, "Identifier '{{name}}' is blacklisted", {
name: node.name
});
}
// Always check object names
if (node.parent.object.type === "Identifier" &&
node.parent.object.name === node.name) {
if (isInvalid(name)) {
report(node);
return {
Identifier: function(node) {
var name = node.name,
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
// Always check object names
if (node.parent.object.type === "Identifier" &&
node.parent.object.name === node.name) {
if (isInvalid(name)) {
report(node);
}
// Report AssignmentExpressions only if they are the left side of the assignment
} else if (effectiveParent.type === "AssignmentExpression" &&
(effectiveParent.right.type !== "MemberExpression" ||
effectiveParent.left.type === "MemberExpression" &&
effectiveParent.left.property.name === node.name)) {
if (isInvalid(name)) {
report(node);
}
}
// Report AssignmentExpressions only if they are the left side of the assignment
} else if (effectiveParent.type === "AssignmentExpression" &&
(effectiveParent.right.type !== "MemberExpression" ||
effectiveParent.left.type === "MemberExpression" &&
effectiveParent.left.property.name === node.name)) {
if (isInvalid(name)) {
// Properties have their own rules
} else if (node.parent.type === "Property") {
if (shouldReport(effectiveParent, name)) {
report(node);
}
}
// Properties have their own rules
} else if (node.parent.type === "Property") {
if (shouldReport(effectiveParent, name)) {
// Report anything that is a match and not a CallExpression
} else if (shouldReport(effectiveParent, name)) {
report(node);
}
// Report anything that is a match and not a CallExpression
} else if (shouldReport(effectiveParent, name)) {
report(node);
}
}
};
};
};
module.exports.schema = {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
};

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

@ -1,9 +1,7 @@
/**
* @fileoverview Rule that warns when identifier names are shorter or longer
* than the values provided in configuration.
* than the values provided in configuration.
* @author Burak Yigit Kaya aka BYK
* @copyright 2015 Burak Yigit Kaya. All rights reserved.
* @copyright 2015 Mathieu M-Gosselin. All rights reserved.
*/
"use strict";
@ -12,98 +10,105 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var options = context.options[0] || {};
var minLength = typeof options.min !== "undefined" ? options.min : 2;
var maxLength = typeof options.max !== "undefined" ? options.max : Infinity;
var properties = options.properties !== "never";
var exceptions = (options.exceptions ? options.exceptions : [])
.reduce(function(obj, item) {
obj[item] = true;
module.exports = {
meta: {
docs: {
description: "enforce minimum and maximum identifier lengths",
category: "Stylistic Issues",
recommended: false
},
return obj;
}, {});
schema: [
{
type: "object",
properties: {
min: {
type: "number"
},
max: {
type: "number"
},
exceptions: {
type: "array",
uniqueItems: true,
items: {
type: "string"
}
},
properties: {
enum: ["always", "never"]
}
},
additionalProperties: false
}
]
},
var SUPPORTED_EXPRESSIONS = {
"MemberExpression": properties && function(parent) {
return !parent.computed && (
create: function(context) {
var options = context.options[0] || {};
var minLength = typeof options.min !== "undefined" ? options.min : 2;
var maxLength = typeof options.max !== "undefined" ? options.max : Infinity;
var properties = options.properties !== "never";
var exceptions = (options.exceptions ? options.exceptions : [])
.reduce(function(obj, item) {
obj[item] = true;
// regular property assignment
parent.parent.left === parent || (
return obj;
}, {});
// 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
)
);
},
"AssignmentPattern": function(parent, node) {
return parent.left === node;
},
"VariableDeclarator": function(parent, node) {
return parent.id === node;
},
"Property": properties && function(parent, node) {
return parent.key === node;
},
"ImportDefaultSpecifier": true,
"RestElement": true,
"FunctionExpression": true,
"ArrowFunctionExpression": true,
"ClassDeclaration": true,
"FunctionDeclaration": true,
"MethodDefinition": true,
"CatchClause": true
};
var SUPPORTED_EXPRESSIONS = {
MemberExpression: properties && function(parent) {
return !parent.computed && (
return {
Identifier: function(node) {
var name = node.name;
var parent = node.parent;
// 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)
);
},
AssignmentPattern: function(parent, node) {
return parent.left === node;
},
VariableDeclarator: function(parent, node) {
return parent.id === node;
},
Property: properties && function(parent, node) {
return parent.key === node;
},
ImportDefaultSpecifier: true,
RestElement: true,
FunctionExpression: true,
ArrowFunctionExpression: true,
ClassDeclaration: true,
FunctionDeclaration: true,
MethodDefinition: true,
CatchClause: true
};
var isShort = name.length < minLength;
var isLong = name.length > maxLength;
return {
Identifier: function(node) {
var name = node.name;
var parent = node.parent;
if (!(isShort || isLong) || exceptions[name]) {
return; // Nothing to report
}
var isShort = name.length < minLength;
var isLong = name.length > maxLength;
var isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
if (!(isShort || isLong) || exceptions[name]) {
return; // Nothing to report
}
if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) {
context.report(
node,
isShort ?
"Identifier name '{{name}}' is too short. (< {{min}})" :
"Identifier name '{{name}}' is too long. (> {{max}})",
{ name: name, min: minLength, max: maxLength }
);
}
}
};
};
var isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
module.exports.schema = [
{
"type": "object",
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"exceptions": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string"
if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) {
context.report(
node,
isShort ?
"Identifier name '{{name}}' is too short. (< {{min}})" :
"Identifier name '{{name}}' is too long. (> {{max}})",
{ name: name, min: minLength, max: maxLength }
);
}
},
"properties": {
"enum": ["always", "never"]
}
},
"additionalProperties": false
};
}
];
};

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to flag non-matching identifiers
* @author Matthieu Larcher
* @copyright 2015 Matthieu Larcher. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
@ -11,121 +9,132 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "require identifiers to match a specified regular expression",
category: "Stylistic Issues",
recommended: false
},
schema: [
{
type: "string"
},
{
type: "object",
properties: {
properties: {
type: "boolean"
}
}
}
]
},
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
create: function(context) {
var pattern = context.options[0] || "^.+$",
regexp = new RegExp(pattern);
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
var options = context.options[1] || {},
properties = options.properties;
var pattern = context.options[0] || "^.+$",
regexp = new RegExp(pattern);
// cast to boolean and default to false
properties = !!properties;
var options = context.options[1] || {},
properties = !!options.properties,
onlyDeclarations = !!options.onlyDeclarations;
/**
* Checks if a string matches the provided pattern
* @param {String} name The string to check.
* @returns {boolean} if the string is a match
* @private
*/
function isInvalid(name) {
return !regexp.test(name);
}
/**
* Checks if a string matches the provided pattern
* @param {String} name The string to check.
* @returns {boolean} if the string is a match
* @private
*/
function isInvalid(name) {
return !regexp.test(name);
}
/**
* Verifies if we should report an error or not based on the effective
* parent node and the identifier name.
* @param {ASTNode} effectiveParent The effective parent node of the node to be reported
* @param {String} name The identifier name of the identifier node
* @returns {boolean} whether an error should be reported or not
*/
function shouldReport(effectiveParent, name) {
return effectiveParent.type !== "CallExpression"
&& effectiveParent.type !== "NewExpression" &&
isInvalid(name);
}
/**
* Verifies if we should report an error or not based on the effective
* parent node and the identifier name.
* @param {ASTNode} effectiveParent The effective parent node of the node to be reported
* @param {String} name The identifier name of the identifier node
* @returns {boolean} whether an error should be reported or not
*/
function shouldReport(effectiveParent, name) {
return effectiveParent.type !== "CallExpression"
&& effectiveParent.type !== "NewExpression" &&
isInvalid(name);
}
/**
* Reports an AST node as a rule violation.
* @param {ASTNode} node The node to report.
* @returns {void}
* @private
*/
function report(node) {
context.report(node, "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", {
name: node.name,
pattern: pattern
});
}
/**
* Reports an AST node as a rule violation.
* @param {ASTNode} node The node to report.
* @returns {void}
* @private
*/
function report(node) {
context.report(node, "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", {
name: node.name,
pattern: pattern
});
}
return {
return {
Identifier: function(node) {
var name = node.name,
parent = node.parent,
effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent;
"Identifier": function(node) {
var name = node.name,
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
if (parent.type === "MemberExpression") {
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
if (!properties) {
return;
}
// return early if properties is false
if (!properties) {
return;
}
// Always check object names
if (parent.object.type === "Identifier" &&
parent.object.name === name) {
if (isInvalid(name)) {
report(node);
}
// Always check object names
if (node.parent.object.type === "Identifier" &&
node.parent.object.name === node.name) {
if (isInvalid(name)) {
report(node);
// Report AssignmentExpressions only if they are the left side of the assignment
} else if (effectiveParent.type === "AssignmentExpression" &&
(effectiveParent.right.type !== "MemberExpression" ||
effectiveParent.left.type === "MemberExpression" &&
effectiveParent.left.property.name === name)) {
if (isInvalid(name)) {
report(node);
}
}
// Report AssignmentExpressions only if they are the left side of the assignment
} else if (effectiveParent.type === "AssignmentExpression" &&
(effectiveParent.right.type !== "MemberExpression" ||
effectiveParent.left.type === "MemberExpression" &&
effectiveParent.left.property.name === node.name)) {
if (isInvalid(name)) {
} else if (parent.type === "Property") {
if (!properties || parent.key.name !== name) {
return;
}
if (shouldReport(effectiveParent, name)) {
report(node);
}
}
// Properties have their own rules
} else if (node.parent.type === "Property") {
} else {
var isDeclaration = effectiveParent.type === "FunctionDeclaration" || effectiveParent.type === "VariableDeclarator";
// return early if properties is false
if (!properties) {
return;
}
if (onlyDeclarations && !isDeclaration) {
return;
}
if (shouldReport(effectiveParent, name)) {
report(node);
if (shouldReport(effectiveParent, name)) {
report(node);
}
}
// Report anything that is a match and not a CallExpression
} else if (shouldReport(effectiveParent, name)) {
report(node);
}
}
};
};
};
module.exports.schema = [
{
"type": "string"
},
{
"type": "object",
"properties": {
"properties": {
"type": "boolean"
}
}
}
];
};

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

File diff suppressed because it is too large

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

@ -1,7 +1,6 @@
/**
* @fileoverview A rule to control the style of variable initializations.
* @author Colin Ihrig
* @copyright 2015 Colin Ihrig. All rights reserved.
*/
"use strict";
@ -43,74 +42,84 @@ function isInitialized(node) {
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "require or disallow initialization in `var` declarations",
category: "Variables",
recommended: false
},
var MODE_ALWAYS = "always",
MODE_NEVER = "never";
schema: {
anyOf: [
{
type: "array",
items: [
{
enum: ["always"]
}
],
minItems: 0,
maxItems: 1
},
{
type: "array",
items: [
{
enum: ["never"]
},
{
type: "object",
properties: {
ignoreForLoopInit: {
type: "boolean"
}
},
additionalProperties: false
}
],
minItems: 0,
maxItems: 2
}
]
}
},
var mode = context.options[0] || MODE_ALWAYS;
var params = context.options[1] || {};
create: function(context) {
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
var MODE_ALWAYS = "always",
MODE_NEVER = "never";
return {
"VariableDeclaration:exit": function(node) {
var mode = context.options[0] || MODE_ALWAYS;
var params = context.options[1] || {};
var kind = node.kind,
declarations = node.declarations;
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
for (var i = 0; i < declarations.length; ++i) {
var declaration = declarations[i],
id = declaration.id,
initialized = isInitialized(declaration),
isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent);
return {
"VariableDeclaration:exit": function(node) {
if (id.type !== "Identifier") {
continue;
}
var kind = node.kind,
declarations = node.declarations;
if (mode === MODE_ALWAYS && !initialized) {
context.report(declaration, "Variable '" + id.name + "' should be initialized on declaration.");
} else if (mode === MODE_NEVER && kind !== "const" && initialized && !isIgnoredForLoop) {
context.report(declaration, "Variable '" + id.name + "' should not be initialized on declaration.");
}
}
}
};
};
for (var i = 0; i < declarations.length; ++i) {
var declaration = declarations[i],
id = declaration.id,
initialized = isInitialized(declaration),
isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent);
module.exports.schema = {
"anyOf": [
{
"type": "array",
"items": [
{
"enum": ["always"]
}
],
"minItems": 0,
"maxItems": 1
},
{
"type": "array",
"items": [
{
"enum": ["never"]
},
{
"type": "object",
"properties": {
"ignoreForLoopInit": {
"type": "boolean"
}
},
"additionalProperties": false
if (id.type !== "Identifier") {
continue;
}
if (mode === MODE_ALWAYS && !initialized) {
context.report(declaration, "Variable '" + id.name + "' should be initialized on declaration.");
} else if (mode === MODE_NEVER && kind !== "const" && initialized && !isIgnoredForLoop) {
context.report(declaration, "Variable '" + id.name + "' should not be initialized on declaration.");
}
}
],
"minItems": 0,
"maxItems": 2
}
]
}
};
}
};

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

@ -1,7 +1,6 @@
/**
* @fileoverview A rule to ensure consistent quotes used in jsx syntax.
* @author Mathias Schreck <https://github.com/lo1tuma>
* @copyright 2015 Mathias Schreck
*/
"use strict";
@ -37,39 +36,51 @@ var QUOTE_SETTINGS = {
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var quoteOption = context.options[0] || "prefer-double",
setting = QUOTE_SETTINGS[quoteOption];
module.exports = {
meta: {
docs: {
description: "enforce the consistent use of either double or single quotes in JSX attributes",
category: "Stylistic Issues",
recommended: false
},
/**
* Checks if the given string literal node uses the expected quotes
* @param {ASTNode} node - A string literal node.
* @returns {boolean} Whether or not the string literal used the expected quotes.
* @public
*/
function usesExpectedQuotes(node) {
return node.value.indexOf(setting.quote) !== -1 || astUtils.isSurroundedBy(node.raw, setting.quote);
}
return {
"JSXAttribute": function(node) {
var attributeValue = node.value;
fixable: "whitespace",
if (attributeValue && astUtils.isStringLiteral(attributeValue) && !usesExpectedQuotes(attributeValue)) {
context.report({
node: attributeValue,
message: "Unexpected usage of " + setting.description + ".",
fix: function(fixer) {
return fixer.replaceText(attributeValue, setting.convert(attributeValue.raw));
}
});
schema: [
{
enum: [ "prefer-single", "prefer-double" ]
}
]
},
create: function(context) {
var quoteOption = context.options[0] || "prefer-double",
setting = QUOTE_SETTINGS[quoteOption];
/**
* Checks if the given string literal node uses the expected quotes
* @param {ASTNode} node - A string literal node.
* @returns {boolean} Whether or not the string literal used the expected quotes.
* @public
*/
function usesExpectedQuotes(node) {
return node.value.indexOf(setting.quote) !== -1 || astUtils.isSurroundedBy(node.raw, setting.quote);
}
};
};
module.exports.schema = [
{
"enum": [ "prefer-single", "prefer-double" ]
return {
JSXAttribute: function(node) {
var attributeValue = node.value;
if (attributeValue && astUtils.isStringLiteral(attributeValue) && !usesExpectedQuotes(attributeValue)) {
context.report({
node: attributeValue,
message: "Unexpected usage of " + setting.description + ".",
fix: function(fixer) {
return fixer.replaceText(attributeValue, setting.convert(attributeValue.raw));
}
});
}
}
};
}
];
};

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to specify spacing of object literal keys and values
* @author Brandon Mills
* @copyright 2014 Brandon Mills. All rights reserved.
*/
"use strict";
@ -111,332 +110,341 @@ var messages = {
value: "{{error}} space before value for {{computed}}key '{{key}}'."
};
module.exports = function(context) {
/**
* OPTIONS
* "key-spacing": [2, {
* beforeColon: false,
* afterColon: true,
* align: "colon" // Optional, or "value"
* }
*/
var options = context.options[0] || {},
multiLineOptions = initOptions({}, (options.multiLine || options)),
singleLineOptions = initOptions({}, (options.singleLine || options));
/**
* Determines if the given property is key-value property.
* @param {ASTNode} property Property node to check.
* @returns {Boolean} Whether the property is a key-value property.
*/
function isKeyValueProperty(property) {
return !(
property.method ||
property.shorthand ||
property.kind !== "init" ||
property.type !== "Property" // Could be "ExperimentalSpreadProperty" or "SpreadProperty"
);
}
module.exports = {
meta: {
docs: {
description: "enforce consistent spacing between keys and values in object literal properties",
category: "Stylistic Issues",
recommended: false
},
/**
* Starting from the given a node (a property.key node here) looks forward
* until it finds the last token before a colon punctuator and returns it.
* @param {ASTNode} node The node to start looking from.
* @returns {ASTNode} The last token before a colon punctuator.
*/
function getLastTokenBeforeColon(node) {
var prevNode;
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
prevNode = node;
node = context.getTokenAfter(node);
schema: [{
anyOf: [
{
type: "object",
properties: {
align: {
enum: ["colon", "value"]
},
mode: {
enum: ["strict", "minimum"]
},
beforeColon: {
type: "boolean"
},
afterColon: {
type: "boolean"
}
},
additionalProperties: false
},
{
type: "object",
properties: {
singleLine: {
type: "object",
properties: {
mode: {
enum: ["strict", "minimum"]
},
beforeColon: {
type: "boolean"
},
afterColon: {
type: "boolean"
}
},
additionalProperties: false
},
multiLine: {
type: "object",
properties: {
align: {
enum: ["colon", "value"]
},
mode: {
enum: ["strict", "minimum"]
},
beforeColon: {
type: "boolean"
},
afterColon: {
type: "boolean"
}
},
additionalProperties: false
}
},
additionalProperties: false
}
]
}]
},
create: function(context) {
/**
* OPTIONS
* "key-spacing": [2, {
* beforeColon: false,
* afterColon: true,
* align: "colon" // Optional, or "value"
* }
*/
var options = context.options[0] || {},
multiLineOptions = initOptions({}, (options.multiLine || options)),
singleLineOptions = initOptions({}, (options.singleLine || options));
/**
* Determines if the given property is key-value property.
* @param {ASTNode} property Property node to check.
* @returns {Boolean} Whether the property is a key-value property.
*/
function isKeyValueProperty(property) {
return !(
(property.method ||
property.shorthand ||
property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadProperty"
);
}
return prevNode;
}
/**
* Starting from the given a node (a property.key node here) looks forward
* until it finds the colon punctuator and returns it.
* @param {ASTNode} node The node to start looking from.
* @returns {ASTNode} The colon punctuator.
*/
function getNextColon(node) {
/**
* Starting from the given a node (a property.key node here) looks forward
* until it finds the last token before a colon punctuator and returns it.
* @param {ASTNode} node The node to start looking from.
* @returns {ASTNode} The last token before a colon punctuator.
*/
function getLastTokenBeforeColon(node) {
var prevNode;
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
prevNode = node;
node = context.getTokenAfter(node);
}
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
node = context.getTokenAfter(node);
return prevNode;
}
return node;
}
/**
* Starting from the given a node (a property.key node here) looks forward
* until it finds the colon punctuator and returns it.
* @param {ASTNode} node The node to start looking from.
* @returns {ASTNode} The colon punctuator.
*/
function getNextColon(node) {
/**
* Gets an object literal property's key as the identifier name or string value.
* @param {ASTNode} property Property node whose key to retrieve.
* @returns {string} The property's key.
*/
function getKey(property) {
var key = property.key;
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
node = context.getTokenAfter(node);
}
if (property.computed) {
return context.getSource().slice(key.range[0], key.range[1]);
return node;
}
return property.key.name || property.key.value;
}
/**
* Gets an object literal property's key as the identifier name or string value.
* @param {ASTNode} property Property node whose key to retrieve.
* @returns {string} The property's key.
*/
function getKey(property) {
var key = property.key;
/**
* Reports an appropriately-formatted error if spacing is incorrect on one
* side of the colon.
* @param {ASTNode} property Key-value pair in an object literal.
* @param {string} side Side being verified - either "key" or "value".
* @param {string} whitespace Actual whitespace string.
* @param {int} expected Expected whitespace length.
* @param {string} mode Value of the mode as "strict" or "minimum"
* @returns {void}
*/
function report(property, side, whitespace, expected, mode) {
var diff = whitespace.length - expected,
key = property.key,
firstTokenAfterColon = context.getTokenAfter(getNextColon(key)),
location = side === "key" ? key.loc.start : firstTokenAfterColon.loc.start;
if ((
diff && mode === "strict" ||
diff < 0 && mode === "minimum" ||
diff > 0 && !expected && mode === "minimum") &&
!(expected && containsLineTerminator(whitespace))
) {
context.report(property[side], location, messages[side], {
error: diff > 0 ? "Extra" : "Missing",
computed: property.computed ? "computed " : "",
key: getKey(property)
});
if (property.computed) {
return context.getSource().slice(key.range[0], key.range[1]);
}
return property.key.name || property.key.value;
}
}
/**
* Gets the number of characters in a key, including quotes around string
* keys and braces around computed property keys.
* @param {ASTNode} property Property of on object literal.
* @returns {int} Width of the key.
*/
function getKeyWidth(property) {
var startToken, endToken;
/**
* Reports an appropriately-formatted error if spacing is incorrect on one
* side of the colon.
* @param {ASTNode} property Key-value pair in an object literal.
* @param {string} side Side being verified - either "key" or "value".
* @param {string} whitespace Actual whitespace string.
* @param {int} expected Expected whitespace length.
* @param {string} mode Value of the mode as "strict" or "minimum"
* @returns {void}
*/
function report(property, side, whitespace, expected, mode) {
var diff = whitespace.length - expected,
key = property.key,
firstTokenAfterColon = context.getTokenAfter(getNextColon(key)),
location = side === "key" ? key.loc.start : firstTokenAfterColon.loc.start;
if ((
diff && mode === "strict" ||
diff < 0 && mode === "minimum" ||
diff > 0 && !expected && mode === "minimum") &&
!(expected && containsLineTerminator(whitespace))
) {
context.report(property[side], location, messages[side], {
error: diff > 0 ? "Extra" : "Missing",
computed: property.computed ? "computed " : "",
key: getKey(property)
});
}
}
startToken = context.getFirstToken(property);
endToken = getLastTokenBeforeColon(property.key);
/**
* Gets the number of characters in a key, including quotes around string
* keys and braces around computed property keys.
* @param {ASTNode} property Property of on object literal.
* @returns {int} Width of the key.
*/
function getKeyWidth(property) {
var startToken, endToken;
return endToken.range[1] - startToken.range[0];
}
startToken = context.getFirstToken(property);
endToken = getLastTokenBeforeColon(property.key);
/**
* Gets the whitespace around the colon in an object literal property.
* @param {ASTNode} property Property node from an object literal.
* @returns {Object} Whitespace before and after the property's colon.
*/
function getPropertyWhitespace(property) {
var whitespace = /(\s*):(\s*)/.exec(context.getSource().slice(
property.key.range[1], property.value.range[0]
));
if (whitespace) {
return {
beforeColon: whitespace[1],
afterColon: whitespace[2]
};
return endToken.range[1] - startToken.range[0];
}
return null;
}
/**
* Creates groups of properties.
* @param {ASTNode} node ObjectExpression node being evaluated.
* @returns {Array.<ASTNode[]>} Groups of property AST node lists.
*/
function createGroups(node) {
if (node.properties.length === 1) {
return [node.properties];
/**
* Gets the whitespace around the colon in an object literal property.
* @param {ASTNode} property Property node from an object literal.
* @returns {Object} Whitespace before and after the property's colon.
*/
function getPropertyWhitespace(property) {
var whitespace = /(\s*):(\s*)/.exec(context.getSource().slice(
property.key.range[1], property.value.range[0]
));
if (whitespace) {
return {
beforeColon: whitespace[1],
afterColon: whitespace[2]
};
}
return null;
}
return node.properties.reduce(function(groups, property) {
var currentGroup = last(groups),
prev = last(currentGroup);
if (!prev || continuesPropertyGroup(prev, property)) {
currentGroup.push(property);
} else {
groups.push([property]);
/**
* Creates groups of properties.
* @param {ASTNode} node ObjectExpression node being evaluated.
* @returns {Array.<ASTNode[]>} Groups of property AST node lists.
*/
function createGroups(node) {
if (node.properties.length === 1) {
return [node.properties];
}
return groups;
}, [
[]
]);
}
return node.properties.reduce(function(groups, property) {
var currentGroup = last(groups),
prev = last(currentGroup);
/**
* Verifies correct vertical alignment of a group of properties.
* @param {ASTNode[]} properties List of Property AST nodes.
* @returns {void}
*/
function verifyGroupAlignment(properties) {
var length = properties.length,
widths = properties.map(getKeyWidth), // Width of keys, including quotes
targetWidth = Math.max.apply(null, widths),
i, property, whitespace, width,
align = multiLineOptions.align,
beforeColon = multiLineOptions.beforeColon,
afterColon = multiLineOptions.afterColon,
mode = multiLineOptions.mode;
// Conditionally include one space before or after colon
targetWidth += (align === "colon" ? beforeColon : afterColon);
for (i = 0; i < length; i++) {
property = properties[i];
whitespace = getPropertyWhitespace(property);
if (whitespace) { // Object literal getters/setters lack a colon
width = widths[i];
if (align === "value") {
report(property, "key", whitespace.beforeColon, beforeColon, mode);
report(property, "value", whitespace.afterColon, targetWidth - width, mode);
} else { // align = "colon"
report(property, "key", whitespace.beforeColon, targetWidth - width, mode);
report(property, "value", whitespace.afterColon, afterColon, mode);
if (!prev || continuesPropertyGroup(prev, property)) {
currentGroup.push(property);
} else {
groups.push([property]);
}
return groups;
}, [
[]
]);
}
/**
* Verifies correct vertical alignment of a group of properties.
* @param {ASTNode[]} properties List of Property AST nodes.
* @returns {void}
*/
function verifyGroupAlignment(properties) {
var length = properties.length,
widths = properties.map(getKeyWidth), // Width of keys, including quotes
targetWidth = Math.max.apply(null, widths),
i, property, whitespace, width,
align = multiLineOptions.align,
beforeColon = multiLineOptions.beforeColon,
afterColon = multiLineOptions.afterColon,
mode = multiLineOptions.mode;
// Conditionally include one space before or after colon
targetWidth += (align === "colon" ? beforeColon : afterColon);
for (i = 0; i < length; i++) {
property = properties[i];
whitespace = getPropertyWhitespace(property);
if (whitespace) { // Object literal getters/setters lack a colon
width = widths[i];
if (align === "value") {
report(property, "key", whitespace.beforeColon, beforeColon, mode);
report(property, "value", whitespace.afterColon, targetWidth - width, mode);
} else { // align = "colon"
report(property, "key", whitespace.beforeColon, targetWidth - width, mode);
report(property, "value", whitespace.afterColon, afterColon, mode);
}
}
}
}
}
/**
* Verifies vertical alignment, taking into account groups of properties.
* @param {ASTNode} node ObjectExpression node being evaluated.
* @returns {void}
*/
function verifyAlignment(node) {
createGroups(node).forEach(function(group) {
verifyGroupAlignment(group.filter(isKeyValueProperty));
});
}
/**
* Verifies vertical alignment, taking into account groups of properties.
* @param {ASTNode} node ObjectExpression node being evaluated.
* @returns {void}
*/
function verifyAlignment(node) {
createGroups(node).forEach(function(group) {
verifyGroupAlignment(group.filter(isKeyValueProperty));
});
}
/**
* Verifies spacing of property conforms to specified options.
* @param {ASTNode} node Property node being evaluated.
* @param {Object} lineOptions Configured singleLine or multiLine options
* @returns {void}
*/
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);
/**
* Verifies spacing of property conforms to specified options.
* @param {ASTNode} node Property node being evaluated.
* @param {Object} lineOptions Configured singleLine or multiLine options
* @returns {void}
*/
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);
}
}
}
/**
* Verifies spacing of each property in a list.
* @param {ASTNode[]} properties List of Property AST nodes.
* @returns {void}
*/
function verifyListSpacing(properties) {
var length = properties.length;
/**
* Verifies spacing of each property in a list.
* @param {ASTNode[]} properties List of Property AST nodes.
* @returns {void}
*/
function verifyListSpacing(properties) {
var length = properties.length;
for (var i = 0; i < length; i++) {
verifySpacing(properties[i], singleLineOptions);
for (var i = 0; i < length; i++) {
verifySpacing(properties[i], singleLineOptions);
}
}
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
if (multiLineOptions.align) { // Verify vertical alignment
if (multiLineOptions.align) { // Verify vertical alignment
return {
"ObjectExpression": function(node) {
if (isSingleLine(node)) {
verifyListSpacing(node.properties.filter(isKeyValueProperty));
} else {
verifyAlignment(node);
return {
ObjectExpression: function(node) {
if (isSingleLine(node)) {
verifyListSpacing(node.properties.filter(isKeyValueProperty));
} else {
verifyAlignment(node);
}
}
}
};
};
} else { // Obey beforeColon and afterColon in each property as configured
} else { // Obey beforeColon and afterColon in each property as configured
return {
"Property": function(node) {
verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
}
};
return {
Property: function(node) {
verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
}
};
}
}
}
};
module.exports.schema = [{
"anyOf": [
{
"type": "object",
"properties": {
"align": {
"enum": ["colon", "value"]
},
"mode": {
"enum": ["strict", "minimum"]
},
"beforeColon": {
"type": "boolean"
},
"afterColon": {
"type": "boolean"
}
},
"additionalProperties": false
},
{
"type": "object",
"properties": {
"singleLine": {
"type": "object",
"properties": {
"mode": {
"enum": ["strict", "minimum"]
},
"beforeColon": {
"type": "boolean"
},
"afterColon": {
"type": "boolean"
}
},
"additionalProperties": false
},
"multiLine": {
"type": "object",
"properties": {
"align": {
"enum": ["colon", "value"]
},
"mode": {
"enum": ["strict", "minimum"]
},
"beforeColon": {
"type": "boolean"
},
"afterColon": {
"type": "boolean"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
]
}];

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to enforce spacing before and after keywords.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -65,470 +63,482 @@ function isCloseParenOfTemplate(token) {
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var sourceCode = context.getSourceCode();
/**
* Reports a given token if there are not space(s) before the token.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the previous
* token to check.
* @returns {void}
*/
function expectSpaceBefore(token, pattern) {
pattern = pattern || PREV_TOKEN;
var prevToken = sourceCode.getTokenBefore(token);
if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) &&
astUtils.isTokenOnSameLine(prevToken, token) &&
!sourceCode.isSpaceBetweenTokens(prevToken, token)
) {
context.report({
loc: token.loc.start,
message: "Expected space(s) before \"{{value}}\".",
data: token,
fix: function(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
}
module.exports = {
meta: {
docs: {
description: "enforce consistent spacing before and after keywords",
category: "Stylistic Issues",
recommended: false
},
/**
* Reports a given token if there are space(s) before the token.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the previous
* token to check.
* @returns {void}
*/
function unexpectSpaceBefore(token, pattern) {
pattern = pattern || PREV_TOKEN;
var prevToken = sourceCode.getTokenBefore(token);
if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) &&
astUtils.isTokenOnSameLine(prevToken, token) &&
sourceCode.isSpaceBetweenTokens(prevToken, token)
) {
context.report({
loc: token.loc.start,
message: "Unexpected space(s) before \"{{value}}\".",
data: token,
fix: function(fixer) {
return fixer.removeRange([prevToken.range[1], token.range[0]]);
}
});
fixable: "whitespace",
schema: [
{
type: "object",
properties: {
before: {type: "boolean"},
after: {type: "boolean"},
overrides: {
type: "object",
properties: KEYS.reduce(function(retv, key) {
retv[key] = {
type: "object",
properties: {
before: {type: "boolean"},
after: {type: "boolean"}
},
additionalProperties: false
};
return retv;
}, {}),
additionalProperties: false
}
},
additionalProperties: false
}
]
},
create: function(context) {
var sourceCode = context.getSourceCode();
/**
* Reports a given token if there are not space(s) before the token.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the previous
* token to check.
* @returns {void}
*/
function expectSpaceBefore(token, pattern) {
pattern = pattern || PREV_TOKEN;
var prevToken = sourceCode.getTokenBefore(token);
if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) &&
astUtils.isTokenOnSameLine(prevToken, token) &&
!sourceCode.isSpaceBetweenTokens(prevToken, token)
) {
context.report({
loc: token.loc.start,
message: "Expected space(s) before \"{{value}}\".",
data: token,
fix: function(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
}
}
/**
* Reports a given token if there are not space(s) after the token.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the next
* token to check.
* @returns {void}
*/
function expectSpaceAfter(token, pattern) {
pattern = pattern || NEXT_TOKEN;
var nextToken = sourceCode.getTokenAfter(token);
if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) &&
astUtils.isTokenOnSameLine(token, nextToken) &&
!sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
context.report({
loc: token.loc.start,
message: "Expected space(s) after \"{{value}}\".",
data: token,
fix: function(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
/**
* Reports a given token if there are space(s) before the token.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the previous
* token to check.
* @returns {void}
*/
function unexpectSpaceBefore(token, pattern) {
pattern = pattern || PREV_TOKEN;
var prevToken = sourceCode.getTokenBefore(token);
if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) &&
astUtils.isTokenOnSameLine(prevToken, token) &&
sourceCode.isSpaceBetweenTokens(prevToken, token)
) {
context.report({
loc: token.loc.start,
message: "Unexpected space(s) before \"{{value}}\".",
data: token,
fix: function(fixer) {
return fixer.removeRange([prevToken.range[1], token.range[0]]);
}
});
}
}
}
/**
* Reports a given token if there are space(s) after the token.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the next
* token to check.
* @returns {void}
*/
function unexpectSpaceAfter(token, pattern) {
pattern = pattern || NEXT_TOKEN;
var nextToken = sourceCode.getTokenAfter(token);
if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) &&
astUtils.isTokenOnSameLine(token, nextToken) &&
sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
context.report({
loc: token.loc.start,
message: "Unexpected space(s) after \"{{value}}\".",
data: token,
fix: function(fixer) {
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
});
/**
* Reports a given token if there are not space(s) after the token.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the next
* token to check.
* @returns {void}
*/
function expectSpaceAfter(token, pattern) {
pattern = pattern || NEXT_TOKEN;
var nextToken = sourceCode.getTokenAfter(token);
if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) &&
astUtils.isTokenOnSameLine(token, nextToken) &&
!sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
context.report({
loc: token.loc.start,
message: "Expected space(s) after \"{{value}}\".",
data: token,
fix: function(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
}
}
/**
* Parses the option object and determines check methods for each keyword.
*
* @param {object|undefined} options - The option object to parse.
* @returns {object} - Normalized option object.
* Keys are keywords (there are for every keyword).
* Values are instances of `{"before": function, "after": function}`.
*/
function parseOptions(options) {
var before = !options || options.before !== false;
var after = !options || options.after !== false;
var defaultValue = {
before: before ? expectSpaceBefore : unexpectSpaceBefore,
after: after ? expectSpaceAfter : unexpectSpaceAfter
};
var overrides = (options && options.overrides) || {};
var retv = Object.create(null);
for (var i = 0; i < KEYS.length; ++i) {
var key = KEYS[i];
var override = overrides[key];
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
};
} else {
retv[key] = defaultValue;
/**
* Reports a given token if there are space(s) after the token.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the next
* token to check.
* @returns {void}
*/
function unexpectSpaceAfter(token, pattern) {
pattern = pattern || NEXT_TOKEN;
var nextToken = sourceCode.getTokenAfter(token);
if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) &&
astUtils.isTokenOnSameLine(token, nextToken) &&
sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
context.report({
loc: token.loc.start,
message: "Unexpected space(s) after \"{{value}}\".",
data: token,
fix: function(fixer) {
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
});
}
}
return retv;
}
/**
* Parses the option object and determines check methods for each keyword.
*
* @param {object|undefined} options - The option object to parse.
* @returns {object} - Normalized option object.
* Keys are keywords (there are for every keyword).
* Values are instances of `{"before": function, "after": function}`.
*/
function parseOptions(options) {
var before = !options || options.before !== false;
var after = !options || options.after !== false;
var defaultValue = {
before: before ? expectSpaceBefore : unexpectSpaceBefore,
after: after ? expectSpaceAfter : unexpectSpaceAfter
};
var overrides = (options && options.overrides) || {};
var retv = Object.create(null);
for (var i = 0; i < KEYS.length; ++i) {
var key = KEYS[i];
var override = overrides[key];
if (override) {
var thisBefore = ("before" in override) ? override.before : before;
var thisAfter = ("after" in override) ? override.after : after;
var checkMethodMap = parseOptions(context.options[0]);
/**
* Reports a given token if usage of spacing followed by the token is
* invalid.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the previous
* token to check.
* @returns {void}
*/
function checkSpacingBefore(token, pattern) {
checkMethodMap[token.value].before(token, pattern);
}
retv[key] = {
before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
};
} else {
retv[key] = defaultValue;
}
}
/**
* Reports a given token if usage of spacing preceded by the token is
* invalid.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the next
* token to check.
* @returns {void}
*/
function checkSpacingAfter(token, pattern) {
checkMethodMap[token.value].after(token, pattern);
}
return retv;
}
/**
* Reports a given token if usage of spacing around the token is invalid.
*
* @param {Token} token - A token to report.
* @returns {void}
*/
function checkSpacingAround(token) {
checkSpacingBefore(token);
checkSpacingAfter(token);
}
var checkMethodMap = parseOptions(context.options[0]);
/**
* Reports a given token if usage of spacing followed by the token is
* invalid.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the previous
* token to check.
* @returns {void}
*/
function checkSpacingBefore(token, pattern) {
checkMethodMap[token.value].before(token, pattern);
}
/**
* Reports the first token of a given node if the first token is a keyword
* and usage of spacing around the token is invalid.
*
* @param {ASTNode|null} node - A node to report.
* @returns {void}
*/
function checkSpacingAroundFirstToken(node) {
var firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && firstToken.type === "Keyword") {
checkSpacingAround(firstToken);
/**
* Reports a given token if usage of spacing preceded by the token is
* invalid.
*
* @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the next
* token to check.
* @returns {void}
*/
function checkSpacingAfter(token, pattern) {
checkMethodMap[token.value].after(token, pattern);
}
}
/**
* Reports the first token of a given node if the first token is a keyword
* and usage of spacing followed by the token is invalid.
*
* This is used for unary operators (e.g. `typeof`), `function`, and `super`.
* Other rules are handling usage of spacing preceded by those keywords.
*
* @param {ASTNode|null} node - A node to report.
* @returns {void}
*/
function checkSpacingBeforeFirstToken(node) {
var firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && firstToken.type === "Keyword") {
checkSpacingBefore(firstToken);
/**
* Reports a given token if usage of spacing around the token is invalid.
*
* @param {Token} token - A token to report.
* @returns {void}
*/
function checkSpacingAround(token) {
checkSpacingBefore(token);
checkSpacingAfter(token);
}
}
/**
* Reports the previous token of a given node if the token is a keyword and
* usage of spacing around the token is invalid.
*
* @param {ASTNode|null} node - A node to report.
* @returns {void}
*/
function checkSpacingAroundTokenBefore(node) {
if (node) {
var token = sourceCode.getTokenBefore(node);
while (token.type !== "Keyword") {
token = sourceCode.getTokenBefore(token);
/**
* Reports the first token of a given node if the first token is a keyword
* and usage of spacing around the token is invalid.
*
* @param {ASTNode|null} node - A node to report.
* @returns {void}
*/
function checkSpacingAroundFirstToken(node) {
var firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && firstToken.type === "Keyword") {
checkSpacingAround(firstToken);
}
checkSpacingAround(token);
}
}
/**
* Reports `class` and `extends` keywords of a given node if usage of
* spacing around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForClass(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.superClass);
}
/**
* Reports `if` and `else` keywords of a given node if usage of spacing
* around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForIfStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.alternate);
}
/**
* Reports the first token of a given node if the first token is a keyword
* and usage of spacing followed by the token is invalid.
*
* This is used for unary operators (e.g. `typeof`), `function`, and `super`.
* Other rules are handling usage of spacing preceded by those keywords.
*
* @param {ASTNode|null} node - A node to report.
* @returns {void}
*/
function checkSpacingBeforeFirstToken(node) {
var firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && firstToken.type === "Keyword") {
checkSpacingBefore(firstToken);
}
}
/**
* Reports `try`, `catch`, and `finally` keywords of a given node if usage
* of spacing around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForTryStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundFirstToken(node.handler);
checkSpacingAroundTokenBefore(node.finalizer);
}
/**
* Reports the previous token of a given node if the token is a keyword and
* usage of spacing around the token is invalid.
*
* @param {ASTNode|null} node - A node to report.
* @returns {void}
*/
function checkSpacingAroundTokenBefore(node) {
if (node) {
var token = sourceCode.getTokenBefore(node);
while (token.type !== "Keyword") {
token = sourceCode.getTokenBefore(token);
}
/**
* Reports `do` and `while` keywords of a given node if usage of spacing
* around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForDoWhileStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.test);
}
checkSpacingAround(token);
}
}
/**
* Reports `for` and `in` keywords of a given node if usage of spacing
* around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForForInStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.right);
}
/**
* Reports `class` and `extends` keywords of a given node if usage of
* spacing around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForClass(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.superClass);
}
/**
* Reports `for` and `of` keywords of a given node if usage of spacing
* around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForForOfStatement(node) {
checkSpacingAroundFirstToken(node);
// `of` is not a keyword token.
var token = sourceCode.getTokenBefore(node.right);
while (token.value !== "of") {
token = sourceCode.getTokenBefore(token);
/**
* Reports `if` and `else` keywords of a given node if usage of spacing
* around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForIfStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.alternate);
}
checkSpacingAround(token);
}
/**
* Reports `import`, `export`, `as`, and `from` keywords of a given node if
* usage of spacing around those keywords is invalid.
*
* This rule handles the `*` token in module declarations.
*
* import*as A from "./a"; /*error Expected space(s) after "import".
* error Expected space(s) before "as".
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
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);
/**
* Reports `try`, `catch`, and `finally` keywords of a given node if usage
* of spacing around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForTryStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundFirstToken(node.handler);
checkSpacingAroundTokenBefore(node.finalizer);
}
}
/**
* Reports `as` keyword of a given node if usage of spacing around this
* keyword is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForImportNamespaceSpecifier(node) {
var asToken = sourceCode.getFirstToken(node, 1);
checkSpacingBefore(asToken, PREV_TOKEN_M);
}
/**
* Reports `do` and `while` keywords of a given node if usage of spacing
* around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForDoWhileStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.test);
}
/**
* Reports `static`, `get`, and `set` keywords of a given node if usage of
* spacing around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForProperty(node) {
if (node.static) {
/**
* Reports `for` and `in` keywords of a given node if usage of spacing
* around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForForInStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.right);
}
if (node.kind === "get" || node.kind === "set") {
var token = sourceCode.getFirstToken(
node,
node.static ? 1 : 0
);
/**
* Reports `for` and `of` keywords of a given node if usage of spacing
* around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForForOfStatement(node) {
checkSpacingAroundFirstToken(node);
// `of` is not a keyword token.
var token = sourceCode.getTokenBefore(node.right);
while (token.value !== "of") {
token = sourceCode.getTokenBefore(token);
}
checkSpacingAround(token);
}
}
return {
// Statements
DebuggerStatement: checkSpacingAroundFirstToken,
WithStatement: checkSpacingAroundFirstToken,
// Statements - Control flow
BreakStatement: checkSpacingAroundFirstToken,
ContinueStatement: checkSpacingAroundFirstToken,
ReturnStatement: checkSpacingAroundFirstToken,
ThrowStatement: checkSpacingAroundFirstToken,
TryStatement: checkSpacingForTryStatement,
// Statements - Choice
IfStatement: checkSpacingForIfStatement,
SwitchStatement: checkSpacingAroundFirstToken,
SwitchCase: checkSpacingAroundFirstToken,
// Statements - Loops
DoWhileStatement: checkSpacingForDoWhileStatement,
ForInStatement: checkSpacingForForInStatement,
ForOfStatement: checkSpacingForForOfStatement,
ForStatement: checkSpacingAroundFirstToken,
WhileStatement: checkSpacingAroundFirstToken,
// Statements - Declarations
ClassDeclaration: checkSpacingForClass,
ExportNamedDeclaration: checkSpacingForModuleDeclaration,
ExportDefaultDeclaration: checkSpacingAroundFirstToken,
ExportAllDeclaration: checkSpacingForModuleDeclaration,
FunctionDeclaration: checkSpacingBeforeFirstToken,
ImportDeclaration: checkSpacingForModuleDeclaration,
VariableDeclaration: checkSpacingAroundFirstToken,
// Expressions
ClassExpression: checkSpacingForClass,
FunctionExpression: checkSpacingBeforeFirstToken,
NewExpression: checkSpacingBeforeFirstToken,
Super: checkSpacingBeforeFirstToken,
ThisExpression: checkSpacingBeforeFirstToken,
UnaryExpression: checkSpacingBeforeFirstToken,
YieldExpression: checkSpacingBeforeFirstToken,
// Others
ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
MethodDefinition: checkSpacingForProperty,
Property: checkSpacingForProperty
};
};
/**
* Reports `import`, `export`, `as`, and `from` keywords of a given node if
* usage of spacing around those keywords is invalid.
*
* This rule handles the `*` token in module declarations.
*
* import*as A from "./a"; /*error Expected space(s) after "import".
* error Expected space(s) before "as".
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
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);
}
}
module.exports.schema = [
{
"type": "object",
"properties": {
"before": {"type": "boolean"},
"after": {"type": "boolean"},
"overrides": {
"type": "object",
"properties": KEYS.reduce(function(retv, key) {
retv[key] = {
"type": "object",
"properties": {
"before": {"type": "boolean"},
"after": {"type": "boolean"}
},
"additionalProperties": false
};
return retv;
}, {}),
"additionalProperties": false
/**
* Reports `as` keyword of a given node if usage of spacing around this
* keyword is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForImportNamespaceSpecifier(node) {
var asToken = sourceCode.getFirstToken(node, 1);
checkSpacingBefore(asToken, PREV_TOKEN_M);
}
/**
* Reports `static`, `get`, and `set` keywords of a given node if usage of
* spacing around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForProperty(node) {
if (node.static) {
checkSpacingAroundFirstToken(node);
}
},
"additionalProperties": false
if (node.kind === "get" || node.kind === "set") {
var token = sourceCode.getFirstToken(
node,
node.static ? 1 : 0
);
checkSpacingAround(token);
}
}
return {
// Statements
DebuggerStatement: checkSpacingAroundFirstToken,
WithStatement: checkSpacingAroundFirstToken,
// Statements - Control flow
BreakStatement: checkSpacingAroundFirstToken,
ContinueStatement: checkSpacingAroundFirstToken,
ReturnStatement: checkSpacingAroundFirstToken,
ThrowStatement: checkSpacingAroundFirstToken,
TryStatement: checkSpacingForTryStatement,
// Statements - Choice
IfStatement: checkSpacingForIfStatement,
SwitchStatement: checkSpacingAroundFirstToken,
SwitchCase: checkSpacingAroundFirstToken,
// Statements - Loops
DoWhileStatement: checkSpacingForDoWhileStatement,
ForInStatement: checkSpacingForForInStatement,
ForOfStatement: checkSpacingForForOfStatement,
ForStatement: checkSpacingAroundFirstToken,
WhileStatement: checkSpacingAroundFirstToken,
// Statements - Declarations
ClassDeclaration: checkSpacingForClass,
ExportNamedDeclaration: checkSpacingForModuleDeclaration,
ExportDefaultDeclaration: checkSpacingAroundFirstToken,
ExportAllDeclaration: checkSpacingForModuleDeclaration,
FunctionDeclaration: checkSpacingBeforeFirstToken,
ImportDeclaration: checkSpacingForModuleDeclaration,
VariableDeclaration: checkSpacingAroundFirstToken,
// Expressions
ClassExpression: checkSpacingForClass,
FunctionExpression: checkSpacingBeforeFirstToken,
NewExpression: checkSpacingBeforeFirstToken,
Super: checkSpacingBeforeFirstToken,
ThisExpression: checkSpacingBeforeFirstToken,
UnaryExpression: checkSpacingBeforeFirstToken,
YieldExpression: checkSpacingBeforeFirstToken,
// Others
ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
MethodDefinition: checkSpacingForProperty,
Property: checkSpacingForProperty
};
}
];
};

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

@ -1,9 +1,6 @@
/**
* @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.
* @copyright 2015 Erik Mueller. All rights reserved.
*/
"use strict";
@ -12,69 +9,81 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "enforce consistent linebreak style",
category: "Stylistic Issues",
recommended: false
},
var EXPECTED_LF_MSG = "Expected linebreaks to be 'LF' but found 'CRLF'.",
EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'.";
fixable: "whitespace",
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
schema: [
{
enum: ["unix", "windows"]
}
]
},
/**
* Builds a fix function that replaces text at the specified range in the source text.
* @param {int[]} range The range to replace
* @param {string} text The text to insert.
* @returns {function} Fixer function
* @private
*/
function createFix(range, text) {
return function(fixer) {
return fixer.replaceTextRange(range, text);
};
}
create: function(context) {
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
var EXPECTED_LF_MSG = "Expected linebreaks to be 'LF' but found 'CRLF'.",
EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'.";
return {
"Program": function checkForlinebreakStyle(node) {
var linebreakStyle = context.options[0] || "unix",
expectedLF = linebreakStyle === "unix",
expectedLFChars = expectedLF ? "\n" : "\r\n",
source = context.getSource(),
pattern = /\r\n|\r|\n|\u2028|\u2029/g,
match,
index,
range;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
var i = 0;
/**
* Builds a fix function that replaces text at the specified range in the source text.
* @param {int[]} range The range to replace
* @param {string} text The text to insert.
* @returns {function} Fixer function
* @private
*/
function createFix(range, text) {
return function(fixer) {
return fixer.replaceTextRange(range, text);
};
}
while ((match = pattern.exec(source)) !== null) {
i++;
if (match[0] === expectedLFChars) {
continue;
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
index = match.index;
range = [index, index + match[0].length];
context.report({
node: node,
loc: {
line: i,
column: context.getSourceLines()[i - 1].length
},
message: expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG,
fix: createFix(range, expectedLFChars)
});
}
}
};
};
return {
Program: function checkForlinebreakStyle(node) {
var linebreakStyle = context.options[0] || "unix",
expectedLF = linebreakStyle === "unix",
expectedLFChars = expectedLF ? "\n" : "\r\n",
source = context.getSource(),
pattern = /\r\n|\r|\n|\u2028|\u2029/g,
match,
index,
range;
var i = 0;
module.exports.schema = [
{
"enum": ["unix", "windows"]
while ((match = pattern.exec(source)) !== null) {
i++;
if (match[0] === expectedLFChars) {
continue;
}
index = match.index;
range = [index, index + match[0].length];
context.report({
node: node,
loc: {
line: i,
column: context.getSourceLines()[i - 1].length
},
message: expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG,
fix: createFix(range, expectedLFChars)
});
}
}
};
}
];
};

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

@ -1,9 +1,6 @@
/**
* @fileoverview Enforces empty lines around comments.
* @author Jamund Ferguson
* @copyright 2015 Mathieu M-Gosselin. All rights reserved.
* @copyright 2015 Jamund Ferguson. All rights reserved.
* @copyright 2015 Gyandeep Singh. All rights reserved.
*/
"use strict";
@ -68,272 +65,282 @@ function contains(val, array) {
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "require empty lines around comments",
category: "Stylistic Issues",
recommended: false
},
var options = context.options[0] ? lodash.assign({}, context.options[0]) : {};
schema: [
{
type: "object",
properties: {
beforeBlockComment: {
type: "boolean"
},
afterBlockComment: {
type: "boolean"
},
beforeLineComment: {
type: "boolean"
},
afterLineComment: {
type: "boolean"
},
allowBlockStart: {
type: "boolean"
},
allowBlockEnd: {
type: "boolean"
},
allowObjectStart: {
type: "boolean"
},
allowObjectEnd: {
type: "boolean"
},
allowArrayStart: {
type: "boolean"
},
allowArrayEnd: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
options.beforeLineComment = options.beforeLineComment || false;
options.afterLineComment = options.afterLineComment || false;
options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
options.afterBlockComment = options.afterBlockComment || false;
options.allowBlockStart = options.allowBlockStart || false;
options.allowBlockEnd = options.allowBlockEnd || false;
create: function(context) {
var sourceCode = context.getSourceCode();
var options = context.options[0] ? lodash.assign({}, context.options[0]) : {};
/**
* Returns whether or not comments are on lines starting with or ending with code
* @param {ASTNode} node The comment node to check.
* @returns {boolean} True if the comment is not alone.
*/
function codeAroundComment(node) {
var token;
options.beforeLineComment = options.beforeLineComment || false;
options.afterLineComment = options.afterLineComment || false;
options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
options.afterBlockComment = options.afterBlockComment || false;
options.allowBlockStart = options.allowBlockStart || false;
options.allowBlockEnd = options.allowBlockEnd || false;
token = node;
do {
token = sourceCode.getTokenOrCommentBefore(token);
} while (token && (token.type === "Block" || token.type === "Line"));
var sourceCode = context.getSourceCode();
if (token && token.loc.end.line === node.loc.start.line) {
return true;
}
/**
* Returns whether or not comments are on lines starting with or ending with code
* @param {ASTNode} node The comment node to check.
* @returns {boolean} True if the comment is not alone.
*/
function codeAroundComment(node) {
var token;
token = node;
do {
token = sourceCode.getTokenOrCommentAfter(token);
} while (token && (token.type === "Block" || token.type === "Line"));
token = node;
do {
token = sourceCode.getTokenOrCommentBefore(token);
} while (token && (token.type === "Block" || token.type === "Line"));
if (token && token.loc.start.line === node.loc.end.line) {
return true;
}
if (token && token.loc.end.line === node.loc.start.line) {
return true;
}
return false;
}
token = node;
do {
token = sourceCode.getTokenOrCommentAfter(token);
} while (token && (token.type === "Block" || token.type === "Line"));
/**
* Returns whether or not comments are inside a node type or not.
* @param {ASTNode} node The Comment node.
* @param {ASTNode} parent The Comment parent node.
* @param {string} nodeType The parent type to check against.
* @returns {boolean} True if the comment is inside nodeType.
*/
function isCommentInsideNodeType(node, parent, nodeType) {
return parent.type === nodeType ||
(parent.body && parent.body.type === nodeType) ||
(parent.consequent && parent.consequent.type === nodeType);
}
if (token && token.loc.start.line === node.loc.end.line) {
return true;
}
/**
* Returns whether or not comments are at the parent start or not.
* @param {ASTNode} node The Comment node.
* @param {string} nodeType The parent type to check against.
* @returns {boolean} True if the comment is at parent start.
*/
function isCommentAtParentStart(node, nodeType) {
var ancestors = context.getAncestors();
var parent;
if (ancestors.length) {
parent = ancestors.pop();
return false;
}
return parent && isCommentInsideNodeType(node, parent, nodeType) &&
node.loc.start.line - parent.loc.start.line === 1;
}
/**
* Returns whether or not comments are at the parent end or not.
* @param {ASTNode} node The Comment node.
* @param {string} nodeType The parent type to check against.
* @returns {boolean} True if the comment is at parent end.
*/
function isCommentAtParentEnd(node, nodeType) {
var ancestors = context.getAncestors();
var parent;
if (ancestors.length) {
parent = ancestors.pop();
/**
* Returns whether or not comments are inside a node type or not.
* @param {ASTNode} node The Comment node.
* @param {ASTNode} parent The Comment parent node.
* @param {string} nodeType The parent type to check against.
* @returns {boolean} True if the comment is inside nodeType.
*/
function isCommentInsideNodeType(node, parent, nodeType) {
return parent.type === nodeType ||
(parent.body && parent.body.type === nodeType) ||
(parent.consequent && parent.consequent.type === nodeType);
}
return parent && isCommentInsideNodeType(node, parent, nodeType) &&
parent.loc.end.line - node.loc.end.line === 1;
}
/**
* Returns whether or not comments are at the block start or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at block start.
*/
function isCommentAtBlockStart(node) {
return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement") || isCommentAtParentStart(node, "SwitchCase");
}
/**
* Returns whether or not comments are at the block end or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at block end.
*/
function isCommentAtBlockEnd(node) {
return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement") || isCommentAtParentEnd(node, "SwitchCase") || isCommentAtParentEnd(node, "SwitchStatement");
}
/**
* Returns whether or not comments are at the object start or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at object start.
*/
function isCommentAtObjectStart(node) {
return isCommentAtParentStart(node, "ObjectExpression") || isCommentAtParentStart(node, "ObjectPattern");
}
/**
* Returns whether or not comments are at the parent start or not.
* @param {ASTNode} node The Comment node.
* @param {string} nodeType The parent type to check against.
* @returns {boolean} True if the comment is at parent start.
*/
function isCommentAtParentStart(node, nodeType) {
var ancestors = context.getAncestors();
var parent;
if (ancestors.length) {
parent = ancestors.pop();
}
/**
* Returns whether or not comments are at the object end or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at object end.
*/
function isCommentAtObjectEnd(node) {
return isCommentAtParentEnd(node, "ObjectExpression") || isCommentAtParentEnd(node, "ObjectPattern");
}
return parent && isCommentInsideNodeType(node, parent, nodeType) &&
node.loc.start.line - parent.loc.start.line === 1;
}
/**
* Returns whether or not comments are at the array start or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at array start.
*/
function isCommentAtArrayStart(node) {
return isCommentAtParentStart(node, "ArrayExpression") || isCommentAtParentStart(node, "ArrayPattern");
}
/**
* Returns whether or not comments are at the parent end or not.
* @param {ASTNode} node The Comment node.
* @param {string} nodeType The parent type to check against.
* @returns {boolean} True if the comment is at parent end.
*/
function isCommentAtParentEnd(node, nodeType) {
var ancestors = context.getAncestors();
var parent;
if (ancestors.length) {
parent = ancestors.pop();
}
/**
* Returns whether or not comments are at the array end or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at array end.
*/
function isCommentAtArrayEnd(node) {
return isCommentAtParentEnd(node, "ArrayExpression") || isCommentAtParentEnd(node, "ArrayPattern");
}
return parent && isCommentInsideNodeType(node, parent, nodeType) &&
parent.loc.end.line - node.loc.end.line === 1;
}
/**
* Checks if a comment node has lines around it (ignores inline comments)
* @param {ASTNode} node The Comment node.
* @param {Object} opts Options to determine the newline.
* @param {Boolean} opts.after Should have a newline after this line.
* @param {Boolean} opts.before Should have a newline before this line.
* @returns {void}
*/
function checkForEmptyLine(node, opts) {
var lines = context.getSourceLines(),
numLines = lines.length + 1,
comments = context.getAllComments(),
commentLines = getCommentLineNums(comments),
emptyLines = getEmptyLineNums(lines),
commentAndEmptyLines = commentLines.concat(emptyLines);
var after = opts.after,
before = opts.before;
var prevLineNum = node.loc.start.line - 1,
nextLineNum = node.loc.end.line + 1,
commentIsNotAlone = codeAroundComment(node);
var blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(node),
blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(node),
objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(node),
objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(node),
arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(node),
arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(node);
var exceptionStartAllowed = blockStartAllowed || objectStartAllowed || arrayStartAllowed;
var exceptionEndAllowed = blockEndAllowed || objectEndAllowed || arrayEndAllowed;
// ignore top of the file and bottom of the file
if (prevLineNum < 1) {
before = false;
/**
* Returns whether or not comments are at the block start or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at block start.
*/
function isCommentAtBlockStart(node) {
return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement") || isCommentAtParentStart(node, "SwitchCase");
}
if (nextLineNum >= numLines) {
after = false;
/**
* Returns whether or not comments are at the block end or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at block end.
*/
function isCommentAtBlockEnd(node) {
return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement") || isCommentAtParentEnd(node, "SwitchCase") || isCommentAtParentEnd(node, "SwitchStatement");
}
// we ignore all inline comments
if (commentIsNotAlone) {
return;
/**
* Returns whether or not comments are at the object start or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at object start.
*/
function isCommentAtObjectStart(node) {
return isCommentAtParentStart(node, "ObjectExpression") || isCommentAtParentStart(node, "ObjectPattern");
}
// check for newline before
if (!exceptionStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) {
context.report(node, "Expected line before comment.");
/**
* Returns whether or not comments are at the object end or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at object end.
*/
function isCommentAtObjectEnd(node) {
return isCommentAtParentEnd(node, "ObjectExpression") || isCommentAtParentEnd(node, "ObjectPattern");
}
// check for newline after
if (!exceptionEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) {
context.report(node, "Expected line after comment.");
/**
* Returns whether or not comments are at the array start or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at array start.
*/
function isCommentAtArrayStart(node) {
return isCommentAtParentStart(node, "ArrayExpression") || isCommentAtParentStart(node, "ArrayPattern");
}
}
/**
* Returns whether or not comments are at the array end or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at array end.
*/
function isCommentAtArrayEnd(node) {
return isCommentAtParentEnd(node, "ArrayExpression") || isCommentAtParentEnd(node, "ArrayPattern");
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Checks if a comment node has lines around it (ignores inline comments)
* @param {ASTNode} node The Comment node.
* @param {Object} opts Options to determine the newline.
* @param {Boolean} opts.after Should have a newline after this line.
* @param {Boolean} opts.before Should have a newline before this line.
* @returns {void}
*/
function checkForEmptyLine(node, opts) {
var lines = context.getSourceLines(),
numLines = lines.length + 1,
comments = context.getAllComments(),
commentLines = getCommentLineNums(comments),
emptyLines = getEmptyLineNums(lines),
commentAndEmptyLines = commentLines.concat(emptyLines);
var after = opts.after,
before = opts.before;
var prevLineNum = node.loc.start.line - 1,
nextLineNum = node.loc.end.line + 1,
commentIsNotAlone = codeAroundComment(node);
var blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(node),
blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(node),
objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(node),
objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(node),
arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(node),
arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(node);
var exceptionStartAllowed = blockStartAllowed || objectStartAllowed || arrayStartAllowed;
var exceptionEndAllowed = blockEndAllowed || objectEndAllowed || arrayEndAllowed;
// ignore top of the file and bottom of the file
if (prevLineNum < 1) {
before = false;
}
if (nextLineNum >= numLines) {
after = false;
}
return {
// we ignore all inline comments
if (commentIsNotAlone) {
return;
}
"LineComment": function(node) {
if (options.beforeLineComment || options.afterLineComment) {
checkForEmptyLine(node, {
after: options.afterLineComment,
before: options.beforeLineComment
});
// check for newline before
if (!exceptionStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) {
context.report(node, "Expected line before comment.");
}
},
"BlockComment": function(node) {
if (options.beforeBlockComment || options.afterBlockComment) {
checkForEmptyLine(node, {
after: options.afterBlockComment,
before: options.beforeBlockComment
});
// check for newline after
if (!exceptionEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) {
context.report(node, "Expected line after comment.");
}
}
};
};
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
module.exports.schema = [
{
"type": "object",
"properties": {
"beforeBlockComment": {
"type": "boolean"
},
"afterBlockComment": {
"type": "boolean"
},
"beforeLineComment": {
"type": "boolean"
},
"afterLineComment": {
"type": "boolean"
},
"allowBlockStart": {
"type": "boolean"
},
"allowBlockEnd": {
"type": "boolean"
},
"allowObjectStart": {
"type": "boolean"
},
"allowObjectEnd": {
"type": "boolean"
},
"allowArrayStart": {
"type": "boolean"
return {
LineComment: function(node) {
if (options.beforeLineComment || options.afterLineComment) {
checkForEmptyLine(node, {
after: options.afterLineComment,
before: options.beforeLineComment
});
}
},
"allowArrayEnd": {
"type": "boolean"
BlockComment: function(node) {
if (options.beforeBlockComment || options.afterBlockComment) {
checkForEmptyLine(node, {
after: options.afterBlockComment,
before: options.beforeBlockComment
});
}
}
},
"additionalProperties": false
};
}
];
};

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

@ -1,7 +1,6 @@
/**
* @fileoverview A rule to set the maximum depth block can be nested in a function.
* @author Ian Christian Myers
* @copyright 2013 Ian Christian Myers. All rights reserved.
*/
"use strict";
@ -10,131 +9,141 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "enforce a maximum depth that blocks can be nested",
category: "Stylistic Issues",
recommended: false
},
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
schema: [
{
oneOf: [
{
type: "integer",
minimum: 0
},
{
type: "object",
properties: {
maximum: {
type: "integer",
minimum: 0
},
max: {
type: "integer",
minimum: 0
}
},
additionalProperties: false
}
]
}
]
},
var functionStack = [],
option = context.options[0],
maxDepth = 4;
create: function(context) {
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;
}
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
functionStack.push(0);
}
var functionStack = [],
option = context.options[0],
maxDepth = 4;
/**
* When parsing is done then pop out the reference
* @returns {void}
* @private
*/
function endFunction() {
functionStack.pop();
}
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;
}
/**
* Save the block and Evaluate the node
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function pushBlock(node) {
var len = ++functionStack[functionStack.length - 1];
if (len > maxDepth) {
context.report(node, "Blocks are nested too deeply ({{depth}}).",
{ depth: len });
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
functionStack.push(0);
}
}
/**
* Pop the saved block
* @returns {void}
* @private
*/
function popBlock() {
functionStack[functionStack.length - 1]--;
}
/**
* When parsing is done then pop out the reference
* @returns {void}
* @private
*/
function endFunction() {
functionStack.pop();
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
/**
* Save the block and Evaluate the node
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function pushBlock(node) {
var len = ++functionStack[functionStack.length - 1];
if (len > maxDepth) {
context.report(node, "Blocks are nested too deeply ({{depth}}).",
{ depth: len });
}
}
return {
"Program": startFunction,
"FunctionDeclaration": startFunction,
"FunctionExpression": startFunction,
"ArrowFunctionExpression": startFunction,
/**
* Pop the saved block
* @returns {void}
* @private
*/
function popBlock() {
functionStack[functionStack.length - 1]--;
}
"IfStatement": function(node) {
if (node.parent.type !== "IfStatement") {
pushBlock(node);
}
},
"SwitchStatement": pushBlock,
"TryStatement": pushBlock,
"DoWhileStatement": pushBlock,
"WhileStatement": pushBlock,
"WithStatement": pushBlock,
"ForStatement": pushBlock,
"ForInStatement": pushBlock,
"ForOfStatement": pushBlock,
"IfStatement:exit": popBlock,
"SwitchStatement:exit": popBlock,
"TryStatement:exit": popBlock,
"DoWhileStatement:exit": popBlock,
"WhileStatement:exit": popBlock,
"WithStatement:exit": popBlock,
"ForStatement:exit": popBlock,
"ForInStatement:exit": popBlock,
"ForOfStatement:exit": popBlock,
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
"Program:exit": endFunction
};
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
};
return {
Program: startFunction,
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
module.exports.schema = [
{
"oneOf": [
{
"type": "integer",
"minimum": 0
IfStatement: function(node) {
if (node.parent.type !== "IfStatement") {
pushBlock(node);
}
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false
}
]
SwitchStatement: pushBlock,
TryStatement: pushBlock,
DoWhileStatement: pushBlock,
WhileStatement: pushBlock,
WithStatement: pushBlock,
ForStatement: pushBlock,
ForInStatement: pushBlock,
ForOfStatement: pushBlock,
"IfStatement:exit": popBlock,
"SwitchStatement:exit": popBlock,
"TryStatement:exit": popBlock,
"DoWhileStatement:exit": popBlock,
"WhileStatement:exit": popBlock,
"WithStatement:exit": popBlock,
"ForStatement:exit": popBlock,
"ForInStatement:exit": popBlock,
"ForOfStatement:exit": popBlock,
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
"Program:exit": endFunction
};
}
];
};

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

@ -1,245 +1,258 @@
/**
* @fileoverview Rule to check for max length on a line.
* @author Matt DuVall <http://www.mattduvall.com>
* @copyright 2013 Matt DuVall. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
// Constants
//------------------------------------------------------------------------------
module.exports = function(context) {
/*
* 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 = /[^:/?#]:\/\/[^?#]/;
/**
* Computes the length of a line that may contain tabs. The width of each
* tab will be the number of spaces to the next tab stop.
* @param {string} line The line.
* @param {int} tabWidth The width of each tab stop in spaces.
* @returns {int} The computed line length.
* @private
*/
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;
}
// 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];
}
var OPTIONS_SCHEMA = {
type: "object",
properties: {
code: {
type: "integer",
minimum: 0
},
comments: {
type: "integer",
minimum: 0
},
tabWidth: {
type: "integer",
minimum: 0
},
ignorePattern: {
type: "string"
},
ignoreComments: {
type: "boolean"
},
ignoreUrls: {
type: "boolean"
},
ignoreTrailingComments: {
type: "boolean"
}
},
additionalProperties: false
};
// …and tabWidth can be optionally specified directly as integers.
if (typeof context.options[1] === "number") {
options.tabWidth = context.options[1];
}
var OPTIONS_OR_INTEGER_SCHEMA = {
anyOf: [
OPTIONS_SCHEMA,
{
type: "integer",
minimum: 0
}
]
};
var maxLength = options.code || 80,
tabWidth = options.tabWidth || 4,
ignorePattern = options.ignorePattern || null,
ignoreComments = options.ignoreComments || false,
ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false,
ignoreUrls = options.ignoreUrls || false,
maxCommentLength = options.comments;
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
if (ignorePattern) {
ignorePattern = new RegExp(ignorePattern);
}
module.exports = {
meta: {
docs: {
description: "enforce a maximum line length",
category: "Stylistic Issues",
recommended: false
},
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Tells if a given comment is trailing: it starts on the current line and
* extends to or past the end of the current line.
* @param {string} line The source line we want to check for a trailing comment on
* @param {number} lineNumber The one-indexed line number for line
* @param {ASTNode} comment The comment to inspect
* @returns {boolean} If the comment is trailing on the given line
*/
function isTrailingComment(line, lineNumber, comment) {
return comment &&
(comment.loc.start.line === lineNumber && lineNumber <= comment.loc.end.line) &&
(comment.loc.end.line > lineNumber || comment.loc.end.column === line.length);
}
schema: [
OPTIONS_OR_INTEGER_SCHEMA,
OPTIONS_OR_INTEGER_SCHEMA,
OPTIONS_SCHEMA
]
},
/**
* Tells if a comment encompasses the entire line.
* @param {string} line The source line with a trailing comment
* @param {number} lineNumber The one-indexed line number this is on
* @param {ASTNode} comment The comment to remove
* @returns {boolean} If the comment covers the entire line
*/
function isFullLineComment(line, lineNumber, comment) {
var start = comment.loc.start,
end = comment.loc.end;
return comment &&
(start.line < lineNumber || (start.line === lineNumber && start.column === 0)) &&
(end.line > lineNumber || end.column === line.length);
}
create: function(context) {
/*
* 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 = /[^:/?#]:\/\/[^?#]/;
/**
* Computes the length of a line that may contain tabs. The width of each
* tab will be the number of spaces to the next tab stop.
* @param {string} line The line.
* @param {int} tabWidth The width of each tab stop in spaces.
* @returns {int} The computed line length.
* @private
*/
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;
}
/**
* Gets the line after the comment and any remaining trailing whitespace is
* stripped.
* @param {string} line The source line with a trailing comment
* @param {number} lineNumber The one-indexed line number this is on
* @param {ASTNode} comment The comment to remove
* @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+$/, "");
}
// 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) : {};
/**
* Check the program for max length
* @param {ASTNode} node Node to examine
* @returns {void}
* @private
*/
function checkProgramForMaxLength(node) {
// …but max code length…
if (typeof context.options[0] === "number") {
options.code = context.options[0];
}
// split (honors line-ending)
var lines = context.getSourceLines(),
// …and tabWidth can be optionally specified directly as integers.
if (typeof context.options[1] === "number") {
options.tabWidth = context.options[1];
}
// list of comments to ignore
comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? context.getAllComments() : [],
var maxLength = options.code || 80,
tabWidth = options.tabWidth || 4,
ignorePattern = options.ignorePattern || null,
ignoreComments = options.ignoreComments || false,
ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false,
ignoreUrls = options.ignoreUrls || false,
maxCommentLength = options.comments;
// we iterate over comments in parallel with the lines
commentsIndex = 0;
if (ignorePattern) {
ignorePattern = new RegExp(ignorePattern);
}
lines.forEach(function(line, i) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Tells if a given comment is trailing: it starts on the current line and
* extends to or past the end of the current line.
* @param {string} line The source line we want to check for a trailing comment on
* @param {number} lineNumber The one-indexed line number for line
* @param {ASTNode} comment The comment to inspect
* @returns {boolean} If the comment is trailing on the given line
*/
function isTrailingComment(line, lineNumber, comment) {
return comment &&
(comment.loc.start.line === lineNumber && lineNumber <= comment.loc.end.line) &&
(comment.loc.end.line > lineNumber || comment.loc.end.column === line.length);
}
// i is zero-indexed, line numbers are one-indexed
var lineNumber = i + 1;
/**
* Tells if a comment encompasses the entire line.
* @param {string} line The source line with a trailing comment
* @param {number} lineNumber The one-indexed line number this is on
* @param {ASTNode} comment The comment to remove
* @returns {boolean} If the comment covers the entire line
*/
function isFullLineComment(line, lineNumber, comment) {
var start = comment.loc.start,
end = comment.loc.end;
return comment &&
(start.line < lineNumber || (start.line === lineNumber && start.column === 0)) &&
(end.line > lineNumber || end.column === line.length);
}
/*
* if we're checking comment length; we need to know whether this
* line is a comment
*/
var lineIsComment = false;
/**
* Gets the line after the comment and any remaining trailing whitespace is
* stripped.
* @param {string} line The source line with a trailing comment
* @param {number} lineNumber The one-indexed line number this is on
* @param {ASTNode} comment The comment to remove
* @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+$/, "");
}
/*
* We can short-circuit the comment checks if we're already out of
* comments to check.
*/
if (commentsIndex < comments.length) {
/**
* Check the program for max length
* @param {ASTNode} node Node to examine
* @returns {void}
* @private
*/
function checkProgramForMaxLength(node) {
// split (honors line-ending)
var lines = context.getSourceLines(),
// list of comments to ignore
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
*/
var lineIsComment = false;
/*
* 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];
if (isFullLineComment(line, lineNumber, comment)) {
lineIsComment = true;
} else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) {
line = stripTrailingComment(line, lineNumber, comment);
}
}
if (ignorePattern && ignorePattern.test(line) ||
ignoreUrls && URL_REGEXP.test(line)) {
// iterate over comments until we find one past the current line
do {
var comment = comments[++commentsIndex];
} while (comment && comment.loc.start.line <= lineNumber);
// ignore this line
return;
}
// and step back by one
comment = comments[--commentsIndex];
var lineLength = computeLineLength(line, tabWidth);
if (isFullLineComment(line, lineNumber, comment)) {
lineIsComment = true;
} else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) {
line = stripTrailingComment(line, lineNumber, comment);
if (lineIsComment && ignoreComments) {
return;
}
}
if (ignorePattern && ignorePattern.test(line) ||
ignoreUrls && URL_REGEXP.test(line)) {
// ignore this line
return;
}
var lineLength = computeLineLength(line, tabWidth);
if (lineIsComment && ignoreComments) {
return;
}
if (lineIsComment && lineLength > maxCommentLength) {
context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum comment line length of " + maxCommentLength + ".");
} else if (lineLength > maxLength) {
context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum line length of " + maxLength + ".");
}
});
}
if (lineIsComment && lineLength > maxCommentLength) {
context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum comment line length of " + maxCommentLength + ".");
} else if (lineLength > maxLength) {
context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum line length of " + maxLength + ".");
}
});
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
"Program": checkProgramForMaxLength
};
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
};
return {
Program: checkProgramForMaxLength
};
var OPTIONS_SCHEMA = {
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": 0
},
"comments": {
"type": "integer",
"minimum": 0
},
"tabWidth": {
"type": "integer",
"minimum": 0
},
"ignorePattern": {
"type": "string"
},
"ignoreComments": {
"type": "boolean"
},
"ignoreUrls": {
"type": "boolean"
},
"ignoreTrailingComments": {
"type": "boolean"
}
},
"additionalProperties": false
};
var OPTIONS_OR_INTEGER_SCHEMA = {
"anyOf": [
OPTIONS_SCHEMA,
{
"type": "integer",
"minimum": 0
}
]
}
};
module.exports.schema = [
OPTIONS_OR_INTEGER_SCHEMA,
OPTIONS_OR_INTEGER_SCHEMA,
OPTIONS_SCHEMA
];

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to enforce a maximum number of nested callbacks.
* @author Ian Christian Myers
* @copyright 2013 Ian Christian Myers. All rights reserved.
*/
"use strict";
@ -10,94 +9,104 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "enforce a maximum depth that callbacks can be nested",
category: "Stylistic Issues",
recommended: false
},
//--------------------------------------------------------------------------
// Constants
//--------------------------------------------------------------------------
var option = context.options[0],
THRESHOLD = 10;
schema: [
{
oneOf: [
{
type: "integer",
minimum: 0
},
{
type: "object",
properties: {
maximum: {
type: "integer",
minimum: 0
},
max: {
type: "integer",
minimum: 0
}
},
additionalProperties: false
}
]
}
]
},
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;
}
create: function(context) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Constants
//--------------------------------------------------------------------------
var option = context.options[0],
THRESHOLD = 10;
var callbackStack = [];
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;
}
/**
* Checks a given function node for too many callbacks.
* @param {ASTNode} node The node to check.
* @returns {void}
* @private
*/
function checkFunction(node) {
var parent = node.parent;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
if (parent.type === "CallExpression") {
callbackStack.push(node);
}
var callbackStack = [];
if (callbackStack.length > THRESHOLD) {
var opts = {num: callbackStack.length, max: THRESHOLD};
/**
* Checks a given function node for too many callbacks.
* @param {ASTNode} node The node to check.
* @returns {void}
* @private
*/
function checkFunction(node) {
var parent = node.parent;
context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts);
}
}
if (parent.type === "CallExpression") {
callbackStack.push(node);
}
/**
* Pops the call stack.
* @returns {void}
* @private
*/
function popStack() {
callbackStack.pop();
}
if (callbackStack.length > THRESHOLD) {
var opts = {num: callbackStack.length, max: THRESHOLD};
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts);
}
}
return {
"ArrowFunctionExpression": checkFunction,
"ArrowFunctionExpression:exit": popStack,
/**
* Pops the call stack.
* @returns {void}
* @private
*/
function popStack() {
callbackStack.pop();
}
"FunctionExpression": checkFunction,
"FunctionExpression:exit": popStack
};
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
};
return {
ArrowFunctionExpression: checkFunction,
"ArrowFunctionExpression:exit": popStack,
FunctionExpression: checkFunction,
"FunctionExpression:exit": popStack
};
module.exports.schema = [
{
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false
}
]
}
];
};

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to flag when a function has too many parameters
* @author Ilya Volodin
* @copyright 2014 Nicholas C. Zakas. All rights reserved.
* @copyright 2013 Ilya Volodin. All rights reserved.
*/
"use strict";
@ -11,65 +9,75 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "enforce a maximum number of parameters in `function` definitions",
category: "Stylistic Issues",
recommended: false
},
var option = context.options[0],
numParams = 3;
schema: [
{
oneOf: [
{
type: "integer",
minimum: 0
},
{
type: "object",
properties: {
maximum: {
type: "integer",
minimum: 0
},
max: {
type: "integer",
minimum: 0
}
},
additionalProperties: false
}
]
}
]
},
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;
}
create: function(context) {
var option = context.options[0],
numParams = 3;
/**
* Checks a function to see if it has too many parameters.
* @param {ASTNode} node The node to check.
* @returns {void}
* @private
*/
function checkFunction(node) {
if (node.params.length > numParams) {
context.report(node, "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", {
count: node.params.length,
max: numParams
});
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;
}
}
return {
"FunctionDeclaration": checkFunction,
"ArrowFunctionExpression": checkFunction,
"FunctionExpression": checkFunction
};
/**
* Checks a function to see if it has too many parameters.
* @param {ASTNode} node The node to check.
* @returns {void}
* @private
*/
function checkFunction(node) {
if (node.params.length > numParams) {
context.report(node, "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", {
count: node.params.length,
max: numParams
});
}
}
};
return {
FunctionDeclaration: checkFunction,
ArrowFunctionExpression: checkFunction,
FunctionExpression: checkFunction
};
module.exports.schema = [
{
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false
}
]
}
];
};

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

@ -1,9 +1,6 @@
/**
* @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";
@ -11,96 +8,106 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "enforce a maximum number of statements allowed per line",
category: "Stylistic Issues",
recommended: false
},
var options = context.options[0] || {},
lastStatementLine = 0,
numberOfStatementsOnThisLine = 0,
maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
schema: [
{
type: "object",
properties: {
max: {
type: "integer"
}
},
additionalProperties: false
}
]
},
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
create: function(context) {
/**
* 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 });
}
var options = context.options[0] || {},
lastStatementLine = 0,
numberOfStatementsOnThisLine = 0,
maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
/**
* 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;
}
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
for (var i = 0, l = nodes.length; i < l; ++i) {
var currentStatement = nodes[i];
/**
* 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 });
}
if (currentStatement.loc.start.line === lastStatementLine) {
++numberOfStatementsOnThisLine;
} else {
numberOfStatementsOnThisLine = 1;
lastStatementLine = currentStatement.loc.end.line;
/**
* 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;
}
if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
report(currentStatement);
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);
}
/**
* Check each line in the body of a node
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkLinesInBody(node) {
enforceMaxStatementsPerLine(node.body);
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
/**
* 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);
}
return {
"Program": checkLinesInBody,
"BlockStatement": checkLinesInBody,
"SwitchCase": checkLinesInConsequent
};
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
};
return {
Program: checkLinesInBody,
BlockStatement: checkLinesInBody,
SwitchCase: checkLinesInConsequent
};
module.exports.schema = [
{
"type": "object",
"properties": {
"max": {
"type": "integer"
}
},
"additionalProperties": false
}
];
};

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

@ -1,7 +1,6 @@
/**
* @fileoverview A rule to set the maximum number of statements in a function.
* @author Ian Christian Myers
* @copyright 2013 Ian Christian Myers. All rights reserved.
*/
"use strict";
@ -10,141 +9,151 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "enforce a maximum number of statements allowed in `function` blocks",
category: "Stylistic Issues",
recommended: false
},
schema: [
{
oneOf: [
{
type: "integer",
minimum: 0
},
{
type: "object",
properties: {
maximum: {
type: "integer",
minimum: 0
},
max: {
type: "integer",
minimum: 0
}
},
additionalProperties: false
}
]
},
{
type: "object",
properties: {
ignoreTopLevelFunctions: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
create: function(context) {
var functionStack = [],
option = context.options[0],
maxStatements = 10,
ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false,
topLevelFunctions = [];
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
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;
}
var functionStack = [],
option = context.options[0],
maxStatements = 10,
ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false,
topLevelFunctions = [];
/**
* Reports a node if it has too many statements
* @param {ASTNode} node node to evaluate
* @param {int} count Number of statements in node
* @param {int} max Maximum number of statements allowed
* @returns {void}
* @private
*/
function reportIfTooManyStatements(node, count, max) {
if (count > max) {
context.report(
node,
"This function has too many statements ({{count}}). Maximum allowed is {{max}}.",
{ count: count, max: max });
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;
}
}
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
functionStack.push(0);
}
/**
* Evaluate the node at the end of function
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function endFunction(node) {
var count = functionStack.pop();
if (ignoreTopLevelFunctions && functionStack.length === 0) {
topLevelFunctions.push({ node: node, count: count});
} else {
reportIfTooManyStatements(node, count, maxStatements);
/**
* Reports a node if it has too many statements
* @param {ASTNode} node node to evaluate
* @param {int} count Number of statements in node
* @param {int} max Maximum number of statements allowed
* @returns {void}
* @private
*/
function reportIfTooManyStatements(node, count, max) {
if (count > max) {
context.report(
node,
"This function has too many statements ({{count}}). Maximum allowed is {{max}}.",
{ count: count, max: max });
}
}
}
/**
* Increment the count of the functions
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function countStatements(node) {
functionStack[functionStack.length - 1] += node.body.length;
}
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
functionStack.push(0);
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
/**
* Evaluate the node at the end of function
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function endFunction(node) {
var count = functionStack.pop();
if (ignoreTopLevelFunctions && functionStack.length === 0) {
topLevelFunctions.push({ node: node, count: count});
} else {
reportIfTooManyStatements(node, count, maxStatements);
}
}
return {
"FunctionDeclaration": startFunction,
"FunctionExpression": startFunction,
"ArrowFunctionExpression": startFunction,
/**
* Increment the count of the functions
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function countStatements(node) {
functionStack[functionStack.length - 1] += node.body.length;
}
"BlockStatement": countStatements,
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
return {
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
"Program:exit": function() {
if (topLevelFunctions.length === 1) {
return;
}
BlockStatement: countStatements,
topLevelFunctions.forEach(function(element) {
var count = element.count;
var node = element.node;
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
reportIfTooManyStatements(node, count, maxStatements);
});
}
};
"Program:exit": function() {
if (topLevelFunctions.length === 1) {
return;
}
};
topLevelFunctions.forEach(function(element) {
var count = element.count;
var node = element.node;
module.exports.schema = [
{
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"maximum": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false
reportIfTooManyStatements(node, count, maxStatements);
});
}
]
},
{
"type": "object",
"properties": {
"ignoreTopLevelFunctions": {
"type": "boolean"
}
},
"additionalProperties": false
};
}
];
};

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to flag use of constructors without capital letters
* @author Nicholas C. Zakas
* @copyright 2014 Jordan Harband. All rights reserved.
* @copyright 2013-2014 Nicholas C. Zakas. All rights reserved.
*/
"use strict";
@ -76,174 +74,184 @@ function calculateCapIsNewExceptions(config) {
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "require constructor `function` names to begin with a capital letter",
category: "Stylistic Issues",
recommended: false
},
schema: [
{
type: "object",
properties: {
newIsCap: {
type: "boolean"
},
capIsNew: {
type: "boolean"
},
newIsCapExceptions: {
type: "array",
items: {
type: "string"
}
},
capIsNewExceptions: {
type: "array",
items: {
type: "string"
}
},
properties: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
create: function(context) {
var config = context.options[0] ? lodash.assign({}, context.options[0]) : {};
var config = context.options[0] ? lodash.assign({}, context.options[0]) : {};
config.newIsCap = config.newIsCap !== false;
config.capIsNew = config.capIsNew !== false;
var skipProperties = config.properties === false;
config.newIsCap = config.newIsCap !== false;
config.capIsNew = config.capIsNew !== false;
var skipProperties = config.properties === false;
var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {});
var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {});
var capIsNewExceptions = calculateCapIsNewExceptions(config);
var capIsNewExceptions = calculateCapIsNewExceptions(config);
var listeners = {};
var listeners = {};
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Get exact callee name from expression
* @param {ASTNode} node CallExpression or NewExpression node
* @returns {string} name
*/
function extractNameFromExpression(node) {
/**
* Get exact callee name from expression
* @param {ASTNode} node CallExpression or NewExpression node
* @returns {string} name
*/
function extractNameFromExpression(node) {
var name = "",
property;
var name = "",
property;
if (node.callee.type === "MemberExpression") {
property = node.callee.property;
if (node.callee.type === "MemberExpression") {
property = node.callee.property;
if (property.type === "Literal" && (typeof property.value === "string")) {
name = property.value;
} else if (property.type === "Identifier" && !node.callee.computed) {
name = property.name;
if (property.type === "Literal" && (typeof property.value === "string")) {
name = property.value;
} else if (property.type === "Identifier" && !node.callee.computed) {
name = property.name;
}
} else {
name = node.callee.name;
}
} else {
name = node.callee.name;
return name;
}
return name;
}
/**
* Returns the capitalization state of the string -
* Whether the first character is uppercase, lowercase, or non-alphabetic
* @param {string} str String
* @returns {string} capitalization state: "non-alpha", "lower", or "upper"
*/
function getCap(str) {
var firstChar = str.charAt(0);
var firstCharLower = firstChar.toLowerCase();
var firstCharUpper = firstChar.toUpperCase();
if (firstCharLower === firstCharUpper) {
// char has no uppercase variant, so it's non-alphabetic
return "non-alpha";
} else if (firstChar === firstCharLower) {
return "lower";
} else {
return "upper";
/**
* Returns the capitalization state of the string -
* Whether the first character is uppercase, lowercase, or non-alphabetic
* @param {string} str String
* @returns {string} capitalization state: "non-alpha", "lower", or "upper"
*/
function getCap(str) {
var firstChar = str.charAt(0);
var firstCharLower = firstChar.toLowerCase();
var firstCharUpper = firstChar.toUpperCase();
if (firstCharLower === firstCharUpper) {
// char has no uppercase variant, so it's non-alphabetic
return "non-alpha";
} else if (firstChar === firstCharLower) {
return "lower";
} else {
return "upper";
}
}
}
/**
* Check if capitalization is allowed for a CallExpression
* @param {Object} allowedMap Object mapping calleeName to a Boolean
* @param {ASTNode} node CallExpression node
* @param {string} calleeName Capitalized callee name from a CallExpression
* @returns {Boolean} Returns true if the callee may be capitalized
*/
function isCapAllowed(allowedMap, node, calleeName) {
if (allowedMap[calleeName] || allowedMap[context.getSource(node.callee)]) {
return true;
}
/**
* Check if capitalization is allowed for a CallExpression
* @param {Object} allowedMap Object mapping calleeName to a Boolean
* @param {ASTNode} node CallExpression node
* @param {string} calleeName Capitalized callee name from a CallExpression
* @returns {Boolean} Returns true if the callee may be capitalized
*/
function isCapAllowed(allowedMap, node, calleeName) {
if (allowedMap[calleeName] || allowedMap[context.getSource(node.callee)]) {
return true;
}
if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
// allow if callee is Date.UTC
return node.callee.object.type === "Identifier" &&
node.callee.object.name === "Date";
}
// allow if callee is Date.UTC
return node.callee.object.type === "Identifier" &&
node.callee.object.name === "Date";
return skipProperties && node.callee.type === "MemberExpression";
}
return skipProperties && node.callee.type === "MemberExpression";
}
/**
* Reports the given message for the given node. The location will be the start of the property or the callee.
* @param {ASTNode} node CallExpression or NewExpression node.
* @param {string} message The message to report.
* @returns {void}
*/
function report(node, message) {
var callee = node.callee;
if (callee.type === "MemberExpression") {
callee = callee.property;
}
/**
* Reports the given message for the given node. The location will be the start of the property or the callee.
* @param {ASTNode} node CallExpression or NewExpression node.
* @param {string} message The message to report.
* @returns {void}
*/
function report(node, message) {
var callee = node.callee;
if (callee.type === "MemberExpression") {
callee = callee.property;
context.report(node, callee.loc.start, message);
}
context.report(node, callee.loc.start, message);
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
if (config.newIsCap) {
listeners.NewExpression = function(node) {
if (config.newIsCap) {
listeners.NewExpression = function(node) {
var constructorName = extractNameFromExpression(node);
var constructorName = extractNameFromExpression(node);
if (constructorName) {
var capitalization = getCap(constructorName);
var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName);
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.");
if (!isAllowed) {
report(node, "A constructor name should not start with a lowercase letter.");
}
}
}
};
}
};
}
if (config.capIsNew) {
listeners.CallExpression = function(node) {
if (config.capIsNew) {
listeners.CallExpression = function(node) {
var calleeName = extractNameFromExpression(node);
var calleeName = extractNameFromExpression(node);
if (calleeName) {
var capitalization = getCap(calleeName);
var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName);
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.");
if (!isAllowed) {
report(node, "A function with a name starting with an uppercase letter should only be used as a constructor.");
}
}
}
};
}
return listeners;
};
};
}
module.exports.schema = [
{
"type": "object",
"properties": {
"newIsCap": {
"type": "boolean"
},
"capIsNew": {
"type": "boolean"
},
"newIsCapExceptions": {
"type": "array",
"items": {
"type": "string"
}
},
"capIsNewExceptions": {
"type": "array",
"items": {
"type": "string"
}
},
"properties": {
"type": "boolean"
}
},
"additionalProperties": false
return listeners;
}
];
};

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

@ -9,22 +9,32 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
return {
"NewExpression": function(node) {
var tokens = context.getTokens(node);
var prenticesTokens = tokens.filter(function(token) {
return token.value === "(" || token.value === ")";
});
if (prenticesTokens.length < 2) {
context.report(node, "Missing '()' invoking a constructor");
module.exports = {
meta: {
docs: {
description: "require parentheses when invoking a constructor with no arguments",
category: "Stylistic Issues",
recommended: false
},
schema: []
},
create: function(context) {
return {
NewExpression: function(node) {
var tokens = context.getTokens(node);
var prenticesTokens = tokens.filter(function(token) {
return token.value === "(" || token.value === ")";
});
if (prenticesTokens.length < 2) {
context.report(node, "Missing '()' invoking a constructor");
}
}
}
};
};
}
};
module.exports.schema = [];

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

@ -1,9 +1,6 @@
/**
* @fileoverview Rule to check empty newline after "var" statement
* @author Gopal Venkatesan
* @copyright 2015 Gopal Venkatesan. All rights reserved.
* @copyright 2015 Casey Visco. All rights reserved.
* @copyright 2015 Ian VanSchooten. All rights reserved.
*/
"use strict";
@ -12,159 +9,169 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var ALWAYS_MESSAGE = "Expected blank line after variable declarations.",
NEVER_MESSAGE = "Unexpected blank line after variable declarations.";
var sourceCode = context.getSourceCode();
// Default `mode` to "always".
var mode = context.options[0] === "never" ? "never" : "always";
// Cache starting and ending line numbers of comments for faster lookup
var commentEndLine = context.getAllComments().reduce(function(result, token) {
result[token.loc.start.line] = token.loc.end.line;
return result;
}, {});
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determine if provided keyword is a variable declaration
* @private
* @param {string} keyword - keyword to test
* @returns {boolean} True if `keyword` is a type of var
*/
function isVar(keyword) {
return keyword === "var" || keyword === "let" || keyword === "const";
}
/**
* Determine if provided keyword is a variant of for specifiers
* @private
* @param {string} keyword - keyword to test
* @returns {boolean} True if `keyword` is a variant of for specifier
*/
function isForTypeSpecifier(keyword) {
return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement";
}
/**
* Determine if provided keyword is an export specifiers
* @private
* @param {string} nodeType - nodeType to test
* @returns {boolean} True if `nodeType` is an export specifier
*/
function isExportSpecifier(nodeType) {
return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" ||
nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration";
}
/**
* Determine if provided node is the last of their parent block.
* @private
* @param {ASTNode} node - node to test
* @returns {boolean} True if `node` is last of their parent block.
*/
function isLastNode(node) {
var token = sourceCode.getTokenAfter(node);
return !token || (token.type === "Punctuator" && token.value === "}");
}
/**
* Determine if a token starts more than one line after a comment ends
* @param {token} token The token being checked
* @param {integer} commentStartLine The line number on which the comment starts
* @returns {boolean} True if `token` does not start immediately after a comment
*/
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);
module.exports = {
meta: {
docs: {
description: "require or disallow an empty line after `var` declarations",
category: "Stylistic Issues",
recommended: false
},
schema: [
{
enum: ["never", "always"]
}
]
},
create: function(context) {
var ALWAYS_MESSAGE = "Expected blank line after variable declarations.",
NEVER_MESSAGE = "Unexpected blank line after variable declarations.";
var sourceCode = context.getSourceCode();
// Default `mode` to "always".
var mode = context.options[0] === "never" ? "never" : "always";
// Cache starting and ending line numbers of comments for faster lookup
var commentEndLine = context.getAllComments().reduce(function(result, token) {
result[token.loc.start.line] = token.loc.end.line;
return result;
}, {});
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determine if provided keyword is a variable declaration
* @private
* @param {string} keyword - keyword to test
* @returns {boolean} True if `keyword` is a type of var
*/
function isVar(keyword) {
return keyword === "var" || keyword === "let" || keyword === "const";
}
return (token.loc.start.line > commentEndLine[commentStartLine] + 1);
}
/**
* Checks that a blank line exists after a variable declaration when mode is
* set to "always", or checks that there is no blank line when mode is set
* to "never"
* @private
* @param {ASTNode} node - `VariableDeclaration` node to test
* @returns {void}
*/
function checkForBlankLine(node) {
var lastToken = sourceCode.getLastToken(node),
nextToken = sourceCode.getTokenAfter(node),
nextLineNum = lastToken.loc.end.line + 1,
noNextLineToken,
hasNextLineComment;
// Ignore if there is no following statement
if (!nextToken) {
return;
/**
* Determine if provided keyword is a variant of for specifiers
* @private
* @param {string} keyword - keyword to test
* @returns {boolean} True if `keyword` is a variant of for specifier
*/
function isForTypeSpecifier(keyword) {
return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement";
}
// Ignore if parent of node is a for variant
if (isForTypeSpecifier(node.parent.type)) {
return;
/**
* Determine if provided keyword is an export specifiers
* @private
* @param {string} nodeType - nodeType to test
* @returns {boolean} True if `nodeType` is an export specifier
*/
function isExportSpecifier(nodeType) {
return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" ||
nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration";
}
// Ignore if parent of node is an export specifier
if (isExportSpecifier(node.parent.type)) {
return;
}
/**
* Determine if provided node is the last of their parent block.
* @private
* @param {ASTNode} node - node to test
* @returns {boolean} True if `node` is last of their parent block.
*/
function isLastNode(node) {
var token = sourceCode.getTokenAfter(node);
// Some coding styles use multiple `var` statements, so do nothing if
// the next token is a `var` statement.
if (nextToken.type === "Keyword" && isVar(nextToken.value)) {
return;
return !token || (token.type === "Punctuator" && token.value === "}");
}
// Ignore if it is last statement in a block
if (isLastNode(node)) {
return;
/**
* Determine if a token starts more than one line after a comment ends
* @param {token} token The token being checked
* @param {integer} commentStartLine The line number on which the comment starts
* @returns {boolean} True if `token` does not start immediately after a comment
*/
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);
}
// Next statement is not a `var`...
noNextLineToken = nextToken.loc.start.line > nextLineNum;
hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined");
if (mode === "never" && noNextLineToken && !hasNextLineComment) {
context.report(node, NEVER_MESSAGE, { identifier: node.name });
/**
* Checks that a blank line exists after a variable declaration when mode is
* set to "always", or checks that there is no blank line when mode is set
* to "never"
* @private
* @param {ASTNode} node - `VariableDeclaration` node to test
* @returns {void}
*/
function checkForBlankLine(node) {
var lastToken = sourceCode.getLastToken(node),
nextToken = sourceCode.getTokenAfter(node),
nextLineNum = lastToken.loc.end.line + 1,
noNextLineToken,
hasNextLineComment;
// Ignore if there is no following statement
if (!nextToken) {
return;
}
// Ignore if parent of node is a for variant
if (isForTypeSpecifier(node.parent.type)) {
return;
}
// Ignore if parent of node is an export specifier
if (isExportSpecifier(node.parent.type)) {
return;
}
// Some coding styles use multiple `var` statements, so do nothing if
// the next token is a `var` statement.
if (nextToken.type === "Keyword" && isVar(nextToken.value)) {
return;
}
// Ignore if it is last statement in a block
if (isLastNode(node)) {
return;
}
// Next statement is not a `var`...
noNextLineToken = nextToken.loc.start.line > nextLineNum;
hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined");
if (mode === "never" && noNextLineToken && !hasNextLineComment) {
context.report(node, NEVER_MESSAGE, { identifier: node.name });
}
// Token on the next line, or comment without blank line
if (
mode === "always" && (
!noNextLineToken ||
hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum)
)
) {
context.report(node, ALWAYS_MESSAGE, { identifier: node.name });
}
}
// Token on the next line, or comment without blank line
if (
mode === "always" && (
!noNextLineToken ||
hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum)
)
) {
context.report(node, ALWAYS_MESSAGE, { identifier: node.name });
}
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
"VariableDeclaration": checkForBlankLine
};
return {
VariableDeclaration: checkForBlankLine
};
};
module.exports.schema = [
{
"enum": ["never", "always"]
}
];
};

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

@ -1,9 +1,6 @@
/**
* @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";
@ -11,133 +8,159 @@
// 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;
module.exports = {
meta: {
docs: {
description: "require an empty line before `return` statements",
category: "Stylistic Issues",
recommended: false
},
schema: []
},
create: 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;
});
}
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;
/**
* 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;
}
// avoid counting lines with inline comments twice
if (comment.loc.start.line === tokenBefore.loc.end.line) {
numLinesComments--;
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, [")"]);
}
}
if (comment.loc.end.line === node.loc.start.line) {
numLinesComments--;
/**
* Returns the number of lines of comments that precede the node
* @param {ASTNode} node - node to check for overlapping comments
* @param {number} lineNumTokenBefore - line number of previous token, to check for overlapping comments
* @returns {number} Number of lines of comments that precede the node
* @private
*/
function calcCommentLines(node, lineNumTokenBefore) {
var comments = sourceCode.getComments(node).leading,
numLinesComments = 0;
if (!comments.length) {
return numLinesComments;
}
});
return numLinesComments;
}
comments.forEach(function(comment) {
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;
}
if (comment.type === "Block") {
numLinesComments += comment.loc.end.line - comment.loc.start.line;
}
/**
* 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."
});
}
// avoid counting lines with inline comments twice
if (comment.loc.start.line === lineNumTokenBefore) {
numLinesComments--;
}
if (comment.loc.end.line === node.loc.start.line) {
numLinesComments--;
}
});
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return numLinesComments;
}
return {
ReturnStatement: function(node) {
if (isFirstNode(node) && hasNewlineBefore(node)) {
reportError(node, false);
} else if (!isFirstNode(node) && !hasNewlineBefore(node)) {
reportError(node, true);
/**
* 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),
lineNumNode = node.loc.start.line,
lineNumTokenBefore,
commentLines;
/**
* Global return (at the beginning of a script) is a special case.
* If there is no token before `return`, then we expect no line
* break before the return. Comments are allowed to occupy lines
* before the global return, just no blank lines.
* Setting lineNumTokenBefore to zero in that case results in the
* desired behavior.
*/
if (tokenBefore) {
lineNumTokenBefore = tokenBefore.loc.end.line;
} else {
lineNumTokenBefore = 0; // global return at beginning of script
}
commentLines = calcCommentLines(node, lineNumTokenBefore);
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."
});
}
};
};
module.exports.schema = [];
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
ReturnStatement: function(node) {
if (isFirstNode(node) && hasNewlineBefore(node)) {
reportError(node, false);
} else if (!isFirstNode(node) && !hasNewlineBefore(node)) {
reportError(node, true);
}
}
};
}
};

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

@ -2,8 +2,6 @@
* @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";
@ -12,45 +10,55 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "require a newline after each call in a method chain",
category: "Stylistic Issues",
recommended: false
},
var options = context.options[0] || {},
ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
schema: [{
type: "object",
properties: {
ignoreChainWithDepth: {
type: "integer",
minimum: 1,
maximum: 10
}
},
additionalProperties: false
}]
},
return {
"CallExpression:exit": function(node) {
if (!node.callee || node.callee.type !== "MemberExpression") {
return;
}
create: function(context) {
var callee = node.callee;
var parent = callee.object;
var depth = 1;
var options = context.options[0] || {},
ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
while (parent && parent.callee) {
depth += 1;
parent = parent.callee.object;
}
return {
"CallExpression:exit": function(node) {
if (!node.callee || node.callee.type !== "MemberExpression") {
return;
}
var callee = node.callee;
var parent = callee.object;
var depth = 1;
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") + "`."
);
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") + "`."
);
}
}
}
};
};
}
};
module.exports.schema = [{
"type": "object",
"properties": {
"ignoreChainWithDepth": {
"type": "integer",
"minimum": 1,
"maximum": 10
}
},
"additionalProperties": false
}];

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

@ -1,8 +1,6 @@
/**
* @fileoverview Rule to flag use of alert, confirm, prompt
* @author Nicholas C. Zakas
* @copyright 2015 Mathias Schreck
* @copyright 2013 Nicholas C. Zakas
*/
"use strict";
@ -98,39 +96,49 @@ function isGlobalThisReferenceOrGlobalWindow(scope, globalScope, node) {
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var globalScope;
module.exports = {
meta: {
docs: {
description: "disallow the use of `alert`, `confirm`, and `prompt`",
category: "Best Practices",
recommended: false
},
return {
schema: []
},
"Program": function() {
globalScope = context.getScope();
},
create: function(context) {
var globalScope;
"CallExpression": function(node) {
var callee = node.callee,
identifierName,
currentScope = context.getScope();
return {
// without window.
if (callee.type === "Identifier") {
identifierName = callee.name;
Program: function() {
globalScope = context.getScope();
},
if (!isShadowed(currentScope, globalScope, callee) && isProhibitedIdentifier(callee.name)) {
report(context, node, identifierName);
}
CallExpression: function(node) {
var callee = node.callee,
identifierName,
currentScope = context.getScope();
// without window.
if (callee.type === "Identifier") {
identifierName = callee.name;
if (!isShadowed(currentScope, globalScope, callee) && isProhibitedIdentifier(callee.name)) {
report(context, node, identifierName);
}
} else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, globalScope, callee.object)) {
identifierName = getPropertyName(callee);
} else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, globalScope, callee.object)) {
identifierName = getPropertyName(callee);
if (isProhibitedIdentifier(identifierName)) {
report(context, node, identifierName);
if (isProhibitedIdentifier(identifierName)) {
report(context, node, identifierName);
}
}
}
}
};
}
};
}
};
module.exports.schema = [];

54
tools/eslint/lib/rules/no-array-constructor.js

@ -9,29 +9,39 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Disallow construction of dense arrays using the Array constructor
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function check(node) {
if (
node.arguments.length !== 1 &&
node.callee.type === "Identifier" &&
node.callee.name === "Array"
) {
context.report(node, "The array literal notation [] is preferrable.");
module.exports = {
meta: {
docs: {
description: "disallow `Array` constructors",
category: "Stylistic Issues",
recommended: false
},
schema: []
},
create: function(context) {
/**
* Disallow construction of dense arrays using the Array constructor
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function check(node) {
if (
node.arguments.length !== 1 &&
node.callee.type === "Identifier" &&
node.callee.name === "Array"
) {
context.report(node, "The array literal notation [] is preferrable.");
}
}
}
return {
"CallExpression": check,
"NewExpression": check
};
return {
CallExpression: check,
NewExpression: check
};
}
};
module.exports.schema = [];

148
tools/eslint/lib/rules/no-bitwise.js

@ -18,82 +18,92 @@ var BITWISE_OPERATORS = [
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var options = context.options[0] || {};
var allowed = options.allow || [];
var int32Hint = options.int32Hint === true;
/**
* Reports an unexpected use of a bitwise operator.
* @param {ASTNode} node Node which contains the bitwise operator.
* @returns {void}
*/
function report(node) {
context.report(node, "Unexpected use of '{{operator}}'.", { operator: node.operator });
}
module.exports = {
meta: {
docs: {
description: "disallow bitwise operators",
category: "Stylistic Issues",
recommended: false
},
/**
* Checks if the given node has a bitwise operator.
* @param {ASTNode} node The node to check.
* @returns {boolean} Whether or not the node has a bitwise operator.
*/
function hasBitwiseOperator(node) {
return BITWISE_OPERATORS.indexOf(node.operator) !== -1;
}
schema: [
{
type: "object",
properties: {
allow: {
type: "array",
items: {
enum: BITWISE_OPERATORS
},
uniqueItems: true
},
int32Hint: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
/**
* Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`.
* @param {ASTNode} node The node to check.
* @returns {boolean} Whether or not the node has a bitwise operator.
*/
function allowedOperator(node) {
return allowed.indexOf(node.operator) !== -1;
}
create: function(context) {
var options = context.options[0] || {};
var allowed = options.allow || [];
var int32Hint = options.int32Hint === true;
/**
* Checks if the given bitwise operator is used for integer typecasting, i.e. "|0"
* @param {ASTNode} node The node to check.
* @returns {boolean} whether the node is used in integer typecasting.
*/
function isInt32Hint(node) {
return int32Hint && node.operator === "|" && node.right &&
node.right.type === "Literal" && node.right.value === 0;
}
/**
* Reports an unexpected use of a bitwise operator.
* @param {ASTNode} node Node which contains the bitwise operator.
* @returns {void}
*/
function report(node) {
context.report(node, "Unexpected use of '{{operator}}'.", { operator: node.operator });
}
/**
* Report if the given node contains a bitwise operator.
* @param {ASTNode} node The node to check.
* @returns {void}
*/
function checkNodeForBitwiseOperator(node) {
if (hasBitwiseOperator(node) && !allowedOperator(node) && !isInt32Hint(node)) {
report(node);
/**
* Checks if the given node has a bitwise operator.
* @param {ASTNode} node The node to check.
* @returns {boolean} Whether or not the node has a bitwise operator.
*/
function hasBitwiseOperator(node) {
return BITWISE_OPERATORS.indexOf(node.operator) !== -1;
}
}
return {
"AssignmentExpression": checkNodeForBitwiseOperator,
"BinaryExpression": checkNodeForBitwiseOperator,
"UnaryExpression": checkNodeForBitwiseOperator
};
/**
* Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`.
* @param {ASTNode} node The node to check.
* @returns {boolean} Whether or not the node has a bitwise operator.
*/
function allowedOperator(node) {
return allowed.indexOf(node.operator) !== -1;
}
};
/**
* Checks if the given bitwise operator is used for integer typecasting, i.e. "|0"
* @param {ASTNode} node The node to check.
* @returns {boolean} whether the node is used in integer typecasting.
*/
function isInt32Hint(node) {
return int32Hint && node.operator === "|" && node.right &&
node.right.type === "Literal" && node.right.value === 0;
}
module.exports.schema = [
{
"type": "object",
"properties": {
"allow": {
"type": "array",
"items": {
"enum": BITWISE_OPERATORS
},
"uniqueItems": true
},
"int32Hint": {
"type": "boolean"
/**
* Report if the given node contains a bitwise operator.
* @param {ASTNode} node The node to check.
* @returns {void}
*/
function checkNodeForBitwiseOperator(node) {
if (hasBitwiseOperator(node) && !allowedOperator(node) && !isInt32Hint(node)) {
report(node);
}
},
"additionalProperties": false
}
return {
AssignmentExpression: checkNodeForBitwiseOperator,
BinaryExpression: checkNodeForBitwiseOperator,
UnaryExpression: checkNodeForBitwiseOperator
};
}
];
};

34
tools/eslint/lib/rules/no-caller.js

@ -9,21 +9,31 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "disallow the use of `arguments.caller` or `arguments.callee`",
category: "Best Practices",
recommended: false
},
return {
schema: []
},
"MemberExpression": function(node) {
var objectName = node.object.name,
propertyName = node.property.name;
create: function(context) {
if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) {
context.report(node, "Avoid arguments.{{property}}.", { property: propertyName });
}
return {
}
};
MemberExpression: function(node) {
var objectName = node.object.name,
propertyName = node.property.name;
};
if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) {
context.report(node, "Avoid arguments.{{property}}.", { property: propertyName });
}
module.exports.schema = [];
}
};
}
};

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to flag use of an lexical declarations inside a case clause
* @author Erik Arvidsson
* @copyright 2015 Erik Arvidsson. All rights reserved.
*/
"use strict";
@ -9,40 +8,50 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Checks whether or not a node is a lexical declaration.
* @param {ASTNode} node A direct child statement of a switch case.
* @returns {boolean} Whether or not the node is a lexical declaration.
*/
function isLexicalDeclaration(node) {
switch (node.type) {
case "FunctionDeclaration":
case "ClassDeclaration":
return true;
case "VariableDeclaration":
return node.kind !== "var";
default:
return false;
module.exports = {
meta: {
docs: {
description: "disallow lexical declarations in case clauses",
category: "Best Practices",
recommended: true
},
schema: []
},
create: function(context) {
/**
* Checks whether or not a node is a lexical declaration.
* @param {ASTNode} node A direct child statement of a switch case.
* @returns {boolean} Whether or not the node is a lexical declaration.
*/
function isLexicalDeclaration(node) {
switch (node.type) {
case "FunctionDeclaration":
case "ClassDeclaration":
return true;
case "VariableDeclaration":
return node.kind !== "var";
default:
return false;
}
}
}
return {
"SwitchCase": function(node) {
for (var i = 0; i < node.consequent.length; i++) {
var statement = node.consequent[i];
return {
SwitchCase: function(node) {
for (var i = 0; i < node.consequent.length; i++) {
var statement = node.consequent[i];
if (isLexicalDeclaration(statement)) {
context.report({
node: node,
message: "Unexpected lexical declaration in case block."
});
if (isLexicalDeclaration(statement)) {
context.report({
node: node,
message: "Unexpected lexical declaration in case block."
});
}
}
}
}
};
};
}
};
module.exports.schema = [];

76
tools/eslint/lib/rules/no-catch-shadow.js

@ -15,44 +15,54 @@ var astUtils = require("../ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Check if the parameters are been shadowed
* @param {object} scope current scope
* @param {string} name parameter name
* @returns {boolean} True is its been shadowed
*/
function paramIsShadowing(scope, name) {
return astUtils.getVariableByName(scope, name) !== null;
}
module.exports = {
meta: {
docs: {
description: "disallow `catch` clause parameters from shadowing variables in the outer scope",
category: "Variables",
recommended: false
},
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
schema: []
},
return {
create: function(context) {
"CatchClause": function(node) {
var scope = context.getScope();
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// When blockBindings is enabled, CatchClause creates its own scope
// so start from one upper scope to exclude the current node
if (scope.block === node) {
scope = scope.upper;
}
/**
* Check if the parameters are been shadowed
* @param {object} scope current scope
* @param {string} name parameter name
* @returns {boolean} True is its been shadowed
*/
function paramIsShadowing(scope, name) {
return astUtils.getVariableByName(scope, name) !== null;
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
if (paramIsShadowing(scope, node.param.name)) {
context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.",
{ name: node.param.name });
CatchClause: function(node) {
var scope = context.getScope();
// When blockBindings is enabled, CatchClause creates its own scope
// so start from one upper scope to exclude the current node
if (scope.block === node) {
scope = scope.upper;
}
if (paramIsShadowing(scope, node.param.name)) {
context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.",
{ name: node.param.name });
}
}
}
};
};
}
};
module.exports.schema = [];

73
tools/eslint/lib/rules/no-class-assign.js

@ -1,7 +1,6 @@
/**
* @fileoverview A rule to disallow modifying variables of class declarations
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/
"use strict";
@ -12,37 +11,47 @@ var astUtils = require("../ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Finds and reports references that are non initializer and writable.
* @param {Variable} variable - A variable to check.
* @returns {void}
*/
function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
context.report(
reference.identifier,
"'{{name}}' is a class.",
{name: reference.identifier.name});
});
}
module.exports = {
meta: {
docs: {
description: "disallow reassigning class members",
category: "ECMAScript 6",
recommended: true
},
schema: []
},
create: function(context) {
/**
* Finds and reports references that are non initializer and writable.
* @param {Variable} variable - A variable to check.
* @returns {void}
*/
function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
context.report(
reference.identifier,
"'{{name}}' is a class.",
{name: reference.identifier.name});
});
}
/**
* Finds and reports references that are non initializer and writable.
* @param {ASTNode} node - A ClassDeclaration/ClassExpression node to check.
* @returns {void}
*/
function checkForClass(node) {
context.getDeclaredVariables(node).forEach(checkVariable);
}
return {
ClassDeclaration: checkForClass,
ClassExpression: checkForClass
};
/**
* Finds and reports references that are non initializer and writable.
* @param {ASTNode} node - A ClassDeclaration/ClassExpression node to check.
* @returns {void}
*/
function checkForClass(node) {
context.getDeclaredVariables(node).forEach(checkVariable);
}
return {
"ClassDeclaration": checkForClass,
"ClassExpression": checkForClass
};
};
module.exports.schema = [];

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

@ -5,130 +5,140 @@
"use strict";
var NODE_DESCRIPTIONS = {
"DoWhileStatement": "a 'do...while' statement",
"ForStatement": "a 'for' statement",
"IfStatement": "an 'if' statement",
"WhileStatement": "a 'while' statement"
DoWhileStatement: "a 'do...while' statement",
ForStatement: "a 'for' statement",
IfStatement: "an 'if' statement",
WhileStatement: "a 'while' statement"
};
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var prohibitAssign = (context.options[0] || "except-parens");
/**
* Check whether an AST node is the test expression for a conditional statement.
* @param {!Object} node The node to test.
* @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`.
*/
function isConditionalTestExpression(node) {
return node.parent &&
node.parent.test &&
node === node.parent.test;
}
/**
* Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement.
* @param {!Object} node The node to use at the start of the search.
* @returns {?Object} The closest ancestor node that represents a conditional statement.
*/
function findConditionalAncestor(node) {
var currentAncestor = node;
do {
if (isConditionalTestExpression(currentAncestor)) {
return currentAncestor.parent;
module.exports = {
meta: {
docs: {
description: "disallow assignment operators in conditional expressions",
category: "Possible Errors",
recommended: true
},
schema: [
{
enum: ["except-parens", "always"]
}
} while ((currentAncestor = currentAncestor.parent));
]
},
create: function(context) {
var prohibitAssign = (context.options[0] || "except-parens");
/**
* Check whether an AST node is the test expression for a conditional statement.
* @param {!Object} node The node to test.
* @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`.
*/
function isConditionalTestExpression(node) {
return node.parent &&
node.parent.test &&
node === node.parent.test;
}
return null;
}
/**
* Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement.
* @param {!Object} node The node to use at the start of the search.
* @returns {?Object} The closest ancestor node that represents a conditional statement.
*/
function findConditionalAncestor(node) {
var currentAncestor = node;
do {
if (isConditionalTestExpression(currentAncestor)) {
return currentAncestor.parent;
}
} while ((currentAncestor = currentAncestor.parent));
return null;
}
/**
* Check whether the code represented by an AST node is enclosed in parentheses.
* @param {!Object} node The node to test.
* @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`.
*/
function isParenthesised(node) {
var previousToken = context.getTokenBefore(node),
nextToken = context.getTokenAfter(node);
return previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
}
/**
* Check whether the code represented by an AST node is enclosed in parentheses.
* @param {!Object} node The node to test.
* @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`.
*/
function isParenthesised(node) {
var previousToken = context.getTokenBefore(node),
nextToken = context.getTokenAfter(node);
return previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
}
/**
* Check whether the code represented by an AST node is enclosed in two sets of parentheses.
* @param {!Object} node The node to test.
* @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
*/
function isParenthesisedTwice(node) {
var previousToken = context.getTokenBefore(node, 1),
nextToken = context.getTokenAfter(node, 1);
return isParenthesised(node) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
}
/**
* Check whether the code represented by an AST node is enclosed in two sets of parentheses.
* @param {!Object} node The node to test.
* @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
*/
function isParenthesisedTwice(node) {
var previousToken = context.getTokenBefore(node, 1),
nextToken = context.getTokenAfter(node, 1);
return isParenthesised(node) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
}
/**
* Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses.
* @param {!Object} node The node for the conditional statement.
* @returns {void}
*/
function testForAssign(node) {
if (node.test &&
(node.test.type === "AssignmentExpression") &&
(node.type === "ForStatement" ?
!isParenthesised(node.test) :
!isParenthesisedTwice(node.test)
)
) {
// must match JSHint's error message
context.report({
node: node,
loc: node.test.loc.start,
message: "Expected a conditional expression and instead saw an assignment."
});
/**
* Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses.
* @param {!Object} node The node for the conditional statement.
* @returns {void}
*/
function testForAssign(node) {
if (node.test &&
(node.test.type === "AssignmentExpression") &&
(node.type === "ForStatement" ?
!isParenthesised(node.test) :
!isParenthesisedTwice(node.test)
)
) {
// must match JSHint's error message
context.report({
node: node,
loc: node.test.loc.start,
message: "Expected a conditional expression and instead saw an assignment."
});
}
}
/**
* Check whether an assignment expression is descended from a conditional statement's test expression.
* @param {!Object} node The node for the assignment expression.
* @returns {void}
*/
function testForConditionalAncestor(node) {
var ancestor = findConditionalAncestor(node);
if (ancestor) {
context.report(ancestor, "Unexpected assignment within {{type}}.", {
type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
});
}
}
}
/**
* Check whether an assignment expression is descended from a conditional statement's test expression.
* @param {!Object} node The node for the assignment expression.
* @returns {void}
*/
function testForConditionalAncestor(node) {
var ancestor = findConditionalAncestor(node);
if (ancestor) {
context.report(ancestor, "Unexpected assignment within {{type}}.", {
type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
});
if (prohibitAssign === "always") {
return {
AssignmentExpression: testForConditionalAncestor
};
}
}
if (prohibitAssign === "always") {
return {
"AssignmentExpression": testForConditionalAncestor
DoWhileStatement: testForAssign,
ForStatement: testForAssign,
IfStatement: testForAssign,
WhileStatement: testForAssign
};
}
return {
"DoWhileStatement": testForAssign,
"ForStatement": testForAssign,
"IfStatement": testForAssign,
"WhileStatement": testForAssign
};
};
module.exports.schema = [
{
"enum": ["except-parens", "always"]
}
];
};

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

@ -2,28 +2,6 @@
* @fileoverview A rule to warn against using arrow functions when they could be
* confused with comparisions
* @author Jxck <https://github.com/Jxck>
* @copyright 2015 Luke Karrys. All rights reserved.
* The MIT License (MIT)
* Copyright (c) 2015 Jxck
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
"use strict";
@ -47,31 +25,41 @@ function isConditional(node) {
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var config = context.options[0] || {};
module.exports = {
meta: {
docs: {
description: "disallow arrow functions where they could be confused with comparisons",
category: "ECMAScript 6",
recommended: false
},
schema: [{
type: "object",
properties: {
allowParens: {type: "boolean"}
},
additionalProperties: false
}]
},
/**
* Reports if an arrow function contains an ambiguous conditional.
* @param {ASTNode} node - A node to check and report.
* @returns {void}
*/
function checkArrowFunc(node) {
var body = node.body;
create: function(context) {
var config = context.options[0] || {};
if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(context, body))) {
context.report(node, "Arrow function used ambiguously with a conditional expression.");
/**
* Reports if an arrow function contains an ambiguous conditional.
* @param {ASTNode} node - A node to check and report.
* @returns {void}
*/
function checkArrowFunc(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.");
}
}
}
return {
"ArrowFunctionExpression": checkArrowFunc
};
return {
ArrowFunctionExpression: checkArrowFunc
};
}
};
module.exports.schema = [{
type: "object",
properties: {
allowParens: {type: "boolean"}
},
additionalProperties: false
}];

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to flag use of console object
* @author Nicholas C. Zakas
* @copyright 2016 Eric Correia. All rights reserved.
*/
"use strict";
@ -10,47 +9,57 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "disallow the use of `console`",
category: "Possible Errors",
recommended: true
},
return {
schema: [
{
type: "object",
properties: {
allow: {
type: "array",
items: {
type: "string"
},
minItems: 1,
uniqueItems: true
}
},
additionalProperties: false
}
]
},
"MemberExpression": function(node) {
create: function(context) {
if (node.object.name === "console") {
var blockConsole = true;
return {
if (context.options.length > 0) {
var allowedProperties = context.options[0].allow;
var passedProperty = node.property.name;
var propertyIsAllowed = (allowedProperties.indexOf(passedProperty) > -1);
MemberExpression: function(node) {
if (propertyIsAllowed) {
blockConsole = false;
if (node.object.name === "console") {
var blockConsole = true;
if (context.options.length > 0) {
var allowedProperties = context.options[0].allow;
var passedProperty = node.property.name;
var propertyIsAllowed = (allowedProperties.indexOf(passedProperty) > -1);
if (propertyIsAllowed) {
blockConsole = false;
}
}
}
if (blockConsole) {
context.report(node, "Unexpected console statement.");
if (blockConsole) {
context.report(node, "Unexpected console statement.");
}
}
}
}
};
};
};
module.exports.schema = [
{
"type": "object",
"properties": {
"allow": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"additionalProperties": false
}
];
};

57
tools/eslint/lib/rules/no-const-assign.js

@ -1,7 +1,6 @@
/**
* @fileoverview A rule to disallow modifying variables that are declared using `const`
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/
"use strict";
@ -12,30 +11,40 @@ var astUtils = require("../ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Finds and reports references that are non initializer and writable.
* @param {Variable} variable - A variable to check.
* @returns {void}
*/
function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
context.report(
reference.identifier,
"'{{name}}' is constant.",
{name: reference.identifier.name});
});
}
module.exports = {
meta: {
docs: {
description: "disallow reassigning `const` variables",
category: "ECMAScript 6",
recommended: true
},
schema: []
},
create: function(context) {
/**
* Finds and reports references that are non initializer and writable.
* @param {Variable} variable - A variable to check.
* @returns {void}
*/
function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
context.report(
reference.identifier,
"'{{name}}' is constant.",
{name: reference.identifier.name});
});
}
return {
"VariableDeclaration": function(node) {
if (node.kind === "const") {
context.getDeclaredVariables(node).forEach(checkVariable);
return {
VariableDeclaration: function(node) {
if (node.kind === "const") {
context.getDeclaredVariables(node).forEach(checkVariable);
}
}
}
};
};
}
};
module.exports.schema = [];

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

@ -1,7 +1,6 @@
/**
* @fileoverview Rule to flag use constant conditions
* @author Christian Schulz <http://rndm.de>
* @copyright 2014 Christian Schulz. All rights reserved.
*/
"use strict";
@ -10,69 +9,110 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Checks if a node has a constant truthiness value.
* @param {ASTNode} node The AST node to check.
* @returns {Bool} true when node's truthiness is constant
* @private
*/
function isConstant(node) {
switch (node.type) {
case "Literal":
case "ArrowFunctionExpression":
case "FunctionExpression":
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
module.exports = {
meta: {
docs: {
description: "disallow constant expressions in conditions",
category: "Possible Errors",
recommended: true
},
schema: []
},
create: function(context) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Checks if a branch node of LogicalExpression short circuits the whole condition
* @param {ASTNode} node The branch of main condition which needs to be checked
* @param {string} operator The operator of the main LogicalExpression.
* @returns {boolean} true when condition short circuits whole condition
*/
function isLogicalIdentity(node, operator) {
switch (node.type) {
case "Literal":
return (operator === "||" && node.value === true) ||
(operator === "&&" && node.value === false);
case "LogicalExpression":
return isLogicalIdentity(node.left, node.operator) ||
isLogicalIdentity(node.right, node.operator);
// no default
}
return false;
}
return false;
}
/**
* Reports when the given node contains a constant condition.
* @param {ASTNode} node The AST node to check.
* @returns {void}
* @private
*/
function checkConstantCondition(node) {
if (node.test && isConstant(node.test)) {
context.report(node, "Unexpected constant condition.");
/**
* Checks if a node has a constant truthiness value.
* @param {ASTNode} node The AST node to check.
* @param {boolean} inBooleanPosition `false` if checking branch of a condition.
* `true` in all other cases
* @returns {Bool} true when node's truthiness is constant
* @private
*/
function isConstant(node, inBooleanPosition) {
switch (node.type) {
case "Literal":
case "ArrowFunctionExpression":
case "FunctionExpression":
case "ObjectExpression":
case "ArrayExpression":
return true;
case "UnaryExpression":
return (node.operator === "typeof" && inBooleanPosition) ||
isConstant(node.argument, true);
case "BinaryExpression":
return isConstant(node.left, false) &&
isConstant(node.right, false) &&
node.operator !== "in";
case "LogicalExpression":
var isLeftConstant = isConstant(node.left, inBooleanPosition);
var isRightConstant = isConstant(node.right, inBooleanPosition);
var isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
var isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator));
return (isLeftConstant && isRightConstant) || isLeftShortCircuit || isRightShortCircuit;
case "AssignmentExpression":
return (node.operator === "=") && isConstant(node.right, inBooleanPosition);
case "SequenceExpression":
return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
// no default
}
return false;
}
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Reports when the given node contains a constant condition.
* @param {ASTNode} node The AST node to check.
* @returns {void}
* @private
*/
function checkConstantCondition(node) {
if (node.test && isConstant(node.test, true)) {
context.report(node, "Unexpected constant condition.");
}
}
return {
"ConditionalExpression": checkConstantCondition,
"IfStatement": checkConstantCondition,
"WhileStatement": checkConstantCondition,
"DoWhileStatement": checkConstantCondition,
"ForStatement": checkConstantCondition
};
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
};
return {
ConditionalExpression: checkConstantCondition,
IfStatement: checkConstantCondition,
WhileStatement: checkConstantCondition,
DoWhileStatement: checkConstantCondition,
ForStatement: checkConstantCondition
};
module.exports.schema = [];
}
};

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

Loading…
Cancel
Save