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 v2.7.0 - April 4, 2016
* 134cb1f Revert "Update: adds nestedBinaryExpressions for no-extra-parens rule (fixes #3065)" (Ilya Volodin) * 134cb1f Revert "Update: adds nestedBinaryExpressions for no-extra-parens rule (fixes #3065)" (Ilya Volodin)

2
tools/eslint/LICENSE

@ -1,5 +1,5 @@
ESLint 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

30
tools/eslint/README.md

@ -8,7 +8,15 @@
# ESLint # ESLint
[Website](http://eslint.org) | [Configuring](http://eslint.org/docs/user-guide/configuring) | [Rules](http://eslint.org/docs/rules/) | [Contributing](http://eslint.org/docs/developer-guide/contributing) | [Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) | [Twitter](https://twitter.com/geteslint) | [Mailing List](https://groups.google.com/group/eslint) | [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: 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 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 ## Configuration
After running `eslint --init`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this: 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 * Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer * Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - reviewer * 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 * Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - committer
* Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - committer * Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - committer
* Ian VanSchooten ([@ianvs](https://github.com/ianvs)) - committer * Ian VanSchooten ([@ianvs](https://github.com/ianvs)) - committer
* Burak Yiğit Kaya ([@byk](https://github.com/byk)) - committer * Burak Yiğit Kaya ([@byk](https://github.com/byk)) - committer
* Alberto Rodríguez ([@alberto](https://github.com/alberto)) - committer
* Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo)) - committer * Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo)) - committer
* Michael Ficarra ([@michaelficarra](https://github.com/michaelficarra)) - 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 ## 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. 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. 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. 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? ### 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. * @fileoverview Main CLI that is run via the eslint command.
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "use strict";

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

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

1
tools/eslint/conf/environments.js

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

2
tools/eslint/conf/eslint.json

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

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

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

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

@ -1,8 +1,6 @@
/** /**
* @fileoverview Main CLI object. * @fileoverview Main CLI object.
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
* @copyright 2014 Nicholas C. Zakas. All rights reserved.
* See LICENSE in root directory for full license.
*/ */
"use strict"; "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. * Processes an source code using ESLint.
* @param {string} text The source code to check. * @param {string} text The source code to check.
@ -179,15 +250,17 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
} else { } else {
messages = eslint.verify(text, config, {
filename: filename,
allowInlineConfig: allowInlineConfig
});
if (fix) { if (fix) {
debug("Generating fixed text for " + filename); fixedResult = multipassFix(text, config, {
fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages); filename: filename,
allowInlineConfig: allowInlineConfig
});
messages = fixedResult.messages; 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. * @fileoverview A class of the code path analyzer.
* @author Toru Nagashima * @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "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. * @fileoverview A class of the code path segment.
* @author Toru Nagashima * @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "use strict";
@ -11,8 +9,7 @@
// Requirements // Requirements
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
var assert = require("assert"), var debug = require("./debug-helpers");
debug = require("./debug-helpers");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
@ -178,7 +175,13 @@ CodePathSegment.newNext = function(id, allPrevSegments) {
* @returns {CodePathSegment} The created segment. * @returns {CodePathSegment} The created segment.
*/ */
CodePathSegment.newUnreachable = function(id, allPrevSegments) { 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} * @returns {void}
*/ */
CodePathSegment.markUsed = function(segment) { CodePathSegment.markUsed = function(segment) {
assert(!segment.internal.used, segment.id + " is marked twice."); if (segment.internal.used) {
return;
}
segment.internal.used = true; segment.internal.used = true;
var i; 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. * @fileoverview A class to manage state of generating a code path.
* @author Toru Nagashima * @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "use strict";

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

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

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

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

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

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

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

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

5
tools/eslint/lib/config.js

@ -1,11 +1,8 @@
/** /**
* @fileoverview Responsible for loading config files * @fileoverview Responsible for loading config files
* @author Seth McLaughlin * @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"; "use strict";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

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

@ -1,8 +1,6 @@
/** /**
* @fileoverview Used for creating a suggested configuration based on project code. * @fileoverview Used for creating a suggested configuration based on project code.
* @author Ian VanSchooten * @author Ian VanSchooten
* @copyright 2015 Ian VanSchooten. All rights reserved.
* See LICENSE in root directory for full license.
*/ */
"use strict"; "use strict";
@ -312,7 +310,13 @@ Registry.prototype = {
var lintResults = eslint.verify(sourceCodes[filename], lintConfig); var lintResults = eslint.verify(sourceCodes[filename], lintConfig);
lintResults.forEach(function(result) { lintResults.forEach(function(result) {
lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
// 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; ruleSetIdx += 1;

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

@ -1,8 +1,6 @@
/** /**
* @fileoverview Helper to locate and load configuration files. * @fileoverview Helper to locate and load configuration files.
* @author Nicholas C. Zakas * @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 */ /* eslint no-use-before-define: 0 */

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

@ -1,7 +1,6 @@
/** /**
* @fileoverview Config initialization wizard. * @fileoverview Config initialization wizard.
* @author Ilya Volodin * @author Ilya Volodin
* @copyright 2015 Ilya Volodin. All rights reserved.
*/ */
"use strict"; "use strict";
@ -50,6 +49,10 @@ function writeFile(config, format) {
ConfigFile.write(config, "./.eslintrc" + extname); ConfigFile.write(config, "./.eslintrc" + extname);
log.info("Successfully created .eslintrc" + extname + " file in " + process.cwd()); log.info("Successfully created .eslintrc" + extname + " file in " + process.cwd());
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) { if (modules.length === 0) {
return; return;
} }
// Add eslint to list in case user does not have it installed locally
modules.unshift("eslint");
installStatus = npmUtil.checkDevDeps(modules); installStatus = npmUtil.checkDevDeps(modules);
// Install packages which aren't already installed // Install packages which aren't already installed
modulesToInstall = Object.keys(installStatus).filter(function(module) { 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) { if (modulesToInstall.length > 0) {
log.info("Installing " + modulesToInstall.join(", ")); log.info("Installing " + modulesToInstall.join(", "));
npmUtil.installSyncSaveDev(modulesToInstall); npmUtil.installSyncSaveDev(modulesToInstall);
@ -366,7 +381,7 @@ function promptUser(callback) {
name: "env", name: "env",
message: "Where will your code run?", message: "Where will your code run?",
default: ["browser"], default: ["browser"],
choices: [{name: "Node", value: "node"}, {name: "Browser", value: "browser"}] choices: [{name: "Browser", value: "browser"}, {name: "Node", value: "node"}]
}, },
{ {
type: "confirm", 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, * @fileoverview Config file operations. This file must be usable in the browser,
* so no Node-specific code can be here. * so no Node-specific code can be here.
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
* @copyright 2015 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "use strict";

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

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

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

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

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

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

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

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

9
tools/eslint/lib/eslint.js

@ -1,9 +1,8 @@
/** /**
* @fileoverview Main ESLint object. * @fileoverview Main ESLint object.
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "use strict";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -345,11 +344,11 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
} }
} else { // comment.type === "Line" } else { // comment.type === "Line"
if (match[1] === "eslint-disable-line") { if (match[1] === "eslint-disable-line") {
disableReporting(reportingConfig, { "line": comment.loc.start.line, "column": 0 }, Object.keys(parseListConfig(value))); disableReporting(reportingConfig, { line: comment.loc.start.line, column: 0 }, Object.keys(parseListConfig(value)));
enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value))); enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value)));
} else if (match[1] === "eslint-disable-next-line") { } else if (match[1] === "eslint-disable-next-line") {
disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value))); 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 // 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; problem.fix = fix;
} }

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

@ -1,10 +1,8 @@
/** /**
* @fileoverview Util class to find config files. * @fileoverview Util class to find config files.
* @author Aliaksei Shytkin * @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"; "use strict";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

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

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

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

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

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

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

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

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

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

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

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

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

4
tools/eslint/lib/logging.js

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

8
tools/eslint/lib/options.js

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

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

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

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

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

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

@ -1,10 +1,6 @@
/** /**
* @fileoverview Disallows or enforces spaces inside of array brackets. * @fileoverview Disallows or enforces spaces inside of array brackets.
* @author Jamund Ferguson * @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"; "use strict";
@ -19,27 +15,27 @@ module.exports = {
docs: { docs: {
description: "Enforce spacing inside array brackets", description: "Enforce spacing inside array brackets",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false, recommended: false
fixable: "whitespace"
}, },
fixable: "whitespace",
schema: [ schema: [
{ {
"enum": ["always", "never"] enum: ["always", "never"]
}, },
{ {
"type": "object", type: "object",
"properties": { properties: {
"singleValue": { singleValue: {
"type": "boolean" type: "boolean"
}, },
"objectsInArrays": { objectsInArrays: {
"type": "boolean" type: "boolean"
}, },
"arraysInArrays": { arraysInArrays: {
"type": "boolean" 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 * @fileoverview Rule to enforce return statements in callbacks of array's methods
* @author Toru Nagashima * @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "use strict";
@ -166,76 +164,86 @@ function isCallbackOfArrayMethod(node) {
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var funcInfo = { meta: {
upper: null, docs: {
codePath: null, description: "enforce `return` statements in callbacks of array methods",
hasReturn: false, category: "Best Practices",
shouldCheck: false recommended: 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)
};
}, },
// Pops this function's information. schema: []
"onCodePathEnd": function() { },
funcInfo = funcInfo.upper;
}, create: function(context) {
var funcInfo = {
// Checks the return statement is valid. upper: null,
"ReturnStatement": function(node) { codePath: null,
if (funcInfo.shouldCheck) { hasReturn: false,
funcInfo.hasReturn = true; 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) { return {
context.report({
node: node, // Stacks this function's information.
message: "Expected a return value." 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. // Reports a given function if the last path is reachable.
"FunctionExpression:exit": checkLastSegment, "FunctionExpression:exit": checkLastSegment,
"ArrowFunctionExpression: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. * @fileoverview Rule to require braces in arrow function body.
* @author Alberto Rodríguez * @author Alberto Rodríguez
* @copyright 2015 Alberto Rodríguez. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "use strict";
@ -10,50 +8,60 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var always = context.options[0] === "always"; meta: {
var asNeeded = !context.options[0] || context.options[0] === "as-needed"; docs: {
description: "require braces around arrow function bodies",
category: "ECMAScript 6",
recommended: false
},
/** schema: [
* Determines whether a arrow function body needs braces {
* @param {ASTNode} node The arrow function node. enum: ["always", "as-needed"]
* @returns {void} }
*/ ]
function validate(node) { },
var arrowBody = node.body;
if (arrowBody.type === "BlockStatement") { create: function(context) {
var blockBody = arrowBody.body; 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") { if (arrowBody.type === "BlockStatement") {
context.report({ var blockBody = arrowBody.body;
node: node,
loc: arrowBody.loc.start, if (blockBody.length !== 1) {
message: "Unexpected block statement surrounding arrow body." return;
}); }
}
} else { if (asNeeded && blockBody[0].type === "ReturnStatement") {
if (always) { context.report({
context.report({ node: node,
node: node, loc: arrowBody.loc.start,
loc: arrowBody.loc.start, message: "Unexpected block statement surrounding arrow body."
message: "Expected 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 = [ return {
{ ArrowFunctionExpression: validate
"enum": ["always", "as-needed"] };
} }
]; };

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

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

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

@ -1,7 +1,6 @@
/** /**
* @fileoverview Rule to define spacing before/after arrow function's arrow. * @fileoverview Rule to define spacing before/after arrow function's arrow.
* @author Jxck * @author Jxck
* @copyright 2015 Jxck. All rights reserved.
*/ */
"use strict"; "use strict";
@ -9,125 +8,137 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
// merge rules with default docs: {
var rule = { before: true, after: true }, description: "enforce consistent spacing before and after the arrow in arrow functions",
option = context.options[0] || {}; category: "ECMAScript 6",
recommended: false
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 };
}
/** fixable: "whitespace",
* Determines whether space(s) before after arrow(`=>`) is satisfy rule.
* if before/after value is `true`, there should be space(s). schema: [
* if before/after value is `false`, there should be no space. {
* @param {ASTNode} node The arrow function node. type: "object",
* @returns {void} properties: {
*/ before: {
function spaces(node) { type: "boolean"
var tokens = getTokens(node); },
var countSpace = countSpaces(tokens); after: {
type: "boolean"
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, " ");
} }
}); },
additionalProperties: false
} }
} else { ]
},
// should be no space before arrow
if (countSpace.before > 0) { create: function(context) {
context.report({
node: tokens.before, // merge rules with default
message: "Unexpected space before =>", var rule = { before: true, after: true },
fix: function(fixer) { option = context.options[0] || {};
return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[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 return { before: before, after: after };
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 { /**
"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 = [ if (rule.after) {
{
"type": "object", // should be space(s) after arrow
"properties": { if (countSpace.after === 0) {
"before": { context.report({
"type": "boolean" node: tokens.after,
}, message: "Missing space after =>",
"after": { fix: function(fixer) {
"type": "boolean" 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 * @fileoverview Rule to check for "block scoped" variables by binding context
* @author Matt DuVall <http://www.mattduvall.com> * @author Matt DuVall <http://www.mattduvall.com>
* @copyright 2015 Toru Nagashima. All rights reserved.
* @copyright 2015 Mathieu M-Gosselin. All rights reserved.
*/ */
"use strict"; "use strict";
@ -10,101 +8,111 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var stack = []; meta: {
docs: {
description: "enforce the use of variables within the scope they are defined",
category: "Best Practices",
recommended: false
},
/** schema: []
* Makes a block scope. },
* @param {ASTNode} node - A node of a scope.
* @returns {void}
*/
function enterScope(node) {
stack.push(node.range);
}
/** create: function(context) {
* Pops the last block scope. var stack = [];
* @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});
}
/** /**
* Finds and reports references which are outside of valid scopes. * Makes a block scope.
* @param {ASTNode} node - A node to get variables. * @param {ASTNode} node - A node of a scope.
* @returns {void} * @returns {void}
*/ */
function checkForVariables(node) { function enterScope(node) {
if (node.kind !== "var") { stack.push(node.range);
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 * Pops the last block scope.
* @param {ASTNode} reference node to examine * @returns {void}
* @returns {boolean} True is its outside the scope
* @private
*/ */
function isOutsideOfScope(reference) { function exitScope() {
var idRange = reference.identifier.range; stack.pop();
return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1];
} }
// Gets declared variables, and checks its references. /**
var variables = context.getDeclaredVariables(node); * Reports a given reference.
* @param {escope.Reference} reference - A reference to report.
for (var i = 0; i < variables.length; ++i) { * @returns {void}
*/
function report(reference) {
var identifier = reference.identifier;
// Reports. context.report(
variables[i] identifier,
.references "'{{name}}' used outside of binding context.",
.filter(isOutsideOfScope) {name: identifier.name});
.forEach(report);
} }
}
return { /**
"Program": function(node) { * Finds and reports references which are outside of valid scopes.
stack = [node.range]; * @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. return {
"BlockStatement": enterScope, Program: function(node) {
"BlockStatement:exit": exitScope, stack = [node.range];
"ForStatement": enterScope, },
"ForStatement:exit": exitScope,
"ForInStatement": enterScope, // Manages scopes.
"ForInStatement:exit": exitScope, BlockStatement: enterScope,
"ForOfStatement": enterScope, "BlockStatement:exit": exitScope,
"ForOfStatement:exit": exitScope, ForStatement: enterScope,
"SwitchStatement": enterScope, "ForStatement:exit": exitScope,
"SwitchStatement:exit": exitScope, ForInStatement: enterScope,
"CatchClause": enterScope, "ForInStatement:exit": exitScope,
"CatchClause:exit": exitScope, ForOfStatement: enterScope,
"ForOfStatement:exit": exitScope,
// Finds and reports references which are outside of valid scope. SwitchStatement: enterScope,
"VariableDeclaration": checkForVariables "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. * @fileoverview A rule to disallow or enforce spaces inside of single line blocks.
* @author Toru Nagashima * @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/ */
"use strict"; "use strict";
@ -12,109 +11,121 @@ var util = require("../ast-utils");
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var always = (context.options[0] !== "never"), meta: {
message = always ? "Requires a space" : "Unexpected space(s)", docs: {
sourceCode = context.getSourceCode(); description: "enforce consistent spacing inside single-line blocks",
category: "Stylistic Issues",
/** recommended: false
* 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);
}
/** fixable: "whitespace",
* Checks whether or not:
* - given tokens are on same line. schema: [
* - there is/isn't a space between given tokens. {enum: ["always", "never"]}
* @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. create: function(context) {
* When the option is `"never"`, `true` if there are not any spaces between given tokens. var always = (context.options[0] !== "never"),
* If given tokens are not on same line, it's always `true`. message = always ? "Requires a space" : "Unexpected space(s)",
*/ sourceCode = context.getSourceCode();
function isValid(left, right) {
return (
!util.isTokenOnSameLine(left, right) ||
sourceCode.isSpaceBetweenTokens(left, right) === always
);
}
/** /**
* Reports invalid spacing style inside braces. * Gets the open brace token from a given node.
* @param {ASTNode} node - A BlockStatement/SwitchStatement node to get. * @param {ASTNode} node - A BlockStatement/SwitchStatement node to get.
* @returns {void} * @returns {Token} The token of the open brace.
*/ */
function checkSpacingInsideBraces(node) { function getOpenBrace(node) {
if (node.type === "SwitchStatement") {
// Gets braces and the first/last token of content. if (node.cases.length > 0) {
var openBrace = getOpenBrace(node); return context.getTokenBefore(node.cases[0]);
var closeBrace = context.getLastToken(node); }
var firstToken = sourceCode.getTokenOrCommentAfter(openBrace); return context.getLastToken(node, 1);
var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace); }
return context.getFirstToken(node);
// 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") { * Checks whether or not:
return; * - 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)) { * Reports invalid spacing style inside braces.
context.report({ * @param {ASTNode} node - A BlockStatement/SwitchStatement node to get.
node: node, * @returns {void}
loc: openBrace.loc.start, */
message: message + " after '{'.", function checkSpacingInsideBraces(node) {
fix: function(fixer) {
if (always) {
return fixer.insertTextBefore(firstToken, " ");
}
return fixer.removeRange([openBrace.range[1], firstToken.range[0]]); // Gets braces and the first/last token of content.
} var openBrace = getOpenBrace(node);
}); var closeBrace = context.getLastToken(node);
} var firstToken = sourceCode.getTokenOrCommentAfter(openBrace);
if (!isValid(lastToken, closeBrace)) { var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace);
context.report({
node: node, // Skip if the node is invalid or empty.
loc: closeBrace.loc.start, if (openBrace.type !== "Punctuator" ||
message: message + " before '}'.", openBrace.value !== "{" ||
fix: function(fixer) { closeBrace.type !== "Punctuator" ||
if (always) { closeBrace.value !== "}" ||
return fixer.insertTextAfter(lastToken, " "); 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 { return {
BlockStatement: checkSpacingInsideBraces, BlockStatement: checkSpacingInsideBraces,
SwitchStatement: checkSpacingInsideBraces SwitchStatement: checkSpacingInsideBraces
}; };
}
}; };
module.exports.schema = [
{enum: ["always", "never"]}
];

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

@ -9,226 +9,236 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var style = context.options[0] || "1tbs", meta: {
params = context.options[1] || {}, docs: {
sourceCode = context.getSourceCode(); description: "enforce consistent brace style for blocks",
category: "Stylistic Issues",
var OPEN_MESSAGE = "Opening curly brace does not appear on the same line as controlling statement.", recommended: false
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;
if (allOnSameLine && params.allowSingleLine) { schema: [
return; {
} enum: ["1tbs", "stroustrup", "allman"]
},
{
type: "object",
properties: {
allowSingleLine: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) { create: function(context) {
context.report(node, OPEN_MESSAGE); var style = context.options[0] || "1tbs",
} else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) { params = context.options[1] || {},
context.report(node, OPEN_MESSAGE_ALLMAN); 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 * Enforces the configured brace style on TryStatements
* @param {ASTNode} node A TryStatement node. * @param {ASTNode} node A TryStatement node.
* @returns {void} * @returns {void}
* @private * @private
*/ */
function checkTryStatement(node) { function checkTryStatement(node) {
var tokens; var tokens;
checkBlock("block", "finalizer")(node); checkBlock("block", "finalizer")(node);
if (isBlock(node.finalizer)) { if (isBlock(node.finalizer)) {
tokens = sourceCode.getTokensBefore(node.finalizer, 2); tokens = sourceCode.getTokensBefore(node.finalizer, 2);
if (style === "1tbs") { if (style === "1tbs") {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) { if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node.finalizer, CLOSE_MESSAGE); 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 * Enforces the configured brace style on CatchClauses
* @param {ASTNode} node A CatchClause node. * @param {ASTNode} node A CatchClause node.
* @returns {void} * @returns {void}
* @private * @private
*/ */
function checkCatchClause(node) { function checkCatchClause(node) {
var previousToken = sourceCode.getTokenBefore(node), var previousToken = sourceCode.getTokenBefore(node),
firstToken = sourceCode.getFirstToken(node); firstToken = sourceCode.getFirstToken(node);
checkBlock("body")(node); checkBlock("body")(node);
if (isBlock(node.body)) { if (isBlock(node.body)) {
if (style === "1tbs") { if (style === "1tbs") {
if (previousToken.loc.start.line !== firstToken.loc.start.line) { if (previousToken.loc.start.line !== firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE); context.report(node, CLOSE_MESSAGE);
} }
} else { } else {
if (previousToken.loc.start.line === firstToken.loc.start.line) { if (previousToken.loc.start.line === firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE_STROUSTRUP_ALLMAN); context.report(node, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
}
} }
} }
} }
}
/** /**
* Enforces the configured brace style on SwitchStatements * Enforces the configured brace style on SwitchStatements
* @param {ASTNode} node A SwitchStatement node. * @param {ASTNode} node A SwitchStatement node.
* @returns {void} * @returns {void}
* @private * @private
*/ */
function checkSwitchStatement(node) { function checkSwitchStatement(node) {
var tokens; var tokens;
if (node.cases && node.cases.length) { if (node.cases && node.cases.length) {
tokens = sourceCode.getTokensBefore(node.cases[0], 2); tokens = sourceCode.getTokensBefore(node.cases[0], 2);
} else { } else {
tokens = sourceCode.getLastTokens(node, 3); tokens = sourceCode.getLastTokens(node, 3);
} }
if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) { if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE); context.report(node, OPEN_MESSAGE);
} else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) { } else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE_ALLMAN); context.report(node, OPEN_MESSAGE_ALLMAN);
}
} }
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public API // Public API
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
return { return {
"FunctionDeclaration": checkBlock("body"), FunctionDeclaration: checkBlock("body"),
"FunctionExpression": checkBlock("body"), FunctionExpression: checkBlock("body"),
"ArrowFunctionExpression": checkBlock("body"), ArrowFunctionExpression: checkBlock("body"),
"IfStatement": checkIfStatement, IfStatement: checkIfStatement,
"TryStatement": checkTryStatement, TryStatement: checkTryStatement,
"CatchClause": checkCatchClause, CatchClause: checkCatchClause,
"DoWhileStatement": checkBlock("body"), DoWhileStatement: checkBlock("body"),
"WhileStatement": checkBlock("body"), WhileStatement: checkBlock("body"),
"WithStatement": checkBlock("body"), WithStatement: checkBlock("body"),
"ForStatement": checkBlock("body"), ForStatement: checkBlock("body"),
"ForInStatement": checkBlock("body"), ForInStatement: checkBlock("body"),
"ForOfStatement": checkBlock("body"), ForOfStatement: checkBlock("body"),
"SwitchStatement": checkSwitchStatement 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. * @fileoverview Enforce return after a callback.
* @author Jamund Ferguson * @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
*/ */
"use strict"; "use strict";
@ -9,136 +8,146 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
var callbacks = context.options[0] || ["callback", "cb", "next"]; docs: {
description: "require `return` statements after callbacks",
//-------------------------------------------------------------------------- category: "Node.js and CommonJS",
// Helpers recommended: false
//-------------------------------------------------------------------------- },
/** schema: [{
* Find the closest parent matching a list of types. type: "array",
* @param {ASTNode} node The node whose parents we are searching items: { type: "string" }
* @param {Array} types The node types to match }]
* @returns {ASTNode} The matched node or undefined. },
*/
function findClosestParentOfType(node, types) { create: function(context) {
if (!node.parent) {
return null; var callbacks = context.options[0] || ["callback", "cb", "next"];
}
if (types.indexOf(node.parent.type) === -1) { //--------------------------------------------------------------------------
return findClosestParentOfType(node.parent, types); // 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. * Check to see if a CallExpression is in our callback list.
* @param {ASTNode} node The callback node * @param {ASTNode} node The node to check against our callback names list.
* @param {ASTNode} parentNode The expression node * @returns {Boolean} Whether or not this function matches our callback name.
* @returns {boolean} Whether or not this is part of a callback expression */
*/ function isCallback(node) {
function isCallbackExpression(node, parentNode) { return node.callee.type === "Identifier" && callbacks.indexOf(node.callee.name) > -1;
// ensure the parent node exists and is an expression
if (!parentNode || parentNode.type !== "ExpressionStatement") {
return false;
} }
// cb() /**
if (parentNode.expression === node) { * Determines whether or not the callback is part of a callback expression.
return true; * @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 // cb()
if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") { if (parentNode.expression === node) {
if (parentNode.expression.right === node) {
return true; 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
//--------------------------------------------------------------------------
//-------------------------------------------------------------------------- return {
// Public CallExpression: function(node) {
//--------------------------------------------------------------------------
return { // if we"re not a callback we can return
"CallExpression": function(node) { if (!isCallback(node)) {
return;
}
// if we"re not a callback we can return // find the closest block, return or loop
if (!isCallback(node)) { var closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {},
return; lastItem, parentType;
}
// find the closest block, return or loop // if our parent is a return we know we're ok
var closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {}, if (closestBlock.type === "ReturnStatement") {
lastItem, parentType; return;
}
// if our parent is a return we know we're ok // arrow functions don't always have blocks and implicitly return
if (closestBlock.type === "ReturnStatement") { if (closestBlock.type === "ArrowFunctionExpression") {
return; return;
} }
// arrow functions don't always have blocks and implicitly return // block statements are part of functions and most if statements
if (closestBlock.type === "ArrowFunctionExpression") { if (closestBlock.type === "BlockStatement") {
return;
}
// block statements are part of functions and most if statements // find the last item in the block
if (closestBlock.type === "BlockStatement") { lastItem = closestBlock.body[closestBlock.body.length - 1];
// find the last item in the block // if the callback is the last thing in a block that might be ok
lastItem = closestBlock.body[closestBlock.body.length - 1]; if (isCallbackExpression(node, lastItem)) {
// if the callback is the last thing in a block that might be ok parentType = closestBlock.parent.type;
if (isCallbackExpression(node, lastItem)) {
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 // but only if the callback is immediately before
if (lastItem.type === "ReturnStatement") { 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 * @fileoverview Rule to flag non-camelcased identifiers
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
* @copyright 2015 Dieter Oberkofler. All rights reserved.
*/ */
"use strict"; "use strict";
@ -10,116 +9,126 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
//-------------------------------------------------------------------------- docs: {
// Helpers description: "enforce camelcase naming convention",
//-------------------------------------------------------------------------- category: "Stylistic Issues",
recommended: false
// 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 {
"Identifier": function(node) { schema: [
{
type: "object",
properties: {
properties: {
enum: ["always", "never"]
}
},
additionalProperties: false
}
]
},
/* create: function(context) {
* 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") { // Helpers
//--------------------------------------------------------------------------
// "never" check properties // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
if (properties === "never") { var reported = [];
return;
}
// Always report underscored object names /**
if (node.parent.object.type === "Identifier" && * Checks if a string contains an underscore and isn't all upper-case
node.parent.object.name === node.name && * @param {String} name The string to check.
isUnderscored(name)) { * @returns {boolean} if the string is underscored
report(node); * @private
*/
function isUnderscored(name) {
// Report AssignmentExpressions only if they are the left side of the assignment // if there's an underscore, it might be A_CONSTANT, which is okay
} else if (effectiveParent.type === "AssignmentExpression" && return name.indexOf("_") > -1 && name !== name.toUpperCase();
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") { * 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 var options = context.options[0] || {},
if (properties === "never") { properties = options.properties || "";
return;
}
if (node.parent.parent && node.parent.parent.type === "ObjectPattern" && if (properties !== "always" && properties !== "never") {
node.parent.key === node && node.parent.value !== node) { properties = "always";
return; }
}
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(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. * @fileoverview Rule to forbid or enforce dangling commas.
* @author Ian Christian Myers * @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"; "use strict";
@ -24,203 +20,204 @@ var lodash = require("lodash");
* @returns {boolean} `true` if a trailing comma is allowed. * @returns {boolean} `true` if a trailing comma is allowed.
*/ */
function isTrailingCommaAllowed(node, lastItem) { function isTrailingCommaAllowed(node, lastItem) {
switch (node.type) { return node.type !== "ArrayPattern" || lastItem.type !== "RestElement";
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;
}
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var mode = context.options[0]; meta: {
var UNEXPECTED_MESSAGE = "Unexpected trailing comma."; docs: {
var MISSING_MESSAGE = "Missing trailing comma."; description: "require or disallow trailing commas",
category: "Possible Errors",
/** recommended: true
* 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. fixable: "code",
*
* @param {ASTNode} node - A node to check. schema: [
* @returns {boolean} `true` if the node is multiline. {
*/ enum: ["always", "always-multiline", "only-multiline", "never"]
function isMultiline(node) { }
var lastItem = lodash.last(node.properties || node.elements || node.specifiers); ]
},
if (!lastItem) {
return false; 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), * Reports a trailing comma if it exists.
lastToken = sourceCode.getTokenAfter(penultimateToken); *
* @param {ASTNode} node - A node to check. Its type is one of
// parentheses are a pain * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
while (lastToken.value === ")") { * ImportDeclaration, and ExportNamedDeclaration.
penultimateToken = lastToken; * @returns {void}
lastToken = sourceCode.getTokenAfter(lastToken); */
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; * Reports the last element of a given node if it does not have a trailing
lastToken = sourceCode.getTokenAfter(lastToken); * 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; /**
} * 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.
* Reports a trailing comma if it exists. *
* * @param {ASTNode} node - A node to check. Its type is one of
* @param {ASTNode} node - A node to check. Its type is one of * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, * ImportDeclaration, and ExportNamedDeclaration.
* ImportDeclaration, and ExportNamedDeclaration. * @returns {void}
* @returns {void} */
*/ function forceTrailingCommaIfMultiline(node) {
function forbidTrailingComma(node) { if (isMultiline(node)) {
var lastItem = lodash.last(node.properties || node.elements || node.specifiers); forceTrailingComma(node);
} else {
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { forbidTrailingComma(node);
return; }
} }
var sourceCode = context.getSourceCode(), /**
trailingToken; * Only if a given node is not multiline, reports the last element of a given node
* when it does not have a trailing comma.
// last item can be surrounded by parentheses for object and array literals * Otherwise, reports a trailing comma if it exists.
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") { *
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node)); * @param {ASTNode} node - A node to check. Its type is one of
} else { * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
trailingToken = sourceCode.getTokenAfter(lastItem); * ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function allowTrailingCommaIfMultiline(node) {
if (!isMultiline(node)) {
forbidTrailingComma(node);
}
} }
if (trailingToken.value === ",") { // Chooses a checking function.
context.report( var checkForTrailingComma;
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;
// last item can be surrounded by parentheses for object and array literals if (mode === "always") {
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") { checkForTrailingComma = forceTrailingComma;
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node)); } else if (mode === "always-multiline") {
checkForTrailingComma = forceTrailingCommaIfMultiline;
} else if (mode === "only-multiline") {
checkForTrailingComma = allowTrailingCommaIfMultiline;
} else { } else {
trailingToken = sourceCode.getTokenAfter(lastItem); checkForTrailingComma = forbidTrailingComma;
} }
if (trailingToken.value !== ",") { return {
context.report( ObjectExpression: checkForTrailingComma,
lastItem, ObjectPattern: checkForTrailingComma,
lastItem.loc.end, ArrayExpression: checkForTrailingComma,
MISSING_MESSAGE); 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 * @fileoverview Comma spacing - validates spacing before and after comma
* @author Vignesh Anand aka vegetableman. * @author Vignesh Anand aka vegetableman.
* @copyright 2014 Vignesh Anand. All rights reserved.
*/ */
"use strict"; "use strict";
@ -11,173 +10,185 @@ var astUtils = require("../ast-utils");
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
var sourceCode = context.getSourceCode(); docs: {
var tokensAndComments = sourceCode.tokensAndComments; description: "enforce consistent spacing before and after commas",
category: "Stylistic Issues",
var options = { recommended: false
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 === ",");
}
/** fixable: "whitespace",
* Reports a spacing error with an appropriate message.
* @param {ASTNode} node The binary expression node to report. schema: [
* @param {string} dir Is the error "before" or "after" the comma? {
* @param {ASTNode} otherNode The node at the left or right of `node` type: "object",
* @returns {void} properties: {
* @private before: {
*/ type: "boolean"
function report(node, dir, otherNode) { },
context.report({ after: {
node: node, type: "boolean"
fix: function(fixer) {
if (options[dir]) {
if (dir === "before") {
return fixer.insertTextBefore(node, " ");
} else {
return fixer.insertTextAfter(node, " ");
} }
} else { },
var start, end; additionalProperties: false
var newText = ""; }
]
},
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]; * Reports a spacing error with an appropriate message.
end = node.range[0]; * @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 { } else {
start = node.range[1]; var start, end;
end = otherNode.range[0]; 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 + " ','."
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);
} }
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) && if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) &&
(options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right)) (options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right))
) { ) {
report(reportItem, "after", tokens.right); report(reportItem, "after", tokens.right);
}
} }
}
/** /**
* Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list. * Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
* @param {ASTNode} node An ArrayExpression or ArrayPattern node. * @param {ASTNode} node An ArrayExpression or ArrayPattern node.
* @returns {void} * @returns {void}
*/ */
function addNullElementsToIgnoreList(node) { function addNullElementsToIgnoreList(node) {
var previousToken = context.getFirstToken(node); var previousToken = context.getFirstToken(node);
node.elements.forEach(function(element) { node.elements.forEach(function(element) {
var token; var token;
if (element === null) { if (element === null) {
token = context.getTokenAfter(previousToken); token = context.getTokenAfter(previousToken);
if (isComma(token)) { if (isComma(token)) {
commaTokensToIgnore.push(token); commaTokensToIgnore.push(token);
}
} else {
token = context.getTokenAfter(element);
} }
} else {
token = context.getTokenAfter(element);
}
previousToken = token; previousToken = token;
}); });
} }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
return { return {
"Program:exit": function() { "Program:exit": function() {
var previousToken, var previousToken,
nextToken; nextToken;
tokensAndComments.forEach(function(token, i) { tokensAndComments.forEach(function(token, i) {
if (!isComma(token)) { if (!isComma(token)) {
return; return;
} }
if (token && token.type === "JSXText") {
return;
}
previousToken = tokensAndComments[i - 1]; if (token && token.type === "JSXText") {
nextToken = tokensAndComments[i + 1]; return;
}
validateCommaItemSpacing({ previousToken = tokensAndComments[i - 1];
comma: token, nextToken = tokensAndComments[i + 1];
left: isComma(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
right: isComma(nextToken) ? null : nextToken
}, token);
});
},
"ArrayExpression": addNullElementsToIgnoreList,
"ArrayPattern": addNullElementsToIgnoreList
}; 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 * @fileoverview Comma style - enforces comma styles of two types: last and first
* @author Vignesh Anand aka vegetableman * @author Vignesh Anand aka vegetableman
* @copyright 2014 Vignesh Anand. All rights reserved.
* @copyright 2015 Evan Simmons. All rights reserved.
*/ */
"use strict"; "use strict";
@ -13,174 +11,184 @@ var astUtils = require("../ast-utils");
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
var style = context.options[0] || "last", docs: {
exceptions = {}; description: "enforce consistent comma style",
category: "Stylistic Issues",
recommended: false
},
if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) { schema: [
exceptions = context.options[1].exceptions; {
} enum: ["first", "last"]
},
{
type: "object",
properties: {
exceptions: {
type: "object",
additionalProperties: {
type: "boolean"
}
}
},
additionalProperties: false
}
]
},
//-------------------------------------------------------------------------- create: function(context) {
// 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 === ",");
}
/** var style = context.options[0] || "last",
* Validates the spacing around single items in lists. exceptions = {};
* @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) {
// if single line if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) && exceptions = context.options[1].exceptions;
astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { }
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 // if single line
context.report(reportItem, { if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
line: commaToken.loc.end.line, astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
column: commaToken.loc.start.column
}, "Bad line breaking before and after ','.");
} 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, { } else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
line: commaToken.loc.end.line,
column: commaToken.loc.end.column
}, "',' should be placed last.");
}
}
/** context.report(reportItem, "',' should be placed first.");
* 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) { } else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
// seed as opening [ context.report(reportItem, {
previousItemToken = context.getFirstToken(node); 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, * Checks the comma placement with regards to a declaration/property/element
currentItemToken = item ? context.getFirstToken(item) : context.getTokenAfter(commaToken), * @param {ASTNode} node The binary expression node to check
reportItem = item || currentItemToken; * @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: * Special case for array literals that have empty last items, such
* - previousItemToken is the last token of the previous item * as [ 1, 2, ]. These arrays only have two items show up in the
* - commaToken is the location of the comma before the current item * AST, so we need to look at the token to verify that there's no
* - currentItemToken is the first token of the current item * dangling comma.
*
* 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)) { if (arrayLiteral) {
validateCommaItemSpacing(previousItemToken, commaToken,
currentItemToken, reportItem); var lastToken = context.getLastToken(node),
} nextToLastToken = context.getTokenBefore(lastToken);
previousItemToken = item ? context.getLastToken(item) : previousItemToken; if (isComma(nextToLastToken)) {
}); validateCommaItemSpacing(
context.getTokenBefore(nextToLastToken),
/* nextToLastToken,
* Special case for array literals that have empty last items, such lastToken,
* as [ 1, 2, ]. These arrays only have two items show up in the lastToken
* 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
);
} }
} }
} }
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
var nodes = {}; var nodes = {};
if (!exceptions.VariableDeclaration) { if (!exceptions.VariableDeclaration) {
nodes.VariableDeclaration = function(node) { nodes.VariableDeclaration = function(node) {
validateComma(node, "declarations"); validateComma(node, "declarations");
}; };
} }
if (!exceptions.ObjectExpression) { if (!exceptions.ObjectExpression) {
nodes.ObjectExpression = function(node) { nodes.ObjectExpression = function(node) {
validateComma(node, "properties"); validateComma(node, "properties");
}; };
} }
if (!exceptions.ArrayExpression) { if (!exceptions.ArrayExpression) {
nodes.ArrayExpression = function(node) { nodes.ArrayExpression = function(node) {
validateComma(node, "elements"); validateComma(node, "elements");
}; };
} }
return nodes;
};
module.exports.schema = [ return nodes;
{
"enum": ["first", "last"]
},
{
"type": "object",
"properties": {
"exceptions": {
"type": "object",
"additionalProperties": {
"type": "boolean"
}
}
},
"additionalProperties": false
} }
]; };

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

@ -10,143 +10,153 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var option = context.options[0], meta: {
THRESHOLD = 20; 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") { create: function(context) {
THRESHOLD = option.maximum; var option = context.options[0],
} THRESHOLD = 20;
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
THRESHOLD = option.max;
}
if (typeof option === "number") {
THRESHOLD = option;
}
//-------------------------------------------------------------------------- if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
// Helpers 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
//--------------------------------------------------------------------------
/** // Using a stack to store complexity (handling nested functions)
* When parsing a new function, store it in our function stack var fns = [];
* @returns {void}
* @private
*/
function startFunction() {
fns.push(1);
}
/** /**
* Evaluate the node at the end of function * When parsing a new function, store it in our function stack
* @param {ASTNode} node node to evaluate * @returns {void}
* @returns {void} * @private
* @private */
*/ function startFunction() {
function endFunction(node) { fns.push(1);
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;
} }
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;
}
/** if (complexity > THRESHOLD) {
* Increase the complexity of the function in context context.report(node, "Function '{{name}}' has a complexity of {{complexity}}.", { name: name, complexity: complexity });
* @returns {void} }
* @private
*/
function increaseComplexity() {
if (fns.length) {
fns[fns.length - 1]++;
} }
}
/** /**
* Increase the switch complexity in context * Increase the complexity of the function in context
* @param {ASTNode} node node to evaluate * @returns {void}
* @returns {void} * @private
* @private */
*/ function increaseComplexity() {
function increaseSwitchComplexity(node) { if (fns.length) {
fns[fns.length - 1]++;
// Avoiding `default` }
if (node.test) {
increaseComplexity(node);
} }
}
/** /**
* Increase the logical path complexity in context * Increase the switch complexity in context
* @param {ASTNode} node node to evaluate * @param {ASTNode} node node to evaluate
* @returns {void} * @returns {void}
* @private * @private
*/ */
function increaseLogicalComplexity(node) { function increaseSwitchComplexity(node) {
// Avoiding && // Avoiding `default`
if (node.operator === "||") { if (node.test) {
increaseComplexity(node); increaseComplexity(node);
}
} }
}
//-------------------------------------------------------------------------- /**
// Public API * Increase the logical path complexity in context
//-------------------------------------------------------------------------- * @param {ASTNode} node node to evaluate
* @returns {void}
return { * @private
"FunctionDeclaration": startFunction, */
"FunctionExpression": startFunction, function increaseLogicalComplexity(node) {
"ArrowFunctionExpression": startFunction,
"FunctionDeclaration:exit": endFunction, // Avoiding &&
"FunctionExpression:exit": endFunction, if (node.operator === "||") {
"ArrowFunctionExpression:exit": endFunction, increaseComplexity(node);
}
"CatchClause": increaseComplexity, }
"ConditionalExpression": increaseComplexity,
"LogicalExpression": increaseLogicalComplexity,
"ForStatement": increaseComplexity,
"ForInStatement": increaseComplexity,
"ForOfStatement": increaseComplexity,
"IfStatement": increaseComplexity,
"SwitchCase": increaseSwitchComplexity,
"WhileStatement": increaseComplexity,
"DoWhileStatement": increaseComplexity
};
}; //--------------------------------------------------------------------------
// 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. * @fileoverview Disallows or enforces spaces inside computed properties.
* @author Jamund Ferguson * @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
*/ */
"use strict"; "use strict";
@ -11,143 +10,155 @@ var astUtils = require("../ast-utils");
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var sourceCode = context.getSourceCode(); meta: {
var propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never" docs: {
description: "enforce consistent spacing inside computed property brackets",
//-------------------------------------------------------------------------- category: "Stylistic Issues",
// Helpers recommended: false
//-------------------------------------------------------------------------- },
/**
* 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, " ");
}
});
}
/** fixable: "whitespace",
* 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, " ");
}
});
}
/** schema: [
* Returns a function that checks the spacing of a node on the property name {
* that was passed in. enum: ["always", "never"]
* @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;
} }
]
},
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 property = node[propertyName];
var before = context.getTokenBefore(property), var before = context.getTokenBefore(property),
first = context.getFirstToken(property), first = context.getFirstToken(property),
last = context.getLastToken(property), last = context.getLastToken(property),
after = context.getTokenAfter(property); after = context.getTokenAfter(property);
if (astUtils.isTokenOnSameLine(before, first)) { if (astUtils.isTokenOnSameLine(before, first)) {
if (propertyNameMustBeSpaced) { if (propertyNameMustBeSpaced) {
if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) { if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) {
reportRequiredBeginningSpace(node, before); reportRequiredBeginningSpace(node, before);
} }
} else { } else {
if (sourceCode.isSpaceBetweenTokens(before, first)) { if (sourceCode.isSpaceBetweenTokens(before, first)) {
reportNoBeginningSpace(node, before, first); reportNoBeginningSpace(node, before, first);
}
} }
} }
}
if (astUtils.isTokenOnSameLine(last, after)) { if (astUtils.isTokenOnSameLine(last, after)) {
if (propertyNameMustBeSpaced) { if (propertyNameMustBeSpaced) {
if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) { if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) {
reportRequiredEndingSpace(node, after); reportRequiredEndingSpace(node, after);
} }
} else { } else {
if (sourceCode.isSpaceBetweenTokens(last, after)) { if (sourceCode.isSpaceBetweenTokens(last, after)) {
reportNoEndingSpace(node, after, last); reportNoEndingSpace(node, after, last);
}
} }
} }
} };
}; }
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return { //--------------------------------------------------------------------------
Property: checkSpacing("key"), // Public
MemberExpression: checkSpacing("property") //--------------------------------------------------------------------------
};
}; 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 // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var funcInfo = null; meta: {
docs: {
/** description: "require `return` statements to either always or never specify values",
* Checks whether of not the implicit returning is consistent if the last category: "Best Practices",
* code path segment is reachable. recommended: false
* },
* @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";
}
// Reports. schema: []
context.report({ },
node: node,
loc: loc,
message: "Expected to return a value at the end of this {{type}}.",
data: {type: type}
});
}
return { create: function(context) {
var funcInfo = null;
// 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) { * Checks whether of not the implicit returning is consistent if the last
var hasReturnValue = Boolean(node.argument); * 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) { // Adjust a location and a message.
funcInfo.hasReturn = true; if (node.type === "Program") {
funcInfo.hasReturnValue = hasReturnValue;
funcInfo.message = "Expected " + (hasReturnValue ? "a" : "no") + " return value."; // The head of program.
} else if (funcInfo.hasReturnValue !== hasReturnValue) { loc = {line: 1, column: 0};
context.report({node: node, message: funcInfo.message}); 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. // Reports.
"Program:exit": checkLastSegment, context.report({
"FunctionDeclaration:exit": checkLastSegment, node: node,
"FunctionExpression:exit": checkLastSegment, loc: loc,
"ArrowFunctionExpression:exit": checkLastSegment 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 * @fileoverview Rule to enforce consistent naming of "this" context variables
* @author Raphael Pigulla * @author Raphael Pigulla
* @copyright 2015 Timothy Jones. All rights reserved.
* @copyright 2015 David Aurelio. All rights reserved.
*/ */
"use strict"; "use strict";
@ -10,131 +8,141 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var aliases = []; meta: {
docs: {
if (context.options.length === 0) { description: "enforce consistent naming when capturing the current execution context",
aliases.push("that"); category: "Stylistic Issues",
} else { recommended: false
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 });
}
/** schema: {
* Checks that an assignment to an identifier only assigns 'this' to the type: "array",
* appropriate alias, and the alias is only assigned to 'this'. items: {
* @param {ASTNode} node - The assigning node. type: "string",
* @param {Identifier} name - The name of the variable assigned to. minLength: 1
* @param {Expression} value - The value of the assignment. },
* @returns {void} uniqueItems: true
*/
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 });
} }
} },
/** create: function(context) {
* Ensures that a variable declaration of the alias in a program or function var aliases = [];
* 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 (variable.defs.some(function(def) { if (context.options.length === 0) {
return def.node.type === "VariableDeclarator" && aliases.push("that");
def.node.init !== null; } else {
})) { aliases = context.options;
return;
} }
// The alias has been declared and not assigned: check it was /**
// assigned later in the same scope. * Reports that a variable declarator or assignment expression is assigning
if (!variable.references.some(function(reference) { * a non-'this' value to the specified alias.
var write = reference.writeExpr; * @param {ASTNode} node - The assigning node.
* @param {string} alias - the name of the alias that was incorrectly used.
return ( * @returns {void}
reference.from === scope && */
write && write.type === "ThisExpression" && function reportBadAssignment(node, alias) {
write.parent.operator === "=" context.report(node,
); "Designated alias '{{alias}}' is not assigned to 'this'.",
})) { { alias: alias });
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();
aliases.forEach(function(alias) { /**
checkWasAssigned(alias, scope); * 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.
return { * @param {Expression} value - The value of the assignment.
"Program:exit": ensureWasAssigned, * @returns {void}
"FunctionExpression:exit": ensureWasAssigned, */
"FunctionDeclaration:exit": ensureWasAssigned, 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; * Ensures that a variable declaration of the alias in a program or function
var isDestructuring = * is assigned to the correct value.
id.type === "ArrayPattern" || id.type === "ObjectPattern"; * @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) { if (variable.defs.some(function(def) {
checkAssignment(node, id.name, node.init); return def.node.type === "VariableDeclarator" &&
def.node.init !== null;
})) {
return;
} }
},
"AssignmentExpression": function(node) { // The alias has been declared and not assigned: check it was
if (node.left.type === "Identifier") { // assigned later in the same scope.
checkAssignment(node, node.left.name, node.right); 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 = { aliases.forEach(function(alias) {
"type": "array", checkWasAssigned(alias, scope);
"items": { });
"type": "string", }
"minLength": 1
}, return {
"uniqueItems": true "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. * @fileoverview A rule to verify `super()` callings in constructor.
* @author Toru Nagashima * @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/ */
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // 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. * Checks whether or not a given node is a constructor.
* @param {ASTNode} node - A node to check. This node type is one of * @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 // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
/* docs: {
* {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]} description: "require `super()` calls in constructors",
* Information for each constructor. category: "ECMAScript 6",
* - upper: Information of the upper constructor. recommended: true
* - 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;
}
/** schema: []
* 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. create: function(context) {
*/
function isCalledInEveryPath(segment) {
/* /*
* If specific segment is the looped segment of the current segment, * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
* skip the segment. * Information for each constructor.
* If not skipped, this never becomes true after a loop. * - 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 && var funcInfo = null;
segment.nextSegments[0].isLoopedPrevSegment(segment)
) {
return true;
}
return segInfoMap[segment.id].calledInEveryPaths;
}
return {
/** /*
* Stacks a constructor information. * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
* @param {CodePath} codePath - A code path which was started. * Information for each code path segment.
* @param {ASTNode} node - The current node. * - calledInSomePaths: A flag of be called `super()` in some code paths.
* @returns {void} * - calledInEveryPaths: A flag of be called `super()` in all code paths.
* - validNodes:
*/ */
"onCodePathStart": function(codePath, node) { var segInfoMap = Object.create(null);
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
};
}
},
/** /**
* Pops a constructor information. * Gets the flag which shows `super()` is called in some paths.
* And reports if `super()` lacked. * @param {CodePathSegment} segment - A code path segment to get.
* @param {CodePath} codePath - A code path which was ended. * @returns {boolean} The flag which shows `super()` is called in some paths
* @param {ASTNode} node - The current node.
* @returns {void}
*/ */
"onCodePathEnd": function(codePath, node) { function isCalledInSomePath(segment) {
return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
// 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
});
}
},
/** /**
* Initialize information of a given code path segment. * Gets the flag which shows `super()` is called in all paths.
* @param {CodePathSegment} segment - A code path segment to initialize. * @param {CodePathSegment} segment - A code path segment to get.
* @returns {void} * @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 * If specific segment is the looped segment of the current segment,
* valid `extends` part. * skip the segment.
* If not skipped, this never becomes true after a loop.
*/ */
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { if (segment.nextSegments.length === 1 &&
return; segment.nextSegments[0].isLoopedPrevSegment(segment)
) {
return true;
} }
return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
}
// Initialize info. return {
var info = segInfoMap[segment.id] = {
calledInSomePaths: false,
calledInEveryPaths: false,
validNodes: []
};
// 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) { // Pop.
info.calledInSomePaths = prevSegments.some(isCalledInSomePath); funcInfo = funcInfo.upper;
info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
}
},
/** if (!hasExtends) {
* Update information of the code path segment when a code path was return;
* 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) {
/* // Reports if `super()` lacked.
* Skip if this is not in a constructor of a class which has a var segments = codePath.returnedSegments;
* valid `extends` part. var calledInEveryPaths = segments.every(isCalledInEveryPath);
*/ var calledInSomePaths = segments.some(isCalledInSomePath);
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
return;
}
// Update information inside of the loop. if (!calledInEveryPaths) {
var isRealLoop = toSegment.prevSegments.length >= 2; 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( // Initialize info.
{first: toSegment, last: fromSegment}, var info = segInfoMap[segment.id] = {
function(segment) { calledInSomePaths: false,
var info = segInfoMap[segment.id]; calledInEveryPaths: false,
validNodes: []
};
// Updates flags. // When there are previous segments, aggregates these.
var prevSegments = segment.prevSegments; var prevSegments = segment.prevSegments;
if (prevSegments.length > 0) {
info.calledInSomePaths = prevSegments.some(isCalledInSomePath); info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
}
},
/**
* Update information of the code path segment when a code path was
* looped.
* @param {CodePathSegment} fromSegment - The code path segment of the
* end of a loop.
* @param {CodePathSegment} toSegment - A code path segment of the head
* of a loop.
* @returns {void}
*/
onCodePathSegmentLoop: function(fromSegment, toSegment) {
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. for (var i = 0; i < nodes.length; ++i) {
if (info.calledInSomePaths || isRealLoop) { var node = nodes[i];
var nodes = info.validNodes;
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) { for (var i = 0; i < segments.length; ++i) {
var node = nodes[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({ context.report({
message: "Unexpected duplicate 'super()'.", message: "Unexpected duplicate 'super()'.",
node: node 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") { * Set the mark to the returned path as `super()` was called.
return; * @param {ASTNode} node - A ReturnStatement node to check.
} * @returns {void}
*/
// Skip if this is not in a constructor. ReturnStatement: function(node) {
if (!(funcInfo && funcInfo.isConstructor)) { if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
return; return;
} }
// Reports if needed. // Skips if no argument.
if (funcInfo.hasExtends) { if (!node.argument) {
return;
}
/* // Returning argument is a substitute of 'super()'.
* This class has a valid `extends` part.
* Checks duplicate `super()`;
*/
var segments = funcInfo.codePath.currentSegments; var segments = funcInfo.codePath.currentSegments;
var duplicate = false;
for (var i = 0; i < segments.length; ++i) { for (var i = 0; i < segments.length; ++i) {
var info = segInfoMap[segments[i].id]; var segment = segments[i];
duplicate = duplicate || info.calledInSomePaths; if (segment.reachable) {
info.calledInSomePaths = info.calledInEveryPaths = true; var info = segInfoMap[segment.id];
}
if (duplicate) { info.calledInSomePaths = info.calledInEveryPaths = true;
context.report({ }
message: "Unexpected duplicate 'super()'.",
node: node
});
} else {
info.validNodes.push(node);
} }
} else { },
/*
* This class does not have a valid `extends` part.
* Disallow `super()`.
*/
context.report({
message: "Unexpected 'super()'.",
node: node
});
}
},
/** /**
* Resets state. * Resets state.
* @returns {void} * @returns {void}
*/ */
"Program:exit": function() { "Program:exit": function() {
segInfoMap = Object.create(null); 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 * @fileoverview Rule to flag statements without curly braces
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
* @copyright 2015 Ivan Nikulin. All rights reserved.
*/ */
"use strict"; "use strict";
@ -15,280 +14,290 @@ var astUtils = require("../ast-utils");
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
var multiOnly = (context.options[0] === "multi"); docs: {
var multiLine = (context.options[0] === "multi-line"); description: "enforce consistent brace style for all control statements",
var multiOrNest = (context.options[0] === "multi-or-nest"); category: "Best Practices",
var consistent = (context.options[1] === "consistent"); recommended: false
},
//--------------------------------------------------------------------------
// 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;
}
/** schema: {
* Gets the `else` keyword token of a given `IfStatement` node. anyOf: [
* @param {ASTNode} node - A `IfStatement` node to get. {
* @returns {Token} The `else` keyword token. type: "array",
*/ items: [
function getElseKeyword(node) { {
var sourceCode = context.getSourceCode(); enum: ["all"]
var token = sourceCode.getTokenAfter(node.consequent); }
],
while (token.type !== "Keyword" || token.value !== "else") { minItems: 0,
token = sourceCode.getTokenAfter(token); 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. * Gets the `else` keyword token of a given `IfStatement` node.
* This returns `true` when below: * @param {ASTNode} node - A `IfStatement` node to get.
* * @returns {Token} The `else` keyword token.
* 1. The given node has the `alternate` node. */
* 2. There is a `IfStatement` which doesn't have `alternate` node in the function getElseKeyword(node) {
* trailing statement chain of the `consequent` node. var sourceCode = context.getSourceCode();
* var token = sourceCode.getTokenAfter(node.consequent);
* @param {ASTNode} node - A IfStatement node to check.
* @returns {boolean} `true` if the node requires braces of the consequent chunk. while (token.type !== "Keyword" || token.value !== "else") {
*/ token = sourceCode.getTokenAfter(token);
function requiresBraceOfConsequent(node) {
if (node.alternate && node.consequent.type === "BlockStatement") {
if (node.consequent.body.length >= 2) {
return true;
} }
node = node.consequent.body[0]; return token;
while (node) { }
if (node.type === "IfStatement" && !node.alternate) {
/**
* 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; 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 * Reports "Expected { after ..." error
* @param {ASTNode} node The node to report. * @param {ASTNode} node The node to report.
* @param {string} name The name to report. * @param {string} name The name to report.
* @param {string} suffix Additional string to add to the end of a report. * @param {string} suffix Additional string to add to the end of a report.
* @returns {void} * @returns {void}
* @private * @private
*/ */
function reportExpectedBraceError(node, name, suffix) { function reportExpectedBraceError(node, name, suffix) {
context.report({ context.report({
node: node, node: node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
message: "Expected { after '{{name}}'{{suffix}}.", message: "Expected { after '{{name}}'{{suffix}}.",
data: { data: {
name: name, name: name,
suffix: (suffix ? " " + suffix : "") suffix: (suffix ? " " + suffix : "")
} }
}); });
} }
/** /**
* Reports "Unnecessary { after ..." error * Reports "Unnecessary { after ..." error
* @param {ASTNode} node The node to report. * @param {ASTNode} node The node to report.
* @param {string} name The name to report. * @param {string} name The name to report.
* @param {string} suffix Additional string to add to the end of a report. * @param {string} suffix Additional string to add to the end of a report.
* @returns {void} * @returns {void}
* @private * @private
*/ */
function reportUnnecessaryBraceError(node, name, suffix) { function reportUnnecessaryBraceError(node, name, suffix) {
context.report({ context.report({
node: node, node: node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
message: "Unnecessary { after '{{name}}'{{suffix}}.", message: "Unnecessary { after '{{name}}'{{suffix}}.",
data: { data: {
name: name, name: name,
suffix: (suffix ? " " + suffix : "") suffix: (suffix ? " " + suffix : "")
} }
}); });
} }
/** /**
* Prepares to check the body of a node to see if it's a block statement. * 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} node The node to report if there's a problem.
* @param {ASTNode} body The body node to check for blocks. * @param {ASTNode} body The body node to check for blocks.
* @param {string} name The name to report if there's a problem. * @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. * @param {string} suffix Additional string to add to the end of a report.
* @returns {object} a prepared check object, with "actual", "expected", "check" properties. * @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. * "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 * "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 * `null` if it doesn't matter, depending on the rule options. It can be modified to change
* the final behavior of "check". * the final behavior of "check".
* "check" will be a function reporting appropriate problems depending on the other * "check" will be a function reporting appropriate problems depending on the other
* properties. * properties.
*/ */
function prepareCheck(node, body, name, suffix) { function prepareCheck(node, body, name, suffix) {
var hasBlock = (body.type === "BlockStatement"); var hasBlock = (body.type === "BlockStatement");
var expected = null; var expected = null;
if (node.type === "IfStatement" && node.consequent === body && requiresBraceOfConsequent(node)) { 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)) {
expected = true; expected = true;
} } else if (multiOnly) {
} else if (multiOrNest) { if (hasBlock && body.body.length === 1) {
if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) { expected = false;
expected = false; }
} else if (!isOneLiner(body)) { } 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; expected = true;
} }
} else {
expected = true;
}
return { return {
actual: hasBlock, actual: hasBlock,
expected: expected, expected: expected,
check: function() { check: function() {
if (this.expected !== null && this.expected !== this.actual) { if (this.expected !== null && this.expected !== this.actual) {
if (this.expected) { if (this.expected) {
reportExpectedBraceError(node, name, suffix); reportExpectedBraceError(node, name, suffix);
} else { } else {
reportUnnecessaryBraceError(node, name, suffix); 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; /**
} * 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
// Public * 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 { preparedChecks.forEach(function(preparedCheck) {
"IfStatement": function(node) { preparedCheck.expected = expected;
if (node.parent.type !== "IfStatement") {
prepareIfChecks(node).forEach(function(preparedCheck) {
preparedCheck.check();
}); });
} }
},
"WhileStatement": function(node) { return preparedChecks;
prepareCheck(node, node.body, "while", "condition").check(); }
},
"DoWhileStatement": function(node) { //--------------------------------------------------------------------------
prepareCheck(node, node.body, "do").check(); // Public
}, //--------------------------------------------------------------------------
"ForStatement": function(node) { return {
prepareCheck(node, node.body, "for", "condition").check(); IfStatement: function(node) {
}, if (node.parent.type !== "IfStatement") {
prepareIfChecks(node).forEach(function(preparedCheck) {
preparedCheck.check();
});
}
},
"ForInStatement": function(node) { WhileStatement: function(node) {
prepareCheck(node, node.body, "for-in").check(); prepareCheck(node, node.body, "while", "condition").check();
}, },
"ForOfStatement": function(node) { DoWhileStatement: function(node) {
prepareCheck(node, node.body, "for-of").check(); prepareCheck(node, node.body, "do").check();
} },
};
};
module.exports.schema = { ForStatement: function(node) {
"anyOf": [ prepareCheck(node, node.body, "for", "condition").check();
{ },
"type": "array",
"items": [ ForInStatement: function(node) {
{ prepareCheck(node, node.body, "for-in").check();
"enum": ["all"] },
}
], ForOfStatement: function(node) {
"minItems": 0, prepareCheck(node, node.body, "for-of").check();
"maxItems": 1 }
}, };
{ }
"type": "array",
"items": [
{
"enum": ["multi", "multi-line", "multi-or-nest"]
},
{
"enum": ["consistent"]
}
],
"minItems": 0,
"maxItems": 2
}
]
}; };

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

@ -4,67 +4,89 @@
*/ */
"use strict"; "use strict";
var COMMENT_VALUE = "no default"; var DEFAULT_COMMENT_PATTERN = /^no default$/;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
//-------------------------------------------------------------------------- docs: {
// Helpers description: "require `default` cases in <code>switch</code> statements",
//-------------------------------------------------------------------------- category: "Best Practices",
recommended: false
/** },
* Shortcut to get last element of array
* @param {*[]} collection Array schema: [{
* @returns {*} Last element type: "object",
*/ properties: {
function last(collection) { commentPattern: {
return collection[collection.length - 1]; 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 * skip check of empty switch because there is no easy way
* to extract comments inside it now * to extract comments inside it now
*/ */
return; return;
} }
var hasDefault = node.cases.some(function(v) { var hasDefault = node.cases.some(function(v) {
return v.test === null; return v.test === null;
}); });
if (!hasDefault) { if (!hasDefault) {
var comment; var comment;
var comments; var comments;
var lastCase = last(node.cases); var lastCase = last(node.cases);
comments = context.getComments(lastCase).trailing; comments = context.getComments(lastCase).trailing;
if (comments.length) { if (comments.length) {
comment = last(comments); comment = last(comments);
} }
if (!comment || comment.value.trim() !== COMMENT_VALUE) { if (!comment || !commentPattern.test(comment.value.trim())) {
context.report(node, "Expected a default case."); 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 * @fileoverview Validates newlines before and after dots
* @author Greg Cochard * @author Greg Cochard
* @copyright 2015 Greg Cochard
*/ */
"use strict"; "use strict";
@ -12,51 +11,61 @@ var astUtils = require("../ast-utils");
// Rule Definition // 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], schema: [
onObject; {
enum: ["object", "property"]
}
]
},
create: function(context) {
var config = context.options[0],
onObject;
// default to onObject if no preference is passed // default to onObject if no preference is passed
onObject = config === "object" || !config; onObject = config === "object" || !config;
/** /**
* Reports if the dot between object and property is on the correct loccation. * Reports if the dot between object and property is on the correct loccation.
* @param {ASTNode} obj The object owning the property. * @param {ASTNode} obj The object owning the property.
* @param {ASTNode} prop The property of the object. * @param {ASTNode} prop The property of the object.
* @param {ASTNode} node The corresponding node of the token. * @param {ASTNode} node The corresponding node of the token.
* @returns {void} * @returns {void}
*/ */
function checkDotLocation(obj, prop, node) { function checkDotLocation(obj, prop, node) {
var dot = context.getTokenBefore(prop); var dot = context.getTokenBefore(prop);
if (dot.type === "Punctuator" && dot.value === ".") { if (dot.type === "Punctuator" && dot.value === ".") {
if (onObject) { if (onObject) {
if (!astUtils.isTokenOnSameLine(obj, dot)) { if (!astUtils.isTokenOnSameLine(obj, dot)) {
context.report(node, dot.loc.start, "Expected dot to be on same line as object."); 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. * Checks the spacing of the dot within a member expression.
* @param {ASTNode} node The node to check. * @param {ASTNode} node The node to check.
* @returns {void} * @returns {void}
*/ */
function checkNode(node) { function checkNode(node) {
checkDotLocation(node.object, node.property, node); checkDotLocation(node.object, node.property, node);
} }
return {
"MemberExpression": checkNode
};
};
module.exports.schema = [ return {
{ MemberExpression: checkNode
"enum": ["object", "property"] };
} }
]; };

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

@ -11,50 +11,60 @@
var validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; var validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
var keywords = require("../util/keywords"); var keywords = require("../util/keywords");
module.exports = function(context) { module.exports = {
var options = context.options[0] || {}; meta: {
var allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords; 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) { create: function(context) {
allowPattern = new RegExp(options.allowPattern); var options = context.options[0] || {};
} var allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords;
return { var allowPattern;
"MemberExpression": function(node) {
if ( if (options.allowPattern) {
node.computed && allowPattern = new RegExp(options.allowPattern);
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.");
}
} }
};
};
module.exports.schema = [ return {
{ MemberExpression: function(node) {
"type": "object", if (
"properties": { node.computed &&
"allowKeywords": { node.property.type === "Literal" &&
"type": "boolean" validIdentifier.test(node.property.value) &&
}, (allowKeywords || keywords.indexOf("" + node.property.value) === -1)
"allowPattern": { ) {
"type": "string" 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 // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
//-------------------------------------------------------------------------- docs: {
// Public description: "enforce at least one newline at the end of files",
//-------------------------------------------------------------------------- category: "Stylistic Issues",
recommended: false
return { },
"Program": function checkBadEOF(node) { fixable: "whitespace",
// Get the whole source code, not for node only. schema: [
var src = context.getSource(), {
location = {column: 1}, enum: ["unix", "windows"]
linebreakStyle = context.options[0] || "unix", }
linebreak = linebreakStyle === "unix" ? "\n" : "\r\n"; ]
},
if (src[src.length - 1] !== "\n") {
create: function(context) {
// file is not newline-terminated
location.line = src.split(/\n/g).length; //--------------------------------------------------------------------------
context.report({ // Public
node: node, //--------------------------------------------------------------------------
loc: location,
message: "Newline required at end of file but not found.", return {
fix: function(fixer) {
return fixer.insertTextAfterRange([0, src.length], linebreak); 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 === * @fileoverview Rule to flag statements that use != and == instead of !== and ===
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "use strict";
@ -11,91 +9,101 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
/** docs: {
* Checks if an expression is a typeof expression description: "require the use of `===` and `!==`",
* @param {ASTNode} node The node to check category: "Best Practices",
* @returns {boolean} if the node is a typeof expression recommended: false
*/ },
function isTypeOf(node) {
return node.type === "UnaryExpression" && node.operator === "typeof"; schema: [
} {
enum: ["smart", "allow-null"]
/** }
* 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 create: function(context) {
*/
function isTypeOfBinary(node) { /**
return isTypeOf(node.left) || isTypeOf(node.right); * 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) * Checks if either operand of a binary expression is a typeof operation
* @param {ASTNode} node The node to check * @param {ASTNode} node The node to check
* @returns {boolean} if operands are of same type * @returns {boolean} if one of the operands is typeof
* @private * @private
*/ */
function areLiteralsAndSameType(node) { function isTypeOfBinary(node) {
return node.left.type === "Literal" && node.right.type === "Literal" && return isTypeOf(node.left) || isTypeOf(node.right);
typeof node.left.value === typeof node.right.value; }
}
/** /**
* Checks if one of the operands is a literal null * Checks if operands are literals of the same type (via typeof)
* @param {ASTNode} node The node to check * @param {ASTNode} node The node to check
* @returns {boolean} if operands are null * @returns {boolean} if operands are of same type
* @private * @private
*/ */
function isNullCheck(node) { function areLiteralsAndSameType(node) {
return (node.right.type === "Literal" && node.right.value === null) || return node.left.type === "Literal" && node.right.type === "Literal" &&
(node.left.type === "Literal" && node.left.value === null); typeof node.left.value === typeof node.right.value;
} }
/** /**
* Gets the location (line and column) of the binary expression's operator * Checks if one of the operands is a literal null
* @param {ASTNode} node The binary expression node to check * @param {ASTNode} node The node to check
* @param {String} operator The operator to find * @returns {boolean} if operands are null
* @returns {Object} { line, column } location of operator * @private
* @private */
*/ function isNullCheck(node) {
function getOperatorLocation(node) { return (node.right.type === "Literal" && node.right.value === null) ||
var opToken = context.getTokenAfter(node.left); (node.left.type === "Literal" && node.left.value === null);
}
return {line: opToken.loc.start.line, column: opToken.loc.start.column};
}
return { /**
"BinaryExpression": function(node) { * Gets the location (line and column) of the binary expression's operator
if (node.operator !== "==" && node.operator !== "!=") { * @param {ASTNode} node The binary expression node to check
return; * @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) || return {
areLiteralsAndSameType(node) || isNullCheck(node))) { BinaryExpression: function(node) {
return; if (node.operator !== "==" && node.operator !== "!=") {
} return;
}
if (context.options[0] === "allow-null" && isNullCheck(node)) { if (context.options[0] === "smart" && (isTypeOfBinary(node) ||
return; areLiteralsAndSameType(node) || isNullCheck(node))) {
} return;
}
context.report({ if (context.options[0] === "allow-null" && isNullCheck(node)) {
node: node, return;
loc: getOperatorLocation(node), }
message: "Expected '{{op}}=' and instead saw '{{op}}'.",
data: { op: node.operator }
});
} 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. * @fileoverview Rule to warn when a function expression does not have a name.
* @author Kyle T. Nunery * @author Kyle T. Nunery
* @copyright 2015 Brandon Mills. All rights reserved.
* @copyright 2014 Kyle T. Nunery. All rights reserved.
*/ */
"use strict"; "use strict";
@ -11,35 +9,45 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
/** docs: {
* Determines whether the current FunctionExpression node is a get, set, or description: "enforce named `function` expressions",
* shorthand method in an object literal or a class. category: "Stylistic Issues",
* @returns {boolean} True if the node is a get, set, or shorthand method. recommended: false
*/ },
function isObjectOrClassMethod() {
var parent = context.getAncestors().pop(); schema: []
},
return (parent.type === "MethodDefinition" || (
parent.type === "Property" && ( create: function(context) {
parent.method ||
parent.kind === "get" || /**
parent.kind === "set" * 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 { return {
"FunctionExpression": function(node) { FunctionExpression: function(node) {
var name = node.id && node.id.name; var name = node.id && node.id.name;
if (!name && !isObjectOrClassMethod()) { if (!name && !isObjectOrClassMethod()) {
context.report(node, "Missing function expression name."); 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 * @fileoverview Rule to enforce a particular function style
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
*/ */
"use strict"; "use strict";
@ -9,76 +8,86 @@
// Rule Definition // 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], create: function(context) {
allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions === true,
enforceDeclarations = (style === "declaration"),
stack = [];
var nodesToCheck = { var style = context.options[0],
"Program": function() { allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions === true,
enforceDeclarations = (style === "declaration"),
stack = []; stack = [];
},
"FunctionDeclaration": function(node) { var nodesToCheck = {
stack.push(false); Program: function() {
stack = [];
},
if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") { FunctionDeclaration: function(node) {
context.report(node, "Expected a function expression."); stack.push(false);
}
},
"FunctionDeclaration:exit": function() {
stack.pop();
},
"FunctionExpression": function(node) { if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") {
stack.push(false); context.report(node, "Expected a function expression.");
}
},
"FunctionDeclaration:exit": function() {
stack.pop();
},
if (enforceDeclarations && node.parent.type === "VariableDeclarator") { FunctionExpression: function(node) {
context.report(node.parent, "Expected a function declaration."); stack.push(false);
}
},
"FunctionExpression:exit": function() {
stack.pop();
},
"ThisExpression": function() { if (enforceDeclarations && node.parent.type === "VariableDeclarator") {
if (stack.length > 0) { context.report(node.parent, "Expected a function declaration.");
stack[stack.length - 1] = true; }
} },
} "FunctionExpression:exit": function() {
}; stack.pop();
},
if (!allowArrowFunctions) { ThisExpression: function() {
nodesToCheck.ArrowFunctionExpression = function() { if (stack.length > 0) {
stack.push(false); stack[stack.length - 1] = true;
}
}
}; };
nodesToCheck["ArrowFunctionExpression:exit"] = function(node) { if (!allowArrowFunctions) {
var hasThisExpr = stack.pop(); nodesToCheck.ArrowFunctionExpression = function() {
stack.push(false);
};
if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") { nodesToCheck["ArrowFunctionExpression:exit"] = function(node) {
context.report(node.parent, "Expected a function declaration."); 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. * @fileoverview Rule to check the spacing around the * in generator functions.
* @author Jamund Ferguson * @author Jamund Ferguson
* @copyright 2015 Brandon Mills. All rights reserved.
* @copyright 2014 Jamund Ferguson. All rights reserved.
*/ */
"use strict"; "use strict";
@ -11,102 +9,114 @@
// Rule Definition // 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) { fixable: "whitespace",
if (!option || typeof option === "string") {
return { schema: [
before: { before: true, after: false }, {
after: { before: false, after: true }, oneOf: [
both: { before: true, after: true }, {
neither: { before: false, after: false } enum: ["before", "after", "both", "neither"]
}[option || "before"]; },
} {
return option; type: "object",
}(context.options[0])); properties: {
before: {type: "boolean"},
/** after: {type: "boolean"}
* Checks the spacing between two tokens before or after the star token. },
* @param {string} side Either "before" or "after". additionalProperties: false
* @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, " ");
} }
return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); ]
} }
}); ]
} },
}
/** create: function(context) {
* 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;
if (!node.generator) { var mode = (function(option) {
return; 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); * Checks the spacing between two tokens before or after the star token.
} else { * @param {string} side Either "before" or "after".
starToken = context.getFirstToken(node, 1); * @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 context.report({
prevToken = context.getTokenBefore(starToken); node: node,
if (prevToken.value === "function" || prevToken.value === "static") { message: message,
checkSpacing("before", prevToken, starToken); 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 { if (!node.generator) {
"FunctionDeclaration": checkFunction, return;
"FunctionExpression": checkFunction }
};
}; if (node.parent.method || node.parent.type === "MethodDefinition") {
starToken = context.getTokenBefore(node, 1);
} else {
starToken = context.getFirstToken(node, 1);
}
module.exports.schema = [ // Only check before when preceded by `function` keyword
{ prevToken = context.getTokenBefore(starToken);
"oneOf": [ if (prevToken.value === "function" || prevToken.value === "static") {
{ checkSpacing("before", prevToken, starToken);
"enum": ["before", "after", "both", "neither"]
},
{
"type": "object",
"properties": {
"before": {"type": "boolean"},
"after": {"type": "boolean"}
},
"additionalProperties": false
} }
]
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 * @fileoverview Rule for disallowing require() outside of the top-level module context
* @author Jamund Ferguson * @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
*/ */
"use strict"; "use strict";
@ -49,22 +48,32 @@ function isShadowed(scope, node) {
return reference && reference.resolved && reference.resolved.defs.length > 0; return reference && reference.resolved && reference.resolved.defs.length > 0;
} }
module.exports = function(context) { module.exports = {
return { meta: {
"CallExpression": function(node) { docs: {
var currentScope = context.getScope(), description: "require `require()` calls to be placed at top-level module scope",
isGoodRequire; category: "Node.js and CommonJS",
recommended: false
},
if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { schema: []
isGoodRequire = context.getAncestors().every(function(parent) { },
return ACCEPTABLE_PARENTS.indexOf(parent.type) > -1;
}); create: function(context) {
if (!isGoodRequire) { return {
context.report(node, "Unexpected require()."); 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 // 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) {
/* return {
* 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") { ForInStatement: function(node) {
context.report(node, "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.");
/*
* 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. * @fileoverview Ensure handling of errors when we know they exist.
* @author Jamund Ferguson * @author Jamund Ferguson
* @copyright 2015 Mathias Schreck.
* @copyright 2014 Jamund Ferguson. All rights reserved.
*/ */
"use strict"; "use strict";
@ -11,73 +9,83 @@
// Rule Definition // 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"
}
]
},
/** create: function(context) {
* 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 firstChar === "^"; var errorArgument = context.options[0] || "err";
}
/** /**
* Checks if the given name matches the configured error argument. * Checks if the given argument should be interpreted as a regexp pattern.
* @param {string} name The name which should be compared. * @param {string} stringToCheck The string which should be checked.
* @returns {boolean} Whether or not the given name matches the configured error variable name. * @returns {boolean} Whether or not the string should be interpreted as a pattern.
*/ */
function matchesConfiguredErrorName(name) { function isPattern(stringToCheck) {
if (isPattern(errorArgument)) { var firstChar = stringToCheck[0];
var regexp = new RegExp(errorArgument);
return regexp.test(name); return firstChar === "^";
} }
return name === errorArgument;
}
/** /**
* Get the parameters of a given function scope. * Checks if the given name matches the configured error argument.
* @param {object} scope The function scope. * @param {string} name The name which should be compared.
* @returns {array} All parameters of the given scope. * @returns {boolean} Whether or not the given name matches the configured error variable name.
*/ */
function getParameters(scope) { function matchesConfiguredErrorName(name) {
return scope.variables.filter(function(variable) { if (isPattern(errorArgument)) {
return variable.defs[0] && variable.defs[0].type === "Parameter"; var regexp = new RegExp(errorArgument);
});
}
/** return regexp.test(name);
* 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 name === errorArgument;
} }
}
return { /**
"FunctionDeclaration": checkForError, * Get the parameters of a given function scope.
"FunctionExpression": checkForError, * @param {object} scope The function scope.
"ArrowFunctionExpression": checkForError * @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 * @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) * @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"; "use strict";
@ -14,97 +10,108 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
docs: {
//-------------------------------------------------------------------------- description: "disallow specified identifiers",
// Helpers 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 // Helpers
* @param {String} name The string to check. //--------------------------------------------------------------------------
* @returns {boolean} if the string is a match
* @private
*/
function isInvalid(name) {
return blacklist.indexOf(name) !== -1;
}
/** var blacklist = context.options;
* 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}}' 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, * Verifies if we should report an error or not based on the effective
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; * 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 return {
if (node.parent.object.type === "Identifier" &&
node.parent.object.name === node.name) { Identifier: function(node) {
if (isInvalid(name)) { var name = node.name,
report(node); 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 // Properties have their own rules
} else if (effectiveParent.type === "AssignmentExpression" && } else if (node.parent.type === "Property") {
(effectiveParent.right.type !== "MemberExpression" ||
effectiveParent.left.type === "MemberExpression" && if (shouldReport(effectiveParent, name)) {
effectiveParent.left.property.name === node.name)) {
if (isInvalid(name)) {
report(node); report(node);
} }
}
// Properties have their own rules // Report anything that is a match and not a CallExpression
} else if (node.parent.type === "Property") { } else if (shouldReport(effectiveParent, name)) {
if (shouldReport(effectiveParent, name)) {
report(node); 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 * @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 * @author Burak Yigit Kaya aka BYK
* @copyright 2015 Burak Yigit Kaya. All rights reserved.
* @copyright 2015 Mathieu M-Gosselin. All rights reserved.
*/ */
"use strict"; "use strict";
@ -12,98 +10,105 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var options = context.options[0] || {}; meta: {
var minLength = typeof options.min !== "undefined" ? options.min : 2; docs: {
var maxLength = typeof options.max !== "undefined" ? options.max : Infinity; description: "enforce minimum and maximum identifier lengths",
var properties = options.properties !== "never"; category: "Stylistic Issues",
var exceptions = (options.exceptions ? options.exceptions : []) recommended: false
.reduce(function(obj, item) { },
obj[item] = true;
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 = { create: function(context) {
"MemberExpression": properties && function(parent) { var options = context.options[0] || {};
return !parent.computed && ( 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 return obj;
parent.parent.left === parent || ( }, {});
// or the last identifier in an ObjectPattern destructuring var SUPPORTED_EXPRESSIONS = {
parent.parent.type === "Property" && parent.parent.value === parent && MemberExpression: properties && function(parent) {
parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent return !parent.computed && (
)
);
},
"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
};
return { // regular property assignment
Identifier: function(node) { (parent.parent.left === parent || // or the last identifier in an ObjectPattern destructuring
var name = node.name; parent.parent.type === "Property" && parent.parent.value === parent &&
var parent = node.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; return {
var isLong = name.length > maxLength; Identifier: function(node) {
var name = node.name;
var parent = node.parent;
if (!(isShort || isLong) || exceptions[name]) { var isShort = name.length < minLength;
return; // Nothing to report 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))) { var isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
context.report(
node,
isShort ?
"Identifier name '{{name}}' is too short. (< {{min}})" :
"Identifier name '{{name}}' is too long. (> {{max}})",
{ name: name, min: minLength, max: maxLength }
);
}
}
};
};
module.exports.schema = [ if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) {
{ context.report(
"type": "object", node,
"properties": { isShort ?
"min": { "Identifier name '{{name}}' is too short. (< {{min}})" :
"type": "number" "Identifier name '{{name}}' is too long. (> {{max}})",
}, { name: name, min: minLength, max: maxLength }
"max": { );
"type": "number"
},
"exceptions": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string"
} }
},
"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 * @fileoverview Rule to flag non-matching identifiers
* @author Matthieu Larcher * @author Matthieu Larcher
* @copyright 2015 Matthieu Larcher. All rights reserved.
* See LICENSE in root directory for full license.
*/ */
"use strict"; "use strict";
@ -11,121 +9,132 @@
// Rule Definition // 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"
}
}
}
]
},
//-------------------------------------------------------------------------- create: function(context) {
// Helpers
//--------------------------------------------------------------------------
var pattern = context.options[0] || "^.+$", //--------------------------------------------------------------------------
regexp = new RegExp(pattern); // Helpers
//--------------------------------------------------------------------------
var options = context.options[1] || {}, var pattern = context.options[0] || "^.+$",
properties = options.properties; regexp = new RegExp(pattern);
// cast to boolean and default to false var options = context.options[1] || {},
properties = !!properties; 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 * Verifies if we should report an error or not based on the effective
* @param {String} name The string to check. * parent node and the identifier name.
* @returns {boolean} if the string is a match * @param {ASTNode} effectiveParent The effective parent node of the node to be reported
* @private * @param {String} name The identifier name of the identifier node
*/ * @returns {boolean} whether an error should be reported or not
function isInvalid(name) { */
return !regexp.test(name); 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 * Reports an AST node as a rule violation.
* parent node and the identifier name. * @param {ASTNode} node The node to report.
* @param {ASTNode} effectiveParent The effective parent node of the node to be reported * @returns {void}
* @param {String} name The identifier name of the identifier node * @private
* @returns {boolean} whether an error should be reported or not */
*/ function report(node) {
function shouldReport(effectiveParent, name) { context.report(node, "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", {
return effectiveParent.type !== "CallExpression" name: node.name,
&& effectiveParent.type !== "NewExpression" && pattern: pattern
isInvalid(name); });
} }
/** return {
* 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 { Identifier: function(node) {
var name = node.name,
parent = node.parent,
effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent;
"Identifier": function(node) { if (parent.type === "MemberExpression") {
var name = node.name,
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
// MemberExpressions get special rules if (!properties) {
if (node.parent.type === "MemberExpression") { return;
}
// return early if properties is false // Always check object names
if (!properties) { if (parent.object.type === "Identifier" &&
return; parent.object.name === name) {
} if (isInvalid(name)) {
report(node);
}
// Always check object names // Report AssignmentExpressions only if they are the left side of the assignment
if (node.parent.object.type === "Identifier" && } else if (effectiveParent.type === "AssignmentExpression" &&
node.parent.object.name === node.name) { (effectiveParent.right.type !== "MemberExpression" ||
if (isInvalid(name)) { effectiveParent.left.type === "MemberExpression" &&
report(node); effectiveParent.left.property.name === name)) {
if (isInvalid(name)) {
report(node);
}
} }
// Report AssignmentExpressions only if they are the left side of the assignment } else if (parent.type === "Property") {
} else if (effectiveParent.type === "AssignmentExpression" &&
(effectiveParent.right.type !== "MemberExpression" || if (!properties || parent.key.name !== name) {
effectiveParent.left.type === "MemberExpression" && return;
effectiveParent.left.property.name === node.name)) { }
if (isInvalid(name)) {
if (shouldReport(effectiveParent, name)) {
report(node); report(node);
} }
}
// Properties have their own rules } else {
} else if (node.parent.type === "Property") { var isDeclaration = effectiveParent.type === "FunctionDeclaration" || effectiveParent.type === "VariableDeclarator";
// return early if properties is false if (onlyDeclarations && !isDeclaration) {
if (!properties) { return;
return; }
}
if (shouldReport(effectiveParent, name)) { if (shouldReport(effectiveParent, name)) {
report(node); 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. * @fileoverview A rule to control the style of variable initializations.
* @author Colin Ihrig * @author Colin Ihrig
* @copyright 2015 Colin Ihrig. All rights reserved.
*/ */
"use strict"; "use strict";
@ -43,74 +42,84 @@ function isInitialized(node) {
// Rule Definition // 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", schema: {
MODE_NEVER = "never"; 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; create: function(context) {
var params = context.options[1] || {};
//-------------------------------------------------------------------------- var MODE_ALWAYS = "always",
// Public API MODE_NEVER = "never";
//--------------------------------------------------------------------------
return { var mode = context.options[0] || MODE_ALWAYS;
"VariableDeclaration:exit": function(node) { var params = context.options[1] || {};
var kind = node.kind, //--------------------------------------------------------------------------
declarations = node.declarations; // Public API
//--------------------------------------------------------------------------
for (var i = 0; i < declarations.length; ++i) { return {
var declaration = declarations[i], "VariableDeclaration:exit": function(node) {
id = declaration.id,
initialized = isInitialized(declaration),
isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent);
if (id.type !== "Identifier") { var kind = node.kind,
continue; declarations = node.declarations;
}
if (mode === MODE_ALWAYS && !initialized) { for (var i = 0; i < declarations.length; ++i) {
context.report(declaration, "Variable '" + id.name + "' should be initialized on declaration."); var declaration = declarations[i],
} else if (mode === MODE_NEVER && kind !== "const" && initialized && !isIgnoredForLoop) { id = declaration.id,
context.report(declaration, "Variable '" + id.name + "' should not be initialized on declaration."); initialized = isInitialized(declaration),
} isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent);
}
}
};
};
module.exports.schema = { if (id.type !== "Identifier") {
"anyOf": [ continue;
{ }
"type": "array",
"items": [ if (mode === MODE_ALWAYS && !initialized) {
{ context.report(declaration, "Variable '" + id.name + "' should be initialized on declaration.");
"enum": ["always"] } else if (mode === MODE_NEVER && kind !== "const" && initialized && !isIgnoredForLoop) {
} context.report(declaration, "Variable '" + id.name + "' should not be initialized on declaration.");
], }
"minItems": 0,
"maxItems": 1
},
{
"type": "array",
"items": [
{
"enum": ["never"]
},
{
"type": "object",
"properties": {
"ignoreForLoopInit": {
"type": "boolean"
}
},
"additionalProperties": false
} }
], }
"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. * @fileoverview A rule to ensure consistent quotes used in jsx syntax.
* @author Mathias Schreck <https://github.com/lo1tuma> * @author Mathias Schreck <https://github.com/lo1tuma>
* @copyright 2015 Mathias Schreck
*/ */
"use strict"; "use strict";
@ -37,39 +36,51 @@ var QUOTE_SETTINGS = {
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var quoteOption = context.options[0] || "prefer-double", meta: {
setting = QUOTE_SETTINGS[quoteOption]; docs: {
description: "enforce the consistent use of either double or single quotes in JSX attributes",
category: "Stylistic Issues",
recommended: false
},
/** fixable: "whitespace",
* 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;
if (attributeValue && astUtils.isStringLiteral(attributeValue) && !usesExpectedQuotes(attributeValue)) { schema: [
context.report({ {
node: attributeValue, enum: [ "prefer-single", "prefer-double" ]
message: "Unexpected usage of " + setting.description + ".",
fix: function(fixer) {
return fixer.replaceText(attributeValue, setting.convert(attributeValue.raw));
}
});
} }
]
},
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 = [ return {
{ JSXAttribute: function(node) {
"enum": [ "prefer-single", "prefer-double" ] 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 * @fileoverview Rule to specify spacing of object literal keys and values
* @author Brandon Mills * @author Brandon Mills
* @copyright 2014 Brandon Mills. All rights reserved.
*/ */
"use strict"; "use strict";
@ -111,332 +110,341 @@ var messages = {
value: "{{error}} space before value for {{computed}}key '{{key}}'." value: "{{error}} space before value for {{computed}}key '{{key}}'."
}; };
module.exports = function(context) { module.exports = {
meta: {
/** docs: {
* OPTIONS description: "enforce consistent spacing between keys and values in object literal properties",
* "key-spacing": [2, { category: "Stylistic Issues",
* beforeColon: false, recommended: 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"
);
}
/** schema: [{
* Starting from the given a node (a property.key node here) looks forward anyOf: [
* until it finds the last token before a colon punctuator and returns it. {
* @param {ASTNode} node The node to start looking from. type: "object",
* @returns {ASTNode} The last token before a colon punctuator. properties: {
*/ align: {
function getLastTokenBeforeColon(node) { enum: ["colon", "value"]
var prevNode; },
mode: {
while (node && (node.type !== "Punctuator" || node.value !== ":")) { enum: ["strict", "minimum"]
prevNode = node; },
node = context.getTokenAfter(node); 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 last token before a colon punctuator and returns it.
/** * @param {ASTNode} node The node to start looking from.
* Starting from the given a node (a property.key node here) looks forward * @returns {ASTNode} The last token before a colon punctuator.
* until it finds the colon punctuator and returns it. */
* @param {ASTNode} node The node to start looking from. function getLastTokenBeforeColon(node) {
* @returns {ASTNode} The colon punctuator. var prevNode;
*/
function getNextColon(node) { while (node && (node.type !== "Punctuator" || node.value !== ":")) {
prevNode = node;
node = context.getTokenAfter(node);
}
while (node && (node.type !== "Punctuator" || node.value !== ":")) { return prevNode;
node = context.getTokenAfter(node);
} }
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) {
/** while (node && (node.type !== "Punctuator" || node.value !== ":")) {
* Gets an object literal property's key as the identifier name or string value. node = context.getTokenAfter(node);
* @param {ASTNode} property Property node whose key to retrieve. }
* @returns {string} The property's key.
*/
function getKey(property) {
var key = property.key;
if (property.computed) { return node;
return context.getSource().slice(key.range[0], key.range[1]);
} }
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;
/** if (property.computed) {
* Reports an appropriately-formatted error if spacing is incorrect on one return context.getSource().slice(key.range[0], key.range[1]);
* side of the colon. }
* @param {ASTNode} property Key-value pair in an object literal.
* @param {string} side Side being verified - either "key" or "value". return property.key.name || property.key.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)
});
} }
}
/** /**
* Gets the number of characters in a key, including quotes around string * Reports an appropriately-formatted error if spacing is incorrect on one
* keys and braces around computed property keys. * side of the colon.
* @param {ASTNode} property Property of on object literal. * @param {ASTNode} property Key-value pair in an object literal.
* @returns {int} Width of the key. * @param {string} side Side being verified - either "key" or "value".
*/ * @param {string} whitespace Actual whitespace string.
function getKeyWidth(property) { * @param {int} expected Expected whitespace length.
var startToken, endToken; * @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);
/** return endToken.range[1] - startToken.range[0];
* 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;
}
/** /**
* Creates groups of properties. * Gets the whitespace around the colon in an object literal property.
* @param {ASTNode} node ObjectExpression node being evaluated. * @param {ASTNode} property Property node from an object literal.
* @returns {Array.<ASTNode[]>} Groups of property AST node lists. * @returns {Object} Whitespace before and after the property's colon.
*/ */
function createGroups(node) { function getPropertyWhitespace(property) {
if (node.properties.length === 1) { var whitespace = /(\s*):(\s*)/.exec(context.getSource().slice(
return [node.properties]; 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), * Creates groups of properties.
prev = last(currentGroup); * @param {ASTNode} node ObjectExpression node being evaluated.
* @returns {Array.<ASTNode[]>} Groups of property AST node lists.
if (!prev || continuesPropertyGroup(prev, property)) { */
currentGroup.push(property); function createGroups(node) {
} else { if (node.properties.length === 1) {
groups.push([property]); return [node.properties];
} }
return groups; return node.properties.reduce(function(groups, property) {
}, [ var currentGroup = last(groups),
[] prev = last(currentGroup);
]);
}
/** if (!prev || continuesPropertyGroup(prev, property)) {
* Verifies correct vertical alignment of a group of properties. currentGroup.push(property);
* @param {ASTNode[]} properties List of Property AST nodes. } else {
* @returns {void} groups.push([property]);
*/ }
function verifyGroupAlignment(properties) {
var length = properties.length, return groups;
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; * Verifies correct vertical alignment of a group of properties.
* @param {ASTNode[]} properties List of Property AST nodes.
// Conditionally include one space before or after colon * @returns {void}
targetWidth += (align === "colon" ? beforeColon : afterColon); */
function verifyGroupAlignment(properties) {
for (i = 0; i < length; i++) { var length = properties.length,
property = properties[i]; widths = properties.map(getKeyWidth), // Width of keys, including quotes
whitespace = getPropertyWhitespace(property); targetWidth = Math.max.apply(null, widths),
if (whitespace) { // Object literal getters/setters lack a colon i, property, whitespace, width,
width = widths[i]; align = multiLineOptions.align,
beforeColon = multiLineOptions.beforeColon,
if (align === "value") { afterColon = multiLineOptions.afterColon,
report(property, "key", whitespace.beforeColon, beforeColon, mode); mode = multiLineOptions.mode;
report(property, "value", whitespace.afterColon, targetWidth - width, mode);
} else { // align = "colon" // Conditionally include one space before or after colon
report(property, "key", whitespace.beforeColon, targetWidth - width, mode); targetWidth += (align === "colon" ? beforeColon : afterColon);
report(property, "value", whitespace.afterColon, afterColon, mode);
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. * Verifies vertical alignment, taking into account groups of properties.
* @param {ASTNode} node ObjectExpression node being evaluated. * @param {ASTNode} node ObjectExpression node being evaluated.
* @returns {void} * @returns {void}
*/ */
function verifyAlignment(node) { function verifyAlignment(node) {
createGroups(node).forEach(function(group) { createGroups(node).forEach(function(group) {
verifyGroupAlignment(group.filter(isKeyValueProperty)); verifyGroupAlignment(group.filter(isKeyValueProperty));
}); });
} }
/** /**
* Verifies spacing of property conforms to specified options. * Verifies spacing of property conforms to specified options.
* @param {ASTNode} node Property node being evaluated. * @param {ASTNode} node Property node being evaluated.
* @param {Object} lineOptions Configured singleLine or multiLine options * @param {Object} lineOptions Configured singleLine or multiLine options
* @returns {void} * @returns {void}
*/ */
function verifySpacing(node, lineOptions) { function verifySpacing(node, lineOptions) {
var actual = getPropertyWhitespace(node); var actual = getPropertyWhitespace(node);
if (actual) { // Object literal getters/setters lack colons if (actual) { // Object literal getters/setters lack colons
report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode); report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode);
report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode); report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode);
}
} }
}
/** /**
* Verifies spacing of each property in a list. * Verifies spacing of each property in a list.
* @param {ASTNode[]} properties List of Property AST nodes. * @param {ASTNode[]} properties List of Property AST nodes.
* @returns {void} * @returns {void}
*/ */
function verifyListSpacing(properties) { function verifyListSpacing(properties) {
var length = properties.length; var length = properties.length;
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
verifySpacing(properties[i], singleLineOptions); verifySpacing(properties[i], singleLineOptions);
}
} }
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public API // Public API
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
if (multiLineOptions.align) { // Verify vertical alignment if (multiLineOptions.align) { // Verify vertical alignment
return { return {
"ObjectExpression": function(node) { ObjectExpression: function(node) {
if (isSingleLine(node)) { if (isSingleLine(node)) {
verifyListSpacing(node.properties.filter(isKeyValueProperty)); verifyListSpacing(node.properties.filter(isKeyValueProperty));
} else { } else {
verifyAlignment(node); verifyAlignment(node);
}
} }
} };
};
} else { // Obey beforeColon and afterColon in each property as configured } else { // Obey beforeColon and afterColon in each property as configured
return { return {
"Property": function(node) { Property: function(node) {
verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions); 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. * @fileoverview Rule to enforce spacing before and after keywords.
* @author Toru Nagashima * @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "use strict";
@ -65,470 +63,482 @@ function isCloseParenOfTemplate(token) {
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var sourceCode = context.getSourceCode(); meta: {
docs: {
/** description: "enforce consistent spacing before and after keywords",
* Reports a given token if there are not space(s) before the token. category: "Stylistic Issues",
* recommended: false
* @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, " ");
}
});
}
}
/** fixable: "whitespace",
* Reports a given token if there are space(s) before the token.
* schema: [
* @param {Token} token - A token to report. {
* @param {RegExp|undefined} pattern - Optional. A pattern of the previous type: "object",
* token to check. properties: {
* @returns {void} before: {type: "boolean"},
*/ after: {type: "boolean"},
function unexpectSpaceBefore(token, pattern) { overrides: {
pattern = pattern || PREV_TOKEN; type: "object",
properties: KEYS.reduce(function(retv, key) {
var prevToken = sourceCode.getTokenBefore(token); retv[key] = {
type: "object",
if (prevToken && properties: {
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && before: {type: "boolean"},
!isOpenParenOfTemplate(prevToken) && after: {type: "boolean"}
astUtils.isTokenOnSameLine(prevToken, token) && },
sourceCode.isSpaceBetweenTokens(prevToken, token) additionalProperties: false
) { };
context.report({ return retv;
loc: token.loc.start, }, {}),
message: "Unexpected space(s) before \"{{value}}\".", additionalProperties: false
data: token, }
fix: function(fixer) { },
return fixer.removeRange([prevToken.range[1], token.range[0]]); 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. * Reports a given token if there are space(s) before the token.
* *
* @param {Token} token - A token to report. * @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the next * @param {RegExp|undefined} pattern - Optional. A pattern of the previous
* token to check. * token to check.
* @returns {void} * @returns {void}
*/ */
function expectSpaceAfter(token, pattern) { function unexpectSpaceBefore(token, pattern) {
pattern = pattern || NEXT_TOKEN; pattern = pattern || PREV_TOKEN;
var nextToken = sourceCode.getTokenAfter(token); var prevToken = sourceCode.getTokenBefore(token);
if (nextToken && if (prevToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isCloseParenOfTemplate(nextToken) && !isOpenParenOfTemplate(prevToken) &&
astUtils.isTokenOnSameLine(token, nextToken) && astUtils.isTokenOnSameLine(prevToken, token) &&
!sourceCode.isSpaceBetweenTokens(token, nextToken) sourceCode.isSpaceBetweenTokens(prevToken, token)
) { ) {
context.report({ context.report({
loc: token.loc.start, loc: token.loc.start,
message: "Expected space(s) after \"{{value}}\".", message: "Unexpected space(s) before \"{{value}}\".",
data: token, data: token,
fix: function(fixer) { fix: function(fixer) {
return fixer.insertTextAfter(token, " "); return fixer.removeRange([prevToken.range[1], token.range[0]]);
} }
}); });
}
} }
}
/** /**
* Reports a given token if there are space(s) after the token. * Reports a given token if there are not space(s) after the token.
* *
* @param {Token} token - A token to report. * @param {Token} token - A token to report.
* @param {RegExp|undefined} pattern - Optional. A pattern of the next * @param {RegExp|undefined} pattern - Optional. A pattern of the next
* token to check. * token to check.
* @returns {void} * @returns {void}
*/ */
function unexpectSpaceAfter(token, pattern) { function expectSpaceAfter(token, pattern) {
pattern = pattern || NEXT_TOKEN; pattern = pattern || NEXT_TOKEN;
var nextToken = sourceCode.getTokenAfter(token); var nextToken = sourceCode.getTokenAfter(token);
if (nextToken && if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) && !isCloseParenOfTemplate(nextToken) &&
astUtils.isTokenOnSameLine(token, nextToken) && astUtils.isTokenOnSameLine(token, nextToken) &&
sourceCode.isSpaceBetweenTokens(token, nextToken) !sourceCode.isSpaceBetweenTokens(token, nextToken)
) { ) {
context.report({ context.report({
loc: token.loc.start, loc: token.loc.start,
message: "Unexpected space(s) after \"{{value}}\".", message: "Expected space(s) after \"{{value}}\".",
data: token, data: token,
fix: function(fixer) { fix: function(fixer) {
return fixer.removeRange([token.range[1], nextToken.range[0]]); return fixer.insertTextAfter(token, " ");
} }
}); });
}
} }
}
/** /**
* Parses the option object and determines check methods for each keyword. * Reports a given token if there are space(s) after the token.
* *
* @param {object|undefined} options - The option object to parse. * @param {Token} token - A token to report.
* @returns {object} - Normalized option object. * @param {RegExp|undefined} pattern - Optional. A pattern of the next
* Keys are keywords (there are for every keyword). * token to check.
* Values are instances of `{"before": function, "after": function}`. * @returns {void}
*/ */
function parseOptions(options) { function unexpectSpaceAfter(token, pattern) {
var before = !options || options.before !== false; pattern = pattern || NEXT_TOKEN;
var after = !options || options.after !== false;
var defaultValue = { var nextToken = sourceCode.getTokenAfter(token);
before: before ? expectSpaceBefore : unexpectSpaceBefore,
after: after ? expectSpaceAfter : unexpectSpaceAfter if (nextToken &&
}; (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
var overrides = (options && options.overrides) || {}; !isCloseParenOfTemplate(nextToken) &&
var retv = Object.create(null); astUtils.isTokenOnSameLine(token, nextToken) &&
sourceCode.isSpaceBetweenTokens(token, nextToken)
for (var i = 0; i < KEYS.length; ++i) { ) {
var key = KEYS[i]; context.report({
var override = overrides[key]; loc: token.loc.start,
message: "Unexpected space(s) after \"{{value}}\".",
if (override) { data: token,
var thisBefore = ("before" in override) ? override.before : before; fix: function(fixer) {
var thisAfter = ("after" in override) ? override.after : after; return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
retv[key] = { });
before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
};
} else {
retv[key] = defaultValue;
} }
} }
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]); retv[key] = {
before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
/** after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
* Reports a given token if usage of spacing followed by the token is };
* invalid. } else {
* retv[key] = defaultValue;
* @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);
}
/** return retv;
* 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);
}
/** var checkMethodMap = parseOptions(context.options[0]);
* Reports a given token if usage of spacing around the token is invalid.
* /**
* @param {Token} token - A token to report. * Reports a given token if usage of spacing followed by the token is
* @returns {void} * invalid.
*/ *
function checkSpacingAround(token) { * @param {Token} token - A token to report.
checkSpacingBefore(token); * @param {RegExp|undefined} pattern - Optional. A pattern of the previous
checkSpacingAfter(token); * 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 * Reports a given token if usage of spacing preceded by the token is
* and usage of spacing around the token is invalid. * invalid.
* *
* @param {ASTNode|null} node - A node to report. * @param {Token} token - A token to report.
* @returns {void} * @param {RegExp|undefined} pattern - Optional. A pattern of the next
*/ * token to check.
function checkSpacingAroundFirstToken(node) { * @returns {void}
var firstToken = node && sourceCode.getFirstToken(node); */
function checkSpacingAfter(token, pattern) {
if (firstToken && firstToken.type === "Keyword") { checkMethodMap[token.value].after(token, pattern);
checkSpacingAround(firstToken);
} }
}
/** /**
* Reports the first token of a given node if the first token is a keyword * Reports a given token if usage of spacing around the token is invalid.
* and usage of spacing followed by the token is invalid. *
* * @param {Token} token - A token to report.
* This is used for unary operators (e.g. `typeof`), `function`, and `super`. * @returns {void}
* Other rules are handling usage of spacing preceded by those keywords. */
* function checkSpacingAround(token) {
* @param {ASTNode|null} node - A node to report. checkSpacingBefore(token);
* @returns {void} checkSpacingAfter(token);
*/
function checkSpacingBeforeFirstToken(node) {
var firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && firstToken.type === "Keyword") {
checkSpacingBefore(firstToken);
} }
}
/** /**
* Reports the previous token of a given node if the token is a keyword and * Reports the first token of a given node if the first token is a keyword
* usage of spacing around the token is invalid. * and usage of spacing around the token is invalid.
* *
* @param {ASTNode|null} node - A node to report. * @param {ASTNode|null} node - A node to report.
* @returns {void} * @returns {void}
*/ */
function checkSpacingAroundTokenBefore(node) { function checkSpacingAroundFirstToken(node) {
if (node) { var firstToken = node && sourceCode.getFirstToken(node);
var token = sourceCode.getTokenBefore(node);
if (firstToken && firstToken.type === "Keyword") {
while (token.type !== "Keyword") { checkSpacingAround(firstToken);
token = sourceCode.getTokenBefore(token);
} }
checkSpacingAround(token);
} }
}
/** /**
* Reports `class` and `extends` keywords of a given node if usage of * Reports the first token of a given node if the first token is a keyword
* spacing around those keywords is invalid. * and usage of spacing followed by the token is invalid.
* *
* @param {ASTNode} node - A node to report. * This is used for unary operators (e.g. `typeof`), `function`, and `super`.
* @returns {void} * Other rules are handling usage of spacing preceded by those keywords.
*/ *
function checkSpacingForClass(node) { * @param {ASTNode|null} node - A node to report.
checkSpacingAroundFirstToken(node); * @returns {void}
checkSpacingAroundTokenBefore(node.superClass); */
} function checkSpacingBeforeFirstToken(node) {
var firstToken = node && sourceCode.getFirstToken(node);
/**
* Reports `if` and `else` keywords of a given node if usage of spacing if (firstToken && firstToken.type === "Keyword") {
* around those keywords is invalid. checkSpacingBefore(firstToken);
* }
* @param {ASTNode} node - A node to report. }
* @returns {void}
*/
function checkSpacingForIfStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.alternate);
}
/** /**
* Reports `try`, `catch`, and `finally` keywords of a given node if usage * Reports the previous token of a given node if the token is a keyword and
* of spacing around those keywords is invalid. * usage of spacing around the token is invalid.
* *
* @param {ASTNode} node - A node to report. * @param {ASTNode|null} node - A node to report.
* @returns {void} * @returns {void}
*/ */
function checkSpacingForTryStatement(node) { function checkSpacingAroundTokenBefore(node) {
checkSpacingAroundFirstToken(node); if (node) {
checkSpacingAroundFirstToken(node.handler); var token = sourceCode.getTokenBefore(node);
checkSpacingAroundTokenBefore(node.finalizer);
} while (token.type !== "Keyword") {
token = sourceCode.getTokenBefore(token);
}
/** checkSpacingAround(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);
}
/** /**
* Reports `for` and `in` keywords of a given node if usage of spacing * Reports `class` and `extends` keywords of a given node if usage of
* around those keywords is invalid. * spacing around those keywords is invalid.
* *
* @param {ASTNode} node - A node to report. * @param {ASTNode} node - A node to report.
* @returns {void} * @returns {void}
*/ */
function checkSpacingForForInStatement(node) { function checkSpacingForClass(node) {
checkSpacingAroundFirstToken(node); checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.right); checkSpacingAroundTokenBefore(node.superClass);
} }
/** /**
* Reports `for` and `of` keywords of a given node if usage of spacing * Reports `if` and `else` keywords of a given node if usage of spacing
* around those keywords is invalid. * around those keywords is invalid.
* *
* @param {ASTNode} node - A node to report. * @param {ASTNode} node - A node to report.
* @returns {void} * @returns {void}
*/ */
function checkSpacingForForOfStatement(node) { function checkSpacingForIfStatement(node) {
checkSpacingAroundFirstToken(node); checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.alternate);
// `of` is not a keyword token.
var token = sourceCode.getTokenBefore(node.right);
while (token.value !== "of") {
token = sourceCode.getTokenBefore(token);
} }
checkSpacingAround(token);
}
/** /**
* Reports `import`, `export`, `as`, and `from` keywords of a given node if * Reports `try`, `catch`, and `finally` keywords of a given node if usage
* usage of spacing around those keywords is invalid. * of spacing around those keywords is invalid.
* *
* This rule handles the `*` token in module declarations. * @param {ASTNode} node - A node to report.
* * @returns {void}
* import*as A from "./a"; /*error Expected space(s) after "import". */
* error Expected space(s) before "as". function checkSpacingForTryStatement(node) {
* checkSpacingAroundFirstToken(node);
* @param {ASTNode} node - A node to report. checkSpacingAroundFirstToken(node.handler);
* @returns {void} checkSpacingAroundTokenBefore(node.finalizer);
*/
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 `as` keyword of a given node if usage of spacing around this * Reports `do` and `while` keywords of a given node if usage of spacing
* keyword is invalid. * around those keywords is invalid.
* *
* @param {ASTNode} node - A node to report. * @param {ASTNode} node - A node to report.
* @returns {void} * @returns {void}
*/ */
function checkSpacingForImportNamespaceSpecifier(node) { function checkSpacingForDoWhileStatement(node) {
var asToken = sourceCode.getFirstToken(node, 1); checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.test);
checkSpacingBefore(asToken, PREV_TOKEN_M); }
}
/** /**
* Reports `static`, `get`, and `set` keywords of a given node if usage of * Reports `for` and `in` keywords of a given node if usage of spacing
* spacing around those keywords is invalid. * around those keywords is invalid.
* *
* @param {ASTNode} node - A node to report. * @param {ASTNode} node - A node to report.
* @returns {void} * @returns {void}
*/ */
function checkSpacingForProperty(node) { function checkSpacingForForInStatement(node) {
if (node.static) {
checkSpacingAroundFirstToken(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); checkSpacingAround(token);
} }
}
return { /**
* Reports `import`, `export`, `as`, and `from` keywords of a given node if
// Statements * usage of spacing around those keywords is invalid.
DebuggerStatement: checkSpacingAroundFirstToken, *
WithStatement: checkSpacingAroundFirstToken, * This rule handles the `*` token in module declarations.
*
// Statements - Control flow * import*as A from "./a"; /*error Expected space(s) after "import".
BreakStatement: checkSpacingAroundFirstToken, * error Expected space(s) before "as".
ContinueStatement: checkSpacingAroundFirstToken, *
ReturnStatement: checkSpacingAroundFirstToken, * @param {ASTNode} node - A node to report.
ThrowStatement: checkSpacingAroundFirstToken, * @returns {void}
TryStatement: checkSpacingForTryStatement, */
function checkSpacingForModuleDeclaration(node) {
// Statements - Choice var firstToken = sourceCode.getFirstToken(node);
IfStatement: checkSpacingForIfStatement,
SwitchStatement: checkSpacingAroundFirstToken, checkSpacingBefore(firstToken, PREV_TOKEN_M);
SwitchCase: checkSpacingAroundFirstToken, checkSpacingAfter(firstToken, NEXT_TOKEN_M);
// Statements - Loops if (node.source) {
DoWhileStatement: checkSpacingForDoWhileStatement, var fromToken = sourceCode.getTokenBefore(node.source);
ForInStatement: checkSpacingForForInStatement,
ForOfStatement: checkSpacingForForOfStatement, checkSpacingBefore(fromToken, PREV_TOKEN_M);
ForStatement: checkSpacingAroundFirstToken, checkSpacingAfter(fromToken, NEXT_TOKEN_M);
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
};
};
module.exports.schema = [ /**
{ * Reports `as` keyword of a given node if usage of spacing around this
"type": "object", * keyword is invalid.
"properties": { *
"before": {"type": "boolean"}, * @param {ASTNode} node - A node to report.
"after": {"type": "boolean"}, * @returns {void}
"overrides": { */
"type": "object", function checkSpacingForImportNamespaceSpecifier(node) {
"properties": KEYS.reduce(function(retv, key) { var asToken = sourceCode.getFirstToken(node, 1);
retv[key] = {
"type": "object", checkSpacingBefore(asToken, PREV_TOKEN_M);
"properties": { }
"before": {"type": "boolean"},
"after": {"type": "boolean"} /**
}, * Reports `static`, `get`, and `set` keywords of a given node if usage of
"additionalProperties": false * spacing around those keywords is invalid.
}; *
return retv; * @param {ASTNode} node - A node to report.
}, {}), * @returns {void}
"additionalProperties": false */
function checkSpacingForProperty(node) {
if (node.static) {
checkSpacingAroundFirstToken(node);
} }
}, if (node.kind === "get" || node.kind === "set") {
"additionalProperties": false 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. * @fileoverview Rule to enforce a single linebreak style.
* @author Erik Mueller * @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"; "use strict";
@ -12,69 +9,81 @@
// Rule Definition // 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'.", fixable: "whitespace",
EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'.";
//-------------------------------------------------------------------------- schema: [
// Helpers {
//-------------------------------------------------------------------------- enum: ["unix", "windows"]
}
]
},
/** create: function(context) {
* 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);
};
}
//-------------------------------------------------------------------------- var EXPECTED_LF_MSG = "Expected linebreaks to be 'LF' but found 'CRLF'.",
// Public EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'.";
//--------------------------------------------------------------------------
return { //--------------------------------------------------------------------------
"Program": function checkForlinebreakStyle(node) { // Helpers
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; /**
* 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++; // Public
if (match[0] === expectedLFChars) { //--------------------------------------------------------------------------
continue;
}
index = match.index; return {
range = [index, index + match[0].length]; Program: function checkForlinebreakStyle(node) {
context.report({ var linebreakStyle = context.options[0] || "unix",
node: node, expectedLF = linebreakStyle === "unix",
loc: { expectedLFChars = expectedLF ? "\n" : "\r\n",
line: i, source = context.getSource(),
column: context.getSourceLines()[i - 1].length pattern = /\r\n|\r|\n|\u2028|\u2029/g,
}, match,
message: expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG, index,
fix: createFix(range, expectedLFChars) range;
});
} var i = 0;
}
};
};
module.exports.schema = [ while ((match = pattern.exec(source)) !== null) {
{ i++;
"enum": ["unix", "windows"] 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. * @fileoverview Enforces empty lines around comments.
* @author Jamund Ferguson * @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"; "use strict";
@ -68,272 +65,282 @@ function contains(val, array) {
// Rule Definition // 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; create: function(context) {
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;
var sourceCode = context.getSourceCode(); var options = context.options[0] ? lodash.assign({}, context.options[0]) : {};
/** options.beforeLineComment = options.beforeLineComment || false;
* Returns whether or not comments are on lines starting with or ending with code options.afterLineComment = options.afterLineComment || false;
* @param {ASTNode} node The comment node to check. options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
* @returns {boolean} True if the comment is not alone. options.afterBlockComment = options.afterBlockComment || false;
*/ options.allowBlockStart = options.allowBlockStart || false;
function codeAroundComment(node) { options.allowBlockEnd = options.allowBlockEnd || false;
var token;
token = node; var sourceCode = context.getSourceCode();
do {
token = sourceCode.getTokenOrCommentBefore(token);
} while (token && (token.type === "Block" || token.type === "Line"));
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; token = node;
do { do {
token = sourceCode.getTokenOrCommentAfter(token); token = sourceCode.getTokenOrCommentBefore(token);
} while (token && (token.type === "Block" || token.type === "Line")); } while (token && (token.type === "Block" || token.type === "Line"));
if (token && token.loc.start.line === node.loc.end.line) { if (token && token.loc.end.line === node.loc.start.line) {
return true; return true;
} }
return false; token = node;
} do {
token = sourceCode.getTokenOrCommentAfter(token);
} while (token && (token.type === "Block" || token.type === "Line"));
/** if (token && token.loc.start.line === node.loc.end.line) {
* Returns whether or not comments are inside a node type or not. return true;
* @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 false;
* 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 parent && isCommentInsideNodeType(node, parent, nodeType) && /**
node.loc.start.line - parent.loc.start.line === 1; * 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 whether or not comments are at the parent end or not. * @returns {boolean} True if the comment is inside nodeType.
* @param {ASTNode} node The Comment node. */
* @param {string} nodeType The parent type to check against. function isCommentInsideNodeType(node, parent, nodeType) {
* @returns {boolean} True if the comment is at parent end. return parent.type === nodeType ||
*/ (parent.body && parent.body.type === nodeType) ||
function isCommentAtParentEnd(node, nodeType) { (parent.consequent && parent.consequent.type === nodeType);
var ancestors = context.getAncestors();
var parent;
if (ancestors.length) {
parent = ancestors.pop();
} }
return parent && isCommentInsideNodeType(node, parent, nodeType) && /**
parent.loc.end.line - node.loc.end.line === 1; * 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.
* Returns whether or not comments are at the block start or not. */
* @param {ASTNode} node The Comment node. function isCommentAtParentStart(node, nodeType) {
* @returns {boolean} True if the comment is at block start. var ancestors = context.getAncestors();
*/ var parent;
function isCommentAtBlockStart(node) {
return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement") || isCommentAtParentStart(node, "SwitchCase"); if (ancestors.length) {
} parent = ancestors.pop();
}
/**
* 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");
}
/** return parent && isCommentInsideNodeType(node, parent, nodeType) &&
* Returns whether or not comments are at the object end or not. node.loc.start.line - parent.loc.start.line === 1;
* @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");
}
/** /**
* Returns whether or not comments are at the array start or not. * Returns whether or not comments are at the parent end or not.
* @param {ASTNode} node The Comment node. * @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at array start. * @param {string} nodeType The parent type to check against.
*/ * @returns {boolean} True if the comment is at parent end.
function isCommentAtArrayStart(node) { */
return isCommentAtParentStart(node, "ArrayExpression") || isCommentAtParentStart(node, "ArrayPattern"); function isCommentAtParentEnd(node, nodeType) {
} var ancestors = context.getAncestors();
var parent;
if (ancestors.length) {
parent = ancestors.pop();
}
/** return parent && isCommentInsideNodeType(node, parent, nodeType) &&
* Returns whether or not comments are at the array end or not. parent.loc.end.line - node.loc.end.line === 1;
* @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");
}
/** /**
* Checks if a comment node has lines around it (ignores inline comments) * Returns whether or not comments are at the block start or not.
* @param {ASTNode} node The Comment node. * @param {ASTNode} node The Comment node.
* @param {Object} opts Options to determine the newline. * @returns {boolean} True if the comment is at block start.
* @param {Boolean} opts.after Should have a newline after this line. */
* @param {Boolean} opts.before Should have a newline before this line. function isCommentAtBlockStart(node) {
* @returns {void} return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement") || isCommentAtParentStart(node, "SwitchCase");
*/
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; /**
* 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) { * Returns whether or not comments are at the object start or not.
return; * @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)) { * Returns whether or not comments are at the object end or not.
context.report(node, "Expected line before comment."); * @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)) { * Returns whether or not comments are at the array start or not.
context.report(node, "Expected line after comment."); * @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) { // check for newline before
if (options.beforeLineComment || options.afterLineComment) { if (!exceptionStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) {
checkForEmptyLine(node, { context.report(node, "Expected line before comment.");
after: options.afterLineComment,
before: options.beforeLineComment
});
} }
},
"BlockComment": function(node) { // check for newline after
if (options.beforeBlockComment || options.afterBlockComment) { if (!exceptionEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) {
checkForEmptyLine(node, { context.report(node, "Expected line after comment.");
after: options.afterBlockComment,
before: options.beforeBlockComment
});
} }
} }
}; //--------------------------------------------------------------------------
}; // Public
//--------------------------------------------------------------------------
module.exports.schema = [ return {
{
"type": "object", LineComment: function(node) {
"properties": { if (options.beforeLineComment || options.afterLineComment) {
"beforeBlockComment": { checkForEmptyLine(node, {
"type": "boolean" after: options.afterLineComment,
}, before: options.beforeLineComment
"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" 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. * @fileoverview A rule to set the maximum depth block can be nested in a function.
* @author Ian Christian Myers * @author Ian Christian Myers
* @copyright 2013 Ian Christian Myers. All rights reserved.
*/ */
"use strict"; "use strict";
@ -10,131 +9,141 @@
// Rule Definition // 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
},
//-------------------------------------------------------------------------- schema: [
// Helpers {
//-------------------------------------------------------------------------- oneOf: [
{
type: "integer",
minimum: 0
},
{
type: "object",
properties: {
maximum: {
type: "integer",
minimum: 0
},
max: {
type: "integer",
minimum: 0
}
},
additionalProperties: false
}
]
}
]
},
var functionStack = [], create: function(context) {
option = context.options[0],
maxDepth = 4;
if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { //--------------------------------------------------------------------------
maxDepth = option.maximum; // Helpers
} //--------------------------------------------------------------------------
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
maxDepth = option.max;
}
if (typeof option === "number") {
maxDepth = option;
}
/** var functionStack = [],
* When parsing a new function, store it in our function stack option = context.options[0],
* @returns {void} maxDepth = 4;
* @private
*/
function startFunction() {
functionStack.push(0);
}
/** if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
* When parsing is done then pop out the reference maxDepth = option.maximum;
* @returns {void} }
* @private if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
*/ maxDepth = option.max;
function endFunction() { }
functionStack.pop(); if (typeof option === "number") {
} maxDepth = option;
}
/** /**
* Save the block and Evaluate the node * When parsing a new function, store it in our function stack
* @param {ASTNode} node node to evaluate * @returns {void}
* @returns {void} * @private
* @private */
*/ function startFunction() {
function pushBlock(node) { functionStack.push(0);
var len = ++functionStack[functionStack.length - 1];
if (len > maxDepth) {
context.report(node, "Blocks are nested too deeply ({{depth}}).",
{ depth: len });
} }
}
/** /**
* Pop the saved block * When parsing is done then pop out the reference
* @returns {void} * @returns {void}
* @private * @private
*/ */
function popBlock() { function endFunction() {
functionStack[functionStack.length - 1]--; 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, * Pop the saved block
"FunctionDeclaration": startFunction, * @returns {void}
"FunctionExpression": startFunction, * @private
"ArrowFunctionExpression": startFunction, */
function popBlock() {
functionStack[functionStack.length - 1]--;
}
"IfStatement": function(node) { //--------------------------------------------------------------------------
if (node.parent.type !== "IfStatement") { // Public API
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
};
}; return {
Program: startFunction,
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
module.exports.schema = [ IfStatement: function(node) {
{ if (node.parent.type !== "IfStatement") {
"oneOf": [ pushBlock(node);
{ }
"type": "integer",
"minimum": 0
}, },
{ SwitchStatement: pushBlock,
"type": "object", TryStatement: pushBlock,
"properties": { DoWhileStatement: pushBlock,
"maximum": { WhileStatement: pushBlock,
"type": "integer", WithStatement: pushBlock,
"minimum": 0 ForStatement: pushBlock,
}, ForInStatement: pushBlock,
"max": { ForOfStatement: pushBlock,
"type": "integer",
"minimum": 0 "IfStatement:exit": popBlock,
} "SwitchStatement:exit": popBlock,
}, "TryStatement:exit": popBlock,
"additionalProperties": false "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. * @fileoverview Rule to check for max length on a line.
* @author Matt DuVall <http://www.mattduvall.com> * @author Matt DuVall <http://www.mattduvall.com>
* @copyright 2013 Matt DuVall. All rights reserved.
*/ */
"use strict"; "use strict";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Constants
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { var OPTIONS_SCHEMA = {
type: "object",
/* properties: {
* Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however: code: {
* - They're matching an entire string that we know is a URI type: "integer",
* - We're matching part of a string where we think there *might* be a URL minimum: 0
* - We're only concerned about URLs, as picking out any URI would cause },
* too many false positives comments: {
* - We don't care about matching the entire URL, any small segment is fine type: "integer",
*/ minimum: 0
var URL_REGEXP = /[^:/?#]:\/\/[^?#]/; },
tabWidth: {
/** type: "integer",
* Computes the length of a line that may contain tabs. The width of each minimum: 0
* tab will be the number of spaces to the next tab stop. },
* @param {string} line The line. ignorePattern: {
* @param {int} tabWidth The width of each tab stop in spaces. type: "string"
* @returns {int} The computed line length. },
* @private ignoreComments: {
*/ type: "boolean"
function computeLineLength(line, tabWidth) { },
var extraCharacterCount = 0; ignoreUrls: {
type: "boolean"
line.replace(/\t/g, function(match, offset) { },
var totalOffset = offset + extraCharacterCount, ignoreTrailingComments: {
previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0, type: "boolean"
spaceCount = tabWidth - previousTabStopOffset; }
},
extraCharacterCount += spaceCount - 1; // -1 for the replaced tab additionalProperties: false
}); };
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];
}
// …and tabWidth can be optionally specified directly as integers. var OPTIONS_OR_INTEGER_SCHEMA = {
if (typeof context.options[1] === "number") { anyOf: [
options.tabWidth = context.options[1]; OPTIONS_SCHEMA,
} {
type: "integer",
minimum: 0
}
]
};
var maxLength = options.code || 80, //------------------------------------------------------------------------------
tabWidth = options.tabWidth || 4, // Rule Definition
ignorePattern = options.ignorePattern || null, //------------------------------------------------------------------------------
ignoreComments = options.ignoreComments || false,
ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false,
ignoreUrls = options.ignoreUrls || false,
maxCommentLength = options.comments;
if (ignorePattern) { module.exports = {
ignorePattern = new RegExp(ignorePattern); meta: {
} docs: {
description: "enforce a maximum line length",
category: "Stylistic Issues",
recommended: false
},
//-------------------------------------------------------------------------- schema: [
// Helpers OPTIONS_OR_INTEGER_SCHEMA,
//-------------------------------------------------------------------------- OPTIONS_OR_INTEGER_SCHEMA,
OPTIONS_SCHEMA
/** ]
* 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);
}
/** create: function(context) {
* 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 * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however:
* @param {ASTNode} comment The comment to remove * - They're matching an entire string that we know is a URI
* @returns {boolean} If the comment covers the entire line * - 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
function isFullLineComment(line, lineNumber, comment) { * too many false positives
var start = comment.loc.start, * - We don't care about matching the entire URL, any small segment is fine
end = comment.loc.end; */
var URL_REGEXP = /[^:/?#]:\/\/[^?#]/;
return comment &&
(start.line < lineNumber || (start.line === lineNumber && start.column === 0)) && /**
(end.line > lineNumber || end.column === line.length); * 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…
* Gets the line after the comment and any remaining trailing whitespace is var lastOption = context.options[context.options.length - 1];
* stripped. var options = typeof lastOption === "object" ? Object.create(lastOption) : {};
* @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+$/, "");
}
/** // …but max code length…
* Check the program for max length if (typeof context.options[0] === "number") {
* @param {ASTNode} node Node to examine options.code = context.options[0];
* @returns {void} }
* @private
*/
function checkProgramForMaxLength(node) {
// split (honors line-ending) // …and tabWidth can be optionally specified directly as integers.
var lines = context.getSourceLines(), if (typeof context.options[1] === "number") {
options.tabWidth = context.options[1];
}
// list of comments to ignore var maxLength = options.code || 80,
comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? context.getAllComments() : [], 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 if (ignorePattern) {
commentsIndex = 0; 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 * Gets the line after the comment and any remaining trailing whitespace is
* line is a comment * stripped.
*/ * @param {string} line The source line with a trailing comment
var lineIsComment = false; * @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 * Check the program for max length
* comments to check. * @param {ASTNode} node Node to examine
*/ * @returns {void}
if (commentsIndex < comments.length) { * @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 // ignore this line
do { return;
var comment = comments[++commentsIndex]; }
} while (comment && comment.loc.start.line <= lineNumber);
// and step back by one var lineLength = computeLineLength(line, tabWidth);
comment = comments[--commentsIndex];
if (isFullLineComment(line, lineNumber, comment)) { if (lineIsComment && ignoreComments) {
lineIsComment = true; return;
} else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) {
line = stripTrailingComment(line, lineNumber, comment);
} }
}
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. * @fileoverview Rule to enforce a maximum number of nested callbacks.
* @author Ian Christian Myers * @author Ian Christian Myers
* @copyright 2013 Ian Christian Myers. All rights reserved.
*/ */
"use strict"; "use strict";
@ -10,94 +9,104 @@
// Rule Definition // 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
},
//-------------------------------------------------------------------------- schema: [
// Constants {
//-------------------------------------------------------------------------- oneOf: [
var option = context.options[0], {
THRESHOLD = 10; 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") { create: function(context) {
THRESHOLD = option.maximum;
}
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
THRESHOLD = option.max;
}
if (typeof option === "number") {
THRESHOLD = option;
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// 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. // Helpers
* @param {ASTNode} node The node to check. //--------------------------------------------------------------------------
* @returns {void}
* @private
*/
function checkFunction(node) {
var parent = node.parent;
if (parent.type === "CallExpression") { var callbackStack = [];
callbackStack.push(node);
}
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);
} }
/** if (callbackStack.length > THRESHOLD) {
* Pops the call stack. var opts = {num: callbackStack.length, max: THRESHOLD};
* @returns {void}
* @private
*/
function popStack() {
callbackStack.pop();
}
//-------------------------------------------------------------------------- context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts);
// Public API }
//-------------------------------------------------------------------------- }
return { /**
"ArrowFunctionExpression": checkFunction, * Pops the call stack.
"ArrowFunctionExpression:exit": popStack, * @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 * @fileoverview Rule to flag when a function has too many parameters
* @author Ilya Volodin * @author Ilya Volodin
* @copyright 2014 Nicholas C. Zakas. All rights reserved.
* @copyright 2013 Ilya Volodin. All rights reserved.
*/ */
"use strict"; "use strict";
@ -11,65 +9,75 @@
// Rule Definition // 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], schema: [
numParams = 3; {
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") { create: function(context) {
numParams = option.maximum;
} var option = context.options[0],
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { numParams = 3;
numParams = option.max;
}
if (typeof option === "number") {
numParams = option;
}
/** if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
* Checks a function to see if it has too many parameters. numParams = option.maximum;
* @param {ASTNode} node The node to check. }
* @returns {void} if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
* @private numParams = option.max;
*/ }
function checkFunction(node) { if (typeof option === "number") {
if (node.params.length > numParams) { numParams = option;
context.report(node, "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", {
count: node.params.length,
max: numParams
});
} }
}
return { /**
"FunctionDeclaration": checkFunction, * Checks a function to see if it has too many parameters.
"ArrowFunctionExpression": checkFunction, * @param {ASTNode} node The node to check.
"FunctionExpression": checkFunction * @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. * @fileoverview Specify the maximum number of statements allowed per line.
* @author Kenneth Williams * @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"; "use strict";
@ -11,96 +8,106 @@
// Rule Definition // 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] || {}, schema: [
lastStatementLine = 0, {
numberOfStatementsOnThisLine = 0, type: "object",
maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1; properties: {
max: {
type: "integer"
}
},
additionalProperties: false
}
]
},
//-------------------------------------------------------------------------- create: function(context) {
// Helpers
//--------------------------------------------------------------------------
/** var options = context.options[0] || {},
* Reports a node lastStatementLine = 0,
* @param {ASTNode} node The node to report numberOfStatementsOnThisLine = 0,
* @returns {void} maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
* @private
*/
function report(node) {
context.report(
node,
"This line has too many statements. Maximum allowed is {{max}}.",
{ max: maxStatementsPerLine });
}
/** //--------------------------------------------------------------------------
* Enforce a maximum number of statements per line // Helpers
* @param {ASTNode} nodes Array of nodes to evaluate //--------------------------------------------------------------------------
* @returns {void}
* @private
*/
function enforceMaxStatementsPerLine(nodes) {
if (nodes.length < 1) {
return;
}
for (var i = 0, l = nodes.length; i < l; ++i) { /**
var currentStatement = nodes[i]; * 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; * Enforce a maximum number of statements per line
} else { * @param {ASTNode} nodes Array of nodes to evaluate
numberOfStatementsOnThisLine = 1; * @returns {void}
lastStatementLine = currentStatement.loc.end.line; * @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 * Check each line in the body of a node
* @param {ASTNode} node node to evaluate * @param {ASTNode} node node to evaluate
* @returns {void} * @returns {void}
* @private * @private
*/ */
function checkLinesInBody(node) { function checkLinesInBody(node) {
enforceMaxStatementsPerLine(node.body); enforceMaxStatementsPerLine(node.body);
} }
/**
* Check each line in the consequent of a switch case
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkLinesInConsequent(node) {
enforceMaxStatementsPerLine(node.consequent);
}
//-------------------------------------------------------------------------- /**
// Public API * 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, // Public API
"BlockStatement": checkLinesInBody, //--------------------------------------------------------------------------
"SwitchCase": checkLinesInConsequent
};
}; 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. * @fileoverview A rule to set the maximum number of statements in a function.
* @author Ian Christian Myers * @author Ian Christian Myers
* @copyright 2013 Ian Christian Myers. All rights reserved.
*/ */
"use strict"; "use strict";
@ -10,141 +9,151 @@
// Rule Definition // 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
}
]
},
//-------------------------------------------------------------------------- create: function(context) {
// Helpers
//--------------------------------------------------------------------------
var functionStack = [], //--------------------------------------------------------------------------
option = context.options[0], // Helpers
maxStatements = 10, //--------------------------------------------------------------------------
ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false,
topLevelFunctions = [];
if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { var functionStack = [],
maxStatements = option.maximum; option = context.options[0],
} maxStatements = 10,
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false,
maxStatements = option.max; topLevelFunctions = [];
}
if (typeof option === "number") {
maxStatements = option;
}
/** if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
* Reports a node if it has too many statements maxStatements = option.maximum;
* @param {ASTNode} node node to evaluate }
* @param {int} count Number of statements in node if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
* @param {int} max Maximum number of statements allowed maxStatements = option.max;
* @returns {void} }
* @private if (typeof option === "number") {
*/ maxStatements = option;
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 });
} }
}
/**
* 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 * Reports a node if it has too many statements
* @param {ASTNode} node node to evaluate * @param {ASTNode} node node to evaluate
* @returns {void} * @param {int} count Number of statements in node
* @private * @param {int} max Maximum number of statements allowed
*/ * @returns {void}
function endFunction(node) { * @private
var count = functionStack.pop(); */
function reportIfTooManyStatements(node, count, max) {
if (ignoreTopLevelFunctions && functionStack.length === 0) { if (count > max) {
topLevelFunctions.push({ node: node, count: count}); context.report(
} else { node,
reportIfTooManyStatements(node, count, maxStatements); "This function has too many statements ({{count}}). Maximum allowed is {{max}}.",
{ count: count, max: max });
}
} }
}
/** /**
* Increment the count of the functions * When parsing a new function, store it in our function stack
* @param {ASTNode} node node to evaluate * @returns {void}
* @returns {void} * @private
* @private */
*/ function startFunction() {
function countStatements(node) { functionStack.push(0);
functionStack[functionStack.length - 1] += node.body.length; }
}
//-------------------------------------------------------------------------- /**
// 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, * Increment the count of the functions
"FunctionExpression": startFunction, * @param {ASTNode} node node to evaluate
"ArrowFunctionExpression": startFunction, * @returns {void}
* @private
*/
function countStatements(node) {
functionStack[functionStack.length - 1] += node.body.length;
}
"BlockStatement": countStatements, //--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
"FunctionDeclaration:exit": endFunction, return {
"FunctionExpression:exit": endFunction, FunctionDeclaration: startFunction,
"ArrowFunctionExpression:exit": endFunction, FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
"Program:exit": function() { BlockStatement: countStatements,
if (topLevelFunctions.length === 1) {
return;
}
topLevelFunctions.forEach(function(element) { "FunctionDeclaration:exit": endFunction,
var count = element.count; "FunctionExpression:exit": endFunction,
var node = element.node; "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 = [ reportIfTooManyStatements(node, count, maxStatements);
{ });
"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
} }
]; };

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

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

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

@ -9,22 +9,32 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
return { docs: {
description: "require parentheses when invoking a constructor with no arguments",
"NewExpression": function(node) { category: "Stylistic Issues",
var tokens = context.getTokens(node); recommended: false
var prenticesTokens = tokens.filter(function(token) { },
return token.value === "(" || token.value === ")";
}); schema: []
},
if (prenticesTokens.length < 2) {
context.report(node, "Missing '()' invoking a constructor"); 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 * @fileoverview Rule to check empty newline after "var" statement
* @author Gopal Venkatesan * @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"; "use strict";
@ -12,159 +9,169 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
var ALWAYS_MESSAGE = "Expected blank line after variable declarations.", docs: {
NEVER_MESSAGE = "Unexpected blank line after variable declarations."; description: "require or disallow an empty line after `var` declarations",
category: "Stylistic Issues",
var sourceCode = context.getSourceCode(); recommended: false
},
// Default `mode` to "always".
var mode = context.options[0] === "never" ? "never" : "always"; schema: [
{
// Cache starting and ending line numbers of comments for faster lookup enum: ["never", "always"]
var commentEndLine = context.getAllComments().reduce(function(result, token) { }
result[token.loc.start.line] = token.loc.end.line; ]
return result; },
}, {});
create: function(context) {
//-------------------------------------------------------------------------- var ALWAYS_MESSAGE = "Expected blank line after variable declarations.",
// Helpers NEVER_MESSAGE = "Unexpected blank line after variable declarations.";
//--------------------------------------------------------------------------
var sourceCode = context.getSourceCode();
/**
* Determine if provided keyword is a variable declaration // Default `mode` to "always".
* @private var mode = context.options[0] === "never" ? "never" : "always";
* @param {string} keyword - keyword to test
* @returns {boolean} True if `keyword` is a type of var // Cache starting and ending line numbers of comments for faster lookup
*/ var commentEndLine = context.getAllComments().reduce(function(result, token) {
function isVar(keyword) { result[token.loc.start.line] = token.loc.end.line;
return keyword === "var" || keyword === "let" || keyword === "const"; return result;
} }, {});
/**
* Determine if provided keyword is a variant of for specifiers //--------------------------------------------------------------------------
* @private // Helpers
* @param {string} keyword - keyword to test //--------------------------------------------------------------------------
* @returns {boolean} True if `keyword` is a variant of for specifier
*/ /**
function isForTypeSpecifier(keyword) { * Determine if provided keyword is a variable declaration
return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; * @private
} * @param {string} keyword - keyword to test
* @returns {boolean} True if `keyword` is a type of var
/** */
* Determine if provided keyword is an export specifiers function isVar(keyword) {
* @private return keyword === "var" || keyword === "let" || keyword === "const";
* @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);
} }
return (token.loc.start.line > commentEndLine[commentStartLine] + 1); /**
} * Determine if provided keyword is a variant of for specifiers
* @private
/** * @param {string} keyword - keyword to test
* Checks that a blank line exists after a variable declaration when mode is * @returns {boolean} True if `keyword` is a variant of for specifier
* set to "always", or checks that there is no blank line when mode is set */
* to "never" function isForTypeSpecifier(keyword) {
* @private return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement";
* @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)) { * Determine if provided keyword is an export specifiers
return; * @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)) { * Determine if provided node is the last of their parent block.
return; * @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 return !token || (token.type === "Punctuator" && token.value === "}");
// 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)) { * Determine if a token starts more than one line after a comment ends
return; * @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; * Checks that a blank line exists after a variable declaration when mode is
hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined"); * set to "always", or checks that there is no blank line when mode is set
* to "never"
if (mode === "never" && noNextLineToken && !hasNextLineComment) { * @private
context.report(node, NEVER_MESSAGE, { identifier: node.name }); * @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 ( // Public
mode === "always" && ( //--------------------------------------------------------------------------
!noNextLineToken ||
hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum)
)
) {
context.report(node, ALWAYS_MESSAGE, { identifier: node.name });
}
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return { return {
"VariableDeclaration": checkForBlankLine 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 * @fileoverview Rule to require newlines before `return` statement
* @author Kai Cataldo * @author Kai Cataldo
* @copyright 2016 Kai Cataldo. All rights reserved.
* See LICENSE file in root directory for full license.
*/ */
"use strict"; "use strict";
@ -11,133 +8,159 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var sourceCode = context.getSourceCode(); meta: {
docs: {
//-------------------------------------------------------------------------- description: "require an empty line before `return` statements",
// Helpers category: "Stylistic Issues",
//-------------------------------------------------------------------------- recommended: false
},
/**
* Tests whether node is preceded by supplied tokens schema: []
* @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 create: function(context) {
* @private var sourceCode = context.getSourceCode();
*/
function isPrecededByTokens(node, testTokens) { //--------------------------------------------------------------------------
var tokenBefore = sourceCode.getTokenBefore(node); // Helpers
//--------------------------------------------------------------------------
return testTokens.some(function(token) {
return tokenBefore.value === token; /**
}); * 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
* Checks whether node is the first node after statement or in block * @private
* @param {ASTNode} node - node to check */
* @returns {boolean} Whether or not the node is the first node after statement or in block function isPrecededByTokens(node, testTokens) {
* @private var tokenBefore = sourceCode.getTokenBefore(node);
*/
function isFirstNode(node) { return testTokens.some(function(token) {
var parentType = node.parent.type; return tokenBefore.value === token;
});
if (node.parent.body) {
return Array.isArray(node.parent.body)
? node.parent.body[0] === node
: node.parent.body === node;
} }
if (parentType === "IfStatement") { /**
return isPrecededByTokens(node, ["else", ")"]); * Checks whether node is the first node after statement or in block
} else if (parentType === "DoWhileStatement") { * @param {ASTNode} node - node to check
return isPrecededByTokens(node, ["do"]); * @returns {boolean} Whether or not the node is the first node after statement or in block
} else if (parentType === "SwitchCase") { * @private
return isPrecededByTokens(node, [":"]); */
} else { function isFirstNode(node) {
return isPrecededByTokens(node, [")"]); var parentType = node.parent.type;
}
} if (node.parent.body) {
return Array.isArray(node.parent.body)
/** ? node.parent.body[0] === node
* Returns the number of lines of comments that precede the node : node.parent.body === node;
* @param {ASTNode} node - node to check for overlapping comments
* @param {token} tokenBefore - previous token to check for overlapping comments
* @returns {number} Number of lines of comments that precede the node
* @private
*/
function calcCommentLines(node, tokenBefore) {
var comments = sourceCode.getComments(node).leading;
var numLinesComments = 0;
if (!comments.length) {
return numLinesComments;
}
comments.forEach(function(comment) {
numLinesComments++;
if (comment.type === "Block") {
numLinesComments += comment.loc.end.line - comment.loc.start.line;
} }
// avoid counting lines with inline comments twice if (parentType === "IfStatement") {
if (comment.loc.start.line === tokenBefore.loc.end.line) { return isPrecededByTokens(node, ["else", ")"]);
numLinesComments--; } 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++;
/** if (comment.type === "Block") {
* Checks whether node is preceded by a newline numLinesComments += comment.loc.end.line - comment.loc.start.line;
* @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;
}
/** // avoid counting lines with inline comments twice
* Reports expected/unexpected newline before return statement if (comment.loc.start.line === lineNumTokenBefore) {
* @param {ASTNode} node - the node to report in the event of an error numLinesComments--;
* @param {boolean} isExpected - whether the newline is expected or not }
* @returns {void}
* @private if (comment.loc.end.line === node.loc.start.line) {
*/ numLinesComments--;
function reportError(node, isExpected) { }
var expected = isExpected ? "Expected" : "Unexpected"; });
context.report({
node: node,
message: expected + " newline before return statement."
});
}
//-------------------------------------------------------------------------- return numLinesComments;
// Public }
//--------------------------------------------------------------------------
return { /**
ReturnStatement: function(node) { * Checks whether node is preceded by a newline
if (isFirstNode(node) && hasNewlineBefore(node)) { * @param {ASTNode} node - node to check
reportError(node, false); * @returns {boolean} Whether or not the node is preceded by a newline
} else if (!isFirstNode(node) && !hasNewlineBefore(node)) { * @private
reportError(node, true); */
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 * @fileoverview Rule to ensure newline per method call when chaining calls
* @author Rajendra Patil * @author Rajendra Patil
* @author Burak Yigit Kaya * @author Burak Yigit Kaya
* @copyright 2016 Rajendra Patil. All rights reserved.
* @copyright 2016 Burak Yigit Kaya. All rights reserved.
*/ */
"use strict"; "use strict";
@ -12,45 +10,55 @@
// Rule Definition // 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] || {}, schema: [{
ignoreChainWithDepth = options.ignoreChainWithDepth || 2; type: "object",
properties: {
ignoreChainWithDepth: {
type: "integer",
minimum: 1,
maximum: 10
}
},
additionalProperties: false
}]
},
return { create: function(context) {
"CallExpression:exit": function(node) {
if (!node.callee || node.callee.type !== "MemberExpression") {
return;
}
var callee = node.callee; var options = context.options[0] || {},
var parent = callee.object; ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
var depth = 1;
while (parent && parent.callee) { return {
depth += 1; "CallExpression:exit": function(node) {
parent = parent.callee.object; 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) { while (parent && parent.callee) {
context.report( depth += 1;
callee.property, parent = parent.callee.object;
callee.property.loc.start, }
"Expected line break after `" + context.getSource(callee.object).replace(/\r\n|\r|\n/g, "\\n") + "`."
); 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 * @fileoverview Rule to flag use of alert, confirm, prompt
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
* @copyright 2015 Mathias Schreck
* @copyright 2013 Nicholas C. Zakas
*/ */
"use strict"; "use strict";
@ -98,39 +96,49 @@ function isGlobalThisReferenceOrGlobalWindow(scope, globalScope, node) {
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var globalScope; meta: {
docs: {
description: "disallow the use of `alert`, `confirm`, and `prompt`",
category: "Best Practices",
recommended: false
},
return { schema: []
},
"Program": function() { create: function(context) {
globalScope = context.getScope(); var globalScope;
},
"CallExpression": function(node) { return {
var callee = node.callee,
identifierName,
currentScope = context.getScope();
// without window. Program: function() {
if (callee.type === "Identifier") { globalScope = context.getScope();
identifierName = callee.name; },
if (!isShadowed(currentScope, globalScope, callee) && isProhibitedIdentifier(callee.name)) { CallExpression: function(node) {
report(context, node, identifierName); 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)) { } else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, globalScope, callee.object)) {
identifierName = getPropertyName(callee); identifierName = getPropertyName(callee);
if (isProhibitedIdentifier(identifierName)) { if (isProhibitedIdentifier(identifierName)) {
report(context, node, identifierName); report(context, node, identifierName);
}
} }
}
} }
}; };
}
}; };
module.exports.schema = [];

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

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

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

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

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

@ -9,21 +9,31 @@
// Rule Definition // 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) { create: function(context) {
var objectName = node.object.name,
propertyName = node.property.name;
if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) { return {
context.report(node, "Avoid arguments.{{property}}.", { property: propertyName });
}
} 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 * @fileoverview Rule to flag use of an lexical declarations inside a case clause
* @author Erik Arvidsson * @author Erik Arvidsson
* @copyright 2015 Erik Arvidsson. All rights reserved.
*/ */
"use strict"; "use strict";
@ -9,40 +8,50 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
/** docs: {
* Checks whether or not a node is a lexical declaration. description: "disallow lexical declarations in case clauses",
* @param {ASTNode} node A direct child statement of a switch case. category: "Best Practices",
* @returns {boolean} Whether or not the node is a lexical declaration. recommended: true
*/ },
function isLexicalDeclaration(node) {
switch (node.type) { schema: []
case "FunctionDeclaration": },
case "ClassDeclaration":
return true; create: function(context) {
case "VariableDeclaration":
return node.kind !== "var"; /**
default: * Checks whether or not a node is a lexical declaration.
return false; * @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 { return {
"SwitchCase": function(node) { SwitchCase: function(node) {
for (var i = 0; i < node.consequent.length; i++) { for (var i = 0; i < node.consequent.length; i++) {
var statement = node.consequent[i]; var statement = node.consequent[i];
if (isLexicalDeclaration(statement)) { if (isLexicalDeclaration(statement)) {
context.report({ context.report({
node: node, node: node,
message: "Unexpected lexical declaration in case block." 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 // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
//-------------------------------------------------------------------------- docs: {
// Helpers description: "disallow `catch` clause parameters from shadowing variables in the outer scope",
//-------------------------------------------------------------------------- category: "Variables",
recommended: false
/** },
* 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;
}
//-------------------------------------------------------------------------- schema: []
// Public API },
//--------------------------------------------------------------------------
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 * Check if the parameters are been shadowed
if (scope.block === node) { * @param {object} scope current scope
scope = scope.upper; * @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)) { CatchClause: function(node) {
context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.", var scope = context.getScope();
{ name: node.param.name });
// 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 * @fileoverview A rule to disallow modifying variables of class declarations
* @author Toru Nagashima * @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/ */
"use strict"; "use strict";
@ -12,37 +11,47 @@ var astUtils = require("../ast-utils");
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
/** docs: {
* Finds and reports references that are non initializer and writable. description: "disallow reassigning class members",
* @param {Variable} variable - A variable to check. category: "ECMAScript 6",
* @returns {void} recommended: true
*/ },
function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) { schema: []
context.report( },
reference.identifier,
"'{{name}}' is a class.", create: function(context) {
{name: reference.identifier.name});
/**
}); * 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"; "use strict";
var NODE_DESCRIPTIONS = { var NODE_DESCRIPTIONS = {
"DoWhileStatement": "a 'do...while' statement", DoWhileStatement: "a 'do...while' statement",
"ForStatement": "a 'for' statement", ForStatement: "a 'for' statement",
"IfStatement": "an 'if' statement", IfStatement: "an 'if' statement",
"WhileStatement": "a 'while' statement" WhileStatement: "a 'while' statement"
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
var prohibitAssign = (context.options[0] || "except-parens"); docs: {
description: "disallow assignment operators in conditional expressions",
/** category: "Possible Errors",
* Check whether an AST node is the test expression for a conditional statement. recommended: true
* @param {!Object} node The node to test. },
* @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`.
*/ schema: [
function isConditionalTestExpression(node) { {
return node.parent && enum: ["except-parens", "always"]
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;
} }
} 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. * Check whether the code represented by an AST node is enclosed in parentheses.
* @param {!Object} node The node to test. * @param {!Object} node The node to test.
* @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`. * @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`.
*/ */
function isParenthesised(node) { function isParenthesised(node) {
var previousToken = context.getTokenBefore(node), var previousToken = context.getTokenBefore(node),
nextToken = context.getTokenAfter(node); nextToken = context.getTokenAfter(node);
return previousToken.value === "(" && previousToken.range[1] <= node.range[0] && return previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1]; nextToken.value === ")" && nextToken.range[0] >= node.range[1];
} }
/** /**
* Check whether the code represented by an AST node is enclosed in two sets of parentheses. * Check whether the code represented by an AST node is enclosed in two sets of parentheses.
* @param {!Object} node The node to test. * @param {!Object} node The node to test.
* @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`. * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
*/ */
function isParenthesisedTwice(node) { function isParenthesisedTwice(node) {
var previousToken = context.getTokenBefore(node, 1), var previousToken = context.getTokenBefore(node, 1),
nextToken = context.getTokenAfter(node, 1); nextToken = context.getTokenAfter(node, 1);
return isParenthesised(node) && return isParenthesised(node) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] && previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1]; 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. * 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. * @param {!Object} node The node for the conditional statement.
* @returns {void} * @returns {void}
*/ */
function testForAssign(node) { function testForAssign(node) {
if (node.test && if (node.test &&
(node.test.type === "AssignmentExpression") && (node.test.type === "AssignmentExpression") &&
(node.type === "ForStatement" ? (node.type === "ForStatement" ?
!isParenthesised(node.test) : !isParenthesised(node.test) :
!isParenthesisedTwice(node.test) !isParenthesisedTwice(node.test)
) )
) { ) {
// must match JSHint's error message // must match JSHint's error message
context.report({ context.report({
node: node, node: node,
loc: node.test.loc.start, loc: node.test.loc.start,
message: "Expected a conditional expression and instead saw an assignment." 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
});
}
} }
}
/** if (prohibitAssign === "always") {
* Check whether an assignment expression is descended from a conditional statement's test expression. return {
* @param {!Object} node The node for the assignment expression. AssignmentExpression: testForConditionalAncestor
* @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 { 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 * @fileoverview A rule to warn against using arrow functions when they could be
* confused with comparisions * confused with comparisions
* @author Jxck <https://github.com/Jxck> * @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"; "use strict";
@ -47,31 +25,41 @@ function isConditional(node) {
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
var config = context.options[0] || {}; 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
}]
},
/** create: function(context) {
* Reports if an arrow function contains an ambiguous conditional. var config = context.options[0] || {};
* @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."); * 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 { return {
"ArrowFunctionExpression": checkArrowFunc 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 * @fileoverview Rule to flag use of console object
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
* @copyright 2016 Eric Correia. All rights reserved.
*/ */
"use strict"; "use strict";
@ -10,47 +9,57 @@
// Rule Definition // 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") { return {
var blockConsole = true;
if (context.options.length > 0) { MemberExpression: function(node) {
var allowedProperties = context.options[0].allow;
var passedProperty = node.property.name;
var propertyIsAllowed = (allowedProperties.indexOf(passedProperty) > -1);
if (propertyIsAllowed) { if (node.object.name === "console") {
blockConsole = false; 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) { if (blockConsole) {
context.report(node, "Unexpected console statement."); 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` * @fileoverview A rule to disallow modifying variables that are declared using `const`
* @author Toru Nagashima * @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/ */
"use strict"; "use strict";
@ -12,30 +11,40 @@ var astUtils = require("../ast-utils");
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
/** docs: {
* Finds and reports references that are non initializer and writable. description: "disallow reassigning `const` variables",
* @param {Variable} variable - A variable to check. category: "ECMAScript 6",
* @returns {void} recommended: true
*/ },
function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) { schema: []
context.report( },
reference.identifier,
"'{{name}}' is constant.", create: function(context) {
{name: reference.identifier.name});
}); /**
} * 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 { return {
"VariableDeclaration": function(node) { VariableDeclaration: function(node) {
if (node.kind === "const") { if (node.kind === "const") {
context.getDeclaredVariables(node).forEach(checkVariable); 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 * @fileoverview Rule to flag use constant conditions
* @author Christian Schulz <http://rndm.de> * @author Christian Schulz <http://rndm.de>
* @copyright 2014 Christian Schulz. All rights reserved.
*/ */
"use strict"; "use strict";
@ -10,69 +9,110 @@
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(context) { module.exports = {
meta: {
//-------------------------------------------------------------------------- docs: {
// Helpers description: "disallow constant expressions in conditions",
//-------------------------------------------------------------------------- category: "Possible Errors",
recommended: true
/** },
* Checks if a node has a constant truthiness value.
* @param {ASTNode} node The AST node to check. schema: []
* @returns {Bool} true when node's truthiness is constant },
* @private
*/ create: function(context) {
function isConstant(node) {
switch (node.type) { //--------------------------------------------------------------------------
case "Literal": // Helpers
case "ArrowFunctionExpression": //--------------------------------------------------------------------------
case "FunctionExpression":
case "ObjectExpression":
case "ArrayExpression": /**
return true; * 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
case "UnaryExpression": * @param {string} operator The operator of the main LogicalExpression.
return isConstant(node.argument); * @returns {boolean} true when condition short circuits whole condition
*/
case "BinaryExpression": function isLogicalIdentity(node, operator) {
case "LogicalExpression": switch (node.type) {
return isConstant(node.left) && isConstant(node.right) && node.operator !== "in"; case "Literal":
return (operator === "||" && node.value === true) ||
case "AssignmentExpression": (operator === "&&" && node.value === false);
return (node.operator === "=") && isConstant(node.right); case "LogicalExpression":
return isLogicalIdentity(node.left, node.operator) ||
case "SequenceExpression": isLogicalIdentity(node.right, node.operator);
return isConstant(node.expressions[node.expressions.length - 1]);
// no default
// no default }
return false;
} }
return false;
}
/** /**
* Reports when the given node contains a constant condition. * Checks if a node has a constant truthiness value.
* @param {ASTNode} node The AST node to check. * @param {ASTNode} node The AST node to check.
* @returns {void} * @param {boolean} inBooleanPosition `false` if checking branch of a condition.
* @private * `true` in all other cases
*/ * @returns {Bool} true when node's truthiness is constant
function checkConstantCondition(node) { * @private
if (node.test && isConstant(node.test)) { */
context.report(node, "Unexpected constant condition."); 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, // Public
"IfStatement": checkConstantCondition, //--------------------------------------------------------------------------
"WhileStatement": checkConstantCondition,
"DoWhileStatement": checkConstantCondition,
"ForStatement": checkConstantCondition
};
}; 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