diff --git a/tools/eslint/CHANGELOG.md b/tools/eslint/CHANGELOG.md index 9454793b42..9b40e8ce14 100644 --- a/tools/eslint/CHANGELOG.md +++ b/tools/eslint/CHANGELOG.md @@ -1,3 +1,95 @@ +v2.9.0 - April 29, 2016 + +* a8a2cd8 Fix: Avoid autoconfig crashes from inline comments (fixes #5992) (#5999) (Ian VanSchooten) +* 23b00e0 Upgrade: npm-license to 0.3.2 (fixes #5996) (#5998) (alberto) +* 377167d Upgrade: ignore to 3.1.2 (fixes #5979) (#5988) (alberto) +* 141b778 Fix: no-control-regex literal handling fixed. (fixes #5737) (#5943) (Efe Gürkan YALAMAN) +* 577757d Fix: Clarify color option (fixes #5928) (#5974) (Grant Snodgrass) +* e7e6581 Docs: Update CLA link (#5980) (Gustav Nikolaj) +* 0be26bc Build: Add nodejs 6 to travis (fixes #5971) (#5973) (Gyandeep Singh) +* e606523 New: Rule `no-unsafe-finally` (fixes #5808) (#5932) (Onur Temizkan) +* 42d1ecc Chore: Add metadata to existing rules - Batch 7 (refs #5417) (#5969) (Vitor Balocco) +* e2ad1ec Update: object-shorthand lints computed methods (fixes #5871) (#5963) (Chris Sauvé) +* d24516a Chore: Add metadata to existing rules - Batch 6 (refs #5417) (#5966) (Vitor Balocco) +* 1e7a3ef Fix: `id-match` false positive in property values (fixes #5885) (#5960) (Mike Sherov) +* 51ddd4b Update: Use process @abstract when processing @return (fixes #5941) (#5945) (Simon Schick) +* 52a4bea Update: Add autofix for `no-whitespace-before-property` (fixes #5927) (#5951) (alberto) +* 46e058d Docs: Correct typo in configuring.md (#5957) (Nick S. Plekhanov) +* 5f8abab Chore: Add metadata to existing rules - Batch 5 (refs #5417) (#5944) (Vitor Balocco) +* 0562f77 Chore: Add missing newlines to test cases (fixes #5947) (Rich Trott) +* fc78e78 Chore: Enable quote-props rule in eslint-config-eslint (refs #5188) (#5938) (Gyandeep Singh) +* 43f6d05 Docs: Update docs to refer to column (#5937) (Sashko Stubailo) +* 586478e Update: Add autofix for `comma-dangle` (fixes #3805) (#5925) (alberto) +* a4f9c5a Docs: Distinguish examples in rules under Stylistic Issues part 3 (Kenneth Williams) +* e7c0737 Chore: Enable no-console rule in eslint-config-eslint (refs #5188) (Kevin Partington) +* 0023fe6 Build: Add “chore” to commit tags (fixes #5880) (#5929) (Mike Sherov) +* 25d626a Upgrade: espree 3.1.4 (fixes #5923, fixes #5756) (Kai Cataldo) +* a01b412 New: Add `no-useless-computed-key` rule (fixes #5402) (Burak Yigit Kaya) +* 9afb9cb Chore: Remove workaround for espree and escope bugs (fixes #5852) (alberto) +* 3ffc582 Chore: Update copyright and license info (alberto) +* 249eb40 Docs: Clarify init sets up local installation (fixes #5874) (Kai Cataldo) +* 6cd8c86 Docs: Describe options in rules under Possible Errors part 1 (Mark Pedrotti) +* f842d18 Fix: `no-this-before-super` crash on unreachable paths (fixes #5894) (Toru Nagashima) +* a02960b Docs: Fix missing delimiter in README links (Kevin Partington) +* 3a9e72c Docs: Update developer guide with new standards (Nicholas C. Zakas) +* cb78585 Update: Add `allowUnboundThis` to `prefer-arrow-callback` (fixes #4668) (Burak Yigit Kaya) +* 02be29f Chore: Remove CLA check from bot (Nicholas C. Zakas) +* 220713e Chore: Add metadata to existing rules - Batch 4 (refs #5417) (Vitor Balocco) +* df53414 Chore: Include jQuery Foundation info (Nicholas C. Zakas) +* f1b2992 Fix: `no-useless-escape` false positive in JSXAttribute (fixes #5882) (Toru Nagashima) +* 74674ad Docs: Move `sort-imports` to 'ECMAScript 6' (Kenneth Williams) +* ae69ddb Docs: Fix severity type in example (Kenneth Williams) +* 19f6fff Update: Autofixing does multiple passes (refs #5329) (Nicholas C. Zakas) +* 1e4b0ca Docs: Reduce length of paragraphs in rules index (Mark Pedrotti) +* 8cfe1eb Docs: Fix a wrong option (Zach Orlovsky) +* 8f6739f Docs: Add alberto as reviewer (alberto) +* 2ae4938 Docs: Fix message for `inline-config` option (alberto) +* 089900b Docs: Fix a wrong rule name in an example (Toru Nagashima) +* c032b41 Docs: Fix emphasis (Toru Nagashima) +* ae606f0 Docs: Update JSCS info in README (alberto) +* a9c5323 Fix: Install ESLint on init if not installed (fixes #5833) (Kai Cataldo) +* ed38358 Docs: Removed incorrect example (James M. Greene) +* af3113c Docs: Fix config comments in indent docs (Brandon Mills) +* 2b39461 Update: `commentPattern` option for `default-case` rule (fixes #5803) (Artyom Lvov) + +v2.8.0 - April 15, 2016 + +* a8821a5 Docs: Distinguish examples in rules under Stylistic Issues part 2 (Kenneth Williams) +* 76913b6 Update: Add metadata to existing rules - Batch 3 (refs #5417) (Vitor Balocco) +* 34ad8d2 Fix: Check that module.paths exists (fixes #5791) (Nicholas C. Zakas) +* 37239b1 Docs: Add new members of the team (Ilya Volodin) +* fb3c2eb Update: allow template literals (fixes #5234) (Jonathan Haines) +* 5a4a935 Update: Add metadata to existing rules - Batch 2 (refs #5417) (Vitor Balocco) +* ea2e625 Fix: newline-before-return handles return as first token (fixes #5816) (Kevin Partington) +* f8db9c9 Update: add nestedBinaryExpressions to no-extra-parens (fixes #3065) (Ilya Volodin) +* 0045d57 Update: `allowNamedFunctions` in `prefer-arrow-callback` (fixes #5675) (alberto) +* 19da72a Update: Add metadata to existing rules - Batch 1 (refs #5417) (Vitor Balocco) +* cc14e43 Fix: `no-fallthrough` empty case with comment (fixes #5799) (alberto) +* 13c8b14 Fix: LogicalExpression checks for short circuit (fixes #5693) (Vamshi krishna) +* 73b225e Fix: Document and fix metadata (refs #5417) (Ilya Volodin) +* 882d199 Docs: Improve options description in `no-redeclare` (alberto) +* 6a71ceb Docs: Improve options description in `no-params-reassign` (alberto) +* 24b6215 Update: Include 'typeof' in rule 'no-constant-condition' (fixes #5228) (Vamshi krishna) +* a959063 Docs: Remove link to deprecated ESLintTester project (refs #3110) (Trey Thomas) +* 6fd7d82 Update: Change order in `eslint --init` env options (fixes #5742) (alberto) +* c59d909 Fix: Extra paren check around object arrow bodies (fixes #5789) (Brandon Mills) +* 6f88546 Docs: Use double quotes for better Win compatibility (fixes #5796) (alberto) +* 02743d5 Fix: catch self-assignment operators in `no-magic-number` (fixes #4400) (alberto) +* c94e74e Docs: Make rule descriptions more consistent (Kenneth Williams) +* 6028252 Docs: Distinguish examples in rules under Stylistic Issues part 1 (Mark Pedrotti) +* ccd8ca9 Fix: Added property onlyDeclaration to id-match rule (fixes #3488) (Gajus Kuizinas) +* 6703c02 Update: no-useless-escape / exact locations of errors (fixes #5751) (Onur Temizkan) +* 3d84b91 Fix: ignore trailing whitespace in template literal (fixes #5786) (Kai Cataldo) +* b0e6bc4 Update: add allowEmptyCatch option to no-empty (fixes #5800) (Kai Cataldo) +* f1f1dd7 Docs: Add @pedrottimark as a committer (Brandon Mills) +* 228f201 Update: `commentPattern` option for `no-fallthrough` rule (fixes #5757) (Artyom Lvov) +* 41db670 Docs: Clarify disable inline comments (Kai Cataldo) +* 9c9a295 Docs: Add note about shell vs node glob parameters in cli (alberto) +* 5308ff9 Docs: Add code backticks to sentence in fixable rules (Mark Pedrotti) +* 965ec06 Docs: fix the examples for space-before-function-paren. (Craig Silverstein) +* 2b202fc Update: Add ignore option to space-before-function-parens (fixes #4127) (Craig Silverstein) +* 24c12ba Fix: improve `constructor-super` errors for literals (fixes #5449) (Toru Nagashima) + v2.7.0 - April 4, 2016 * 134cb1f Revert "Update: adds nestedBinaryExpressions for no-extra-parens rule (fixes #3065)" (Ilya Volodin) diff --git a/tools/eslint/LICENSE b/tools/eslint/LICENSE index 3f7b4baaa1..d41bdf7951 100644 --- a/tools/eslint/LICENSE +++ b/tools/eslint/LICENSE @@ -1,5 +1,5 @@ ESLint -Copyright (c) 2013 Nicholas C. Zakas. All rights reserved. +Copyright jQuery Foundation and other contributors, https://jquery.org/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/tools/eslint/README.md b/tools/eslint/README.md index a7864f8abb..9cbb090926 100644 --- a/tools/eslint/README.md +++ b/tools/eslint/README.md @@ -8,7 +8,15 @@ # ESLint -[Website](http://eslint.org) | [Configuring](http://eslint.org/docs/user-guide/configuring) | [Rules](http://eslint.org/docs/rules/) | [Contributing](http://eslint.org/docs/developer-guide/contributing) | [Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) | [Twitter](https://twitter.com/geteslint) | [Mailing List](https://groups.google.com/group/eslint) | [Chat Room](https://gitter.im/eslint/eslint) +[Website](http://eslint.org) | +[Configuring](http://eslint.org/docs/user-guide/configuring) | +[Rules](http://eslint.org/docs/rules/) | +[Contributing](http://eslint.org/docs/developer-guide/contributing) | +[Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) | +[Code of Conduct](https://jquery.org/conduct/) | +[Twitter](https://twitter.com/geteslint) | +[Mailing List](https://groups.google.com/group/eslint) | +[Chat Room](https://gitter.im/eslint/eslint) ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions: @@ -32,6 +40,8 @@ After that, you can run ESLint on any JavaScript file: eslint test.js test2.js +**Note:** `eslint --init` is intended for setting up and configuring ESLint on a per-project basis and will perform a local installation of ESLint and its plugins in the directory in which it is run. If you prefer using a global installation of ESLint, any plugins used in your configuration must also be installed globally. + ## Configuration After running `eslint --init`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this: @@ -67,13 +77,19 @@ These folks keep the project moving and are resources for help: * Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer * Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer * Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - reviewer +* Alberto Rodríguez ([@alberto](https://github.com/alberto)) - reviewer * Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - committer * Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - committer * Ian VanSchooten ([@ianvs](https://github.com/ianvs)) - committer * Burak Yiğit Kaya ([@byk](https://github.com/byk)) - committer -* Alberto Rodríguez ([@alberto](https://github.com/alberto)) - committer * Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo)) - committer * Michael Ficarra ([@michaelficarra](https://github.com/michaelficarra)) - committer +* Mark Pedrotti ([@pedrottimark](https://github.com/pedrottimark)) - committer +* Oleg Gaidarenko ([@markelog](https://github.com/markelog)) - committer +* Mike Sherov [@mikesherov](https://github.com/mikesherov)) - committer +* Henry Zhu ([@hzoo](https://github.com/hzoo)) - committer +* Marat Dulin ([@mdevils](https://github.com/mdevils)) - committer +* Alexej Yaroshevich ([@zxqfox](https://github.com/zxqfox)) - committer ## Releases @@ -98,15 +114,19 @@ I do like JSHint. And I like Anton and Rick. Neither of those were deciding fact That's not really a question, but I got it. I'm not trying to convince you that ESLint is better than JSHint. The only thing I know is that ESLint is better than JSHint for what I'm doing. In the off chance you're doing something similar, it might be better for you. Otherwise, keep using JSHint, I'm certainly not going to tell you to stop using it. -### How does ESLint performance compare to JSHint and JSCS? +### How does ESLint performance compare to JSHint? ESLint is slower than JSHint, usually 2-3x slower on a single file. This is because ESLint uses Espree to construct an AST before it can evaluate your code whereas JSHint evaluates your code as it's being parsed. The speed is also based on the number of rules you enable; the more rules you enable, the slower the process. Despite being slower, we believe that ESLint is fast enough to replace JSHint without causing significant pain. -ESLint is faster than JSCS, as ESLint uses a single-pass traversal for analysis whereas JSCS using a querying model. +### I heard ESLint is going to replace JSCS? + +Yes. Since we are solving the same problems, ESLint and JSCS teams have decided to join forces and work together in the development of ESLint instead of competing with each other. You can read more about this in both [ESLint](http://eslint.org/blog/2016/04/welcoming-jscs-to-eslint) and [JSCS](https://medium.com/@markelog/jscs-end-of-the-line-bc9bf0b3fdb2#.u76sx334n) announcements. + +### So, should I stop using JSCS and start using ESLint? -If you are using both JSHint and JSCS on your files, then using just ESLint will be faster. +Not yet. We are still working to smooth the transition. You can see our progress [here](https://github.com/eslint/eslint/milestones/JSCS%20Compatibility). We’ll announce when all of the changes necessary to support JSCS users in ESLint are complete and will start encouraging JSCS users to switch to ESLint at that time. Meanwhile, we recommend you to upgrade to JSCS 3.0 and provide feedback to the team. ### Is ESLint just linting or does it also check style? diff --git a/tools/eslint/bin/eslint.js b/tools/eslint/bin/eslint.js old mode 100644 new mode 100755 index 19e5720a71..1dbdbe8338 --- a/tools/eslint/bin/eslint.js +++ b/tools/eslint/bin/eslint.js @@ -3,8 +3,6 @@ /** * @fileoverview Main CLI that is run via the eslint command. * @author Nicholas C. Zakas - * @copyright 2013 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/conf/cli-options.js b/tools/eslint/conf/cli-options.js index 02d1f53542..83c815987b 100644 --- a/tools/eslint/conf/cli-options.js +++ b/tools/eslint/conf/cli-options.js @@ -1,8 +1,6 @@ /** * @fileoverview Default CLIEngineOptions. * @author Ian VanSchooten - * @copyright 2016 Ian VanSchooten. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; diff --git a/tools/eslint/conf/environments.js b/tools/eslint/conf/environments.js index 13c6da81f1..ee76503827 100644 --- a/tools/eslint/conf/environments.js +++ b/tools/eslint/conf/environments.js @@ -1,7 +1,6 @@ /** * @fileoverview Defines environment settings and globals. * @author Elan Shanker - * @copyright 2014 Elan Shanker. All rights reserved. */ "use strict"; diff --git a/tools/eslint/conf/eslint.json b/tools/eslint/conf/eslint.json index 21af92e0da..33336a504f 100644 --- a/tools/eslint/conf/eslint.json +++ b/tools/eslint/conf/eslint.json @@ -109,11 +109,13 @@ "no-unmodified-loop-condition": "off", "no-unneeded-ternary": "off", "no-unreachable": "error", + "no-unsafe-finally": "off", "no-unused-expressions": "off", "no-unused-labels": "error", "no-unused-vars": "error", "no-use-before-define": "off", "no-useless-call": "off", + "no-useless-computed-key": "off", "no-useless-concat": "off", "no-useless-constructor": "off", "no-useless-escape": "off", diff --git a/tools/eslint/lib/ast-utils.js b/tools/eslint/lib/ast-utils.js index 63b1fb628f..e008beeb2a 100644 --- a/tools/eslint/lib/ast-utils.js +++ b/tools/eslint/lib/ast-utils.js @@ -1,8 +1,6 @@ /** * @fileoverview Common utils for AST. * @author Gyandeep Singh - * @copyright 2015 Gyandeep Singh. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/cli-engine.js b/tools/eslint/lib/cli-engine.js index 2cc62f955e..7fa1a794ea 100644 --- a/tools/eslint/lib/cli-engine.js +++ b/tools/eslint/lib/cli-engine.js @@ -1,8 +1,6 @@ /** * @fileoverview Main CLI object. * @author Nicholas C. Zakas - * @copyright 2014 Nicholas C. Zakas. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; @@ -115,6 +113,79 @@ function calculateStatsPerRun(results) { }); } +/** + * Performs multiple autofix passes over the text until as many fixes as possible + * have been applied. + * @param {string} text The source text to apply fixes to. + * @param {Object} config The ESLint config object to use. + * @param {Object} options The ESLint options object to use. + * @param {string} options.filename The filename from which the text was read. + * @param {boolean} options.allowInlineConfig Flag indicating if inline comments + * should be allowed. + * @returns {Object} The result of the fix operation as returned from the + * SourceCodeFixer. + * @private + */ +function multipassFix(text, config, options) { + + var messages = [], + fixedResult, + fixed = false, + passNumber = 0, + lastMessageCount, + MAX_PASSES = 10; + + /** + * This loop continues until one of the following is true: + * + * 1. No more fixes have been applied. + * 2. There are no more linting errors reported. + * 3. The number of linting errors is no different between two passes. + * 4. Ten passes have been made. + * + * That means anytime a fix is successfully applied, there will be another pass. + * Essentially, guaranteeing a minimum of two passes. + */ + do { + passNumber++; + lastMessageCount = messages.length; + + debug("Linting code for " + options.filename + " (pass " + passNumber + ")"); + messages = eslint.verify(text, config, options); + + debug("Generating fixed text for " + options.filename + " (pass " + passNumber + ")"); + fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages); + + // keep track if any fixes were ever applied - important for return value + fixed = fixed || fixedResult.fixed; + + // update to use the fixed output instead of the original text + text = fixedResult.output; + + } while ( + fixedResult.fixed && fixedResult.messages.length > 0 && + fixedResult.messages.length !== lastMessageCount && + passNumber < MAX_PASSES + ); + + + /* + * If the last result had fixes, we need to lint again to me sure we have + * the most up-to-date information. + */ + if (fixedResult.fixed) { + fixedResult.messages = eslint.verify(text, config, options); + } + + + // ensure the last result properly reflects if fixes were done + fixedResult.fixed = fixed; + fixedResult.output = text; + + return fixedResult; + +} + /** * Processes an source code using ESLint. * @param {string} text The source code to check. @@ -179,15 +250,17 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) { } else { - messages = eslint.verify(text, config, { - filename: filename, - allowInlineConfig: allowInlineConfig - }); - if (fix) { - debug("Generating fixed text for " + filename); - fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages); + fixedResult = multipassFix(text, config, { + filename: filename, + allowInlineConfig: allowInlineConfig + }); messages = fixedResult.messages; + } else { + messages = eslint.verify(text, config, { + filename: filename, + allowInlineConfig: allowInlineConfig + }); } } diff --git a/tools/eslint/lib/code-path-analysis/code-path-analyzer.js b/tools/eslint/lib/code-path-analysis/code-path-analyzer.js index afb9980430..4e55cccee6 100644 --- a/tools/eslint/lib/code-path-analysis/code-path-analyzer.js +++ b/tools/eslint/lib/code-path-analysis/code-path-analyzer.js @@ -1,8 +1,6 @@ /** * @fileoverview A class of the code path analyzer. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/code-path-analysis/code-path-segment.js b/tools/eslint/lib/code-path-analysis/code-path-segment.js index ea292efc36..d5361ccd07 100644 --- a/tools/eslint/lib/code-path-analysis/code-path-segment.js +++ b/tools/eslint/lib/code-path-analysis/code-path-segment.js @@ -1,8 +1,6 @@ /** * @fileoverview A class of the code path segment. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -11,8 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -var assert = require("assert"), - debug = require("./debug-helpers"); +var debug = require("./debug-helpers"); //------------------------------------------------------------------------------ // Helpers @@ -178,7 +175,13 @@ CodePathSegment.newNext = function(id, allPrevSegments) { * @returns {CodePathSegment} The created segment. */ CodePathSegment.newUnreachable = function(id, allPrevSegments) { - return new CodePathSegment(id, flattenUnusedSegments(allPrevSegments), false); + var segment = new CodePathSegment(id, flattenUnusedSegments(allPrevSegments), false); + + // In `if (a) return a; foo();` case, the unreachable segment preceded by + // the return statement is not used but must not be remove. + CodePathSegment.markUsed(segment); + + return segment; }; /** @@ -203,7 +206,9 @@ CodePathSegment.newDisconnected = function(id, allPrevSegments) { * @returns {void} */ CodePathSegment.markUsed = function(segment) { - assert(!segment.internal.used, segment.id + " is marked twice."); + if (segment.internal.used) { + return; + } segment.internal.used = true; var i; diff --git a/tools/eslint/lib/code-path-analysis/code-path-state.js b/tools/eslint/lib/code-path-analysis/code-path-state.js index b620ed718b..0492d832c2 100644 --- a/tools/eslint/lib/code-path-analysis/code-path-state.js +++ b/tools/eslint/lib/code-path-analysis/code-path-state.js @@ -1,8 +1,6 @@ /** * @fileoverview A class to manage state of generating a code path. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/code-path-analysis/code-path.js b/tools/eslint/lib/code-path-analysis/code-path.js index 2b45ed944f..035e34e712 100644 --- a/tools/eslint/lib/code-path-analysis/code-path.js +++ b/tools/eslint/lib/code-path-analysis/code-path.js @@ -1,8 +1,6 @@ /** * @fileoverview A class of the code path. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/code-path-analysis/debug-helpers.js b/tools/eslint/lib/code-path-analysis/debug-helpers.js index 6ecfabe955..e68c94bc49 100644 --- a/tools/eslint/lib/code-path-analysis/debug-helpers.js +++ b/tools/eslint/lib/code-path-analysis/debug-helpers.js @@ -1,8 +1,6 @@ /** * @fileoverview Helpers to debug for code path analysis. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/code-path-analysis/fork-context.js b/tools/eslint/lib/code-path-analysis/fork-context.js index af82e68e0c..00e0f97c40 100644 --- a/tools/eslint/lib/code-path-analysis/fork-context.js +++ b/tools/eslint/lib/code-path-analysis/fork-context.js @@ -5,8 +5,6 @@ * This has a fork list and manages it. * * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/code-path-analysis/id-generator.js b/tools/eslint/lib/code-path-analysis/id-generator.js index 530ea95844..f33858cacd 100644 --- a/tools/eslint/lib/code-path-analysis/id-generator.js +++ b/tools/eslint/lib/code-path-analysis/id-generator.js @@ -5,8 +5,6 @@ * information of the code path. * * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/config.js b/tools/eslint/lib/config.js index cf14717d4f..b9c7061953 100644 --- a/tools/eslint/lib/config.js +++ b/tools/eslint/lib/config.js @@ -1,11 +1,8 @@ /** * @fileoverview Responsible for loading config files * @author Seth McLaughlin - * @copyright 2014-2016 Nicholas C. Zakas. All rights reserved. - * @copyright 2014 Michael McLaughlin. All rights reserved. - * @copyright 2013 Seth McLaughlin. All rights reserved. - * See LICENSE in root directory for full license. */ + "use strict"; //------------------------------------------------------------------------------ diff --git a/tools/eslint/lib/config/autoconfig.js b/tools/eslint/lib/config/autoconfig.js index ccb9f1a2fe..be3d097074 100644 --- a/tools/eslint/lib/config/autoconfig.js +++ b/tools/eslint/lib/config/autoconfig.js @@ -1,8 +1,6 @@ /** * @fileoverview Used for creating a suggested configuration based on project code. * @author Ian VanSchooten - * @copyright 2015 Ian VanSchooten. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; @@ -312,7 +310,13 @@ Registry.prototype = { var lintResults = eslint.verify(sourceCodes[filename], lintConfig); lintResults.forEach(function(result) { - lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1; + + // It is possible that the error is from a configuration comment + // in a linted file, in which case there may not be a config + // set in this ruleSetIdx. (https://github.com/eslint/eslint/issues/5992) + if (lintedRegistry.rules[result.ruleId][ruleSetIdx]) { + lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1; + } }); ruleSetIdx += 1; diff --git a/tools/eslint/lib/config/config-file.js b/tools/eslint/lib/config/config-file.js index f5ef3e88c6..51a81c7331 100644 --- a/tools/eslint/lib/config/config-file.js +++ b/tools/eslint/lib/config/config-file.js @@ -1,8 +1,6 @@ /** * @fileoverview Helper to locate and load configuration files. * @author Nicholas C. Zakas - * @copyright 2015 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ /* eslint no-use-before-define: 0 */ diff --git a/tools/eslint/lib/config/config-initializer.js b/tools/eslint/lib/config/config-initializer.js index f1d5121e4a..3d0e78fefe 100644 --- a/tools/eslint/lib/config/config-initializer.js +++ b/tools/eslint/lib/config/config-initializer.js @@ -1,7 +1,6 @@ /** * @fileoverview Config initialization wizard. * @author Ilya Volodin - * @copyright 2015 Ilya Volodin. All rights reserved. */ "use strict"; @@ -50,6 +49,10 @@ function writeFile(config, format) { ConfigFile.write(config, "./.eslintrc" + extname); log.info("Successfully created .eslintrc" + extname + " file in " + process.cwd()); + + if (config.installedESLint) { + log.info("ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy."); + } } /** @@ -76,12 +79,24 @@ function installModules(config) { if (modules.length === 0) { return; } + + // Add eslint to list in case user does not have it installed locally + modules.unshift("eslint"); + installStatus = npmUtil.checkDevDeps(modules); // Install packages which aren't already installed modulesToInstall = Object.keys(installStatus).filter(function(module) { - return installStatus[module] === false; + var notInstalled = installStatus[module] === false; + + if (module === "eslint" && notInstalled) { + log.info("Local ESLint installation not found."); + config.installedESLint = true; + } + + return notInstalled; }); + if (modulesToInstall.length > 0) { log.info("Installing " + modulesToInstall.join(", ")); npmUtil.installSyncSaveDev(modulesToInstall); @@ -366,7 +381,7 @@ function promptUser(callback) { name: "env", message: "Where will your code run?", default: ["browser"], - choices: [{name: "Node", value: "node"}, {name: "Browser", value: "browser"}] + choices: [{name: "Browser", value: "browser"}, {name: "Node", value: "node"}] }, { type: "confirm", diff --git a/tools/eslint/lib/config/config-ops.js b/tools/eslint/lib/config/config-ops.js index 6cf406ce57..727d3afa04 100644 --- a/tools/eslint/lib/config/config-ops.js +++ b/tools/eslint/lib/config/config-ops.js @@ -2,8 +2,6 @@ * @fileoverview Config file operations. This file must be usable in the browser, * so no Node-specific code can be here. * @author Nicholas C. Zakas - * @copyright 2015 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/config/config-rule.js b/tools/eslint/lib/config/config-rule.js index 513e0f4bda..c0a394efee 100644 --- a/tools/eslint/lib/config/config-rule.js +++ b/tools/eslint/lib/config/config-rule.js @@ -1,8 +1,6 @@ /** * @fileoverview Create configurations for a rule * @author Ian VanSchooten - * @copyright 2016 Ian VanSchooten. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/config/config-validator.js b/tools/eslint/lib/config/config-validator.js index d273b26683..ebc70516c1 100644 --- a/tools/eslint/lib/config/config-validator.js +++ b/tools/eslint/lib/config/config-validator.js @@ -1,8 +1,6 @@ /** * @fileoverview Validates configs. * @author Brandon Mills - * @copyright 2015 Brandon Mills - * See LICENSE file in root directory for full license. */ "use strict"; @@ -37,16 +35,16 @@ function getRuleOptionsSchema(id) { if (Array.isArray(schema)) { if (schema.length) { return { - "type": "array", - "items": schema, - "minItems": 0, - "maxItems": schema.length + type: "array", + items: schema, + minItems: 0, + maxItems: schema.length }; } else { return { - "type": "array", - "minItems": 0, - "maxItems": 0 + type: "array", + minItems: 0, + maxItems: 0 }; } } diff --git a/tools/eslint/lib/config/environments.js b/tools/eslint/lib/config/environments.js index 6470eec1b3..e7711836e5 100644 --- a/tools/eslint/lib/config/environments.js +++ b/tools/eslint/lib/config/environments.js @@ -1,8 +1,6 @@ /** * @fileoverview Environments manager * @author Nicholas C. Zakas - * @copyright 2016 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/config/plugins.js b/tools/eslint/lib/config/plugins.js index d1bfbd83fa..e157eb123c 100644 --- a/tools/eslint/lib/config/plugins.js +++ b/tools/eslint/lib/config/plugins.js @@ -1,8 +1,6 @@ /** * @fileoverview Plugins manager * @author Nicholas C. Zakas - * @copyright 2016 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/eslint.js b/tools/eslint/lib/eslint.js index b3e6c7483b..3a52bb3af1 100644 --- a/tools/eslint/lib/eslint.js +++ b/tools/eslint/lib/eslint.js @@ -1,9 +1,8 @@ /** * @fileoverview Main ESLint object. * @author Nicholas C. Zakas - * @copyright 2013 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ + "use strict"; //------------------------------------------------------------------------------ @@ -345,11 +344,11 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa } } else { // comment.type === "Line" if (match[1] === "eslint-disable-line") { - disableReporting(reportingConfig, { "line": comment.loc.start.line, "column": 0 }, Object.keys(parseListConfig(value))); + disableReporting(reportingConfig, { line: comment.loc.start.line, column: 0 }, Object.keys(parseListConfig(value))); enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value))); } else if (match[1] === "eslint-disable-next-line") { disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value))); - enableReporting(reportingConfig, { "line": comment.loc.start.line + 2 }, Object.keys(parseListConfig(value))); + enableReporting(reportingConfig, { line: comment.loc.start.line + 2 }, Object.keys(parseListConfig(value))); } } } @@ -964,7 +963,7 @@ module.exports = (function() { }; // ensure there's range and text properties as well as metadata switch, otherwise it's not a valid fix - if (fix && Array.isArray(fix.range) && (typeof fix.text === "string") && (!meta || !meta.docs || meta.docs.fixable)) { + if (fix && Array.isArray(fix.range) && (typeof fix.text === "string") && (!meta || meta.fixable)) { problem.fix = fix; } diff --git a/tools/eslint/lib/file-finder.js b/tools/eslint/lib/file-finder.js index 9e24659df7..4dbb7544a3 100644 --- a/tools/eslint/lib/file-finder.js +++ b/tools/eslint/lib/file-finder.js @@ -1,10 +1,8 @@ /** * @fileoverview Util class to find config files. * @author Aliaksei Shytkin - * @copyright 2014 Michael McLaughlin. All rights reserved. - * @copyright 2014 Aliaksei Shytkin. All rights reserved. - * See LICENSE in root directory for full license. */ + "use strict"; //------------------------------------------------------------------------------ diff --git a/tools/eslint/lib/formatters/html.js b/tools/eslint/lib/formatters/html.js index e8b5cd78de..da3f7596fd 100644 --- a/tools/eslint/lib/formatters/html.js +++ b/tools/eslint/lib/formatters/html.js @@ -1,7 +1,6 @@ /** * @fileoverview HTML reporter * @author Julian Laval - * @copyright 2015 Julian Laval. All rights reserved. */ "use strict"; diff --git a/tools/eslint/lib/formatters/json.js b/tools/eslint/lib/formatters/json.js index c1101970ef..82138af187 100644 --- a/tools/eslint/lib/formatters/json.js +++ b/tools/eslint/lib/formatters/json.js @@ -1,7 +1,6 @@ /** * @fileoverview JSON reporter * @author Burak Yigit Kaya aka BYK - * @copyright 2015 Burak Yigit Kaya. All rights reserved. */ "use strict"; diff --git a/tools/eslint/lib/formatters/table.js b/tools/eslint/lib/formatters/table.js index faa691c6ea..e287644455 100644 --- a/tools/eslint/lib/formatters/table.js +++ b/tools/eslint/lib/formatters/table.js @@ -1,7 +1,6 @@ /** * @fileoverview "table reporter. * @author Gajus Kuizinas - * @copyright 2016 Gajus Kuizinas . All rights reserved. */ "use strict"; diff --git a/tools/eslint/lib/formatters/unix.js b/tools/eslint/lib/formatters/unix.js index ba2e31f2f8..ce429fee25 100644 --- a/tools/eslint/lib/formatters/unix.js +++ b/tools/eslint/lib/formatters/unix.js @@ -1,7 +1,6 @@ /** * @fileoverview unix-style formatter. * @author oshi-shinobu - * @copyright 2015 oshi-shinobu. All rights reserved. */ "use strict"; diff --git a/tools/eslint/lib/formatters/visualstudio.js b/tools/eslint/lib/formatters/visualstudio.js index d0a3d99eb7..5d53dde289 100644 --- a/tools/eslint/lib/formatters/visualstudio.js +++ b/tools/eslint/lib/formatters/visualstudio.js @@ -1,8 +1,6 @@ /** * @fileoverview Visual Studio compatible formatter * @author Ronald Pijnacker - * @copyright 2015 Ronald Pijnacker. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/ignored-paths.js b/tools/eslint/lib/ignored-paths.js index d907b49589..c6a710a9b5 100644 --- a/tools/eslint/lib/ignored-paths.js +++ b/tools/eslint/lib/ignored-paths.js @@ -2,6 +2,7 @@ * @fileoverview Responsible for loading ignore config files and managing ignore patterns * @author Jonathan Rajavuori */ + "use strict"; //------------------------------------------------------------------------------ diff --git a/tools/eslint/lib/logging.js b/tools/eslint/lib/logging.js index f0ded09295..3a60879c61 100644 --- a/tools/eslint/lib/logging.js +++ b/tools/eslint/lib/logging.js @@ -1,10 +1,12 @@ /** * @fileoverview Handle logging for ESLint * @author Gyandeep Singh - * @copyright 2015 Gyandeep Singh. All rights reserved. */ + "use strict"; +/* eslint no-console: "off" */ + /* istanbul ignore next */ module.exports = { diff --git a/tools/eslint/lib/options.js b/tools/eslint/lib/options.js index e7f70ea092..7d61458581 100644 --- a/tools/eslint/lib/options.js +++ b/tools/eslint/lib/options.js @@ -1,8 +1,8 @@ /** * @fileoverview Options configuration for optionator. * @author George Zahariev - * See LICENSE in root directory for full license. */ + "use strict"; //------------------------------------------------------------------------------ @@ -173,8 +173,8 @@ module.exports = optionator({ { option: "color", type: "Boolean", - default: "true", - description: "Disable color in piped output" + alias: "no-color", + description: "Force enabling/disabling of color" }, { heading: "Miscellaneous" @@ -213,7 +213,7 @@ module.exports = optionator({ option: "inline-config", type: "Boolean", default: "true", - description: "Allow comments to change eslint config/rules" + description: "Prevent comments from changing config or rules" }, { option: "print-config", diff --git a/tools/eslint/lib/rule-context.js b/tools/eslint/lib/rule-context.js index 03f5b886fd..88e68abd73 100644 --- a/tools/eslint/lib/rule-context.js +++ b/tools/eslint/lib/rule-context.js @@ -1,8 +1,6 @@ /** * @fileoverview RuleContext utility for rules * @author Nicholas C. Zakas - * @copyright 2013 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/rules/accessor-pairs.js b/tools/eslint/lib/rules/accessor-pairs.js index 8f8f5293dc..1b91ef2715 100644 --- a/tools/eslint/lib/rules/accessor-pairs.js +++ b/tools/eslint/lib/rules/accessor-pairs.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag wrapping non-iife in parens * @author Gyandeep Singh - * @copyright 2015 Gyandeep Singh. All rights reserved. */ "use strict"; @@ -79,16 +78,16 @@ module.exports = { recommended: false }, schema: [{ - "type": "object", - "properties": { - "getWithoutSet": { - "type": "boolean" + type: "object", + properties: { + getWithoutSet: { + type: "boolean" }, - "setWithoutGet": { - "type": "boolean" + setWithoutGet: { + type: "boolean" } }, - "additionalProperties": false + additionalProperties: false }] }, create: function(context) { @@ -147,7 +146,7 @@ module.exports = { } return { - "ObjectExpression": function(node) { + ObjectExpression: function(node) { if (checkSetWithoutGet || checkGetWithoutSet) { checkLonelySetGet(node); } diff --git a/tools/eslint/lib/rules/array-bracket-spacing.js b/tools/eslint/lib/rules/array-bracket-spacing.js index 617d6279b1..379ed0fa59 100644 --- a/tools/eslint/lib/rules/array-bracket-spacing.js +++ b/tools/eslint/lib/rules/array-bracket-spacing.js @@ -1,10 +1,6 @@ /** * @fileoverview Disallows or enforces spaces inside of array brackets. * @author Jamund Ferguson - * @copyright 2015 Jamund Ferguson. All rights reserved. - * @copyright 2014 Brandyn Bennett. All rights reserved. - * @copyright 2014 Michael Ficarra. No rights reserved. - * @copyright 2014 Vignesh Anand. All rights reserved. */ "use strict"; @@ -19,27 +15,27 @@ module.exports = { docs: { description: "Enforce spacing inside array brackets", category: "Stylistic Issues", - recommended: false, - fixable: "whitespace" + recommended: false }, + fixable: "whitespace", schema: [ { - "enum": ["always", "never"] + enum: ["always", "never"] }, { - "type": "object", - "properties": { - "singleValue": { - "type": "boolean" + type: "object", + properties: { + singleValue: { + type: "boolean" }, - "objectsInArrays": { - "type": "boolean" + objectsInArrays: { + type: "boolean" }, - "arraysInArrays": { - "type": "boolean" + arraysInArrays: { + type: "boolean" } }, - "additionalProperties": false + additionalProperties: false } ] }, diff --git a/tools/eslint/lib/rules/array-callback-return.js b/tools/eslint/lib/rules/array-callback-return.js index 01fe01dd85..714c189c6b 100644 --- a/tools/eslint/lib/rules/array-callback-return.js +++ b/tools/eslint/lib/rules/array-callback-return.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to enforce return statements in callbacks of array's methods * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -166,76 +164,86 @@ function isCallbackOfArrayMethod(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var funcInfo = { - upper: null, - codePath: null, - hasReturn: false, - shouldCheck: false - }; - - /** - * Checks whether or not the last code path segment is reachable. - * Then reports this function if the segment is reachable. - * - * If the last code path segment is reachable, there are paths which are not - * returned or thrown. - * - * @param {ASTNode} node - A node to check. - * @returns {void} - */ - function checkLastSegment(node) { - if (funcInfo.shouldCheck && - funcInfo.codePath.currentSegments.some(isReachable) - ) { - context.report({ - node: node, - loc: getLocation(node, context.getSourceCode()).loc.start, - message: funcInfo.hasReturn - ? "Expected to return a value at the end of this function." - : "Expected to return a value in this function." - }); - } - } - - return { - - // Stacks this function's information. - "onCodePathStart": function(codePath, node) { - funcInfo = { - upper: funcInfo, - codePath: codePath, - hasReturn: false, - shouldCheck: - TARGET_NODE_TYPE.test(node.type) && - node.body.type === "BlockStatement" && - isCallbackOfArrayMethod(node) - }; +module.exports = { + meta: { + docs: { + description: "enforce `return` statements in callbacks of array methods", + category: "Best Practices", + recommended: false }, - // Pops this function's information. - "onCodePathEnd": function() { - funcInfo = funcInfo.upper; - }, - - // Checks the return statement is valid. - "ReturnStatement": function(node) { - if (funcInfo.shouldCheck) { - funcInfo.hasReturn = true; + schema: [] + }, + + create: function(context) { + var funcInfo = { + upper: null, + codePath: null, + hasReturn: false, + shouldCheck: false + }; + + /** + * Checks whether or not the last code path segment is reachable. + * Then reports this function if the segment is reachable. + * + * If the last code path segment is reachable, there are paths which are not + * returned or thrown. + * + * @param {ASTNode} node - A node to check. + * @returns {void} + */ + function checkLastSegment(node) { + if (funcInfo.shouldCheck && + funcInfo.codePath.currentSegments.some(isReachable) + ) { + context.report({ + node: node, + loc: getLocation(node, context.getSourceCode()).loc.start, + message: funcInfo.hasReturn + ? "Expected to return a value at the end of this function." + : "Expected to return a value in this function." + }); + } + } - if (!node.argument) { - context.report({ - node: node, - message: "Expected a return value." - }); + return { + + // Stacks this function's information. + onCodePathStart: function(codePath, node) { + funcInfo = { + upper: funcInfo, + codePath: codePath, + hasReturn: false, + shouldCheck: + TARGET_NODE_TYPE.test(node.type) && + node.body.type === "BlockStatement" && + isCallbackOfArrayMethod(node) + }; + }, + + // Pops this function's information. + onCodePathEnd: function() { + funcInfo = funcInfo.upper; + }, + + // Checks the return statement is valid. + ReturnStatement: function(node) { + if (funcInfo.shouldCheck) { + funcInfo.hasReturn = true; + + if (!node.argument) { + context.report({ + node: node, + message: "Expected a return value." + }); + } } - } - }, + }, - // Reports a given function if the last path is reachable. - "FunctionExpression:exit": checkLastSegment, - "ArrowFunctionExpression:exit": checkLastSegment - }; + // Reports a given function if the last path is reachable. + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/arrow-body-style.js b/tools/eslint/lib/rules/arrow-body-style.js index 3acf4942ca..79fde90f80 100644 --- a/tools/eslint/lib/rules/arrow-body-style.js +++ b/tools/eslint/lib/rules/arrow-body-style.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to require braces in arrow function body. * @author Alberto Rodríguez - * @copyright 2015 Alberto Rodríguez. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -10,50 +8,60 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var always = context.options[0] === "always"; - var asNeeded = !context.options[0] || context.options[0] === "as-needed"; +module.exports = { + meta: { + docs: { + description: "require braces around arrow function bodies", + category: "ECMAScript 6", + recommended: false + }, - /** - * Determines whether a arrow function body needs braces - * @param {ASTNode} node The arrow function node. - * @returns {void} - */ - function validate(node) { - var arrowBody = node.body; + schema: [ + { + enum: ["always", "as-needed"] + } + ] + }, - if (arrowBody.type === "BlockStatement") { - var blockBody = arrowBody.body; + create: function(context) { + var always = context.options[0] === "always"; + var asNeeded = !context.options[0] || context.options[0] === "as-needed"; - if (blockBody.length !== 1) { - return; - } + /** + * Determines whether a arrow function body needs braces + * @param {ASTNode} node The arrow function node. + * @returns {void} + */ + function validate(node) { + var arrowBody = node.body; - if (asNeeded && blockBody[0].type === "ReturnStatement") { - context.report({ - node: node, - loc: arrowBody.loc.start, - message: "Unexpected block statement surrounding arrow body." - }); - } - } else { - if (always) { - context.report({ - node: node, - loc: arrowBody.loc.start, - message: "Expected block statement surrounding arrow body." - }); + if (arrowBody.type === "BlockStatement") { + var blockBody = arrowBody.body; + + if (blockBody.length !== 1) { + return; + } + + if (asNeeded && blockBody[0].type === "ReturnStatement") { + context.report({ + node: node, + loc: arrowBody.loc.start, + message: "Unexpected block statement surrounding arrow body." + }); + } + } else { + if (always) { + context.report({ + node: node, + loc: arrowBody.loc.start, + message: "Expected block statement surrounding arrow body." + }); + } } } - } - - return { - "ArrowFunctionExpression": validate - }; -}; -module.exports.schema = [ - { - "enum": ["always", "as-needed"] + return { + ArrowFunctionExpression: validate + }; } -]; +}; diff --git a/tools/eslint/lib/rules/arrow-parens.js b/tools/eslint/lib/rules/arrow-parens.js index 2332dda121..78ce045017 100644 --- a/tools/eslint/lib/rules/arrow-parens.js +++ b/tools/eslint/lib/rules/arrow-parens.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to require parens in arrow function arguments. * @author Jxck - * @copyright 2015 Jxck. All rights reserved. */ "use strict"; @@ -9,44 +8,54 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var message = "Expected parentheses around arrow function argument."; - var asNeededMessage = "Unexpected parentheses around single function argument"; - var asNeeded = context.options[0] === "as-needed"; - - /** - * Determines whether a arrow function argument end with `)` - * @param {ASTNode} node The arrow function node. - * @returns {void} - */ - function parens(node) { - var token = context.getFirstToken(node); - - // as-needed: x => x - if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") { - if (token.type === "Punctuator" && token.value === "(") { - context.report(node, asNeededMessage); +module.exports = { + meta: { + docs: { + description: "require parentheses around arrow function arguments", + category: "ECMAScript 6", + recommended: false + }, + + schema: [ + { + enum: ["always", "as-needed"] + } + ] + }, + + create: function(context) { + var message = "Expected parentheses around arrow function argument."; + var asNeededMessage = "Unexpected parentheses around single function argument"; + var asNeeded = context.options[0] === "as-needed"; + + /** + * Determines whether a arrow function argument end with `)` + * @param {ASTNode} node The arrow function node. + * @returns {void} + */ + function parens(node) { + var token = context.getFirstToken(node); + + // as-needed: x => x + if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") { + if (token.type === "Punctuator" && token.value === "(") { + context.report(node, asNeededMessage); + } + return; } - return; - } - if (token.type === "Identifier") { - var after = context.getTokenAfter(token); + if (token.type === "Identifier") { + var after = context.getTokenAfter(token); - // (x) => x - if (after.value !== ")") { - context.report(node, message); + // (x) => x + if (after.value !== ")") { + context.report(node, message); + } } } - } - return { - "ArrowFunctionExpression": parens - }; -}; - -module.exports.schema = [ - { - "enum": ["always", "as-needed"] + return { + ArrowFunctionExpression: parens + }; } -]; +}; diff --git a/tools/eslint/lib/rules/arrow-spacing.js b/tools/eslint/lib/rules/arrow-spacing.js index e1ea0daaa4..82cec87ed2 100644 --- a/tools/eslint/lib/rules/arrow-spacing.js +++ b/tools/eslint/lib/rules/arrow-spacing.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to define spacing before/after arrow function's arrow. * @author Jxck - * @copyright 2015 Jxck. All rights reserved. */ "use strict"; @@ -9,125 +8,137 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - // merge rules with default - var rule = { before: true, after: true }, - option = context.options[0] || {}; - - rule.before = option.before !== false; - rule.after = option.after !== false; - - /** - * Get tokens of arrow(`=>`) and before/after arrow. - * @param {ASTNode} node The arrow function node. - * @returns {Object} Tokens of arrow and before/after arrow. - */ - function getTokens(node) { - var t = context.getFirstToken(node); - var before; - - while (t.type !== "Punctuator" || t.value !== "=>") { - before = t; - t = context.getTokenAfter(t); - } - var after = context.getTokenAfter(t); - - return { before: before, arrow: t, after: after }; - } - - /** - * Count spaces before/after arrow(`=>`) token. - * @param {Object} tokens Tokens before/after arrow. - * @returns {Object} count of space before/after arrow. - */ - function countSpaces(tokens) { - var before = tokens.arrow.range[0] - tokens.before.range[1]; - var after = tokens.after.range[0] - tokens.arrow.range[1]; - - return { before: before, after: after }; - } +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing before and after the arrow in arrow functions", + category: "ECMAScript 6", + recommended: false + }, - /** - * Determines whether space(s) before after arrow(`=>`) is satisfy rule. - * if before/after value is `true`, there should be space(s). - * if before/after value is `false`, there should be no space. - * @param {ASTNode} node The arrow function node. - * @returns {void} - */ - function spaces(node) { - var tokens = getTokens(node); - var countSpace = countSpaces(tokens); - - if (rule.before) { - - // should be space(s) before arrow - if (countSpace.before === 0) { - context.report({ - node: tokens.before, - message: "Missing space before =>", - fix: function(fixer) { - return fixer.insertTextBefore(tokens.arrow, " "); + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { + type: "boolean" + }, + after: { + type: "boolean" } - }); + }, + additionalProperties: false } - } else { - - // should be no space before arrow - if (countSpace.before > 0) { - context.report({ - node: tokens.before, - message: "Unexpected space before =>", - fix: function(fixer) { - return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]); - } - }); + ] + }, + + create: function(context) { + + // merge rules with default + var rule = { before: true, after: true }, + option = context.options[0] || {}; + + rule.before = option.before !== false; + rule.after = option.after !== false; + + /** + * Get tokens of arrow(`=>`) and before/after arrow. + * @param {ASTNode} node The arrow function node. + * @returns {Object} Tokens of arrow and before/after arrow. + */ + function getTokens(node) { + var t = context.getFirstToken(node); + var before; + + while (t.type !== "Punctuator" || t.value !== "=>") { + before = t; + t = context.getTokenAfter(t); } + var after = context.getTokenAfter(t); + + return { before: before, arrow: t, after: after }; } - if (rule.after) { + /** + * Count spaces before/after arrow(`=>`) token. + * @param {Object} tokens Tokens before/after arrow. + * @returns {Object} count of space before/after arrow. + */ + function countSpaces(tokens) { + var before = tokens.arrow.range[0] - tokens.before.range[1]; + var after = tokens.after.range[0] - tokens.arrow.range[1]; - // should be space(s) after arrow - if (countSpace.after === 0) { - context.report({ - node: tokens.after, - message: "Missing space after =>", - fix: function(fixer) { - return fixer.insertTextAfter(tokens.arrow, " "); - } - }); - } - } else { - - // should be no space after arrow - if (countSpace.after > 0) { - context.report({ - node: tokens.after, - message: "Unexpected space after =>", - fix: function(fixer) { - return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]); - } - }); - } + return { before: before, after: after }; } - } - return { - "ArrowFunctionExpression": spaces - }; -}; + /** + * Determines whether space(s) before after arrow(`=>`) is satisfy rule. + * if before/after value is `true`, there should be space(s). + * if before/after value is `false`, there should be no space. + * @param {ASTNode} node The arrow function node. + * @returns {void} + */ + function spaces(node) { + var tokens = getTokens(node); + var countSpace = countSpaces(tokens); + + if (rule.before) { + + // should be space(s) before arrow + if (countSpace.before === 0) { + context.report({ + node: tokens.before, + message: "Missing space before =>", + fix: function(fixer) { + return fixer.insertTextBefore(tokens.arrow, " "); + } + }); + } + } else { + + // should be no space before arrow + if (countSpace.before > 0) { + context.report({ + node: tokens.before, + message: "Unexpected space before =>", + fix: function(fixer) { + return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]); + } + }); + } + } -module.exports.schema = [ - { - "type": "object", - "properties": { - "before": { - "type": "boolean" - }, - "after": { - "type": "boolean" + if (rule.after) { + + // should be space(s) after arrow + if (countSpace.after === 0) { + context.report({ + node: tokens.after, + message: "Missing space after =>", + fix: function(fixer) { + return fixer.insertTextAfter(tokens.arrow, " "); + } + }); + } + } else { + + // should be no space after arrow + if (countSpace.after > 0) { + context.report({ + node: tokens.after, + message: "Unexpected space after =>", + fix: function(fixer) { + return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]); + } + }); + } } - }, - "additionalProperties": false + } + + return { + ArrowFunctionExpression: spaces + }; } -]; +}; diff --git a/tools/eslint/lib/rules/block-scoped-var.js b/tools/eslint/lib/rules/block-scoped-var.js index 42d8422a00..3da07adcba 100644 --- a/tools/eslint/lib/rules/block-scoped-var.js +++ b/tools/eslint/lib/rules/block-scoped-var.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to check for "block scoped" variables by binding context * @author Matt DuVall - * @copyright 2015 Toru Nagashima. All rights reserved. - * @copyright 2015 Mathieu M-Gosselin. All rights reserved. */ "use strict"; @@ -10,101 +8,111 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var stack = []; +module.exports = { + meta: { + docs: { + description: "enforce the use of variables within the scope they are defined", + category: "Best Practices", + recommended: false + }, - /** - * Makes a block scope. - * @param {ASTNode} node - A node of a scope. - * @returns {void} - */ - function enterScope(node) { - stack.push(node.range); - } + schema: [] + }, - /** - * Pops the last block scope. - * @returns {void} - */ - function exitScope() { - stack.pop(); - } - - /** - * Reports a given reference. - * @param {escope.Reference} reference - A reference to report. - * @returns {void} - */ - function report(reference) { - var identifier = reference.identifier; - - context.report( - identifier, - "'{{name}}' used outside of binding context.", - {name: identifier.name}); - } + create: function(context) { + var stack = []; - /** - * Finds and reports references which are outside of valid scopes. - * @param {ASTNode} node - A node to get variables. - * @returns {void} - */ - function checkForVariables(node) { - if (node.kind !== "var") { - return; + /** + * Makes a block scope. + * @param {ASTNode} node - A node of a scope. + * @returns {void} + */ + function enterScope(node) { + stack.push(node.range); } - // Defines a predicate to check whether or not a given reference is outside of valid scope. - var scopeRange = stack[stack.length - 1]; - /** - * Check if a reference is out of scope - * @param {ASTNode} reference node to examine - * @returns {boolean} True is its outside the scope - * @private + * Pops the last block scope. + * @returns {void} */ - function isOutsideOfScope(reference) { - var idRange = reference.identifier.range; - - return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1]; + function exitScope() { + stack.pop(); } - // Gets declared variables, and checks its references. - var variables = context.getDeclaredVariables(node); - - for (var i = 0; i < variables.length; ++i) { + /** + * Reports a given reference. + * @param {escope.Reference} reference - A reference to report. + * @returns {void} + */ + function report(reference) { + var identifier = reference.identifier; - // Reports. - variables[i] - .references - .filter(isOutsideOfScope) - .forEach(report); + context.report( + identifier, + "'{{name}}' used outside of binding context.", + {name: identifier.name}); } - } - return { - "Program": function(node) { - stack = [node.range]; - }, + /** + * Finds and reports references which are outside of valid scopes. + * @param {ASTNode} node - A node to get variables. + * @returns {void} + */ + function checkForVariables(node) { + if (node.kind !== "var") { + return; + } + + // Defines a predicate to check whether or not a given reference is outside of valid scope. + var scopeRange = stack[stack.length - 1]; + + /** + * Check if a reference is out of scope + * @param {ASTNode} reference node to examine + * @returns {boolean} True is its outside the scope + * @private + */ + function isOutsideOfScope(reference) { + var idRange = reference.identifier.range; + + return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1]; + } + + // Gets declared variables, and checks its references. + var variables = context.getDeclaredVariables(node); + + for (var i = 0; i < variables.length; ++i) { + + // Reports. + variables[i] + .references + .filter(isOutsideOfScope) + .forEach(report); + } + } - // Manages scopes. - "BlockStatement": enterScope, - "BlockStatement:exit": exitScope, - "ForStatement": enterScope, - "ForStatement:exit": exitScope, - "ForInStatement": enterScope, - "ForInStatement:exit": exitScope, - "ForOfStatement": enterScope, - "ForOfStatement:exit": exitScope, - "SwitchStatement": enterScope, - "SwitchStatement:exit": exitScope, - "CatchClause": enterScope, - "CatchClause:exit": exitScope, - - // Finds and reports references which are outside of valid scope. - "VariableDeclaration": checkForVariables - }; + return { + Program: function(node) { + stack = [node.range]; + }, + + // Manages scopes. + BlockStatement: enterScope, + "BlockStatement:exit": exitScope, + ForStatement: enterScope, + "ForStatement:exit": exitScope, + ForInStatement: enterScope, + "ForInStatement:exit": exitScope, + ForOfStatement: enterScope, + "ForOfStatement:exit": exitScope, + SwitchStatement: enterScope, + "SwitchStatement:exit": exitScope, + CatchClause: enterScope, + "CatchClause:exit": exitScope, + + // Finds and reports references which are outside of valid scope. + VariableDeclaration: checkForVariables + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/block-spacing.js b/tools/eslint/lib/rules/block-spacing.js index 5e9d990253..b6dc6e701d 100644 --- a/tools/eslint/lib/rules/block-spacing.js +++ b/tools/eslint/lib/rules/block-spacing.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to disallow or enforce spaces inside of single line blocks. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -12,109 +11,121 @@ var util = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var always = (context.options[0] !== "never"), - message = always ? "Requires a space" : "Unexpected space(s)", - sourceCode = context.getSourceCode(); - - /** - * Gets the open brace token from a given node. - * @param {ASTNode} node - A BlockStatement/SwitchStatement node to get. - * @returns {Token} The token of the open brace. - */ - function getOpenBrace(node) { - if (node.type === "SwitchStatement") { - if (node.cases.length > 0) { - return context.getTokenBefore(node.cases[0]); - } - return context.getLastToken(node, 1); - } - return context.getFirstToken(node); - } +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing inside single-line blocks", + category: "Stylistic Issues", + recommended: false + }, - /** - * Checks whether or not: - * - given tokens are on same line. - * - there is/isn't a space between given tokens. - * @param {Token} left - A token to check. - * @param {Token} right - The token which is next to `left`. - * @returns {boolean} - * When the option is `"always"`, `true` if there are one or more spaces between given tokens. - * When the option is `"never"`, `true` if there are not any spaces between given tokens. - * If given tokens are not on same line, it's always `true`. - */ - function isValid(left, right) { - return ( - !util.isTokenOnSameLine(left, right) || - sourceCode.isSpaceBetweenTokens(left, right) === always - ); - } + fixable: "whitespace", + + schema: [ + {enum: ["always", "never"]} + ] + }, + + create: function(context) { + var always = (context.options[0] !== "never"), + message = always ? "Requires a space" : "Unexpected space(s)", + sourceCode = context.getSourceCode(); - /** - * Reports invalid spacing style inside braces. - * @param {ASTNode} node - A BlockStatement/SwitchStatement node to get. - * @returns {void} - */ - function checkSpacingInsideBraces(node) { - - // Gets braces and the first/last token of content. - var openBrace = getOpenBrace(node); - var closeBrace = context.getLastToken(node); - var firstToken = sourceCode.getTokenOrCommentAfter(openBrace); - var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace); - - // Skip if the node is invalid or empty. - if (openBrace.type !== "Punctuator" || - openBrace.value !== "{" || - closeBrace.type !== "Punctuator" || - closeBrace.value !== "}" || - firstToken === closeBrace - ) { - return; + /** + * Gets the open brace token from a given node. + * @param {ASTNode} node - A BlockStatement/SwitchStatement node to get. + * @returns {Token} The token of the open brace. + */ + function getOpenBrace(node) { + if (node.type === "SwitchStatement") { + if (node.cases.length > 0) { + return context.getTokenBefore(node.cases[0]); + } + return context.getLastToken(node, 1); + } + return context.getFirstToken(node); } - // Skip line comments for option never - if (!always && firstToken.type === "Line") { - return; + /** + * Checks whether or not: + * - given tokens are on same line. + * - there is/isn't a space between given tokens. + * @param {Token} left - A token to check. + * @param {Token} right - The token which is next to `left`. + * @returns {boolean} + * When the option is `"always"`, `true` if there are one or more spaces between given tokens. + * When the option is `"never"`, `true` if there are not any spaces between given tokens. + * If given tokens are not on same line, it's always `true`. + */ + function isValid(left, right) { + return ( + !util.isTokenOnSameLine(left, right) || + sourceCode.isSpaceBetweenTokens(left, right) === always + ); } - // Check. - if (!isValid(openBrace, firstToken)) { - context.report({ - node: node, - loc: openBrace.loc.start, - message: message + " after '{'.", - fix: function(fixer) { - if (always) { - return fixer.insertTextBefore(firstToken, " "); - } + /** + * Reports invalid spacing style inside braces. + * @param {ASTNode} node - A BlockStatement/SwitchStatement node to get. + * @returns {void} + */ + function checkSpacingInsideBraces(node) { - return fixer.removeRange([openBrace.range[1], firstToken.range[0]]); - } - }); - } - if (!isValid(lastToken, closeBrace)) { - context.report({ - node: node, - loc: closeBrace.loc.start, - message: message + " before '}'.", - fix: function(fixer) { - if (always) { - return fixer.insertTextAfter(lastToken, " "); + // Gets braces and the first/last token of content. + var openBrace = getOpenBrace(node); + var closeBrace = context.getLastToken(node); + var firstToken = sourceCode.getTokenOrCommentAfter(openBrace); + var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace); + + // Skip if the node is invalid or empty. + if (openBrace.type !== "Punctuator" || + openBrace.value !== "{" || + closeBrace.type !== "Punctuator" || + closeBrace.value !== "}" || + firstToken === closeBrace + ) { + return; + } + + // Skip line comments for option never + if (!always && firstToken.type === "Line") { + return; + } + + // Check. + if (!isValid(openBrace, firstToken)) { + context.report({ + node: node, + loc: openBrace.loc.start, + message: message + " after '{'.", + fix: function(fixer) { + if (always) { + return fixer.insertTextBefore(firstToken, " "); + } + + return fixer.removeRange([openBrace.range[1], firstToken.range[0]]); } + }); + } + if (!isValid(lastToken, closeBrace)) { + context.report({ + node: node, + loc: closeBrace.loc.start, + message: message + " before '}'.", + fix: function(fixer) { + if (always) { + return fixer.insertTextAfter(lastToken, " "); + } - return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]); - } - }); + return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]); + } + }); + } } - } - return { - BlockStatement: checkSpacingInsideBraces, - SwitchStatement: checkSpacingInsideBraces - }; + return { + BlockStatement: checkSpacingInsideBraces, + SwitchStatement: checkSpacingInsideBraces + }; + } }; - -module.exports.schema = [ - {enum: ["always", "never"]} -]; diff --git a/tools/eslint/lib/rules/brace-style.js b/tools/eslint/lib/rules/brace-style.js index 4887d66b8d..785a71e4e7 100644 --- a/tools/eslint/lib/rules/brace-style.js +++ b/tools/eslint/lib/rules/brace-style.js @@ -9,226 +9,236 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var style = context.options[0] || "1tbs", - params = context.options[1] || {}, - sourceCode = context.getSourceCode(); - - var OPEN_MESSAGE = "Opening curly brace does not appear on the same line as controlling statement.", - OPEN_MESSAGE_ALLMAN = "Opening curly brace appears on the same line as controlling statement.", - BODY_MESSAGE = "Statement inside of curly braces should be on next line.", - CLOSE_MESSAGE = "Closing curly brace does not appear on the same line as the subsequent block.", - CLOSE_MESSAGE_SINGLE = "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.", - CLOSE_MESSAGE_STROUSTRUP_ALLMAN = "Closing curly brace appears on the same line as the subsequent block."; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Determines if a given node is a block statement. - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node is a block statement, false if not. - * @private - */ - function isBlock(node) { - return node && node.type === "BlockStatement"; - } - - /** - * Check if the token is an punctuator with a value of curly brace - * @param {object} token - Token to check - * @returns {boolean} true if its a curly punctuator - * @private - */ - function isCurlyPunctuator(token) { - return token.value === "{" || token.value === "}"; - } - - /** - * Binds a list of properties to a function that verifies that the opening - * curly brace is on the same line as its controlling statement of a given - * node. - * @param {...string} The properties to check on the node. - * @returns {Function} A function that will perform the check on a node - * @private - */ - function checkBlock() { - var blockProperties = arguments; - - return function(node) { - Array.prototype.forEach.call(blockProperties, function(blockProp) { - var block = node[blockProp], - previousToken, - curlyToken, - curlyTokenEnd, - allOnSameLine; - - if (!isBlock(block)) { - return; - } - - previousToken = sourceCode.getTokenBefore(block); - curlyToken = sourceCode.getFirstToken(block); - curlyTokenEnd = sourceCode.getLastToken(block); - allOnSameLine = previousToken.loc.start.line === curlyTokenEnd.loc.start.line; +module.exports = { + meta: { + docs: { + description: "enforce consistent brace style for blocks", + category: "Stylistic Issues", + recommended: false + }, - if (allOnSameLine && params.allowSingleLine) { - return; - } + schema: [ + { + enum: ["1tbs", "stroustrup", "allman"] + }, + { + type: "object", + properties: { + allowSingleLine: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) { - context.report(node, OPEN_MESSAGE); - } else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) { - context.report(node, OPEN_MESSAGE_ALLMAN); - } + create: function(context) { + var style = context.options[0] || "1tbs", + params = context.options[1] || {}, + sourceCode = context.getSourceCode(); + + var OPEN_MESSAGE = "Opening curly brace does not appear on the same line as controlling statement.", + OPEN_MESSAGE_ALLMAN = "Opening curly brace appears on the same line as controlling statement.", + BODY_MESSAGE = "Statement inside of curly braces should be on next line.", + CLOSE_MESSAGE = "Closing curly brace does not appear on the same line as the subsequent block.", + CLOSE_MESSAGE_SINGLE = "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.", + CLOSE_MESSAGE_STROUSTRUP_ALLMAN = "Closing curly brace appears on the same line as the subsequent block."; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determines if a given node is a block statement. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a block statement, false if not. + * @private + */ + function isBlock(node) { + return node && node.type === "BlockStatement"; + } - if (!block.body.length) { - return; - } + /** + * Check if the token is an punctuator with a value of curly brace + * @param {object} token - Token to check + * @returns {boolean} true if its a curly punctuator + * @private + */ + function isCurlyPunctuator(token) { + return token.value === "{" || token.value === "}"; + } - if (curlyToken.loc.start.line === block.body[0].loc.start.line) { - context.report(block.body[0], BODY_MESSAGE); - } + /** + * Binds a list of properties to a function that verifies that the opening + * curly brace is on the same line as its controlling statement of a given + * node. + * @param {...string} The properties to check on the node. + * @returns {Function} A function that will perform the check on a node + * @private + */ + function checkBlock() { + var blockProperties = arguments; + + return function(node) { + Array.prototype.forEach.call(blockProperties, function(blockProp) { + var block = node[blockProp], + previousToken, + curlyToken, + curlyTokenEnd, + allOnSameLine; + + if (!isBlock(block)) { + return; + } + + previousToken = sourceCode.getTokenBefore(block); + curlyToken = sourceCode.getFirstToken(block); + curlyTokenEnd = sourceCode.getLastToken(block); + allOnSameLine = previousToken.loc.start.line === curlyTokenEnd.loc.start.line; + + if (allOnSameLine && params.allowSingleLine) { + return; + } + + if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) { + context.report(node, OPEN_MESSAGE); + } else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) { + context.report(node, OPEN_MESSAGE_ALLMAN); + } + + if (!block.body.length) { + return; + } + + if (curlyToken.loc.start.line === block.body[0].loc.start.line) { + context.report(block.body[0], BODY_MESSAGE); + } + + if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.start.line) { + context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE); + } + }); + }; + } - if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.start.line) { - context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE); + /** + * Enforces the configured brace style on IfStatements + * @param {ASTNode} node An IfStatement node. + * @returns {void} + * @private + */ + function checkIfStatement(node) { + var tokens; + + checkBlock("consequent", "alternate")(node); + + if (node.alternate) { + + tokens = sourceCode.getTokensBefore(node.alternate, 2); + + if (style === "1tbs") { + if (tokens[0].loc.start.line !== tokens[1].loc.start.line && + node.consequent.type === "BlockStatement" && + isCurlyPunctuator(tokens[0])) { + context.report(node.alternate, CLOSE_MESSAGE); + } + } else if (tokens[0].loc.start.line === tokens[1].loc.start.line) { + context.report(node.alternate, CLOSE_MESSAGE_STROUSTRUP_ALLMAN); } - }); - }; - } - - /** - * Enforces the configured brace style on IfStatements - * @param {ASTNode} node An IfStatement node. - * @returns {void} - * @private - */ - function checkIfStatement(node) { - var tokens; - - checkBlock("consequent", "alternate")(node); - - if (node.alternate) { - tokens = sourceCode.getTokensBefore(node.alternate, 2); - - if (style === "1tbs") { - if (tokens[0].loc.start.line !== tokens[1].loc.start.line && - node.consequent.type === "BlockStatement" && - isCurlyPunctuator(tokens[0])) { - context.report(node.alternate, CLOSE_MESSAGE); - } - } else if (tokens[0].loc.start.line === tokens[1].loc.start.line) { - context.report(node.alternate, CLOSE_MESSAGE_STROUSTRUP_ALLMAN); } - } - } - /** - * Enforces the configured brace style on TryStatements - * @param {ASTNode} node A TryStatement node. - * @returns {void} - * @private - */ - function checkTryStatement(node) { - var tokens; - - checkBlock("block", "finalizer")(node); - - if (isBlock(node.finalizer)) { - tokens = sourceCode.getTokensBefore(node.finalizer, 2); - if (style === "1tbs") { - if (tokens[0].loc.start.line !== tokens[1].loc.start.line) { - context.report(node.finalizer, CLOSE_MESSAGE); + /** + * Enforces the configured brace style on TryStatements + * @param {ASTNode} node A TryStatement node. + * @returns {void} + * @private + */ + function checkTryStatement(node) { + var tokens; + + checkBlock("block", "finalizer")(node); + + if (isBlock(node.finalizer)) { + tokens = sourceCode.getTokensBefore(node.finalizer, 2); + if (style === "1tbs") { + if (tokens[0].loc.start.line !== tokens[1].loc.start.line) { + context.report(node.finalizer, CLOSE_MESSAGE); + } + } else if (tokens[0].loc.start.line === tokens[1].loc.start.line) { + context.report(node.finalizer, CLOSE_MESSAGE_STROUSTRUP_ALLMAN); } - } else if (tokens[0].loc.start.line === tokens[1].loc.start.line) { - context.report(node.finalizer, CLOSE_MESSAGE_STROUSTRUP_ALLMAN); } } - } - /** - * Enforces the configured brace style on CatchClauses - * @param {ASTNode} node A CatchClause node. - * @returns {void} - * @private - */ - function checkCatchClause(node) { - var previousToken = sourceCode.getTokenBefore(node), - firstToken = sourceCode.getFirstToken(node); - - checkBlock("body")(node); - - if (isBlock(node.body)) { - if (style === "1tbs") { - if (previousToken.loc.start.line !== firstToken.loc.start.line) { - context.report(node, CLOSE_MESSAGE); - } - } else { - if (previousToken.loc.start.line === firstToken.loc.start.line) { - context.report(node, CLOSE_MESSAGE_STROUSTRUP_ALLMAN); + /** + * Enforces the configured brace style on CatchClauses + * @param {ASTNode} node A CatchClause node. + * @returns {void} + * @private + */ + function checkCatchClause(node) { + var previousToken = sourceCode.getTokenBefore(node), + firstToken = sourceCode.getFirstToken(node); + + checkBlock("body")(node); + + if (isBlock(node.body)) { + if (style === "1tbs") { + if (previousToken.loc.start.line !== firstToken.loc.start.line) { + context.report(node, CLOSE_MESSAGE); + } + } else { + if (previousToken.loc.start.line === firstToken.loc.start.line) { + context.report(node, CLOSE_MESSAGE_STROUSTRUP_ALLMAN); + } } } } - } - /** - * Enforces the configured brace style on SwitchStatements - * @param {ASTNode} node A SwitchStatement node. - * @returns {void} - * @private - */ - function checkSwitchStatement(node) { - var tokens; - - if (node.cases && node.cases.length) { - tokens = sourceCode.getTokensBefore(node.cases[0], 2); - } else { - tokens = sourceCode.getLastTokens(node, 3); - } + /** + * Enforces the configured brace style on SwitchStatements + * @param {ASTNode} node A SwitchStatement node. + * @returns {void} + * @private + */ + function checkSwitchStatement(node) { + var tokens; + + if (node.cases && node.cases.length) { + tokens = sourceCode.getTokensBefore(node.cases[0], 2); + } else { + tokens = sourceCode.getLastTokens(node, 3); + } - if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) { - context.report(node, OPEN_MESSAGE); - } else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) { - context.report(node, OPEN_MESSAGE_ALLMAN); + if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) { + context.report(node, OPEN_MESSAGE); + } else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) { + context.report(node, OPEN_MESSAGE_ALLMAN); + } } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - "FunctionDeclaration": checkBlock("body"), - "FunctionExpression": checkBlock("body"), - "ArrowFunctionExpression": checkBlock("body"), - "IfStatement": checkIfStatement, - "TryStatement": checkTryStatement, - "CatchClause": checkCatchClause, - "DoWhileStatement": checkBlock("body"), - "WhileStatement": checkBlock("body"), - "WithStatement": checkBlock("body"), - "ForStatement": checkBlock("body"), - "ForInStatement": checkBlock("body"), - "ForOfStatement": checkBlock("body"), - "SwitchStatement": checkSwitchStatement - }; - -}; + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: checkBlock("body"), + FunctionExpression: checkBlock("body"), + ArrowFunctionExpression: checkBlock("body"), + IfStatement: checkIfStatement, + TryStatement: checkTryStatement, + CatchClause: checkCatchClause, + DoWhileStatement: checkBlock("body"), + WhileStatement: checkBlock("body"), + WithStatement: checkBlock("body"), + ForStatement: checkBlock("body"), + ForInStatement: checkBlock("body"), + ForOfStatement: checkBlock("body"), + SwitchStatement: checkSwitchStatement + }; -module.exports.schema = [ - { - "enum": ["1tbs", "stroustrup", "allman"] - }, - { - "type": "object", - "properties": { - "allowSingleLine": { - "type": "boolean" - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/callback-return.js b/tools/eslint/lib/rules/callback-return.js index 7680386cf4..a995da3a98 100644 --- a/tools/eslint/lib/rules/callback-return.js +++ b/tools/eslint/lib/rules/callback-return.js @@ -1,7 +1,6 @@ /** * @fileoverview Enforce return after a callback. * @author Jamund Ferguson - * @copyright 2015 Jamund Ferguson. All rights reserved. */ "use strict"; @@ -9,136 +8,146 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var callbacks = context.options[0] || ["callback", "cb", "next"]; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Find the closest parent matching a list of types. - * @param {ASTNode} node The node whose parents we are searching - * @param {Array} types The node types to match - * @returns {ASTNode} The matched node or undefined. - */ - function findClosestParentOfType(node, types) { - if (!node.parent) { - return null; - } - if (types.indexOf(node.parent.type) === -1) { - return findClosestParentOfType(node.parent, types); +module.exports = { + meta: { + docs: { + description: "require `return` statements after callbacks", + category: "Node.js and CommonJS", + recommended: false + }, + + schema: [{ + type: "array", + items: { type: "string" } + }] + }, + + create: function(context) { + + var callbacks = context.options[0] || ["callback", "cb", "next"]; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Find the closest parent matching a list of types. + * @param {ASTNode} node The node whose parents we are searching + * @param {Array} types The node types to match + * @returns {ASTNode} The matched node or undefined. + */ + function findClosestParentOfType(node, types) { + if (!node.parent) { + return null; + } + if (types.indexOf(node.parent.type) === -1) { + return findClosestParentOfType(node.parent, types); + } + return node.parent; } - return node.parent; - } - - /** - * Check to see if a CallExpression is in our callback list. - * @param {ASTNode} node The node to check against our callback names list. - * @returns {Boolean} Whether or not this function matches our callback name. - */ - function isCallback(node) { - return node.callee.type === "Identifier" && callbacks.indexOf(node.callee.name) > -1; - } - /** - * Determines whether or not the callback is part of a callback expression. - * @param {ASTNode} node The callback node - * @param {ASTNode} parentNode The expression node - * @returns {boolean} Whether or not this is part of a callback expression - */ - function isCallbackExpression(node, parentNode) { - - // ensure the parent node exists and is an expression - if (!parentNode || parentNode.type !== "ExpressionStatement") { - return false; + /** + * Check to see if a CallExpression is in our callback list. + * @param {ASTNode} node The node to check against our callback names list. + * @returns {Boolean} Whether or not this function matches our callback name. + */ + function isCallback(node) { + return node.callee.type === "Identifier" && callbacks.indexOf(node.callee.name) > -1; } - // cb() - if (parentNode.expression === node) { - return true; - } + /** + * Determines whether or not the callback is part of a callback expression. + * @param {ASTNode} node The callback node + * @param {ASTNode} parentNode The expression node + * @returns {boolean} Whether or not this is part of a callback expression + */ + function isCallbackExpression(node, parentNode) { + + // ensure the parent node exists and is an expression + if (!parentNode || parentNode.type !== "ExpressionStatement") { + return false; + } - // special case for cb && cb() and similar - if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") { - if (parentNode.expression.right === node) { + // cb() + if (parentNode.expression === node) { return true; } + + // special case for cb && cb() and similar + if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") { + if (parentNode.expression.right === node) { + return true; + } + } + + return false; } - return false; - } + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + return { + CallExpression: function(node) { - return { - "CallExpression": function(node) { + // if we"re not a callback we can return + if (!isCallback(node)) { + return; + } - // if we"re not a callback we can return - if (!isCallback(node)) { - return; - } + // find the closest block, return or loop + var closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {}, + lastItem, parentType; - // find the closest block, return or loop - var closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {}, - lastItem, parentType; + // if our parent is a return we know we're ok + if (closestBlock.type === "ReturnStatement") { + return; + } - // if our parent is a return we know we're ok - if (closestBlock.type === "ReturnStatement") { - return; - } + // arrow functions don't always have blocks and implicitly return + if (closestBlock.type === "ArrowFunctionExpression") { + return; + } - // arrow functions don't always have blocks and implicitly return - if (closestBlock.type === "ArrowFunctionExpression") { - return; - } + // block statements are part of functions and most if statements + if (closestBlock.type === "BlockStatement") { - // block statements are part of functions and most if statements - if (closestBlock.type === "BlockStatement") { + // find the last item in the block + lastItem = closestBlock.body[closestBlock.body.length - 1]; - // find the last item in the block - lastItem = closestBlock.body[closestBlock.body.length - 1]; + // if the callback is the last thing in a block that might be ok + if (isCallbackExpression(node, lastItem)) { - // if the callback is the last thing in a block that might be ok - if (isCallbackExpression(node, lastItem)) { + parentType = closestBlock.parent.type; - parentType = closestBlock.parent.type; + // but only if the block is part of a function + if (parentType === "FunctionExpression" || + parentType === "FunctionDeclaration" || + parentType === "ArrowFunctionExpression" + ) { + return; + } - // but only if the block is part of a function - if (parentType === "FunctionExpression" || - parentType === "FunctionDeclaration" || - parentType === "ArrowFunctionExpression" - ) { - return; } - } + // ending a block with a return is also ok + if (lastItem.type === "ReturnStatement") { - // ending a block with a return is also ok - if (lastItem.type === "ReturnStatement") { - - // but only if the callback is immediately before - if (isCallbackExpression(node, closestBlock.body[closestBlock.body.length - 2])) { - return; + // but only if the callback is immediately before + if (isCallbackExpression(node, closestBlock.body[closestBlock.body.length - 2])) { + return; + } } + } - } + // as long as you're the child of a function at this point you should be asked to return + if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) { + context.report(node, "Expected return with your callback function."); + } - // as long as you're the child of a function at this point you should be asked to return - if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) { - context.report(node, "Expected return with your callback function."); } - } - - }; + }; + } }; - -module.exports.schema = [{ - type: "array", - items: { type: "string" } -}]; diff --git a/tools/eslint/lib/rules/camelcase.js b/tools/eslint/lib/rules/camelcase.js index d8f87fcf3f..28f9e8296b 100644 --- a/tools/eslint/lib/rules/camelcase.js +++ b/tools/eslint/lib/rules/camelcase.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag non-camelcased identifiers * @author Nicholas C. Zakas - * @copyright 2015 Dieter Oberkofler. All rights reserved. */ "use strict"; @@ -10,116 +9,126 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - // contains reported nodes to avoid reporting twice on destructuring with shorthand notation - var reported = []; - - /** - * Checks if a string contains an underscore and isn't all upper-case - * @param {String} name The string to check. - * @returns {boolean} if the string is underscored - * @private - */ - function isUnderscored(name) { - - // if there's an underscore, it might be A_CONSTANT, which is okay - return name.indexOf("_") > -1 && name !== name.toUpperCase(); - } - - /** - * Reports an AST node as a rule violation. - * @param {ASTNode} node The node to report. - * @returns {void} - * @private - */ - function report(node) { - if (reported.indexOf(node) < 0) { - reported.push(node); - context.report(node, "Identifier '{{name}}' is not in camel case.", { name: node.name }); - } - } - - var options = context.options[0] || {}, - properties = options.properties || ""; - - if (properties !== "always" && properties !== "never") { - properties = "always"; - } - - return { +module.exports = { + meta: { + docs: { + description: "enforce camelcase naming convention", + category: "Stylistic Issues", + recommended: false + }, - "Identifier": function(node) { + schema: [ + { + type: "object", + properties: { + properties: { + enum: ["always", "never"] + } + }, + additionalProperties: false + } + ] + }, - /* - * Leading and trailing underscores are commonly used to flag - * private/protected identifiers, strip them - */ - var name = node.name.replace(/^_+|_+$/g, ""), - effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; + create: function(context) { - // MemberExpressions get special rules - if (node.parent.type === "MemberExpression") { + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - // "never" check properties - if (properties === "never") { - return; - } + // contains reported nodes to avoid reporting twice on destructuring with shorthand notation + var reported = []; - // Always report underscored object names - if (node.parent.object.type === "Identifier" && - node.parent.object.name === node.name && - isUnderscored(name)) { - report(node); + /** + * Checks if a string contains an underscore and isn't all upper-case + * @param {String} name The string to check. + * @returns {boolean} if the string is underscored + * @private + */ + function isUnderscored(name) { - // Report AssignmentExpressions only if they are the left side of the assignment - } else if (effectiveParent.type === "AssignmentExpression" && - isUnderscored(name) && - (effectiveParent.right.type !== "MemberExpression" || - effectiveParent.left.type === "MemberExpression" && - effectiveParent.left.property.name === node.name)) { - report(node); - } + // if there's an underscore, it might be A_CONSTANT, which is okay + return name.indexOf("_") > -1 && name !== name.toUpperCase(); + } - // Properties have their own rules - } else if (node.parent.type === "Property") { + /** + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private + */ + function report(node) { + if (reported.indexOf(node) < 0) { + reported.push(node); + context.report(node, "Identifier '{{name}}' is not in camel case.", { name: node.name }); + } + } - // "never" check properties - if (properties === "never") { - return; - } + var options = context.options[0] || {}, + properties = options.properties || ""; - if (node.parent.parent && node.parent.parent.type === "ObjectPattern" && - node.parent.key === node && node.parent.value !== node) { - return; - } + if (properties !== "always" && properties !== "never") { + properties = "always"; + } - if (isUnderscored(name) && effectiveParent.type !== "CallExpression") { + return { + + Identifier: function(node) { + + /* + * Leading and trailing underscores are commonly used to flag + * private/protected identifiers, strip them + */ + var name = node.name.replace(/^_+|_+$/g, ""), + effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; + + // MemberExpressions get special rules + if (node.parent.type === "MemberExpression") { + + // "never" check properties + if (properties === "never") { + return; + } + + // Always report underscored object names + if (node.parent.object.type === "Identifier" && + node.parent.object.name === node.name && + isUnderscored(name)) { + report(node); + + // Report AssignmentExpressions only if they are the left side of the assignment + } else if (effectiveParent.type === "AssignmentExpression" && + isUnderscored(name) && + (effectiveParent.right.type !== "MemberExpression" || + effectiveParent.left.type === "MemberExpression" && + effectiveParent.left.property.name === node.name)) { + report(node); + } + + // Properties have their own rules + } else if (node.parent.type === "Property") { + + // "never" check properties + if (properties === "never") { + return; + } + + if (node.parent.parent && node.parent.parent.type === "ObjectPattern" && + node.parent.key === node && node.parent.value !== node) { + return; + } + + if (isUnderscored(name) && effectiveParent.type !== "CallExpression") { + report(node); + } + + // Report anything that is underscored that isn't a CallExpression + } else if (isUnderscored(name) && effectiveParent.type !== "CallExpression") { report(node); } - - // Report anything that is underscored that isn't a CallExpression - } else if (isUnderscored(name) && effectiveParent.type !== "CallExpression") { - report(node); } - } - }; - -}; + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "properties": { - "enum": ["always", "never"] - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/comma-dangle.js b/tools/eslint/lib/rules/comma-dangle.js index 4aab893930..7c2451b608 100644 --- a/tools/eslint/lib/rules/comma-dangle.js +++ b/tools/eslint/lib/rules/comma-dangle.js @@ -1,10 +1,6 @@ /** * @fileoverview Rule to forbid or enforce dangling commas. * @author Ian Christian Myers - * @copyright 2015 Toru Nagashima - * @copyright 2015 Mathias Schreck - * @copyright 2013 Ian Christian Myers - * See LICENSE file in root directory for full license. */ "use strict"; @@ -24,203 +20,204 @@ var lodash = require("lodash"); * @returns {boolean} `true` if a trailing comma is allowed. */ function isTrailingCommaAllowed(node, lastItem) { - switch (node.type) { - case "ArrayPattern": - - // TODO(t-nagashima): Remove SpreadElement after https://github.com/eslint/espree/issues/194 was fixed. - return ( - lastItem.type !== "RestElement" && - lastItem.type !== "SpreadElement" - ); - - // TODO(t-nagashima): Remove this case after https://github.com/eslint/espree/issues/195 was fixed. - case "ArrayExpression": - return ( - node.parent.type !== "ForOfStatement" || - node.parent.left !== node || - lastItem.type !== "SpreadElement" - ); - - default: - return true; - } + return node.type !== "ArrayPattern" || lastItem.type !== "RestElement"; } //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var mode = context.options[0]; - var UNEXPECTED_MESSAGE = "Unexpected trailing comma."; - var MISSING_MESSAGE = "Missing trailing comma."; - - /** - * Checks whether or not a given node is multiline. - * This rule handles a given node as multiline when the closing parenthesis - * and the last element are not on the same line. - * - * @param {ASTNode} node - A node to check. - * @returns {boolean} `true` if the node is multiline. - */ - function isMultiline(node) { - var lastItem = lodash.last(node.properties || node.elements || node.specifiers); - - if (!lastItem) { - return false; +module.exports = { + meta: { + docs: { + description: "require or disallow trailing commas", + category: "Possible Errors", + recommended: true + }, + + fixable: "code", + + schema: [ + { + enum: ["always", "always-multiline", "only-multiline", "never"] + } + ] + }, + + create: function(context) { + var mode = context.options[0]; + var UNEXPECTED_MESSAGE = "Unexpected trailing comma."; + var MISSING_MESSAGE = "Missing trailing comma."; + + /** + * Checks whether or not a given node is multiline. + * This rule handles a given node as multiline when the closing parenthesis + * and the last element are not on the same line. + * + * @param {ASTNode} node - A node to check. + * @returns {boolean} `true` if the node is multiline. + */ + function isMultiline(node) { + var lastItem = lodash.last(node.properties || node.elements || node.specifiers); + + if (!lastItem) { + return false; + } + + var sourceCode = context.getSourceCode(), + penultimateToken = sourceCode.getLastToken(lastItem), + lastToken = sourceCode.getTokenAfter(penultimateToken); + + // parentheses are a pain + while (lastToken.value === ")") { + penultimateToken = lastToken; + lastToken = sourceCode.getTokenAfter(lastToken); + } + + if (lastToken.value === ",") { + penultimateToken = lastToken; + lastToken = sourceCode.getTokenAfter(lastToken); + } + + return lastToken.loc.end.line !== penultimateToken.loc.end.line; } - var sourceCode = context.getSourceCode(), - penultimateToken = sourceCode.getLastToken(lastItem), - lastToken = sourceCode.getTokenAfter(penultimateToken); - - // parentheses are a pain - while (lastToken.value === ")") { - penultimateToken = lastToken; - lastToken = sourceCode.getTokenAfter(lastToken); + /** + * Reports a trailing comma if it exists. + * + * @param {ASTNode} node - A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function forbidTrailingComma(node) { + var lastItem = lodash.last(node.properties || node.elements || node.specifiers); + + if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { + return; + } + + var sourceCode = context.getSourceCode(), + trailingToken; + + // last item can be surrounded by parentheses for object and array literals + if (node.type === "ObjectExpression" || node.type === "ArrayExpression") { + trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node)); + } else { + trailingToken = sourceCode.getTokenAfter(lastItem); + } + + if (trailingToken.value === ",") { + context.report({ + node: lastItem, + loc: trailingToken.loc.start, + message: UNEXPECTED_MESSAGE, + fix: function(fixer) { + return fixer.remove(trailingToken); + } + }); + } } - if (lastToken.value === ",") { - penultimateToken = lastToken; - lastToken = sourceCode.getTokenAfter(lastToken); + /** + * Reports the last element of a given node if it does not have a trailing + * comma. + * + * If a given node is `ArrayPattern` which has `RestElement`, the trailing + * comma is disallowed, so report if it exists. + * + * @param {ASTNode} node - A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function forceTrailingComma(node) { + var lastItem = lodash.last(node.properties || node.elements || node.specifiers); + + if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { + return; + } + if (!isTrailingCommaAllowed(node, lastItem)) { + forbidTrailingComma(node); + return; + } + + var sourceCode = context.getSourceCode(), + trailingToken; + + // last item can be surrounded by parentheses for object and array literals + if (node.type === "ObjectExpression" || node.type === "ArrayExpression") { + trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node)); + } else { + trailingToken = sourceCode.getTokenAfter(lastItem); + } + + if (trailingToken.value !== ",") { + context.report({ + node: lastItem, + loc: lastItem.loc.end, + message: MISSING_MESSAGE, + fix: function(fixer) { + return fixer.insertTextAfter(lastItem, ","); + } + }); + } } - return lastToken.loc.end.line !== penultimateToken.loc.end.line; - } - - /** - * Reports a trailing comma if it exists. - * - * @param {ASTNode} node - A node to check. Its type is one of - * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, - * ImportDeclaration, and ExportNamedDeclaration. - * @returns {void} - */ - function forbidTrailingComma(node) { - var lastItem = lodash.last(node.properties || node.elements || node.specifiers); - - if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { - return; + /** + * If a given node is multiline, reports the last element of a given node + * when it does not have a trailing comma. + * Otherwise, reports a trailing comma if it exists. + * + * @param {ASTNode} node - A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function forceTrailingCommaIfMultiline(node) { + if (isMultiline(node)) { + forceTrailingComma(node); + } else { + forbidTrailingComma(node); + } } - var sourceCode = context.getSourceCode(), - trailingToken; - - // last item can be surrounded by parentheses for object and array literals - if (node.type === "ObjectExpression" || node.type === "ArrayExpression") { - trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node)); - } else { - trailingToken = sourceCode.getTokenAfter(lastItem); + /** + * Only if a given node is not multiline, reports the last element of a given node + * when it does not have a trailing comma. + * Otherwise, reports a trailing comma if it exists. + * + * @param {ASTNode} node - A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function allowTrailingCommaIfMultiline(node) { + if (!isMultiline(node)) { + forbidTrailingComma(node); + } } - if (trailingToken.value === ",") { - context.report( - lastItem, - trailingToken.loc.start, - UNEXPECTED_MESSAGE); - } - } - - /** - * Reports the last element of a given node if it does not have a trailing - * comma. - * - * If a given node is `ArrayPattern` which has `RestElement`, the trailing - * comma is disallowed, so report if it exists. - * - * @param {ASTNode} node - A node to check. Its type is one of - * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, - * ImportDeclaration, and ExportNamedDeclaration. - * @returns {void} - */ - function forceTrailingComma(node) { - var lastItem = lodash.last(node.properties || node.elements || node.specifiers); - - if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { - return; - } - if (!isTrailingCommaAllowed(node, lastItem)) { - forbidTrailingComma(node); - return; - } - - var sourceCode = context.getSourceCode(), - trailingToken; + // Chooses a checking function. + var checkForTrailingComma; - // last item can be surrounded by parentheses for object and array literals - if (node.type === "ObjectExpression" || node.type === "ArrayExpression") { - trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node)); + if (mode === "always") { + checkForTrailingComma = forceTrailingComma; + } else if (mode === "always-multiline") { + checkForTrailingComma = forceTrailingCommaIfMultiline; + } else if (mode === "only-multiline") { + checkForTrailingComma = allowTrailingCommaIfMultiline; } else { - trailingToken = sourceCode.getTokenAfter(lastItem); + checkForTrailingComma = forbidTrailingComma; } - if (trailingToken.value !== ",") { - context.report( - lastItem, - lastItem.loc.end, - MISSING_MESSAGE); - } + return { + ObjectExpression: checkForTrailingComma, + ObjectPattern: checkForTrailingComma, + ArrayExpression: checkForTrailingComma, + ArrayPattern: checkForTrailingComma, + ImportDeclaration: checkForTrailingComma, + ExportNamedDeclaration: checkForTrailingComma + }; } - - /** - * If a given node is multiline, reports the last element of a given node - * when it does not have a trailing comma. - * Otherwise, reports a trailing comma if it exists. - * - * @param {ASTNode} node - A node to check. Its type is one of - * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, - * ImportDeclaration, and ExportNamedDeclaration. - * @returns {void} - */ - function forceTrailingCommaIfMultiline(node) { - if (isMultiline(node)) { - forceTrailingComma(node); - } else { - forbidTrailingComma(node); - } - } - - /** - * Only if a given node is not multiline, reports the last element of a given node - * when it does not have a trailing comma. - * Otherwise, reports a trailing comma if it exists. - * - * @param {ASTNode} node - A node to check. Its type is one of - * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, - * ImportDeclaration, and ExportNamedDeclaration. - * @returns {void} - */ - function allowTrailingCommaIfMultiline(node) { - if (!isMultiline(node)) { - forbidTrailingComma(node); - } - } - - // Chooses a checking function. - var checkForTrailingComma; - - if (mode === "always") { - checkForTrailingComma = forceTrailingComma; - } else if (mode === "always-multiline") { - checkForTrailingComma = forceTrailingCommaIfMultiline; - } else if (mode === "only-multiline") { - checkForTrailingComma = allowTrailingCommaIfMultiline; - } else { - checkForTrailingComma = forbidTrailingComma; - } - - return { - "ObjectExpression": checkForTrailingComma, - "ObjectPattern": checkForTrailingComma, - "ArrayExpression": checkForTrailingComma, - "ArrayPattern": checkForTrailingComma, - "ImportDeclaration": checkForTrailingComma, - "ExportNamedDeclaration": checkForTrailingComma - }; }; - -module.exports.schema = [ - { - "enum": ["always", "always-multiline", "only-multiline", "never"] - } -]; diff --git a/tools/eslint/lib/rules/comma-spacing.js b/tools/eslint/lib/rules/comma-spacing.js index 7d2b2d7949..2a4ec1f417 100644 --- a/tools/eslint/lib/rules/comma-spacing.js +++ b/tools/eslint/lib/rules/comma-spacing.js @@ -1,7 +1,6 @@ /** * @fileoverview Comma spacing - validates spacing before and after comma * @author Vignesh Anand aka vegetableman. - * @copyright 2014 Vignesh Anand. All rights reserved. */ "use strict"; @@ -11,173 +10,185 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var sourceCode = context.getSourceCode(); - var tokensAndComments = sourceCode.tokensAndComments; - - var options = { - before: context.options[0] ? !!context.options[0].before : false, - after: context.options[0] ? !!context.options[0].after : true - }; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - // list of comma tokens to ignore for the check of leading whitespace - var commaTokensToIgnore = []; - - /** - * Determines if a given token is a comma operator. - * @param {ASTNode} token The token to check. - * @returns {boolean} True if the token is a comma, false if not. - * @private - */ - function isComma(token) { - return !!token && (token.type === "Punctuator") && (token.value === ","); - } +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing before and after commas", + category: "Stylistic Issues", + recommended: false + }, - /** - * Reports a spacing error with an appropriate message. - * @param {ASTNode} node The binary expression node to report. - * @param {string} dir Is the error "before" or "after" the comma? - * @param {ASTNode} otherNode The node at the left or right of `node` - * @returns {void} - * @private - */ - function report(node, dir, otherNode) { - context.report({ - node: node, - fix: function(fixer) { - if (options[dir]) { - if (dir === "before") { - return fixer.insertTextBefore(node, " "); - } else { - return fixer.insertTextAfter(node, " "); + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { + type: "boolean" + }, + after: { + type: "boolean" } - } else { - var start, end; - var newText = ""; + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + + var sourceCode = context.getSourceCode(); + var tokensAndComments = sourceCode.tokensAndComments; + + var options = { + before: context.options[0] ? !!context.options[0].before : false, + after: context.options[0] ? !!context.options[0].after : true + }; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // list of comma tokens to ignore for the check of leading whitespace + var commaTokensToIgnore = []; + + /** + * Determines if a given token is a comma operator. + * @param {ASTNode} token The token to check. + * @returns {boolean} True if the token is a comma, false if not. + * @private + */ + function isComma(token) { + return !!token && (token.type === "Punctuator") && (token.value === ","); + } - if (dir === "before") { - start = otherNode.range[1]; - end = node.range[0]; + /** + * Reports a spacing error with an appropriate message. + * @param {ASTNode} node The binary expression node to report. + * @param {string} dir Is the error "before" or "after" the comma? + * @param {ASTNode} otherNode The node at the left or right of `node` + * @returns {void} + * @private + */ + function report(node, dir, otherNode) { + context.report({ + node: node, + fix: function(fixer) { + if (options[dir]) { + if (dir === "before") { + return fixer.insertTextBefore(node, " "); + } else { + return fixer.insertTextAfter(node, " "); + } } else { - start = node.range[1]; - end = otherNode.range[0]; + var start, end; + var newText = ""; + + if (dir === "before") { + start = otherNode.range[1]; + end = node.range[0]; + } else { + start = node.range[1]; + end = otherNode.range[0]; + } + + return fixer.replaceTextRange([start, end], newText); } - - return fixer.replaceTextRange([start, end], newText); - } - }, - message: options[dir] ? - "A space is required " + dir + " ','." : - "There should be no space " + dir + " ','." - }); - } - - /** - * Validates the spacing around a comma token. - * @param {Object} tokens - The tokens to be validated. - * @param {Token} tokens.comma The token representing the comma. - * @param {Token} [tokens.left] The last token before the comma. - * @param {Token} [tokens.right] The first token after the comma. - * @param {Token|ASTNode} reportItem The item to use when reporting an error. - * @returns {void} - * @private - */ - function validateCommaItemSpacing(tokens, reportItem) { - if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) && - (options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma)) - ) { - report(reportItem, "before", tokens.left); + }, + message: options[dir] ? + "A space is required " + dir + " ','." : + "There should be no space " + dir + " ','." + }); } - if (tokens.right && !options.after && tokens.right.type === "Line") { - return; - } + /** + * Validates the spacing around a comma token. + * @param {Object} tokens - The tokens to be validated. + * @param {Token} tokens.comma The token representing the comma. + * @param {Token} [tokens.left] The last token before the comma. + * @param {Token} [tokens.right] The first token after the comma. + * @param {Token|ASTNode} reportItem The item to use when reporting an error. + * @returns {void} + * @private + */ + function validateCommaItemSpacing(tokens, reportItem) { + if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) && + (options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma)) + ) { + report(reportItem, "before", tokens.left); + } + + if (tokens.right && !options.after && tokens.right.type === "Line") { + return; + } - if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) && - (options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right)) - ) { - report(reportItem, "after", tokens.right); + if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) && + (options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right)) + ) { + report(reportItem, "after", tokens.right); + } } - } - /** - * Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list. - * @param {ASTNode} node An ArrayExpression or ArrayPattern node. - * @returns {void} - */ - function addNullElementsToIgnoreList(node) { - var previousToken = context.getFirstToken(node); + /** + * Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list. + * @param {ASTNode} node An ArrayExpression or ArrayPattern node. + * @returns {void} + */ + function addNullElementsToIgnoreList(node) { + var previousToken = context.getFirstToken(node); - node.elements.forEach(function(element) { - var token; + node.elements.forEach(function(element) { + var token; - if (element === null) { - token = context.getTokenAfter(previousToken); + if (element === null) { + token = context.getTokenAfter(previousToken); - if (isComma(token)) { - commaTokensToIgnore.push(token); + if (isComma(token)) { + commaTokensToIgnore.push(token); + } + } else { + token = context.getTokenAfter(element); } - } else { - token = context.getTokenAfter(element); - } - previousToken = token; - }); - } + previousToken = token; + }); + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { - "Program:exit": function() { + return { + "Program:exit": function() { - var previousToken, - nextToken; + var previousToken, + nextToken; - tokensAndComments.forEach(function(token, i) { + tokensAndComments.forEach(function(token, i) { - if (!isComma(token)) { - return; - } - - if (token && token.type === "JSXText") { - return; - } + if (!isComma(token)) { + return; + } - previousToken = tokensAndComments[i - 1]; - nextToken = tokensAndComments[i + 1]; + if (token && token.type === "JSXText") { + return; + } - validateCommaItemSpacing({ - comma: token, - left: isComma(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken, - right: isComma(nextToken) ? null : nextToken - }, token); - }); - }, - "ArrayExpression": addNullElementsToIgnoreList, - "ArrayPattern": addNullElementsToIgnoreList + previousToken = tokensAndComments[i - 1]; + nextToken = tokensAndComments[i + 1]; - }; + validateCommaItemSpacing({ + comma: token, + left: isComma(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken, + right: isComma(nextToken) ? null : nextToken + }, token); + }); + }, + ArrayExpression: addNullElementsToIgnoreList, + ArrayPattern: addNullElementsToIgnoreList -}; + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "before": { - "type": "boolean" - }, - "after": { - "type": "boolean" - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/comma-style.js b/tools/eslint/lib/rules/comma-style.js index af001372c5..9c7d266d3d 100644 --- a/tools/eslint/lib/rules/comma-style.js +++ b/tools/eslint/lib/rules/comma-style.js @@ -1,8 +1,6 @@ /** * @fileoverview Comma style - enforces comma styles of two types: last and first * @author Vignesh Anand aka vegetableman - * @copyright 2014 Vignesh Anand. All rights reserved. - * @copyright 2015 Evan Simmons. All rights reserved. */ "use strict"; @@ -13,174 +11,184 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var style = context.options[0] || "last", - exceptions = {}; +module.exports = { + meta: { + docs: { + description: "enforce consistent comma style", + category: "Stylistic Issues", + recommended: false + }, - if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) { - exceptions = context.options[1].exceptions; - } + schema: [ + { + enum: ["first", "last"] + }, + { + type: "object", + properties: { + exceptions: { + type: "object", + additionalProperties: { + type: "boolean" + } + } + }, + additionalProperties: false + } + ] + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Determines if a given token is a comma operator. - * @param {ASTNode} token The token to check. - * @returns {boolean} True if the token is a comma, false if not. - * @private - */ - function isComma(token) { - return !!token && (token.type === "Punctuator") && (token.value === ","); - } + create: function(context) { - /** - * Validates the spacing around single items in lists. - * @param {Token} previousItemToken The last token from the previous item. - * @param {Token} commaToken The token representing the comma. - * @param {Token} currentItemToken The first token of the current item. - * @param {Token} reportItem The item to use when reporting an error. - * @returns {void} - * @private - */ - function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) { + var style = context.options[0] || "last", + exceptions = {}; - // if single line - if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) && - astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { + if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) { + exceptions = context.options[1].exceptions; + } - return; + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determines if a given token is a comma operator. + * @param {ASTNode} token The token to check. + * @returns {boolean} True if the token is a comma, false if not. + * @private + */ + function isComma(token) { + return !!token && (token.type === "Punctuator") && (token.value === ","); + } - } else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) && - !astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { + /** + * Validates the spacing around single items in lists. + * @param {Token} previousItemToken The last token from the previous item. + * @param {Token} commaToken The token representing the comma. + * @param {Token} currentItemToken The first token of the current item. + * @param {Token} reportItem The item to use when reporting an error. + * @returns {void} + * @private + */ + function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) { - // lone comma - context.report(reportItem, { - line: commaToken.loc.end.line, - column: commaToken.loc.start.column - }, "Bad line breaking before and after ','."); + // if single line + if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) && + astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { - } else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) { + return; - context.report(reportItem, "',' should be placed first."); + } else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) && + !astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { - } else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) { + // lone comma + context.report(reportItem, { + line: commaToken.loc.end.line, + column: commaToken.loc.start.column + }, "Bad line breaking before and after ','."); - context.report(reportItem, { - line: commaToken.loc.end.line, - column: commaToken.loc.end.column - }, "',' should be placed last."); - } - } + } else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) { - /** - * Checks the comma placement with regards to a declaration/property/element - * @param {ASTNode} node The binary expression node to check - * @param {string} property The property of the node containing child nodes. - * @private - * @returns {void} - */ - function validateComma(node, property) { - var items = node[property], - arrayLiteral = (node.type === "ArrayExpression"), - previousItemToken; + context.report(reportItem, "',' should be placed first."); - if (items.length > 1 || arrayLiteral) { + } else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) { - // seed as opening [ - previousItemToken = context.getFirstToken(node); + context.report(reportItem, { + line: commaToken.loc.end.line, + column: commaToken.loc.end.column + }, "',' should be placed last."); + } + } - items.forEach(function(item) { - var commaToken = item ? context.getTokenBefore(item) : previousItemToken, - currentItemToken = item ? context.getFirstToken(item) : context.getTokenAfter(commaToken), - reportItem = item || currentItemToken; + /** + * Checks the comma placement with regards to a declaration/property/element + * @param {ASTNode} node The binary expression node to check + * @param {string} property The property of the node containing child nodes. + * @private + * @returns {void} + */ + function validateComma(node, property) { + var items = node[property], + arrayLiteral = (node.type === "ArrayExpression"), + previousItemToken; + + if (items.length > 1 || arrayLiteral) { + + // seed as opening [ + previousItemToken = context.getFirstToken(node); + + items.forEach(function(item) { + var commaToken = item ? context.getTokenBefore(item) : previousItemToken, + currentItemToken = item ? context.getFirstToken(item) : context.getTokenAfter(commaToken), + reportItem = item || currentItemToken; + + /* + * This works by comparing three token locations: + * - previousItemToken is the last token of the previous item + * - commaToken is the location of the comma before the current item + * - currentItemToken is the first token of the current item + * + * These values get switched around if item is undefined. + * previousItemToken will refer to the last token not belonging + * to the current item, which could be a comma or an opening + * square bracket. currentItemToken could be a comma. + * + * All comparisons are done based on these tokens directly, so + * they are always valid regardless of an undefined item. + */ + if (isComma(commaToken)) { + validateCommaItemSpacing(previousItemToken, commaToken, + currentItemToken, reportItem); + } + + previousItemToken = item ? context.getLastToken(item) : previousItemToken; + }); /* - * This works by comparing three token locations: - * - previousItemToken is the last token of the previous item - * - commaToken is the location of the comma before the current item - * - currentItemToken is the first token of the current item - * - * These values get switched around if item is undefined. - * previousItemToken will refer to the last token not belonging - * to the current item, which could be a comma or an opening - * square bracket. currentItemToken could be a comma. - * - * All comparisons are done based on these tokens directly, so - * they are always valid regardless of an undefined item. + * Special case for array literals that have empty last items, such + * as [ 1, 2, ]. These arrays only have two items show up in the + * AST, so we need to look at the token to verify that there's no + * dangling comma. */ - if (isComma(commaToken)) { - validateCommaItemSpacing(previousItemToken, commaToken, - currentItemToken, reportItem); - } - - previousItemToken = item ? context.getLastToken(item) : previousItemToken; - }); - - /* - * Special case for array literals that have empty last items, such - * as [ 1, 2, ]. These arrays only have two items show up in the - * AST, so we need to look at the token to verify that there's no - * dangling comma. - */ - if (arrayLiteral) { - - var lastToken = context.getLastToken(node), - nextToLastToken = context.getTokenBefore(lastToken); - - if (isComma(nextToLastToken)) { - validateCommaItemSpacing( - context.getTokenBefore(nextToLastToken), - nextToLastToken, - lastToken, - lastToken - ); + if (arrayLiteral) { + + var lastToken = context.getLastToken(node), + nextToLastToken = context.getTokenBefore(lastToken); + + if (isComma(nextToLastToken)) { + validateCommaItemSpacing( + context.getTokenBefore(nextToLastToken), + nextToLastToken, + lastToken, + lastToken + ); + } } } } - } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - var nodes = {}; + var nodes = {}; - if (!exceptions.VariableDeclaration) { - nodes.VariableDeclaration = function(node) { - validateComma(node, "declarations"); - }; - } - if (!exceptions.ObjectExpression) { - nodes.ObjectExpression = function(node) { - validateComma(node, "properties"); - }; - } - if (!exceptions.ArrayExpression) { - nodes.ArrayExpression = function(node) { - validateComma(node, "elements"); - }; - } - - return nodes; -}; + if (!exceptions.VariableDeclaration) { + nodes.VariableDeclaration = function(node) { + validateComma(node, "declarations"); + }; + } + if (!exceptions.ObjectExpression) { + nodes.ObjectExpression = function(node) { + validateComma(node, "properties"); + }; + } + if (!exceptions.ArrayExpression) { + nodes.ArrayExpression = function(node) { + validateComma(node, "elements"); + }; + } -module.exports.schema = [ - { - "enum": ["first", "last"] - }, - { - "type": "object", - "properties": { - "exceptions": { - "type": "object", - "additionalProperties": { - "type": "boolean" - } - } - }, - "additionalProperties": false + return nodes; } -]; +}; diff --git a/tools/eslint/lib/rules/complexity.js b/tools/eslint/lib/rules/complexity.js index 0cbdd0aad2..029b739808 100644 --- a/tools/eslint/lib/rules/complexity.js +++ b/tools/eslint/lib/rules/complexity.js @@ -10,143 +10,153 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var option = context.options[0], - THRESHOLD = 20; +module.exports = { + meta: { + docs: { + description: "enforce a maximum cyclomatic complexity allowed in a program", + category: "Best Practices", + recommended: false + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0 + }, + max: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + ] + }, - if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { - THRESHOLD = option.maximum; - } - if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { - THRESHOLD = option.max; - } - if (typeof option === "number") { - THRESHOLD = option; - } + create: function(context) { + var option = context.options[0], + THRESHOLD = 20; - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { + THRESHOLD = option.maximum; + } + if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { + THRESHOLD = option.max; + } + if (typeof option === "number") { + THRESHOLD = option; + } - // Using a stack to store complexity (handling nested functions) - var fns = []; + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * When parsing a new function, store it in our function stack - * @returns {void} - * @private - */ - function startFunction() { - fns.push(1); - } + // Using a stack to store complexity (handling nested functions) + var fns = []; - /** - * Evaluate the node at the end of function - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function endFunction(node) { - var complexity = fns.pop(), - name = "anonymous"; - - if (node.id) { - name = node.id.name; - } else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") { - name = node.parent.key.name; + /** + * When parsing a new function, store it in our function stack + * @returns {void} + * @private + */ + function startFunction() { + fns.push(1); } - if (complexity > THRESHOLD) { - context.report(node, "Function '{{name}}' has a complexity of {{complexity}}.", { name: name, complexity: complexity }); - } - } + /** + * Evaluate the node at the end of function + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function endFunction(node) { + var complexity = fns.pop(), + name = "anonymous"; + + if (node.id) { + name = node.id.name; + } else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") { + name = node.parent.key.name; + } - /** - * Increase the complexity of the function in context - * @returns {void} - * @private - */ - function increaseComplexity() { - if (fns.length) { - fns[fns.length - 1]++; + if (complexity > THRESHOLD) { + context.report(node, "Function '{{name}}' has a complexity of {{complexity}}.", { name: name, complexity: complexity }); + } } - } - /** - * Increase the switch complexity in context - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function increaseSwitchComplexity(node) { - - // Avoiding `default` - if (node.test) { - increaseComplexity(node); + /** + * Increase the complexity of the function in context + * @returns {void} + * @private + */ + function increaseComplexity() { + if (fns.length) { + fns[fns.length - 1]++; + } } - } - /** - * Increase the logical path complexity in context - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function increaseLogicalComplexity(node) { - - // Avoiding && - if (node.operator === "||") { - increaseComplexity(node); + /** + * Increase the switch complexity in context + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function increaseSwitchComplexity(node) { + + // Avoiding `default` + if (node.test) { + increaseComplexity(node); + } } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - "FunctionDeclaration": startFunction, - "FunctionExpression": startFunction, - "ArrowFunctionExpression": startFunction, - "FunctionDeclaration:exit": endFunction, - "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction, - - "CatchClause": increaseComplexity, - "ConditionalExpression": increaseComplexity, - "LogicalExpression": increaseLogicalComplexity, - "ForStatement": increaseComplexity, - "ForInStatement": increaseComplexity, - "ForOfStatement": increaseComplexity, - "IfStatement": increaseComplexity, - "SwitchCase": increaseSwitchComplexity, - "WhileStatement": increaseComplexity, - "DoWhileStatement": increaseComplexity - }; + /** + * Increase the logical path complexity in context + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function increaseLogicalComplexity(node) { + + // Avoiding && + if (node.operator === "||") { + increaseComplexity(node); + } + } -}; + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction, + + CatchClause: increaseComplexity, + ConditionalExpression: increaseComplexity, + LogicalExpression: increaseLogicalComplexity, + ForStatement: increaseComplexity, + ForInStatement: increaseComplexity, + ForOfStatement: increaseComplexity, + IfStatement: increaseComplexity, + SwitchCase: increaseSwitchComplexity, + WhileStatement: increaseComplexity, + DoWhileStatement: increaseComplexity + }; -module.exports.schema = [ - { - "oneOf": [ - { - "type": "integer", - "minimum": 0 - }, - { - "type": "object", - "properties": { - "maximum": { - "type": "integer", - "minimum": 0 - }, - "max": { - "type": "integer", - "minimum": 0 - } - }, - "additionalProperties": false - } - ] } -]; +}; diff --git a/tools/eslint/lib/rules/computed-property-spacing.js b/tools/eslint/lib/rules/computed-property-spacing.js index 6462dfb90e..1ae674e90d 100644 --- a/tools/eslint/lib/rules/computed-property-spacing.js +++ b/tools/eslint/lib/rules/computed-property-spacing.js @@ -1,7 +1,6 @@ /** * @fileoverview Disallows or enforces spaces inside computed properties. * @author Jamund Ferguson - * @copyright 2015 Jamund Ferguson. All rights reserved. */ "use strict"; @@ -11,143 +10,155 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var sourceCode = context.getSourceCode(); - var propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never" - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Reports that there shouldn't be a space after the first token - * @param {ASTNode} node - The node to report in the event of an error. - * @param {Token} token - The token to use for the report. - * @param {Token} tokenAfter - The token after `token`. - * @returns {void} - */ - function reportNoBeginningSpace(node, token, tokenAfter) { - context.report({ - node: node, - loc: token.loc.start, - message: "There should be no space after '" + token.value + "'", - fix: function(fixer) { - return fixer.removeRange([token.range[1], tokenAfter.range[0]]); - } - }); - } - - /** - * Reports that there shouldn't be a space before the last token - * @param {ASTNode} node - The node to report in the event of an error. - * @param {Token} token - The token to use for the report. - * @param {Token} tokenBefore - The token before `token`. - * @returns {void} - */ - function reportNoEndingSpace(node, token, tokenBefore) { - context.report({ - node: node, - loc: token.loc.start, - message: "There should be no space before '" + token.value + "'", - fix: function(fixer) { - return fixer.removeRange([tokenBefore.range[1], token.range[0]]); - } - }); - } - - /** - * Reports that there should be a space after the first token - * @param {ASTNode} node - The node to report in the event of an error. - * @param {Token} token - The token to use for the report. - * @returns {void} - */ - function reportRequiredBeginningSpace(node, token) { - context.report({ - node: node, - loc: token.loc.start, - message: "A space is required after '" + token.value + "'", - fix: function(fixer) { - return fixer.insertTextAfter(token, " "); - } - }); - } +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing inside computed property brackets", + category: "Stylistic Issues", + recommended: false + }, - /** - * Reports that there should be a space before the last token - * @param {ASTNode} node - The node to report in the event of an error. - * @param {Token} token - The token to use for the report. - * @returns {void} - */ - function reportRequiredEndingSpace(node, token) { - context.report({ - node: node, - loc: token.loc.start, - message: "A space is required before '" + token.value + "'", - fix: function(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } + fixable: "whitespace", - /** - * Returns a function that checks the spacing of a node on the property name - * that was passed in. - * @param {String} propertyName The property on the node to check for spacing - * @returns {Function} A function that will check spacing on a node - */ - function checkSpacing(propertyName) { - return function(node) { - if (!node.computed) { - return; + schema: [ + { + enum: ["always", "never"] } + ] + }, + + create: function(context) { + var sourceCode = context.getSourceCode(); + var propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never" + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports that there shouldn't be a space after the first token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @param {Token} tokenAfter - The token after `token`. + * @returns {void} + */ + function reportNoBeginningSpace(node, token, tokenAfter) { + context.report({ + node: node, + loc: token.loc.start, + message: "There should be no space after '" + token.value + "'", + fix: function(fixer) { + return fixer.removeRange([token.range[1], tokenAfter.range[0]]); + } + }); + } + + /** + * Reports that there shouldn't be a space before the last token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @param {Token} tokenBefore - The token before `token`. + * @returns {void} + */ + function reportNoEndingSpace(node, token, tokenBefore) { + context.report({ + node: node, + loc: token.loc.start, + message: "There should be no space before '" + token.value + "'", + fix: function(fixer) { + return fixer.removeRange([tokenBefore.range[1], token.range[0]]); + } + }); + } + + /** + * Reports that there should be a space after the first token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningSpace(node, token) { + context.report({ + node: node, + loc: token.loc.start, + message: "A space is required after '" + token.value + "'", + fix: function(fixer) { + return fixer.insertTextAfter(token, " "); + } + }); + } + + /** + * Reports that there should be a space before the last token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingSpace(node, token) { + context.report({ + node: node, + loc: token.loc.start, + message: "A space is required before '" + token.value + "'", + fix: function(fixer) { + return fixer.insertTextBefore(token, " "); + } + }); + } + + /** + * Returns a function that checks the spacing of a node on the property name + * that was passed in. + * @param {String} propertyName The property on the node to check for spacing + * @returns {Function} A function that will check spacing on a node + */ + function checkSpacing(propertyName) { + return function(node) { + if (!node.computed) { + return; + } - var property = node[propertyName]; - - var before = context.getTokenBefore(property), - first = context.getFirstToken(property), - last = context.getLastToken(property), - after = context.getTokenAfter(property); - - if (astUtils.isTokenOnSameLine(before, first)) { - if (propertyNameMustBeSpaced) { - if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) { - reportRequiredBeginningSpace(node, before); - } - } else { - if (sourceCode.isSpaceBetweenTokens(before, first)) { - reportNoBeginningSpace(node, before, first); + var property = node[propertyName]; + + var before = context.getTokenBefore(property), + first = context.getFirstToken(property), + last = context.getLastToken(property), + after = context.getTokenAfter(property); + + if (astUtils.isTokenOnSameLine(before, first)) { + if (propertyNameMustBeSpaced) { + if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) { + reportRequiredBeginningSpace(node, before); + } + } else { + if (sourceCode.isSpaceBetweenTokens(before, first)) { + reportNoBeginningSpace(node, before, first); + } } } - } - if (astUtils.isTokenOnSameLine(last, after)) { - if (propertyNameMustBeSpaced) { - if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) { - reportRequiredEndingSpace(node, after); - } - } else { - if (sourceCode.isSpaceBetweenTokens(last, after)) { - reportNoEndingSpace(node, after, last); + if (astUtils.isTokenOnSameLine(last, after)) { + if (propertyNameMustBeSpaced) { + if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) { + reportRequiredEndingSpace(node, after); + } + } else { + if (sourceCode.isSpaceBetweenTokens(last, after)) { + reportNoEndingSpace(node, after, last); + } } } - } - }; - } - + }; + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - return { - Property: checkSpacing("key"), - MemberExpression: checkSpacing("property") - }; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- -}; + return { + Property: checkSpacing("key"), + MemberExpression: checkSpacing("property") + }; -module.exports.schema = [ - { - "enum": ["always", "never"] } -]; +}; diff --git a/tools/eslint/lib/rules/consistent-return.js b/tools/eslint/lib/rules/consistent-return.js index af0b582304..0e9a8c8b0f 100644 --- a/tools/eslint/lib/rules/consistent-return.js +++ b/tools/eslint/lib/rules/consistent-return.js @@ -27,100 +27,110 @@ function isUnreachable(segment) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var funcInfo = null; - - /** - * Checks whether of not the implicit returning is consistent if the last - * code path segment is reachable. - * - * @param {ASTNode} node - A program/function node to check. - * @returns {void} - */ - function checkLastSegment(node) { - var loc, type; - - /* - * Skip if it expected no return value or unreachable. - * When unreachable, all paths are returned or thrown. - */ - if (!funcInfo.hasReturnValue || - funcInfo.codePath.currentSegments.every(isUnreachable) || - astUtils.isES5Constructor(node) - ) { - return; - } - - // Adjust a location and a message. - if (node.type === "Program") { - - // The head of program. - loc = {line: 1, column: 0}; - type = "program"; - } else if (node.type === "ArrowFunctionExpression") { - - // `=>` token - loc = context.getSourceCode().getTokenBefore(node.body).loc.start; - type = "function"; - } else if ( - node.parent.type === "MethodDefinition" || - (node.parent.type === "Property" && node.parent.method) - ) { - - // Method name. - loc = node.parent.key.loc.start; - type = "method"; - } else { - - // Function name or `function` keyword. - loc = (node.id || node).loc.start; - type = "function"; - } +module.exports = { + meta: { + docs: { + description: "require `return` statements to either always or never specify values", + category: "Best Practices", + recommended: false + }, - // Reports. - context.report({ - node: node, - loc: loc, - message: "Expected to return a value at the end of this {{type}}.", - data: {type: type} - }); - } + schema: [] + }, - return { - - // Initializes/Disposes state of each code path. - "onCodePathStart": function(codePath) { - funcInfo = { - upper: funcInfo, - codePath: codePath, - hasReturn: false, - hasReturnValue: false, - message: "" - }; - }, - "onCodePathEnd": function() { - funcInfo = funcInfo.upper; - }, + create: function(context) { + var funcInfo = null; - // Reports a given return statement if it's inconsistent. - "ReturnStatement": function(node) { - var hasReturnValue = Boolean(node.argument); + /** + * Checks whether of not the implicit returning is consistent if the last + * code path segment is reachable. + * + * @param {ASTNode} node - A program/function node to check. + * @returns {void} + */ + function checkLastSegment(node) { + var loc, type; + + /* + * Skip if it expected no return value or unreachable. + * When unreachable, all paths are returned or thrown. + */ + if (!funcInfo.hasReturnValue || + funcInfo.codePath.currentSegments.every(isUnreachable) || + astUtils.isES5Constructor(node) + ) { + return; + } - if (!funcInfo.hasReturn) { - funcInfo.hasReturn = true; - funcInfo.hasReturnValue = hasReturnValue; - funcInfo.message = "Expected " + (hasReturnValue ? "a" : "no") + " return value."; - } else if (funcInfo.hasReturnValue !== hasReturnValue) { - context.report({node: node, message: funcInfo.message}); + // Adjust a location and a message. + if (node.type === "Program") { + + // The head of program. + loc = {line: 1, column: 0}; + type = "program"; + } else if (node.type === "ArrowFunctionExpression") { + + // `=>` token + loc = context.getSourceCode().getTokenBefore(node.body).loc.start; + type = "function"; + } else if ( + node.parent.type === "MethodDefinition" || + (node.parent.type === "Property" && node.parent.method) + ) { + + // Method name. + loc = node.parent.key.loc.start; + type = "method"; + } else { + + // Function name or `function` keyword. + loc = (node.id || node).loc.start; + type = "function"; } - }, - // Reports a given program/function if the implicit returning is not consistent. - "Program:exit": checkLastSegment, - "FunctionDeclaration:exit": checkLastSegment, - "FunctionExpression:exit": checkLastSegment, - "ArrowFunctionExpression:exit": checkLastSegment - }; -}; + // Reports. + context.report({ + node: node, + loc: loc, + message: "Expected to return a value at the end of this {{type}}.", + data: {type: type} + }); + } -module.exports.schema = []; + return { + + // Initializes/Disposes state of each code path. + onCodePathStart: function(codePath) { + funcInfo = { + upper: funcInfo, + codePath: codePath, + hasReturn: false, + hasReturnValue: false, + message: "" + }; + }, + onCodePathEnd: function() { + funcInfo = funcInfo.upper; + }, + + // Reports a given return statement if it's inconsistent. + ReturnStatement: function(node) { + var hasReturnValue = Boolean(node.argument); + + if (!funcInfo.hasReturn) { + funcInfo.hasReturn = true; + funcInfo.hasReturnValue = hasReturnValue; + funcInfo.message = "Expected " + (hasReturnValue ? "a" : "no") + " return value."; + } else if (funcInfo.hasReturnValue !== hasReturnValue) { + context.report({node: node, message: funcInfo.message}); + } + }, + + // Reports a given program/function if the implicit returning is not consistent. + "Program:exit": checkLastSegment, + "FunctionDeclaration:exit": checkLastSegment, + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment + }; + } +}; diff --git a/tools/eslint/lib/rules/consistent-this.js b/tools/eslint/lib/rules/consistent-this.js index 09c6a91d1a..042e1a0aae 100644 --- a/tools/eslint/lib/rules/consistent-this.js +++ b/tools/eslint/lib/rules/consistent-this.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to enforce consistent naming of "this" context variables * @author Raphael Pigulla - * @copyright 2015 Timothy Jones. All rights reserved. - * @copyright 2015 David Aurelio. All rights reserved. */ "use strict"; @@ -10,131 +8,141 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var aliases = []; - - if (context.options.length === 0) { - aliases.push("that"); - } else { - aliases = context.options; - } - - /** - * Reports that a variable declarator or assignment expression is assigning - * a non-'this' value to the specified alias. - * @param {ASTNode} node - The assigning node. - * @param {string} alias - the name of the alias that was incorrectly used. - * @returns {void} - */ - function reportBadAssignment(node, alias) { - context.report(node, - "Designated alias '{{alias}}' is not assigned to 'this'.", - { alias: alias }); - } +module.exports = { + meta: { + docs: { + description: "enforce consistent naming when capturing the current execution context", + category: "Stylistic Issues", + recommended: false + }, - /** - * Checks that an assignment to an identifier only assigns 'this' to the - * appropriate alias, and the alias is only assigned to 'this'. - * @param {ASTNode} node - The assigning node. - * @param {Identifier} name - The name of the variable assigned to. - * @param {Expression} value - The value of the assignment. - * @returns {void} - */ - function checkAssignment(node, name, value) { - var isThis = value.type === "ThisExpression"; - - if (aliases.indexOf(name) !== -1) { - if (!isThis || node.operator && node.operator !== "=") { - reportBadAssignment(node, name); - } - } else if (isThis) { - context.report(node, - "Unexpected alias '{{name}}' for 'this'.", { name: name }); + schema: { + type: "array", + items: { + type: "string", + minLength: 1 + }, + uniqueItems: true } - } + }, - /** - * Ensures that a variable declaration of the alias in a program or function - * is assigned to the correct value. - * @param {string} alias alias the check the assignment of. - * @param {object} scope scope of the current code we are checking. - * @private - * @returns {void} - */ - function checkWasAssigned(alias, scope) { - var variable = scope.set.get(alias); - - if (!variable) { - return; - } + create: function(context) { + var aliases = []; - if (variable.defs.some(function(def) { - return def.node.type === "VariableDeclarator" && - def.node.init !== null; - })) { - return; + if (context.options.length === 0) { + aliases.push("that"); + } else { + aliases = context.options; } - // The alias has been declared and not assigned: check it was - // assigned later in the same scope. - if (!variable.references.some(function(reference) { - var write = reference.writeExpr; - - return ( - reference.from === scope && - write && write.type === "ThisExpression" && - write.parent.operator === "=" - ); - })) { - variable.defs.map(function(def) { - return def.node; - }).forEach(function(node) { - reportBadAssignment(node, alias); - }); + /** + * Reports that a variable declarator or assignment expression is assigning + * a non-'this' value to the specified alias. + * @param {ASTNode} node - The assigning node. + * @param {string} alias - the name of the alias that was incorrectly used. + * @returns {void} + */ + function reportBadAssignment(node, alias) { + context.report(node, + "Designated alias '{{alias}}' is not assigned to 'this'.", + { alias: alias }); } - } - - /** - * Check each alias to ensure that is was assinged to the correct value. - * @returns {void} - */ - function ensureWasAssigned() { - var scope = context.getScope(); - aliases.forEach(function(alias) { - checkWasAssigned(alias, scope); - }); - } - - return { - "Program:exit": ensureWasAssigned, - "FunctionExpression:exit": ensureWasAssigned, - "FunctionDeclaration:exit": ensureWasAssigned, + /** + * Checks that an assignment to an identifier only assigns 'this' to the + * appropriate alias, and the alias is only assigned to 'this'. + * @param {ASTNode} node - The assigning node. + * @param {Identifier} name - The name of the variable assigned to. + * @param {Expression} value - The value of the assignment. + * @returns {void} + */ + function checkAssignment(node, name, value) { + var isThis = value.type === "ThisExpression"; + + if (aliases.indexOf(name) !== -1) { + if (!isThis || node.operator && node.operator !== "=") { + reportBadAssignment(node, name); + } + } else if (isThis) { + context.report(node, + "Unexpected alias '{{name}}' for 'this'.", { name: name }); + } + } - "VariableDeclarator": function(node) { - var id = node.id; - var isDestructuring = - id.type === "ArrayPattern" || id.type === "ObjectPattern"; + /** + * Ensures that a variable declaration of the alias in a program or function + * is assigned to the correct value. + * @param {string} alias alias the check the assignment of. + * @param {object} scope scope of the current code we are checking. + * @private + * @returns {void} + */ + function checkWasAssigned(alias, scope) { + var variable = scope.set.get(alias); + + if (!variable) { + return; + } - if (node.init !== null && !isDestructuring) { - checkAssignment(node, id.name, node.init); + if (variable.defs.some(function(def) { + return def.node.type === "VariableDeclarator" && + def.node.init !== null; + })) { + return; } - }, - "AssignmentExpression": function(node) { - if (node.left.type === "Identifier") { - checkAssignment(node, node.left.name, node.right); + // The alias has been declared and not assigned: check it was + // assigned later in the same scope. + if (!variable.references.some(function(reference) { + var write = reference.writeExpr; + + return ( + reference.from === scope && + write && write.type === "ThisExpression" && + write.parent.operator === "=" + ); + })) { + variable.defs.map(function(def) { + return def.node; + }).forEach(function(node) { + reportBadAssignment(node, alias); + }); } } - }; -}; + /** + * Check each alias to ensure that is was assinged to the correct value. + * @returns {void} + */ + function ensureWasAssigned() { + var scope = context.getScope(); -module.exports.schema = { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true + aliases.forEach(function(alias) { + checkWasAssigned(alias, scope); + }); + } + + return { + "Program:exit": ensureWasAssigned, + "FunctionExpression:exit": ensureWasAssigned, + "FunctionDeclaration:exit": ensureWasAssigned, + + VariableDeclarator: function(node) { + var id = node.id; + var isDestructuring = + id.type === "ArrayPattern" || id.type === "ObjectPattern"; + + if (node.init !== null && !isDestructuring) { + checkAssignment(node, id.name, node.init); + } + }, + + AssignmentExpression: function(node) { + if (node.left.type === "Identifier") { + checkAssignment(node, node.left.name, node.right); + } + } + }; + + } }; diff --git a/tools/eslint/lib/rules/constructor-super.js b/tools/eslint/lib/rules/constructor-super.js index 9e0b9eac8e..4b2aacf575 100644 --- a/tools/eslint/lib/rules/constructor-super.js +++ b/tools/eslint/lib/rules/constructor-super.js @@ -1,21 +1,24 @@ /** * @fileoverview A rule to verify `super()` callings in constructor. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -var astUtils = require("../ast-utils"); - //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ +/** + * Checks whether a given code path segment is reachable or not. + * + * @param {CodePathSegment} segment - A code path segment to check. + * @returns {boolean} `true` if the segment is reachable. + */ +function isReachable(segment) { + return segment.reachable; +} + /** * Checks whether or not a given node is a constructor. * @param {ASTNode} node - A node to check. This node type is one of @@ -31,273 +34,352 @@ function isConstructorFunction(node) { ); } +/** + * Checks whether a given node can be a constructor or not. + * + * @param {ASTNode} node - A node to check. + * @returns {boolean} `true` if the node can be a constructor. + */ +function isPossibleConstructor(node) { + if (!node) { + return false; + } + + switch (node.type) { + case "ClassExpression": + case "FunctionExpression": + case "ThisExpression": + case "MemberExpression": + case "CallExpression": + case "NewExpression": + case "YieldExpression": + case "TaggedTemplateExpression": + case "MetaProperty": + return true; + + case "Identifier": + return node.name !== "undefined"; + + case "AssignmentExpression": + return isPossibleConstructor(node.right); + + case "LogicalExpression": + return ( + isPossibleConstructor(node.left) || + isPossibleConstructor(node.right) + ); + + case "ConditionalExpression": + return ( + isPossibleConstructor(node.alternate) || + isPossibleConstructor(node.consequent) + ); + + case "SequenceExpression": + var lastExpression = node.expressions[node.expressions.length - 1]; + + return isPossibleConstructor(lastExpression); + + default: + return false; + } +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /* - * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]} - * Information for each constructor. - * - upper: Information of the upper constructor. - * - hasExtends: A flag which shows whether own class has a valid `extends` - * part. - * - scope: The scope of own class. - * - codePath: The code path object of the constructor. - */ - var funcInfo = null; - - /* - * {Map} - * Information for each code path segment. - * - calledInSomePaths: A flag of be called `super()` in some code paths. - * - calledInEveryPaths: A flag of be called `super()` in all code paths. - * - validNodes: - */ - var segInfoMap = Object.create(null); - - /** - * Gets the flag which shows `super()` is called in some paths. - * @param {CodePathSegment} segment - A code path segment to get. - * @returns {boolean} The flag which shows `super()` is called in some paths - */ - function isCalledInSomePath(segment) { - return segInfoMap[segment.id].calledInSomePaths; - } +module.exports = { + meta: { + docs: { + description: "require `super()` calls in constructors", + category: "ECMAScript 6", + recommended: true + }, - /** - * Gets the flag which shows `super()` is called in all paths. - * @param {CodePathSegment} segment - A code path segment to get. - * @returns {boolean} The flag which shows `super()` is called in all paths. - */ - function isCalledInEveryPath(segment) { + schema: [] + }, + + create: function(context) { /* - * If specific segment is the looped segment of the current segment, - * skip the segment. - * If not skipped, this never becomes true after a loop. + * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]} + * Information for each constructor. + * - upper: Information of the upper constructor. + * - hasExtends: A flag which shows whether own class has a valid `extends` + * part. + * - scope: The scope of own class. + * - codePath: The code path object of the constructor. */ - if (segment.nextSegments.length === 1 && - segment.nextSegments[0].isLoopedPrevSegment(segment) - ) { - return true; - } - return segInfoMap[segment.id].calledInEveryPaths; - } - - return { + var funcInfo = null; - /** - * Stacks a constructor information. - * @param {CodePath} codePath - A code path which was started. - * @param {ASTNode} node - The current node. - * @returns {void} + /* + * {Map} + * Information for each code path segment. + * - calledInSomePaths: A flag of be called `super()` in some code paths. + * - calledInEveryPaths: A flag of be called `super()` in all code paths. + * - validNodes: */ - "onCodePathStart": function(codePath, node) { - if (isConstructorFunction(node)) { - - // Class > ClassBody > MethodDefinition > FunctionExpression - var classNode = node.parent.parent.parent; - - funcInfo = { - upper: funcInfo, - isConstructor: true, - hasExtends: Boolean( - classNode.superClass && - !astUtils.isNullOrUndefined(classNode.superClass) - ), - codePath: codePath - }; - } else { - funcInfo = { - upper: funcInfo, - isConstructor: false, - hasExtends: false, - codePath: codePath - }; - } - }, + var segInfoMap = Object.create(null); /** - * Pops a constructor information. - * And reports if `super()` lacked. - * @param {CodePath} codePath - A code path which was ended. - * @param {ASTNode} node - The current node. - * @returns {void} + * Gets the flag which shows `super()` is called in some paths. + * @param {CodePathSegment} segment - A code path segment to get. + * @returns {boolean} The flag which shows `super()` is called in some paths */ - "onCodePathEnd": function(codePath, node) { - - // Skip if own class which has a valid `extends` part. - var hasExtends = funcInfo.hasExtends; - - funcInfo = funcInfo.upper; - - if (!hasExtends) { - return; - } - - // Reports if `super()` lacked. - var segments = codePath.returnedSegments; - var calledInEveryPaths = segments.every(isCalledInEveryPath); - var calledInSomePaths = segments.some(isCalledInSomePath); - - if (!calledInEveryPaths) { - context.report({ - message: calledInSomePaths ? - "Lacked a call of 'super()' in some code paths." : - "Expected to call 'super()'.", - node: node.parent - }); - } - }, + function isCalledInSomePath(segment) { + return segment.reachable && segInfoMap[segment.id].calledInSomePaths; + } /** - * Initialize information of a given code path segment. - * @param {CodePathSegment} segment - A code path segment to initialize. - * @returns {void} + * Gets the flag which shows `super()` is called in all paths. + * @param {CodePathSegment} segment - A code path segment to get. + * @returns {boolean} The flag which shows `super()` is called in all paths. */ - "onCodePathSegmentStart": function(segment) { + function isCalledInEveryPath(segment) { /* - * Skip if this is not in a constructor of a class which has a - * valid `extends` part. + * If specific segment is the looped segment of the current segment, + * skip the segment. + * If not skipped, this never becomes true after a loop. */ - if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { - return; + if (segment.nextSegments.length === 1 && + segment.nextSegments[0].isLoopedPrevSegment(segment) + ) { + return true; } + return segment.reachable && segInfoMap[segment.id].calledInEveryPaths; + } - // Initialize info. - var info = segInfoMap[segment.id] = { - calledInSomePaths: false, - calledInEveryPaths: false, - validNodes: [] - }; + return { - // When there are previous segments, aggregates these. - var prevSegments = segment.prevSegments; + /** + * Stacks a constructor information. + * @param {CodePath} codePath - A code path which was started. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathStart: function(codePath, node) { + if (isConstructorFunction(node)) { + + // Class > ClassBody > MethodDefinition > FunctionExpression + var classNode = node.parent.parent.parent; + var superClass = classNode.superClass; + + funcInfo = { + upper: funcInfo, + isConstructor: true, + hasExtends: Boolean(superClass), + superIsConstructor: isPossibleConstructor(superClass), + codePath: codePath + }; + } else { + funcInfo = { + upper: funcInfo, + isConstructor: false, + hasExtends: false, + superIsConstructor: false, + codePath: codePath + }; + } + }, + + /** + * Pops a constructor information. + * And reports if `super()` lacked. + * @param {CodePath} codePath - A code path which was ended. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathEnd: function(codePath, node) { + var hasExtends = funcInfo.hasExtends; - if (prevSegments.length > 0) { - info.calledInSomePaths = prevSegments.some(isCalledInSomePath); - info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); - } - }, + // Pop. + funcInfo = funcInfo.upper; - /** - * Update information of the code path segment when a code path was - * looped. - * @param {CodePathSegment} fromSegment - The code path segment of the - * end of a loop. - * @param {CodePathSegment} toSegment - A code path segment of the head - * of a loop. - * @returns {void} - */ - "onCodePathSegmentLoop": function(fromSegment, toSegment) { + if (!hasExtends) { + return; + } - /* - * Skip if this is not in a constructor of a class which has a - * valid `extends` part. - */ - if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { - return; - } + // Reports if `super()` lacked. + var segments = codePath.returnedSegments; + var calledInEveryPaths = segments.every(isCalledInEveryPath); + var calledInSomePaths = segments.some(isCalledInSomePath); - // Update information inside of the loop. - var isRealLoop = toSegment.prevSegments.length >= 2; + if (!calledInEveryPaths) { + context.report({ + message: calledInSomePaths ? + "Lacked a call of 'super()' in some code paths." : + "Expected to call 'super()'.", + node: node.parent + }); + } + }, + + /** + * Initialize information of a given code path segment. + * @param {CodePathSegment} segment - A code path segment to initialize. + * @returns {void} + */ + onCodePathSegmentStart: function(segment) { + if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { + return; + } - funcInfo.codePath.traverseSegments( - {first: toSegment, last: fromSegment}, - function(segment) { - var info = segInfoMap[segment.id]; + // Initialize info. + var info = segInfoMap[segment.id] = { + calledInSomePaths: false, + calledInEveryPaths: false, + validNodes: [] + }; - // Updates flags. - var prevSegments = segment.prevSegments; + // When there are previous segments, aggregates these. + var prevSegments = segment.prevSegments; + if (prevSegments.length > 0) { info.calledInSomePaths = prevSegments.some(isCalledInSomePath); info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); + } + }, + + /** + * Update information of the code path segment when a code path was + * looped. + * @param {CodePathSegment} fromSegment - The code path segment of the + * end of a loop. + * @param {CodePathSegment} toSegment - A code path segment of the head + * of a loop. + * @returns {void} + */ + onCodePathSegmentLoop: function(fromSegment, toSegment) { + if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { + return; + } + + // Update information inside of the loop. + var isRealLoop = toSegment.prevSegments.length >= 2; + + funcInfo.codePath.traverseSegments( + {first: toSegment, last: fromSegment}, + function(segment) { + var info = segInfoMap[segment.id]; + var prevSegments = segment.prevSegments; + + // Updates flags. + info.calledInSomePaths = prevSegments.some(isCalledInSomePath); + info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); + + // If flags become true anew, reports the valid nodes. + if (info.calledInSomePaths || isRealLoop) { + var nodes = info.validNodes; + + info.validNodes = []; - // If flags become true anew, reports the valid nodes. - if (info.calledInSomePaths || isRealLoop) { - var nodes = info.validNodes; + for (var i = 0; i < nodes.length; ++i) { + var node = nodes[i]; - info.validNodes = []; + context.report({ + message: "Unexpected duplicate 'super()'.", + node: node + }); + } + } + } + ); + }, + + /** + * Checks for a call of `super()`. + * @param {ASTNode} node - A CallExpression node to check. + * @returns {void} + */ + "CallExpression:exit": function(node) { + if (!(funcInfo && funcInfo.isConstructor)) { + return; + } + + // Skips except `super()`. + if (node.callee.type !== "Super") { + return; + } + + // Reports if needed. + if (funcInfo.hasExtends) { + var segments = funcInfo.codePath.currentSegments; + var reachable = false; + var duplicate = false; - for (var i = 0; i < nodes.length; ++i) { - var node = nodes[i]; + for (var i = 0; i < segments.length; ++i) { + var segment = segments[i]; + if (segment.reachable) { + var info = segInfoMap[segment.id]; + + reachable = true; + duplicate = duplicate || info.calledInSomePaths; + info.calledInSomePaths = info.calledInEveryPaths = true; + } + } + + if (reachable) { + if (duplicate) { context.report({ message: "Unexpected duplicate 'super()'.", node: node }); + } else if (!funcInfo.superIsConstructor) { + context.report({ + message: "Unexpected 'super()' because 'super' is not a constructor.", + node: node + }); + } else { + info.validNodes.push(node); } } + } else if (funcInfo.codePath.currentSegments.some(isReachable)) { + context.report({ + message: "Unexpected 'super()'.", + node: node + }); } - ); - }, - - /** - * Checks for a call of `super()`. - * @param {ASTNode} node - A CallExpression node to check. - * @returns {void} - */ - "CallExpression:exit": function(node) { + }, - // Skip if the node is not `super()`. - if (node.callee.type !== "Super") { - return; - } - - // Skip if this is not in a constructor. - if (!(funcInfo && funcInfo.isConstructor)) { - return; - } + /** + * Set the mark to the returned path as `super()` was called. + * @param {ASTNode} node - A ReturnStatement node to check. + * @returns {void} + */ + ReturnStatement: function(node) { + if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { + return; + } - // Reports if needed. - if (funcInfo.hasExtends) { + // Skips if no argument. + if (!node.argument) { + return; + } - /* - * This class has a valid `extends` part. - * Checks duplicate `super()`; - */ + // Returning argument is a substitute of 'super()'. var segments = funcInfo.codePath.currentSegments; - var duplicate = false; for (var i = 0; i < segments.length; ++i) { - var info = segInfoMap[segments[i].id]; + var segment = segments[i]; - duplicate = duplicate || info.calledInSomePaths; - info.calledInSomePaths = info.calledInEveryPaths = true; - } + if (segment.reachable) { + var info = segInfoMap[segment.id]; - if (duplicate) { - context.report({ - message: "Unexpected duplicate 'super()'.", - node: node - }); - } else { - info.validNodes.push(node); + info.calledInSomePaths = info.calledInEveryPaths = true; + } } - } else { - - /* - * This class does not have a valid `extends` part. - * Disallow `super()`. - */ - context.report({ - message: "Unexpected 'super()'.", - node: node - }); - } - }, + }, - /** - * Resets state. - * @returns {void} - */ - "Program:exit": function() { - segInfoMap = Object.create(null); - } - }; + /** + * Resets state. + * @returns {void} + */ + "Program:exit": function() { + segInfoMap = Object.create(null); + } + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/curly.js b/tools/eslint/lib/rules/curly.js index 5253a3fb6b..0bc5fdb3de 100644 --- a/tools/eslint/lib/rules/curly.js +++ b/tools/eslint/lib/rules/curly.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag statements without curly braces * @author Nicholas C. Zakas - * @copyright 2015 Ivan Nikulin. All rights reserved. */ "use strict"; @@ -15,280 +14,290 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var multiOnly = (context.options[0] === "multi"); - var multiLine = (context.options[0] === "multi-line"); - var multiOrNest = (context.options[0] === "multi-or-nest"); - var consistent = (context.options[1] === "consistent"); - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Determines if a given node is a one-liner that's on the same line as it's preceding code. - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code. - * @private - */ - function isCollapsedOneLiner(node) { - var before = context.getTokenBefore(node), - last = context.getLastToken(node); - - return before.loc.start.line === last.loc.end.line; - } - - /** - * Determines if a given node is a one-liner. - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node is a one-liner. - * @private - */ - function isOneLiner(node) { - var first = context.getFirstToken(node), - last = context.getLastToken(node); - - return first.loc.start.line === last.loc.end.line; - } +module.exports = { + meta: { + docs: { + description: "enforce consistent brace style for all control statements", + category: "Best Practices", + recommended: false + }, - /** - * Gets the `else` keyword token of a given `IfStatement` node. - * @param {ASTNode} node - A `IfStatement` node to get. - * @returns {Token} The `else` keyword token. - */ - function getElseKeyword(node) { - var sourceCode = context.getSourceCode(); - var token = sourceCode.getTokenAfter(node.consequent); - - while (token.type !== "Keyword" || token.value !== "else") { - token = sourceCode.getTokenAfter(token); + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["all"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["multi", "multi-line", "multi-or-nest"] + }, + { + enum: ["consistent"] + } + ], + minItems: 0, + maxItems: 2 + } + ] + } + }, + + create: function(context) { + + var multiOnly = (context.options[0] === "multi"); + var multiLine = (context.options[0] === "multi-line"); + var multiOrNest = (context.options[0] === "multi-or-nest"); + var consistent = (context.options[1] === "consistent"); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determines if a given node is a one-liner that's on the same line as it's preceding code. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code. + * @private + */ + function isCollapsedOneLiner(node) { + var before = context.getTokenBefore(node), + last = context.getLastToken(node); + + return before.loc.start.line === last.loc.end.line; } - return token; - } + /** + * Determines if a given node is a one-liner. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a one-liner. + * @private + */ + function isOneLiner(node) { + var first = context.getFirstToken(node), + last = context.getLastToken(node); + + return first.loc.start.line === last.loc.end.line; + } - /** - * Checks a given IfStatement node requires braces of the consequent chunk. - * This returns `true` when below: - * - * 1. The given node has the `alternate` node. - * 2. There is a `IfStatement` which doesn't have `alternate` node in the - * trailing statement chain of the `consequent` node. - * - * @param {ASTNode} node - A IfStatement node to check. - * @returns {boolean} `true` if the node requires braces of the consequent chunk. - */ - function requiresBraceOfConsequent(node) { - if (node.alternate && node.consequent.type === "BlockStatement") { - if (node.consequent.body.length >= 2) { - return true; + /** + * Gets the `else` keyword token of a given `IfStatement` node. + * @param {ASTNode} node - A `IfStatement` node to get. + * @returns {Token} The `else` keyword token. + */ + function getElseKeyword(node) { + var sourceCode = context.getSourceCode(); + var token = sourceCode.getTokenAfter(node.consequent); + + while (token.type !== "Keyword" || token.value !== "else") { + token = sourceCode.getTokenAfter(token); } - node = node.consequent.body[0]; - while (node) { - if (node.type === "IfStatement" && !node.alternate) { + return token; + } + + /** + * Checks a given IfStatement node requires braces of the consequent chunk. + * This returns `true` when below: + * + * 1. The given node has the `alternate` node. + * 2. There is a `IfStatement` which doesn't have `alternate` node in the + * trailing statement chain of the `consequent` node. + * + * @param {ASTNode} node - A IfStatement node to check. + * @returns {boolean} `true` if the node requires braces of the consequent chunk. + */ + function requiresBraceOfConsequent(node) { + if (node.alternate && node.consequent.type === "BlockStatement") { + if (node.consequent.body.length >= 2) { return true; } - node = astUtils.getTrailingStatement(node); + + node = node.consequent.body[0]; + while (node) { + if (node.type === "IfStatement" && !node.alternate) { + return true; + } + node = astUtils.getTrailingStatement(node); + } } - } - return false; - } + return false; + } - /** - * Reports "Expected { after ..." error - * @param {ASTNode} node The node to report. - * @param {string} name The name to report. - * @param {string} suffix Additional string to add to the end of a report. - * @returns {void} - * @private - */ - function reportExpectedBraceError(node, name, suffix) { - context.report({ - node: node, - loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, - message: "Expected { after '{{name}}'{{suffix}}.", - data: { - name: name, - suffix: (suffix ? " " + suffix : "") - } - }); - } + /** + * Reports "Expected { after ..." error + * @param {ASTNode} node The node to report. + * @param {string} name The name to report. + * @param {string} suffix Additional string to add to the end of a report. + * @returns {void} + * @private + */ + function reportExpectedBraceError(node, name, suffix) { + context.report({ + node: node, + loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + message: "Expected { after '{{name}}'{{suffix}}.", + data: { + name: name, + suffix: (suffix ? " " + suffix : "") + } + }); + } - /** - * Reports "Unnecessary { after ..." error - * @param {ASTNode} node The node to report. - * @param {string} name The name to report. - * @param {string} suffix Additional string to add to the end of a report. - * @returns {void} - * @private - */ - function reportUnnecessaryBraceError(node, name, suffix) { - context.report({ - node: node, - loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, - message: "Unnecessary { after '{{name}}'{{suffix}}.", - data: { - name: name, - suffix: (suffix ? " " + suffix : "") - } - }); - } + /** + * Reports "Unnecessary { after ..." error + * @param {ASTNode} node The node to report. + * @param {string} name The name to report. + * @param {string} suffix Additional string to add to the end of a report. + * @returns {void} + * @private + */ + function reportUnnecessaryBraceError(node, name, suffix) { + context.report({ + node: node, + loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + message: "Unnecessary { after '{{name}}'{{suffix}}.", + data: { + name: name, + suffix: (suffix ? " " + suffix : "") + } + }); + } - /** - * Prepares to check the body of a node to see if it's a block statement. - * @param {ASTNode} node The node to report if there's a problem. - * @param {ASTNode} body The body node to check for blocks. - * @param {string} name The name to report if there's a problem. - * @param {string} suffix Additional string to add to the end of a report. - * @returns {object} a prepared check object, with "actual", "expected", "check" properties. - * "actual" will be `true` or `false` whether the body is already a block statement. - * "expected" will be `true` or `false` if the body should be a block statement or not, or - * `null` if it doesn't matter, depending on the rule options. It can be modified to change - * the final behavior of "check". - * "check" will be a function reporting appropriate problems depending on the other - * properties. - */ - function prepareCheck(node, body, name, suffix) { - var hasBlock = (body.type === "BlockStatement"); - var expected = null; - - if (node.type === "IfStatement" && node.consequent === body && requiresBraceOfConsequent(node)) { - expected = true; - } else if (multiOnly) { - if (hasBlock && body.body.length === 1) { - expected = false; - } - } else if (multiLine) { - if (!isCollapsedOneLiner(body)) { + /** + * Prepares to check the body of a node to see if it's a block statement. + * @param {ASTNode} node The node to report if there's a problem. + * @param {ASTNode} body The body node to check for blocks. + * @param {string} name The name to report if there's a problem. + * @param {string} suffix Additional string to add to the end of a report. + * @returns {object} a prepared check object, with "actual", "expected", "check" properties. + * "actual" will be `true` or `false` whether the body is already a block statement. + * "expected" will be `true` or `false` if the body should be a block statement or not, or + * `null` if it doesn't matter, depending on the rule options. It can be modified to change + * the final behavior of "check". + * "check" will be a function reporting appropriate problems depending on the other + * properties. + */ + function prepareCheck(node, body, name, suffix) { + var hasBlock = (body.type === "BlockStatement"); + var expected = null; + + if (node.type === "IfStatement" && node.consequent === body && requiresBraceOfConsequent(node)) { expected = true; - } - } else if (multiOrNest) { - if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) { - expected = false; - } else if (!isOneLiner(body)) { + } else if (multiOnly) { + if (hasBlock && body.body.length === 1) { + expected = false; + } + } else if (multiLine) { + if (!isCollapsedOneLiner(body)) { + expected = true; + } + } else if (multiOrNest) { + if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) { + expected = false; + } else if (!isOneLiner(body)) { + expected = true; + } + } else { expected = true; } - } else { - expected = true; - } - return { - actual: hasBlock, - expected: expected, - check: function() { - if (this.expected !== null && this.expected !== this.actual) { - if (this.expected) { - reportExpectedBraceError(node, name, suffix); - } else { - reportUnnecessaryBraceError(node, name, suffix); + return { + actual: hasBlock, + expected: expected, + check: function() { + if (this.expected !== null && this.expected !== this.actual) { + if (this.expected) { + reportExpectedBraceError(node, name, suffix); + } else { + reportUnnecessaryBraceError(node, name, suffix); + } } } - } - }; - } - - /** - * Prepares to check the bodies of a "if", "else if" and "else" chain. - * @param {ASTNode} node The first IfStatement node of the chain. - * @returns {object[]} prepared checks for each body of the chain. See `prepareCheck` for more - * information. - */ - function prepareIfChecks(node) { - var preparedChecks = []; - - do { - preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition")); - if (node.alternate && node.alternate.type !== "IfStatement") { - preparedChecks.push(prepareCheck(node, node.alternate, "else")); - break; - } - node = node.alternate; - } while (node); - - if (consistent) { - - /* - * If any node should have or already have braces, make sure they - * all have braces. - * If all nodes shouldn't have braces, make sure they don't. - */ - var expected = preparedChecks.some(function(preparedCheck) { - if (preparedCheck.expected !== null) { - return preparedCheck.expected; - } - return preparedCheck.actual; - }); - - preparedChecks.forEach(function(preparedCheck) { - preparedCheck.expected = expected; - }); + }; } - return preparedChecks; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + /** + * Prepares to check the bodies of a "if", "else if" and "else" chain. + * @param {ASTNode} node The first IfStatement node of the chain. + * @returns {object[]} prepared checks for each body of the chain. See `prepareCheck` for more + * information. + */ + function prepareIfChecks(node) { + var preparedChecks = []; + + do { + preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition")); + if (node.alternate && node.alternate.type !== "IfStatement") { + preparedChecks.push(prepareCheck(node, node.alternate, "else")); + break; + } + node = node.alternate; + } while (node); + + if (consistent) { + + /* + * If any node should have or already have braces, make sure they + * all have braces. + * If all nodes shouldn't have braces, make sure they don't. + */ + var expected = preparedChecks.some(function(preparedCheck) { + if (preparedCheck.expected !== null) { + return preparedCheck.expected; + } + return preparedCheck.actual; + }); - return { - "IfStatement": function(node) { - if (node.parent.type !== "IfStatement") { - prepareIfChecks(node).forEach(function(preparedCheck) { - preparedCheck.check(); + preparedChecks.forEach(function(preparedCheck) { + preparedCheck.expected = expected; }); } - }, - "WhileStatement": function(node) { - prepareCheck(node, node.body, "while", "condition").check(); - }, + return preparedChecks; + } - "DoWhileStatement": function(node) { - prepareCheck(node, node.body, "do").check(); - }, + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - "ForStatement": function(node) { - prepareCheck(node, node.body, "for", "condition").check(); - }, + return { + IfStatement: function(node) { + if (node.parent.type !== "IfStatement") { + prepareIfChecks(node).forEach(function(preparedCheck) { + preparedCheck.check(); + }); + } + }, - "ForInStatement": function(node) { - prepareCheck(node, node.body, "for-in").check(); - }, + WhileStatement: function(node) { + prepareCheck(node, node.body, "while", "condition").check(); + }, - "ForOfStatement": function(node) { - prepareCheck(node, node.body, "for-of").check(); - } - }; -}; + DoWhileStatement: function(node) { + prepareCheck(node, node.body, "do").check(); + }, -module.exports.schema = { - "anyOf": [ - { - "type": "array", - "items": [ - { - "enum": ["all"] - } - ], - "minItems": 0, - "maxItems": 1 - }, - { - "type": "array", - "items": [ - { - "enum": ["multi", "multi-line", "multi-or-nest"] - }, - { - "enum": ["consistent"] - } - ], - "minItems": 0, - "maxItems": 2 - } - ] + ForStatement: function(node) { + prepareCheck(node, node.body, "for", "condition").check(); + }, + + ForInStatement: function(node) { + prepareCheck(node, node.body, "for-in").check(); + }, + + ForOfStatement: function(node) { + prepareCheck(node, node.body, "for-of").check(); + } + }; + } }; diff --git a/tools/eslint/lib/rules/default-case.js b/tools/eslint/lib/rules/default-case.js index 6bcc791238..a4f3eef3cc 100644 --- a/tools/eslint/lib/rules/default-case.js +++ b/tools/eslint/lib/rules/default-case.js @@ -4,67 +4,89 @@ */ "use strict"; -var COMMENT_VALUE = "no default"; +var DEFAULT_COMMENT_PATTERN = /^no default$/; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Shortcut to get last element of array - * @param {*[]} collection Array - * @returns {*} Last element - */ - function last(collection) { - return collection[collection.length - 1]; - } +module.exports = { + meta: { + docs: { + description: "require `default` cases in switch statements", + category: "Best Practices", + recommended: false + }, + + schema: [{ + type: "object", + properties: { + commentPattern: { + type: "string" + } + }, + additionalProperties: false + }] + }, + + create: function(context) { + var options = context.options[0] || {}; + var commentPattern = options.commentPattern ? + new RegExp(options.commentPattern) : + DEFAULT_COMMENT_PATTERN; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Shortcut to get last element of array + * @param {*[]} collection Array + * @returns {*} Last element + */ + function last(collection) { + return collection[collection.length - 1]; + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { + return { - "SwitchStatement": function(node) { + SwitchStatement: function(node) { - if (!node.cases.length) { + if (!node.cases.length) { - /* - * skip check of empty switch because there is no easy way - * to extract comments inside it now - */ - return; - } + /* + * skip check of empty switch because there is no easy way + * to extract comments inside it now + */ + return; + } - var hasDefault = node.cases.some(function(v) { - return v.test === null; - }); + var hasDefault = node.cases.some(function(v) { + return v.test === null; + }); - if (!hasDefault) { + if (!hasDefault) { - var comment; - var comments; + var comment; + var comments; - var lastCase = last(node.cases); + var lastCase = last(node.cases); - comments = context.getComments(lastCase).trailing; + comments = context.getComments(lastCase).trailing; - if (comments.length) { - comment = last(comments); - } + if (comments.length) { + comment = last(comments); + } - if (!comment || comment.value.trim() !== COMMENT_VALUE) { - context.report(node, "Expected a default case."); + if (!comment || !commentPattern.test(comment.value.trim())) { + context.report(node, "Expected a default case."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/dot-location.js b/tools/eslint/lib/rules/dot-location.js index 89e82269aa..0e1c257d39 100644 --- a/tools/eslint/lib/rules/dot-location.js +++ b/tools/eslint/lib/rules/dot-location.js @@ -1,7 +1,6 @@ /** * @fileoverview Validates newlines before and after dots * @author Greg Cochard - * @copyright 2015 Greg Cochard */ "use strict"; @@ -12,51 +11,61 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce consistent newlines before and after dots", + category: "Best Practices", + recommended: false + }, - var config = context.options[0], - onObject; + schema: [ + { + enum: ["object", "property"] + } + ] + }, + + create: function(context) { + + var config = context.options[0], + onObject; - // default to onObject if no preference is passed - onObject = config === "object" || !config; + // default to onObject if no preference is passed + onObject = config === "object" || !config; - /** - * Reports if the dot between object and property is on the correct loccation. - * @param {ASTNode} obj The object owning the property. - * @param {ASTNode} prop The property of the object. - * @param {ASTNode} node The corresponding node of the token. - * @returns {void} - */ - function checkDotLocation(obj, prop, node) { - var dot = context.getTokenBefore(prop); + /** + * Reports if the dot between object and property is on the correct loccation. + * @param {ASTNode} obj The object owning the property. + * @param {ASTNode} prop The property of the object. + * @param {ASTNode} node The corresponding node of the token. + * @returns {void} + */ + function checkDotLocation(obj, prop, node) { + var dot = context.getTokenBefore(prop); - if (dot.type === "Punctuator" && dot.value === ".") { - if (onObject) { - if (!astUtils.isTokenOnSameLine(obj, dot)) { - context.report(node, dot.loc.start, "Expected dot to be on same line as object."); + if (dot.type === "Punctuator" && dot.value === ".") { + if (onObject) { + if (!astUtils.isTokenOnSameLine(obj, dot)) { + context.report(node, dot.loc.start, "Expected dot to be on same line as object."); + } + } else if (!astUtils.isTokenOnSameLine(dot, prop)) { + context.report(node, dot.loc.start, "Expected dot to be on same line as property."); } - } else if (!astUtils.isTokenOnSameLine(dot, prop)) { - context.report(node, dot.loc.start, "Expected dot to be on same line as property."); } } - } - /** - * Checks the spacing of the dot within a member expression. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkNode(node) { - checkDotLocation(node.object, node.property, node); - } - - return { - "MemberExpression": checkNode - }; -}; + /** + * Checks the spacing of the dot within a member expression. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkNode(node) { + checkDotLocation(node.object, node.property, node); + } -module.exports.schema = [ - { - "enum": ["object", "property"] + return { + MemberExpression: checkNode + }; } -]; +}; diff --git a/tools/eslint/lib/rules/dot-notation.js b/tools/eslint/lib/rules/dot-notation.js index f4d0bca6d2..07e0b0a8db 100644 --- a/tools/eslint/lib/rules/dot-notation.js +++ b/tools/eslint/lib/rules/dot-notation.js @@ -11,50 +11,60 @@ var validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; var keywords = require("../util/keywords"); -module.exports = function(context) { - var options = context.options[0] || {}; - var allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords; +module.exports = { + meta: { + docs: { + description: "enforce dot notation whenever possible", + category: "Best Practices", + recommended: false + }, - var allowPattern; + schema: [ + { + type: "object", + properties: { + allowKeywords: { + type: "boolean" + }, + allowPattern: { + type: "string" + } + }, + additionalProperties: false + } + ] + }, - if (options.allowPattern) { - allowPattern = new RegExp(options.allowPattern); - } + create: function(context) { + var options = context.options[0] || {}; + var allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords; - return { - "MemberExpression": function(node) { - if ( - node.computed && - node.property.type === "Literal" && - validIdentifier.test(node.property.value) && - (allowKeywords || keywords.indexOf("" + node.property.value) === -1) - ) { - if (!(allowPattern && allowPattern.test(node.property.value))) { - context.report(node.property, "[" + JSON.stringify(node.property.value) + "] is better written in dot notation."); - } - } - if ( - !allowKeywords && - !node.computed && - keywords.indexOf("" + node.property.name) !== -1 - ) { - context.report(node.property, "." + node.property.name + " is a syntax error."); - } + var allowPattern; + + if (options.allowPattern) { + allowPattern = new RegExp(options.allowPattern); } - }; -}; -module.exports.schema = [ - { - "type": "object", - "properties": { - "allowKeywords": { - "type": "boolean" - }, - "allowPattern": { - "type": "string" + return { + MemberExpression: function(node) { + if ( + node.computed && + node.property.type === "Literal" && + validIdentifier.test(node.property.value) && + (allowKeywords || keywords.indexOf("" + node.property.value) === -1) + ) { + if (!(allowPattern && allowPattern.test(node.property.value))) { + context.report(node.property, "[" + JSON.stringify(node.property.value) + "] is better written in dot notation."); + } + } + if ( + !allowKeywords && + !node.computed && + keywords.indexOf("" + node.property.name) !== -1 + ) { + context.report(node.property, "." + node.property.name + " is a syntax error."); + } } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/eol-last.js b/tools/eslint/lib/rules/eol-last.js index fe5b0aa425..1bd7c2897a 100644 --- a/tools/eslint/lib/rules/eol-last.js +++ b/tools/eslint/lib/rules/eol-last.js @@ -8,43 +8,55 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - - "Program": function checkBadEOF(node) { - - // Get the whole source code, not for node only. - var src = context.getSource(), - location = {column: 1}, - linebreakStyle = context.options[0] || "unix", - linebreak = linebreakStyle === "unix" ? "\n" : "\r\n"; - - if (src[src.length - 1] !== "\n") { - - // file is not newline-terminated - location.line = src.split(/\n/g).length; - context.report({ - node: node, - loc: location, - message: "Newline required at end of file but not found.", - fix: function(fixer) { - return fixer.insertTextAfterRange([0, src.length], linebreak); - } - }); +module.exports = { + meta: { + docs: { + description: "enforce at least one newline at the end of files", + category: "Stylistic Issues", + recommended: false + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["unix", "windows"] + } + ] + }, + + create: function(context) { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + Program: function checkBadEOF(node) { + + // Get the whole source code, not for node only. + var src = context.getSource(), + location = {column: 1}, + linebreakStyle = context.options[0] || "unix", + linebreak = linebreakStyle === "unix" ? "\n" : "\r\n"; + + if (src[src.length - 1] !== "\n") { + + // file is not newline-terminated + location.line = src.split(/\n/g).length; + context.report({ + node: node, + loc: location, + message: "Newline required at end of file but not found.", + fix: function(fixer) { + return fixer.insertTextAfterRange([0, src.length], linebreak); + } + }); + } } - } - - }; -}; + }; -module.exports.schema = [ - { - "enum": ["unix", "windows"] } -]; +}; diff --git a/tools/eslint/lib/rules/eqeqeq.js b/tools/eslint/lib/rules/eqeqeq.js index 613a3e18ed..f1d1d15429 100644 --- a/tools/eslint/lib/rules/eqeqeq.js +++ b/tools/eslint/lib/rules/eqeqeq.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag statements that use != and == instead of !== and === * @author Nicholas C. Zakas - * @copyright 2013 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -11,91 +9,101 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Checks if an expression is a typeof expression - * @param {ASTNode} node The node to check - * @returns {boolean} if the node is a typeof expression - */ - function isTypeOf(node) { - return node.type === "UnaryExpression" && node.operator === "typeof"; - } - - /** - * Checks if either operand of a binary expression is a typeof operation - * @param {ASTNode} node The node to check - * @returns {boolean} if one of the operands is typeof - * @private - */ - function isTypeOfBinary(node) { - return isTypeOf(node.left) || isTypeOf(node.right); - } +module.exports = { + meta: { + docs: { + description: "require the use of `===` and `!==`", + category: "Best Practices", + recommended: false + }, + + schema: [ + { + enum: ["smart", "allow-null"] + } + ] + }, + + create: function(context) { + + /** + * Checks if an expression is a typeof expression + * @param {ASTNode} node The node to check + * @returns {boolean} if the node is a typeof expression + */ + function isTypeOf(node) { + return node.type === "UnaryExpression" && node.operator === "typeof"; + } - /** - * Checks if operands are literals of the same type (via typeof) - * @param {ASTNode} node The node to check - * @returns {boolean} if operands are of same type - * @private - */ - function areLiteralsAndSameType(node) { - return node.left.type === "Literal" && node.right.type === "Literal" && - typeof node.left.value === typeof node.right.value; - } + /** + * Checks if either operand of a binary expression is a typeof operation + * @param {ASTNode} node The node to check + * @returns {boolean} if one of the operands is typeof + * @private + */ + function isTypeOfBinary(node) { + return isTypeOf(node.left) || isTypeOf(node.right); + } - /** - * Checks if one of the operands is a literal null - * @param {ASTNode} node The node to check - * @returns {boolean} if operands are null - * @private - */ - function isNullCheck(node) { - return (node.right.type === "Literal" && node.right.value === null) || - (node.left.type === "Literal" && node.left.value === null); - } + /** + * Checks if operands are literals of the same type (via typeof) + * @param {ASTNode} node The node to check + * @returns {boolean} if operands are of same type + * @private + */ + function areLiteralsAndSameType(node) { + return node.left.type === "Literal" && node.right.type === "Literal" && + typeof node.left.value === typeof node.right.value; + } - /** - * Gets the location (line and column) of the binary expression's operator - * @param {ASTNode} node The binary expression node to check - * @param {String} operator The operator to find - * @returns {Object} { line, column } location of operator - * @private - */ - function getOperatorLocation(node) { - var opToken = context.getTokenAfter(node.left); - - return {line: opToken.loc.start.line, column: opToken.loc.start.column}; - } + /** + * Checks if one of the operands is a literal null + * @param {ASTNode} node The node to check + * @returns {boolean} if operands are null + * @private + */ + function isNullCheck(node) { + return (node.right.type === "Literal" && node.right.value === null) || + (node.left.type === "Literal" && node.left.value === null); + } - return { - "BinaryExpression": function(node) { - if (node.operator !== "==" && node.operator !== "!=") { - return; - } + /** + * Gets the location (line and column) of the binary expression's operator + * @param {ASTNode} node The binary expression node to check + * @param {String} operator The operator to find + * @returns {Object} { line, column } location of operator + * @private + */ + function getOperatorLocation(node) { + var opToken = context.getTokenAfter(node.left); + + return {line: opToken.loc.start.line, column: opToken.loc.start.column}; + } - if (context.options[0] === "smart" && (isTypeOfBinary(node) || - areLiteralsAndSameType(node) || isNullCheck(node))) { - return; - } + return { + BinaryExpression: function(node) { + if (node.operator !== "==" && node.operator !== "!=") { + return; + } - if (context.options[0] === "allow-null" && isNullCheck(node)) { - return; - } + if (context.options[0] === "smart" && (isTypeOfBinary(node) || + areLiteralsAndSameType(node) || isNullCheck(node))) { + return; + } - context.report({ - node: node, - loc: getOperatorLocation(node), - message: "Expected '{{op}}=' and instead saw '{{op}}'.", - data: { op: node.operator } - }); + if (context.options[0] === "allow-null" && isNullCheck(node)) { + return; + } - } - }; + context.report({ + node: node, + loc: getOperatorLocation(node), + message: "Expected '{{op}}=' and instead saw '{{op}}'.", + data: { op: node.operator } + }); -}; + } + }; -module.exports.schema = [ - { - "enum": ["smart", "allow-null"] } -]; +}; diff --git a/tools/eslint/lib/rules/func-names.js b/tools/eslint/lib/rules/func-names.js index a4fb59edd6..51a1ffe046 100644 --- a/tools/eslint/lib/rules/func-names.js +++ b/tools/eslint/lib/rules/func-names.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to warn when a function expression does not have a name. * @author Kyle T. Nunery - * @copyright 2015 Brandon Mills. All rights reserved. - * @copyright 2014 Kyle T. Nunery. All rights reserved. */ "use strict"; @@ -11,35 +9,45 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Determines whether the current FunctionExpression node is a get, set, or - * shorthand method in an object literal or a class. - * @returns {boolean} True if the node is a get, set, or shorthand method. - */ - function isObjectOrClassMethod() { - var parent = context.getAncestors().pop(); - - return (parent.type === "MethodDefinition" || ( - parent.type === "Property" && ( - parent.method || - parent.kind === "get" || - parent.kind === "set" - ) - )); - } +module.exports = { + meta: { + docs: { + description: "enforce named `function` expressions", + category: "Stylistic Issues", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + /** + * Determines whether the current FunctionExpression node is a get, set, or + * shorthand method in an object literal or a class. + * @returns {boolean} True if the node is a get, set, or shorthand method. + */ + function isObjectOrClassMethod() { + var parent = context.getAncestors().pop(); + + return (parent.type === "MethodDefinition" || ( + parent.type === "Property" && ( + parent.method || + parent.kind === "get" || + parent.kind === "set" + ) + )); + } - return { - "FunctionExpression": function(node) { + return { + FunctionExpression: function(node) { - var name = node.id && node.id.name; + var name = node.id && node.id.name; - if (!name && !isObjectOrClassMethod()) { - context.report(node, "Missing function expression name."); + if (!name && !isObjectOrClassMethod()) { + context.report(node, "Missing function expression name."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/func-style.js b/tools/eslint/lib/rules/func-style.js index 661c015738..9dad6c0755 100644 --- a/tools/eslint/lib/rules/func-style.js +++ b/tools/eslint/lib/rules/func-style.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to enforce a particular function style * @author Nicholas C. Zakas - * @copyright 2013 Nicholas C. Zakas. All rights reserved. */ "use strict"; @@ -9,76 +8,86 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce the consistent use of either `function` declarations or expressions", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + enum: ["declaration", "expression"] + }, + { + type: "object", + properties: { + allowArrowFunctions: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - var style = context.options[0], - allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions === true, - enforceDeclarations = (style === "declaration"), - stack = []; + create: function(context) { - var nodesToCheck = { - "Program": function() { + var style = context.options[0], + allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions === true, + enforceDeclarations = (style === "declaration"), stack = []; - }, - "FunctionDeclaration": function(node) { - stack.push(false); + var nodesToCheck = { + Program: function() { + stack = []; + }, - if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") { - context.report(node, "Expected a function expression."); - } - }, - "FunctionDeclaration:exit": function() { - stack.pop(); - }, + FunctionDeclaration: function(node) { + stack.push(false); - "FunctionExpression": function(node) { - stack.push(false); + if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") { + context.report(node, "Expected a function expression."); + } + }, + "FunctionDeclaration:exit": function() { + stack.pop(); + }, - if (enforceDeclarations && node.parent.type === "VariableDeclarator") { - context.report(node.parent, "Expected a function declaration."); - } - }, - "FunctionExpression:exit": function() { - stack.pop(); - }, + FunctionExpression: function(node) { + stack.push(false); - "ThisExpression": function() { - if (stack.length > 0) { - stack[stack.length - 1] = true; - } - } - }; + if (enforceDeclarations && node.parent.type === "VariableDeclarator") { + context.report(node.parent, "Expected a function declaration."); + } + }, + "FunctionExpression:exit": function() { + stack.pop(); + }, - if (!allowArrowFunctions) { - nodesToCheck.ArrowFunctionExpression = function() { - stack.push(false); + ThisExpression: function() { + if (stack.length > 0) { + stack[stack.length - 1] = true; + } + } }; - nodesToCheck["ArrowFunctionExpression:exit"] = function(node) { - var hasThisExpr = stack.pop(); + if (!allowArrowFunctions) { + nodesToCheck.ArrowFunctionExpression = function() { + stack.push(false); + }; - if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") { - context.report(node.parent, "Expected a function declaration."); - } - }; - } + nodesToCheck["ArrowFunctionExpression:exit"] = function(node) { + var hasThisExpr = stack.pop(); - return nodesToCheck; + if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") { + context.report(node.parent, "Expected a function declaration."); + } + }; + } -}; + return nodesToCheck; -module.exports.schema = [ - { - "enum": ["declaration", "expression"] - }, - { - "type": "object", - "properties": { - "allowArrowFunctions": { - "type": "boolean" - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/generator-star-spacing.js b/tools/eslint/lib/rules/generator-star-spacing.js index 94f2ca1f69..f05f9f4201 100644 --- a/tools/eslint/lib/rules/generator-star-spacing.js +++ b/tools/eslint/lib/rules/generator-star-spacing.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to check the spacing around the * in generator functions. * @author Jamund Ferguson - * @copyright 2015 Brandon Mills. All rights reserved. - * @copyright 2014 Jamund Ferguson. All rights reserved. */ "use strict"; @@ -11,102 +9,114 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing around `*` operators in generator functions", + category: "ECMAScript 6", + recommended: false + }, - var mode = (function(option) { - if (!option || typeof option === "string") { - return { - before: { before: true, after: false }, - after: { before: false, after: true }, - both: { before: true, after: true }, - neither: { before: false, after: false } - }[option || "before"]; - } - return option; - }(context.options[0])); - - /** - * Checks the spacing between two tokens before or after the star token. - * @param {string} side Either "before" or "after". - * @param {Token} leftToken `function` keyword token if side is "before", or - * star token if side is "after". - * @param {Token} rightToken Star token if side is "before", or identifier - * token if side is "after". - * @returns {void} - */ - function checkSpacing(side, leftToken, rightToken) { - if (!!(rightToken.range[0] - leftToken.range[1]) !== mode[side]) { - var after = leftToken.value === "*"; - var spaceRequired = mode[side]; - var node = after ? leftToken : rightToken; - var type = spaceRequired ? "Missing" : "Unexpected"; - var message = type + " space " + side + " *."; - - context.report({ - node: node, - message: message, - fix: function(fixer) { - if (spaceRequired) { - if (after) { - return fixer.insertTextAfter(node, " "); - } - return fixer.insertTextBefore(node, " "); + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["before", "after", "both", "neither"] + }, + { + type: "object", + properties: { + before: {type: "boolean"}, + after: {type: "boolean"} + }, + additionalProperties: false } - return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); - } - }); - } - } + ] + } + ] + }, - /** - * Enforces the spacing around the star if node is a generator function. - * @param {ASTNode} node A function expression or declaration node. - * @returns {void} - */ - function checkFunction(node) { - var prevToken, starToken, nextToken; + create: function(context) { - if (!node.generator) { - return; - } + var mode = (function(option) { + if (!option || typeof option === "string") { + return { + before: { before: true, after: false }, + after: { before: false, after: true }, + both: { before: true, after: true }, + neither: { before: false, after: false } + }[option || "before"]; + } + return option; + }(context.options[0])); - if (node.parent.method || node.parent.type === "MethodDefinition") { - starToken = context.getTokenBefore(node, 1); - } else { - starToken = context.getFirstToken(node, 1); - } + /** + * Checks the spacing between two tokens before or after the star token. + * @param {string} side Either "before" or "after". + * @param {Token} leftToken `function` keyword token if side is "before", or + * star token if side is "after". + * @param {Token} rightToken Star token if side is "before", or identifier + * token if side is "after". + * @returns {void} + */ + function checkSpacing(side, leftToken, rightToken) { + if (!!(rightToken.range[0] - leftToken.range[1]) !== mode[side]) { + var after = leftToken.value === "*"; + var spaceRequired = mode[side]; + var node = after ? leftToken : rightToken; + var type = spaceRequired ? "Missing" : "Unexpected"; + var message = type + " space " + side + " *."; - // Only check before when preceded by `function` keyword - prevToken = context.getTokenBefore(starToken); - if (prevToken.value === "function" || prevToken.value === "static") { - checkSpacing("before", prevToken, starToken); + context.report({ + node: node, + message: message, + fix: function(fixer) { + if (spaceRequired) { + if (after) { + return fixer.insertTextAfter(node, " "); + } + return fixer.insertTextBefore(node, " "); + } + return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); + } + }); + } } - nextToken = context.getTokenAfter(starToken); - checkSpacing("after", starToken, nextToken); - } + /** + * Enforces the spacing around the star if node is a generator function. + * @param {ASTNode} node A function expression or declaration node. + * @returns {void} + */ + function checkFunction(node) { + var prevToken, starToken, nextToken; - return { - "FunctionDeclaration": checkFunction, - "FunctionExpression": checkFunction - }; + if (!node.generator) { + return; + } -}; + if (node.parent.method || node.parent.type === "MethodDefinition") { + starToken = context.getTokenBefore(node, 1); + } else { + starToken = context.getFirstToken(node, 1); + } -module.exports.schema = [ - { - "oneOf": [ - { - "enum": ["before", "after", "both", "neither"] - }, - { - "type": "object", - "properties": { - "before": {"type": "boolean"}, - "after": {"type": "boolean"} - }, - "additionalProperties": false + // Only check before when preceded by `function` keyword + prevToken = context.getTokenBefore(starToken); + if (prevToken.value === "function" || prevToken.value === "static") { + checkSpacing("before", prevToken, starToken); } - ] + + nextToken = context.getTokenAfter(starToken); + checkSpacing("after", starToken, nextToken); + } + + return { + FunctionDeclaration: checkFunction, + FunctionExpression: checkFunction + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/global-require.js b/tools/eslint/lib/rules/global-require.js index d693964458..d1298719a9 100644 --- a/tools/eslint/lib/rules/global-require.js +++ b/tools/eslint/lib/rules/global-require.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule for disallowing require() outside of the top-level module context * @author Jamund Ferguson - * @copyright 2015 Jamund Ferguson. All rights reserved. */ "use strict"; @@ -49,22 +48,32 @@ function isShadowed(scope, node) { return reference && reference.resolved && reference.resolved.defs.length > 0; } -module.exports = function(context) { - return { - "CallExpression": function(node) { - var currentScope = context.getScope(), - isGoodRequire; +module.exports = { + meta: { + docs: { + description: "require `require()` calls to be placed at top-level module scope", + category: "Node.js and CommonJS", + recommended: false + }, - if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { - isGoodRequire = context.getAncestors().every(function(parent) { - return ACCEPTABLE_PARENTS.indexOf(parent.type) > -1; - }); - if (!isGoodRequire) { - context.report(node, "Unexpected require()."); + schema: [] + }, + + create: function(context) { + return { + CallExpression: function(node) { + var currentScope = context.getScope(), + isGoodRequire; + + if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { + isGoodRequire = context.getAncestors().every(function(parent) { + return ACCEPTABLE_PARENTS.indexOf(parent.type) > -1; + }); + if (!isGoodRequire) { + context.report(node, "Unexpected require()."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/guard-for-in.js b/tools/eslint/lib/rules/guard-for-in.js index 925e11817b..b43dda39e4 100644 --- a/tools/eslint/lib/rules/guard-for-in.js +++ b/tools/eslint/lib/rules/guard-for-in.js @@ -9,24 +9,34 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require `for-in` loops to include an `if` statement", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "ForInStatement": function(node) { + create: function(context) { - /* - * If the for-in statement has {}, then the real body is the body - * of the BlockStatement. Otherwise, just use body as provided. - */ - var body = node.body.type === "BlockStatement" ? node.body.body[0] : node.body; + return { - if (body && body.type !== "IfStatement") { - context.report(node, "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype."); + ForInStatement: function(node) { + + /* + * If the for-in statement has {}, then the real body is the body + * of the BlockStatement. Otherwise, just use body as provided. + */ + var body = node.body.type === "BlockStatement" ? node.body.body[0] : node.body; + + if (body && body.type !== "IfStatement") { + context.report(node, "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/handle-callback-err.js b/tools/eslint/lib/rules/handle-callback-err.js index 622288c996..09bf0da977 100644 --- a/tools/eslint/lib/rules/handle-callback-err.js +++ b/tools/eslint/lib/rules/handle-callback-err.js @@ -1,8 +1,6 @@ /** * @fileoverview Ensure handling of errors when we know they exist. * @author Jamund Ferguson - * @copyright 2015 Mathias Schreck. - * @copyright 2014 Jamund Ferguson. All rights reserved. */ "use strict"; @@ -11,73 +9,83 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require error handling in callbacks", + category: "Node.js and CommonJS", + recommended: false + }, - var errorArgument = context.options[0] || "err"; + schema: [ + { + type: "string" + } + ] + }, - /** - * Checks if the given argument should be interpreted as a regexp pattern. - * @param {string} stringToCheck The string which should be checked. - * @returns {boolean} Whether or not the string should be interpreted as a pattern. - */ - function isPattern(stringToCheck) { - var firstChar = stringToCheck[0]; + create: function(context) { - return firstChar === "^"; - } + var errorArgument = context.options[0] || "err"; - /** - * Checks if the given name matches the configured error argument. - * @param {string} name The name which should be compared. - * @returns {boolean} Whether or not the given name matches the configured error variable name. - */ - function matchesConfiguredErrorName(name) { - if (isPattern(errorArgument)) { - var regexp = new RegExp(errorArgument); + /** + * Checks if the given argument should be interpreted as a regexp pattern. + * @param {string} stringToCheck The string which should be checked. + * @returns {boolean} Whether or not the string should be interpreted as a pattern. + */ + function isPattern(stringToCheck) { + var firstChar = stringToCheck[0]; - return regexp.test(name); + return firstChar === "^"; } - return name === errorArgument; - } - /** - * Get the parameters of a given function scope. - * @param {object} scope The function scope. - * @returns {array} All parameters of the given scope. - */ - function getParameters(scope) { - return scope.variables.filter(function(variable) { - return variable.defs[0] && variable.defs[0].type === "Parameter"; - }); - } + /** + * Checks if the given name matches the configured error argument. + * @param {string} name The name which should be compared. + * @returns {boolean} Whether or not the given name matches the configured error variable name. + */ + function matchesConfiguredErrorName(name) { + if (isPattern(errorArgument)) { + var regexp = new RegExp(errorArgument); - /** - * Check to see if we're handling the error object properly. - * @param {ASTNode} node The AST node to check. - * @returns {void} - */ - function checkForError(node) { - var scope = context.getScope(), - parameters = getParameters(scope), - firstParameter = parameters[0]; - - if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) { - if (firstParameter.references.length === 0) { - context.report(node, "Expected error to be handled."); + return regexp.test(name); } + return name === errorArgument; } - } - return { - "FunctionDeclaration": checkForError, - "FunctionExpression": checkForError, - "ArrowFunctionExpression": checkForError - }; + /** + * Get the parameters of a given function scope. + * @param {object} scope The function scope. + * @returns {array} All parameters of the given scope. + */ + function getParameters(scope) { + return scope.variables.filter(function(variable) { + return variable.defs[0] && variable.defs[0].type === "Parameter"; + }); + } -}; + /** + * Check to see if we're handling the error object properly. + * @param {ASTNode} node The AST node to check. + * @returns {void} + */ + function checkForError(node) { + var scope = context.getScope(), + parameters = getParameters(scope), + firstParameter = parameters[0]; + + if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) { + if (firstParameter.references.length === 0) { + context.report(node, "Expected error to be handled."); + } + } + } + + return { + FunctionDeclaration: checkForError, + FunctionExpression: checkForError, + ArrowFunctionExpression: checkForError + }; -module.exports.schema = [ - { - "type": "string" } -]; +}; diff --git a/tools/eslint/lib/rules/id-blacklist.js b/tools/eslint/lib/rules/id-blacklist.js index 42eaf53786..142d8d21f4 100644 --- a/tools/eslint/lib/rules/id-blacklist.js +++ b/tools/eslint/lib/rules/id-blacklist.js @@ -1,11 +1,7 @@ /** * @fileoverview Rule that warns when identifier names that are - blacklisted in the configuration are used. + * blacklisted in the configuration are used. * @author Keith Cirkel (http://keithcirkel.co.uk) - * Based on id-match rule: - * @author Matthieu Larcher - * @copyright 2015 Matthieu Larcher. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; @@ -14,97 +10,108 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- +module.exports = { + meta: { + docs: { + description: "disallow specified identifiers", + category: "Stylistic Issues", + recommended: false + }, + + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, - var blacklist = context.options; + create: function(context) { - /** - * Checks if a string matches the provided pattern - * @param {String} name The string to check. - * @returns {boolean} if the string is a match - * @private - */ - function isInvalid(name) { - return blacklist.indexOf(name) !== -1; - } + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * Verifies if we should report an error or not based on the effective - * parent node and the identifier name. - * @param {ASTNode} effectiveParent The effective parent node of the node to be reported - * @param {String} name The identifier name of the identifier node - * @returns {boolean} whether an error should be reported or not - */ - function shouldReport(effectiveParent, name) { - return effectiveParent.type !== "CallExpression" - && effectiveParent.type !== "NewExpression" && - isInvalid(name); - } + var blacklist = context.options; - /** - * Reports an AST node as a rule violation. - * @param {ASTNode} node The node to report. - * @returns {void} - * @private - */ - function report(node) { - context.report(node, "Identifier '{{name}}' is blacklisted", { - name: node.name - }); - } - return { + /** + * Checks if a string matches the provided pattern + * @param {String} name The string to check. + * @returns {boolean} if the string is a match + * @private + */ + function isInvalid(name) { + return blacklist.indexOf(name) !== -1; + } - "Identifier": function(node) { - var name = node.name, - effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; + /** + * Verifies if we should report an error or not based on the effective + * parent node and the identifier name. + * @param {ASTNode} effectiveParent The effective parent node of the node to be reported + * @param {String} name The identifier name of the identifier node + * @returns {boolean} whether an error should be reported or not + */ + function shouldReport(effectiveParent, name) { + return effectiveParent.type !== "CallExpression" + && effectiveParent.type !== "NewExpression" && + isInvalid(name); + } - // MemberExpressions get special rules - if (node.parent.type === "MemberExpression") { + /** + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private + */ + function report(node) { + context.report(node, "Identifier '{{name}}' is blacklisted", { + name: node.name + }); + } - // Always check object names - if (node.parent.object.type === "Identifier" && - node.parent.object.name === node.name) { - if (isInvalid(name)) { - report(node); + return { + + Identifier: function(node) { + var name = node.name, + effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; + + // MemberExpressions get special rules + if (node.parent.type === "MemberExpression") { + + // Always check object names + if (node.parent.object.type === "Identifier" && + node.parent.object.name === node.name) { + if (isInvalid(name)) { + report(node); + } + + // Report AssignmentExpressions only if they are the left side of the assignment + } else if (effectiveParent.type === "AssignmentExpression" && + (effectiveParent.right.type !== "MemberExpression" || + effectiveParent.left.type === "MemberExpression" && + effectiveParent.left.property.name === node.name)) { + if (isInvalid(name)) { + report(node); + } } - // Report AssignmentExpressions only if they are the left side of the assignment - } else if (effectiveParent.type === "AssignmentExpression" && - (effectiveParent.right.type !== "MemberExpression" || - effectiveParent.left.type === "MemberExpression" && - effectiveParent.left.property.name === node.name)) { - if (isInvalid(name)) { + // Properties have their own rules + } else if (node.parent.type === "Property") { + + if (shouldReport(effectiveParent, name)) { report(node); } - } - // Properties have their own rules - } else if (node.parent.type === "Property") { - - if (shouldReport(effectiveParent, name)) { + // Report anything that is a match and not a CallExpression + } else if (shouldReport(effectiveParent, name)) { report(node); } - - // Report anything that is a match and not a CallExpression - } else if (shouldReport(effectiveParent, name)) { - report(node); } - } - }; + }; -}; -module.exports.schema = { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true + } }; diff --git a/tools/eslint/lib/rules/id-length.js b/tools/eslint/lib/rules/id-length.js index 783ef55c39..43437513ac 100644 --- a/tools/eslint/lib/rules/id-length.js +++ b/tools/eslint/lib/rules/id-length.js @@ -1,9 +1,7 @@ /** * @fileoverview Rule that warns when identifier names are shorter or longer - * than the values provided in configuration. + * than the values provided in configuration. * @author Burak Yigit Kaya aka BYK - * @copyright 2015 Burak Yigit Kaya. All rights reserved. - * @copyright 2015 Mathieu M-Gosselin. All rights reserved. */ "use strict"; @@ -12,98 +10,105 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0] || {}; - var minLength = typeof options.min !== "undefined" ? options.min : 2; - var maxLength = typeof options.max !== "undefined" ? options.max : Infinity; - var properties = options.properties !== "never"; - var exceptions = (options.exceptions ? options.exceptions : []) - .reduce(function(obj, item) { - obj[item] = true; +module.exports = { + meta: { + docs: { + description: "enforce minimum and maximum identifier lengths", + category: "Stylistic Issues", + recommended: false + }, - return obj; - }, {}); + schema: [ + { + type: "object", + properties: { + min: { + type: "number" + }, + max: { + type: "number" + }, + exceptions: { + type: "array", + uniqueItems: true, + items: { + type: "string" + } + }, + properties: { + enum: ["always", "never"] + } + }, + additionalProperties: false + } + ] + }, - var SUPPORTED_EXPRESSIONS = { - "MemberExpression": properties && function(parent) { - return !parent.computed && ( + create: function(context) { + var options = context.options[0] || {}; + var minLength = typeof options.min !== "undefined" ? options.min : 2; + var maxLength = typeof options.max !== "undefined" ? options.max : Infinity; + var properties = options.properties !== "never"; + var exceptions = (options.exceptions ? options.exceptions : []) + .reduce(function(obj, item) { + obj[item] = true; - // regular property assignment - parent.parent.left === parent || ( + return obj; + }, {}); - // or the last identifier in an ObjectPattern destructuring - parent.parent.type === "Property" && parent.parent.value === parent && - parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent - ) - ); - }, - "AssignmentPattern": function(parent, node) { - return parent.left === node; - }, - "VariableDeclarator": function(parent, node) { - return parent.id === node; - }, - "Property": properties && function(parent, node) { - return parent.key === node; - }, - "ImportDefaultSpecifier": true, - "RestElement": true, - "FunctionExpression": true, - "ArrowFunctionExpression": true, - "ClassDeclaration": true, - "FunctionDeclaration": true, - "MethodDefinition": true, - "CatchClause": true - }; + var SUPPORTED_EXPRESSIONS = { + MemberExpression: properties && function(parent) { + return !parent.computed && ( - return { - Identifier: function(node) { - var name = node.name; - var parent = node.parent; + // regular property assignment + (parent.parent.left === parent || // or the last identifier in an ObjectPattern destructuring + parent.parent.type === "Property" && parent.parent.value === parent && + parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent) + ); + }, + AssignmentPattern: function(parent, node) { + return parent.left === node; + }, + VariableDeclarator: function(parent, node) { + return parent.id === node; + }, + Property: properties && function(parent, node) { + return parent.key === node; + }, + ImportDefaultSpecifier: true, + RestElement: true, + FunctionExpression: true, + ArrowFunctionExpression: true, + ClassDeclaration: true, + FunctionDeclaration: true, + MethodDefinition: true, + CatchClause: true + }; - var isShort = name.length < minLength; - var isLong = name.length > maxLength; + return { + Identifier: function(node) { + var name = node.name; + var parent = node.parent; - if (!(isShort || isLong) || exceptions[name]) { - return; // Nothing to report - } + var isShort = name.length < minLength; + var isLong = name.length > maxLength; - var isValidExpression = SUPPORTED_EXPRESSIONS[parent.type]; + if (!(isShort || isLong) || exceptions[name]) { + return; // Nothing to report + } - if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) { - context.report( - node, - isShort ? - "Identifier name '{{name}}' is too short. (< {{min}})" : - "Identifier name '{{name}}' is too long. (> {{max}})", - { name: name, min: minLength, max: maxLength } - ); - } - } - }; -}; + var isValidExpression = SUPPORTED_EXPRESSIONS[parent.type]; -module.exports.schema = [ - { - "type": "object", - "properties": { - "min": { - "type": "number" - }, - "max": { - "type": "number" - }, - "exceptions": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string" + if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) { + context.report( + node, + isShort ? + "Identifier name '{{name}}' is too short. (< {{min}})" : + "Identifier name '{{name}}' is too long. (> {{max}})", + { name: name, min: minLength, max: maxLength } + ); } - }, - "properties": { - "enum": ["always", "never"] } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/id-match.js b/tools/eslint/lib/rules/id-match.js index df6fe6c801..4c9f435108 100644 --- a/tools/eslint/lib/rules/id-match.js +++ b/tools/eslint/lib/rules/id-match.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag non-matching identifiers * @author Matthieu Larcher - * @copyright 2015 Matthieu Larcher. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; @@ -11,121 +9,132 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require identifiers to match a specified regular expression", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + type: "string" + }, + { + type: "object", + properties: { + properties: { + type: "boolean" + } + } + } + ] + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + create: function(context) { - var pattern = context.options[0] || "^.+$", - regexp = new RegExp(pattern); + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - var options = context.options[1] || {}, - properties = options.properties; + var pattern = context.options[0] || "^.+$", + regexp = new RegExp(pattern); - // cast to boolean and default to false - properties = !!properties; + var options = context.options[1] || {}, + properties = !!options.properties, + onlyDeclarations = !!options.onlyDeclarations; + /** + * Checks if a string matches the provided pattern + * @param {String} name The string to check. + * @returns {boolean} if the string is a match + * @private + */ + function isInvalid(name) { + return !regexp.test(name); + } - /** - * Checks if a string matches the provided pattern - * @param {String} name The string to check. - * @returns {boolean} if the string is a match - * @private - */ - function isInvalid(name) { - return !regexp.test(name); - } + /** + * Verifies if we should report an error or not based on the effective + * parent node and the identifier name. + * @param {ASTNode} effectiveParent The effective parent node of the node to be reported + * @param {String} name The identifier name of the identifier node + * @returns {boolean} whether an error should be reported or not + */ + function shouldReport(effectiveParent, name) { + return effectiveParent.type !== "CallExpression" + && effectiveParent.type !== "NewExpression" && + isInvalid(name); + } - /** - * Verifies if we should report an error or not based on the effective - * parent node and the identifier name. - * @param {ASTNode} effectiveParent The effective parent node of the node to be reported - * @param {String} name The identifier name of the identifier node - * @returns {boolean} whether an error should be reported or not - */ - function shouldReport(effectiveParent, name) { - return effectiveParent.type !== "CallExpression" - && effectiveParent.type !== "NewExpression" && - isInvalid(name); - } + /** + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private + */ + function report(node) { + context.report(node, "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", { + name: node.name, + pattern: pattern + }); + } - /** - * Reports an AST node as a rule violation. - * @param {ASTNode} node The node to report. - * @returns {void} - * @private - */ - function report(node) { - context.report(node, "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", { - name: node.name, - pattern: pattern - }); - } + return { - return { + Identifier: function(node) { + var name = node.name, + parent = node.parent, + effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent; - "Identifier": function(node) { - var name = node.name, - effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; + if (parent.type === "MemberExpression") { - // MemberExpressions get special rules - if (node.parent.type === "MemberExpression") { + if (!properties) { + return; + } - // return early if properties is false - if (!properties) { - return; - } + // Always check object names + if (parent.object.type === "Identifier" && + parent.object.name === name) { + if (isInvalid(name)) { + report(node); + } - // Always check object names - if (node.parent.object.type === "Identifier" && - node.parent.object.name === node.name) { - if (isInvalid(name)) { - report(node); + // Report AssignmentExpressions only if they are the left side of the assignment + } else if (effectiveParent.type === "AssignmentExpression" && + (effectiveParent.right.type !== "MemberExpression" || + effectiveParent.left.type === "MemberExpression" && + effectiveParent.left.property.name === name)) { + if (isInvalid(name)) { + report(node); + } } - // Report AssignmentExpressions only if they are the left side of the assignment - } else if (effectiveParent.type === "AssignmentExpression" && - (effectiveParent.right.type !== "MemberExpression" || - effectiveParent.left.type === "MemberExpression" && - effectiveParent.left.property.name === node.name)) { - if (isInvalid(name)) { + } else if (parent.type === "Property") { + + if (!properties || parent.key.name !== name) { + return; + } + + if (shouldReport(effectiveParent, name)) { report(node); } - } - // Properties have their own rules - } else if (node.parent.type === "Property") { + } else { + var isDeclaration = effectiveParent.type === "FunctionDeclaration" || effectiveParent.type === "VariableDeclarator"; - // return early if properties is false - if (!properties) { - return; - } + if (onlyDeclarations && !isDeclaration) { + return; + } - if (shouldReport(effectiveParent, name)) { - report(node); + if (shouldReport(effectiveParent, name)) { + report(node); + } } - - // Report anything that is a match and not a CallExpression - } else if (shouldReport(effectiveParent, name)) { - report(node); } - } - - }; -}; + }; -module.exports.schema = [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "properties": { - "type": "boolean" - } - } } -]; +}; diff --git a/tools/eslint/lib/rules/indent.js b/tools/eslint/lib/rules/indent.js index 24a6241f59..3e8f4b92e1 100644 --- a/tools/eslint/lib/rules/indent.js +++ b/tools/eslint/lib/rules/indent.js @@ -1,31 +1,10 @@ /** * @fileoverview This option sets a specific tab width for your code - + * * This rule has been ported and modified from nodeca. * @author Vitaly Puzrin * @author Gyandeep Singh - * @copyright 2015 Vitaly Puzrin. All rights reserved. - * @copyright 2015 Gyandeep Singh. All rights reserved. - Copyright (C) 2014 by Vitaly Puzrin - - 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"; @@ -35,742 +14,754 @@ var util = require("util"); var lodash = require("lodash"); -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce consistent indentation", + category: "Stylistic Issues", + recommended: false + }, - var MESSAGE = "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}."; - var DEFAULT_VARIABLE_INDENT = 1; + fixable: "whitespace", - var indentType = "space"; - var indentSize = 4; - var options = { - SwitchCase: 0, - VariableDeclarator: { - var: DEFAULT_VARIABLE_INDENT, - let: DEFAULT_VARIABLE_INDENT, - const: DEFAULT_VARIABLE_INDENT - } - }; - - if (context.options.length) { - if (context.options[0] === "tab") { - indentSize = 1; - indentType = "tab"; - } else /* istanbul ignore else : this will be caught by options validation */ if (typeof context.options[0] === "number") { - indentSize = context.options[0]; - indentType = "space"; - } + schema: [ + { + oneOf: [ + { + enum: ["tab"] + }, + { + type: "integer", + minimum: 0 + } + ] + }, + { + type: "object", + properties: { + SwitchCase: { + type: "integer", + minimum: 0 + }, + VariableDeclarator: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + var: { + type: "integer", + minimum: 0 + }, + let: { + type: "integer", + minimum: 0 + }, + const: { + type: "integer", + minimum: 0 + } + } + } + ] + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + + var MESSAGE = "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}."; + var DEFAULT_VARIABLE_INDENT = 1; + + var indentType = "space"; + var indentSize = 4; + var options = { + SwitchCase: 0, + VariableDeclarator: { + var: DEFAULT_VARIABLE_INDENT, + let: DEFAULT_VARIABLE_INDENT, + const: DEFAULT_VARIABLE_INDENT + } + }; + + if (context.options.length) { + if (context.options[0] === "tab") { + indentSize = 1; + indentType = "tab"; + } else /* istanbul ignore else : this will be caught by options validation */ if (typeof context.options[0] === "number") { + indentSize = context.options[0]; + indentType = "space"; + } - if (context.options[1]) { - var opts = context.options[1]; + if (context.options[1]) { + var opts = context.options[1]; - options.SwitchCase = opts.SwitchCase || 0; - var variableDeclaratorRules = opts.VariableDeclarator; + options.SwitchCase = opts.SwitchCase || 0; + var variableDeclaratorRules = opts.VariableDeclarator; - if (typeof variableDeclaratorRules === "number") { - options.VariableDeclarator = { - var: variableDeclaratorRules, - let: variableDeclaratorRules, - const: variableDeclaratorRules - }; - } else if (typeof variableDeclaratorRules === "object") { - lodash.assign(options.VariableDeclarator, variableDeclaratorRules); + if (typeof variableDeclaratorRules === "number") { + options.VariableDeclarator = { + var: variableDeclaratorRules, + let: variableDeclaratorRules, + const: variableDeclaratorRules + }; + } else if (typeof variableDeclaratorRules === "object") { + lodash.assign(options.VariableDeclarator, variableDeclaratorRules); + } } } - } - var indentPattern = { - normal: indentType === "space" ? /^ +/ : /^\t+/, - excludeCommas: indentType === "space" ? /^[ ,]+/ : /^[\t,]+/ - }; - - var caseIndentStore = {}; - - /** - * Reports a given indent violation and properly pluralizes the message - * @param {ASTNode} node Node violating the indent rule - * @param {int} needed Expected indentation character count - * @param {int} gotten Indentation character count in the actual node/code - * @param {Object=} loc Error line and column location - * @param {boolean} isLastNodeCheck Is the error for last node check - * @returns {void} - */ - function report(node, needed, gotten, loc, isLastNodeCheck) { - var msgContext = { - needed: needed, - type: indentType, - characters: needed === 1 ? "character" : "characters", - gotten: gotten + var indentPattern = { + normal: indentType === "space" ? /^ +/ : /^\t+/, + excludeCommas: indentType === "space" ? /^[ ,]+/ : /^[\t,]+/ }; - var indentChar = indentType === "space" ? " " : "\t"; + + var caseIndentStore = {}; /** - * Responsible for fixing the indentation issue fix - * @returns {Function} function to be executed by the fixer - * @private + * Reports a given indent violation and properly pluralizes the message + * @param {ASTNode} node Node violating the indent rule + * @param {int} needed Expected indentation character count + * @param {int} gotten Indentation character count in the actual node/code + * @param {Object=} loc Error line and column location + * @param {boolean} isLastNodeCheck Is the error for last node check + * @returns {void} */ - function getFixerFunction() { - var rangeToFix = []; - - if (needed > gotten) { - var spaces = "" + new Array(needed - gotten + 1).join(indentChar); // replace with repeat in future + function report(node, needed, gotten, loc, isLastNodeCheck) { + var msgContext = { + needed: needed, + type: indentType, + characters: needed === 1 ? "character" : "characters", + gotten: gotten + }; + var indentChar = indentType === "space" ? " " : "\t"; + + /** + * Responsible for fixing the indentation issue fix + * @returns {Function} function to be executed by the fixer + * @private + */ + function getFixerFunction() { + var rangeToFix = []; + + if (needed > gotten) { + var spaces = "" + new Array(needed - gotten + 1).join(indentChar); // replace with repeat in future + + if (isLastNodeCheck === true) { + rangeToFix = [ + node.range[1] - 1, + node.range[1] - 1 + ]; + } else { + rangeToFix = [ + node.range[0], + node.range[0] + ]; + } - if (isLastNodeCheck === true) { - rangeToFix = [ - node.range[1] - 1, - node.range[1] - 1 - ]; + return function(fixer) { + return fixer.insertTextBeforeRange(rangeToFix, spaces); + }; } else { - rangeToFix = [ - node.range[0], - node.range[0] - ]; - } + if (isLastNodeCheck === true) { + rangeToFix = [ + node.range[1] - (gotten - needed) - 1, + node.range[1] - 1 + ]; + } else { + rangeToFix = [ + node.range[0] - (gotten - needed), + node.range[0] + ]; + } - return function(fixer) { - return fixer.insertTextBeforeRange(rangeToFix, spaces); - }; - } else { - if (isLastNodeCheck === true) { - rangeToFix = [ - node.range[1] - (gotten - needed) - 1, - node.range[1] - 1 - ]; - } else { - rangeToFix = [ - node.range[0] - (gotten - needed), - node.range[0] - ]; + return function(fixer) { + return fixer.removeRange(rangeToFix); + }; } + } - return function(fixer) { - return fixer.removeRange(rangeToFix); - }; + if (loc) { + context.report({ + node: node, + loc: loc, + message: MESSAGE, + data: msgContext, + fix: getFixerFunction() + }); + } else { + context.report({ + node: node, + message: MESSAGE, + data: msgContext, + fix: getFixerFunction() + }); } } - if (loc) { - context.report({ - node: node, - loc: loc, - message: MESSAGE, - data: msgContext, - fix: getFixerFunction() - }); - } else { - context.report({ - node: node, - message: MESSAGE, - data: msgContext, - fix: getFixerFunction() - }); + /** + * Get node indent + * @param {ASTNode|Token} node Node to examine + * @param {boolean} [byLastLine=false] get indent of node's last line + * @param {boolean} [excludeCommas=false] skip comma on start of line + * @returns {int} Indent + */ + function getNodeIndent(node, byLastLine, excludeCommas) { + var token = byLastLine ? context.getLastToken(node) : context.getFirstToken(node); + var src = context.getSource(token, token.loc.start.column); + var regExp = excludeCommas ? indentPattern.excludeCommas : indentPattern.normal; + var indent = regExp.exec(src); + + return indent ? indent[0].length : 0; } - } - /** - * Get node indent - * @param {ASTNode|Token} node Node to examine - * @param {boolean} [byLastLine=false] get indent of node's last line - * @param {boolean} [excludeCommas=false] skip comma on start of line - * @returns {int} Indent - */ - function getNodeIndent(node, byLastLine, excludeCommas) { - var token = byLastLine ? context.getLastToken(node) : context.getFirstToken(node); - var src = context.getSource(token, token.loc.start.column); - var regExp = excludeCommas ? indentPattern.excludeCommas : indentPattern.normal; - var indent = regExp.exec(src); - - return indent ? indent[0].length : 0; - } + /** + * Checks node is the first in its own start line. By default it looks by start line. + * @param {ASTNode} node The node to check + * @param {boolean} [byEndLocation=false] Lookup based on start position or end + * @returns {boolean} true if its the first in the its start line + */ + function isNodeFirstInLine(node, byEndLocation) { + var firstToken = byEndLocation === true ? context.getLastToken(node, 1) : context.getTokenBefore(node), + startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line, + endLine = firstToken ? firstToken.loc.end.line : -1; - /** - * Checks node is the first in its own start line. By default it looks by start line. - * @param {ASTNode} node The node to check - * @param {boolean} [byEndLocation=false] Lookup based on start position or end - * @returns {boolean} true if its the first in the its start line - */ - function isNodeFirstInLine(node, byEndLocation) { - var firstToken = byEndLocation === true ? context.getLastToken(node, 1) : context.getTokenBefore(node), - startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line, - endLine = firstToken ? firstToken.loc.end.line : -1; - - return startLine !== endLine; - } + return startLine !== endLine; + } - /** - * Check indent for node - * @param {ASTNode} node Node to check - * @param {int} indent needed indent - * @param {boolean} [excludeCommas=false] skip comma on start of line - * @returns {void} - */ - function checkNodeIndent(node, indent, excludeCommas) { - var nodeIndent = getNodeIndent(node, false, excludeCommas); - - if ( - node.type !== "ArrayExpression" && node.type !== "ObjectExpression" && - nodeIndent !== indent && isNodeFirstInLine(node) - ) { - report(node, indent, nodeIndent); + /** + * Check indent for node + * @param {ASTNode} node Node to check + * @param {int} indent needed indent + * @param {boolean} [excludeCommas=false] skip comma on start of line + * @returns {void} + */ + function checkNodeIndent(node, indent, excludeCommas) { + var nodeIndent = getNodeIndent(node, false, excludeCommas); + + if ( + node.type !== "ArrayExpression" && node.type !== "ObjectExpression" && + nodeIndent !== indent && isNodeFirstInLine(node) + ) { + report(node, indent, nodeIndent); + } } - } - /** - * Check indent for nodes list - * @param {ASTNode[]} nodes list of node objects - * @param {int} indent needed indent - * @param {boolean} [excludeCommas=false] skip comma on start of line - * @returns {void} - */ - function checkNodesIndent(nodes, indent, excludeCommas) { - nodes.forEach(function(node) { - if (node.type === "IfStatement" && node.alternate) { - var elseToken = context.getTokenBefore(node.alternate); - - checkNodeIndent(elseToken, indent, excludeCommas); - } - checkNodeIndent(node, indent, excludeCommas); - }); - } + /** + * Check indent for nodes list + * @param {ASTNode[]} nodes list of node objects + * @param {int} indent needed indent + * @param {boolean} [excludeCommas=false] skip comma on start of line + * @returns {void} + */ + function checkNodesIndent(nodes, indent, excludeCommas) { + nodes.forEach(function(node) { + if (node.type === "IfStatement" && node.alternate) { + var elseToken = context.getTokenBefore(node.alternate); - /** - * Check last node line indent this detects, that block closed correctly - * @param {ASTNode} node Node to examine - * @param {int} lastLineIndent needed indent - * @returns {void} - */ - function checkLastNodeLineIndent(node, lastLineIndent) { - var lastToken = context.getLastToken(node); - var endIndent = getNodeIndent(lastToken, true); - - if (endIndent !== lastLineIndent && isNodeFirstInLine(node, true)) { - report( - node, - lastLineIndent, - endIndent, - { line: lastToken.loc.start.line, column: lastToken.loc.start.column }, - true - ); + checkNodeIndent(elseToken, indent, excludeCommas); + } + checkNodeIndent(node, indent, excludeCommas); + }); } - } - /** - * Check first node line indent is correct - * @param {ASTNode} node Node to examine - * @param {int} firstLineIndent needed indent - * @returns {void} - */ - function checkFirstNodeLineIndent(node, firstLineIndent) { - var startIndent = getNodeIndent(node, false); - - if (startIndent !== firstLineIndent && isNodeFirstInLine(node)) { - report( - node, - firstLineIndent, - startIndent, - { line: node.loc.start.line, column: node.loc.start.column } - ); + /** + * Check last node line indent this detects, that block closed correctly + * @param {ASTNode} node Node to examine + * @param {int} lastLineIndent needed indent + * @returns {void} + */ + function checkLastNodeLineIndent(node, lastLineIndent) { + var lastToken = context.getLastToken(node); + var endIndent = getNodeIndent(lastToken, true); + + if (endIndent !== lastLineIndent && isNodeFirstInLine(node, true)) { + report( + node, + lastLineIndent, + endIndent, + { line: lastToken.loc.start.line, column: lastToken.loc.start.column }, + true + ); + } } - } - /** - * Returns the VariableDeclarator based on the current node - * if not present then return null - * @param {ASTNode} node node to examine - * @returns {ASTNode|void} if found then node otherwise null - */ - function getVariableDeclaratorNode(node) { - var parent = node.parent; - - while (parent.type !== "VariableDeclarator" && parent.type !== "Program") { - parent = parent.parent; + /** + * Check first node line indent is correct + * @param {ASTNode} node Node to examine + * @param {int} firstLineIndent needed indent + * @returns {void} + */ + function checkFirstNodeLineIndent(node, firstLineIndent) { + var startIndent = getNodeIndent(node, false); + + if (startIndent !== firstLineIndent && isNodeFirstInLine(node)) { + report( + node, + firstLineIndent, + startIndent, + { line: node.loc.start.line, column: node.loc.start.column } + ); + } } - return parent.type === "VariableDeclarator" ? parent : null; - } + /** + * Returns the VariableDeclarator based on the current node + * if not present then return null + * @param {ASTNode} node node to examine + * @returns {ASTNode|void} if found then node otherwise null + */ + function getVariableDeclaratorNode(node) { + var parent = node.parent; - /** - * Check to see if the node is part of the multi-line variable declaration. - * Also if its on the same line as the varNode - * @param {ASTNode} node node to check - * @param {ASTNode} varNode variable declaration node to check against - * @returns {boolean} True if all the above condition satisfy - */ - function isNodeInVarOnTop(node, varNode) { - return varNode && - varNode.parent.loc.start.line === node.loc.start.line && - varNode.parent.declarations.length > 1; - } + while (parent.type !== "VariableDeclarator" && parent.type !== "Program") { + parent = parent.parent; + } - /** - * Check to see if the argument before the callee node is multi-line and - * there should only be 1 argument before the callee node - * @param {ASTNode} node node to check - * @returns {boolean} True if arguments are multi-line - */ - function isArgBeforeCalleeNodeMultiline(node) { - var parent = node.parent; - - if (parent.arguments.length >= 2 && parent.arguments[1] === node) { - return parent.arguments[0].loc.end.line > parent.arguments[0].loc.start.line; + return parent.type === "VariableDeclarator" ? parent : null; } - return false; - } - - /** - * Check indent for function block content - * @param {ASTNode} node node to examine - * @returns {void} - */ - function checkIndentInFunctionBlock(node) { - - /* - * Search first caller in chain. - * Ex.: - * - * Models <- Identifier - * .User - * .find() - * .exec(function() { - * // function body - * }); - * - * Looks for 'Models' + /** + * Check to see if the node is part of the multi-line variable declaration. + * Also if its on the same line as the varNode + * @param {ASTNode} node node to check + * @param {ASTNode} varNode variable declaration node to check against + * @returns {boolean} True if all the above condition satisfy */ - var calleeNode = node.parent; // FunctionExpression - var indent; + function isNodeInVarOnTop(node, varNode) { + return varNode && + varNode.parent.loc.start.line === node.loc.start.line && + varNode.parent.declarations.length > 1; + } - if (calleeNode.parent && - (calleeNode.parent.type === "Property" || - calleeNode.parent.type === "ArrayExpression")) { + /** + * Check to see if the argument before the callee node is multi-line and + * there should only be 1 argument before the callee node + * @param {ASTNode} node node to check + * @returns {boolean} True if arguments are multi-line + */ + function isArgBeforeCalleeNodeMultiline(node) { + var parent = node.parent; - // If function is part of array or object, comma can be put at left - indent = getNodeIndent(calleeNode, false, false); - } else { + if (parent.arguments.length >= 2 && parent.arguments[1] === node) { + return parent.arguments[0].loc.end.line > parent.arguments[0].loc.start.line; + } - // If function is standalone, simple calculate indent - indent = getNodeIndent(calleeNode); + return false; } - if (calleeNode.parent.type === "CallExpression") { - var calleeParent = calleeNode.parent; - - if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") { - if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) { - indent = getNodeIndent(calleeParent); - } + /** + * Check indent for function block content + * @param {ASTNode} node node to examine + * @returns {void} + */ + function checkIndentInFunctionBlock(node) { + + /* + * Search first caller in chain. + * Ex.: + * + * Models <- Identifier + * .User + * .find() + * .exec(function() { + * // function body + * }); + * + * Looks for 'Models' + */ + var calleeNode = node.parent; // FunctionExpression + var indent; + + if (calleeNode.parent && + (calleeNode.parent.type === "Property" || + calleeNode.parent.type === "ArrayExpression")) { + + // If function is part of array or object, comma can be put at left + indent = getNodeIndent(calleeNode, false, false); } else { - if (isArgBeforeCalleeNodeMultiline(calleeNode) && - calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line && - !isNodeFirstInLine(calleeNode)) { - indent = getNodeIndent(calleeParent); + + // If function is standalone, simple calculate indent + indent = getNodeIndent(calleeNode); + } + + if (calleeNode.parent.type === "CallExpression") { + var calleeParent = calleeNode.parent; + + if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") { + if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) { + indent = getNodeIndent(calleeParent); + } + } else { + if (isArgBeforeCalleeNodeMultiline(calleeNode) && + calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line && + !isNodeFirstInLine(calleeNode)) { + indent = getNodeIndent(calleeParent); + } } } - } - // function body indent should be indent + indent size - indent += indentSize; + // function body indent should be indent + indent size + indent += indentSize; - // check if the node is inside a variable - var parentVarNode = getVariableDeclaratorNode(node); + // check if the node is inside a variable + var parentVarNode = getVariableDeclaratorNode(node); - if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) { - indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; - } + if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) { + indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; + } - if (node.body.length > 0) { - checkNodesIndent(node.body, indent); - } + if (node.body.length > 0) { + checkNodesIndent(node.body, indent); + } - checkLastNodeLineIndent(node, indent - indentSize); - } + checkLastNodeLineIndent(node, indent - indentSize); + } - /** - * Checks if the given node starts and ends on the same line - * @param {ASTNode} node The node to check - * @returns {boolean} Whether or not the block starts and ends on the same line. - */ - function isSingleLineNode(node) { - var lastToken = context.getLastToken(node), - startLine = node.loc.start.line, - endLine = lastToken.loc.end.line; + /** + * Checks if the given node starts and ends on the same line + * @param {ASTNode} node The node to check + * @returns {boolean} Whether or not the block starts and ends on the same line. + */ + function isSingleLineNode(node) { + var lastToken = context.getLastToken(node), + startLine = node.loc.start.line, + endLine = lastToken.loc.end.line; - return startLine === endLine; - } + return startLine === endLine; + } - /** - * Check to see if the first element inside an array is an object and on the same line as the node - * If the node is not an array then it will return false. - * @param {ASTNode} node node to check - * @returns {boolean} success/failure - */ - function isFirstArrayElementOnSameLine(node) { - if (node.type === "ArrayExpression" && node.elements[0]) { - return node.elements[0].loc.start.line === node.loc.start.line && node.elements[0].type === "ObjectExpression"; - } else { - return false; + /** + * Check to see if the first element inside an array is an object and on the same line as the node + * If the node is not an array then it will return false. + * @param {ASTNode} node node to check + * @returns {boolean} success/failure + */ + function isFirstArrayElementOnSameLine(node) { + if (node.type === "ArrayExpression" && node.elements[0]) { + return node.elements[0].loc.start.line === node.loc.start.line && node.elements[0].type === "ObjectExpression"; + } else { + return false; + } } - } - /** - * Check indent for array block content or object block content - * @param {ASTNode} node node to examine - * @returns {void} - */ - function checkIndentInArrayOrObjectBlock(node) { + /** + * Check indent for array block content or object block content + * @param {ASTNode} node node to examine + * @returns {void} + */ + function checkIndentInArrayOrObjectBlock(node) { - // Skip inline - if (isSingleLineNode(node)) { - return; - } + // Skip inline + if (isSingleLineNode(node)) { + return; + } - var elements = (node.type === "ArrayExpression") ? node.elements : node.properties; + var elements = (node.type === "ArrayExpression") ? node.elements : node.properties; - // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null - elements = elements.filter(function(elem) { - return elem !== null; - }); + // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null + elements = elements.filter(function(elem) { + return elem !== null; + }); - // Skip if first element is in same line with this node - if (elements.length > 0 && elements[0].loc.start.line === node.loc.start.line) { - return; - } + // Skip if first element is in same line with this node + if (elements.length > 0 && elements[0].loc.start.line === node.loc.start.line) { + return; + } - var nodeIndent; - var elementsIndent; - var parentVarNode = getVariableDeclaratorNode(node); + var nodeIndent; + var elementsIndent; + var parentVarNode = getVariableDeclaratorNode(node); - // TODO - come up with a better strategy in future - if (isNodeFirstInLine(node)) { - var parent = node.parent; - var effectiveParent = parent; + // TODO - come up with a better strategy in future + if (isNodeFirstInLine(node)) { + var parent = node.parent; + var effectiveParent = parent; - if (parent.type === "MemberExpression") { - if (isNodeFirstInLine(parent)) { - effectiveParent = parent.parent.parent; - } else { - effectiveParent = parent.parent; + if (parent.type === "MemberExpression") { + if (isNodeFirstInLine(parent)) { + effectiveParent = parent.parent.parent; + } else { + effectiveParent = parent.parent; + } } - } - nodeIndent = getNodeIndent(effectiveParent); - if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) { - if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) { - if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === effectiveParent.loc.start.line) { - nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]); - } else if ( - parent.type === "ObjectExpression" || - parent.type === "ArrayExpression" || - parent.type === "CallExpression" || - parent.type === "ArrowFunctionExpression" || - parent.type === "NewExpression" - ) { - nodeIndent = nodeIndent + indentSize; + nodeIndent = getNodeIndent(effectiveParent); + if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) { + if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) { + if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === effectiveParent.loc.start.line) { + nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]); + } else if ( + parent.type === "ObjectExpression" || + parent.type === "ArrayExpression" || + parent.type === "CallExpression" || + parent.type === "ArrowFunctionExpression" || + parent.type === "NewExpression" + ) { + nodeIndent = nodeIndent + indentSize; + } } + } else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && effectiveParent.type !== "MemberExpression" && effectiveParent.type !== "ExpressionStatement" && effectiveParent.type !== "AssignmentExpression" && effectiveParent.type !== "Property") { + nodeIndent = nodeIndent + indentSize; } - } else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && effectiveParent.type !== "MemberExpression" && effectiveParent.type !== "ExpressionStatement" && effectiveParent.type !== "AssignmentExpression" && effectiveParent.type !== "Property") { - nodeIndent = nodeIndent + indentSize; + + elementsIndent = nodeIndent + indentSize; + + checkFirstNodeLineIndent(node, nodeIndent); + } else { + nodeIndent = getNodeIndent(node); + elementsIndent = nodeIndent + indentSize; } - elementsIndent = nodeIndent + indentSize; + /* + * Check if the node is a multiple variable declaration; if so, then + * make sure indentation takes that into account. + */ + if (isNodeInVarOnTop(node, parentVarNode)) { + elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; + } - checkFirstNodeLineIndent(node, nodeIndent); - } else { - nodeIndent = getNodeIndent(node); - elementsIndent = nodeIndent + indentSize; + // Comma can be placed before property name + checkNodesIndent(elements, elementsIndent, true); + + if (elements.length > 0) { + + // Skip last block line check if last item in same line + if (elements[elements.length - 1].loc.end.line === node.loc.end.line) { + return; + } + } + + checkLastNodeLineIndent(node, elementsIndent - indentSize); } - /* - * Check if the node is a multiple variable declaration; if so, then - * make sure indentation takes that into account. + /** + * Check if the node or node body is a BlockStatement or not + * @param {ASTNode} node node to test + * @returns {boolean} True if it or its body is a block statement */ - if (isNodeInVarOnTop(node, parentVarNode)) { - elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; + function isNodeBodyBlock(node) { + return node.type === "BlockStatement" || node.type === "ClassBody" || (node.body && node.body.type === "BlockStatement") || + (node.consequent && node.consequent.type === "BlockStatement"); } - // Comma can be placed before property name - checkNodesIndent(elements, elementsIndent, true); - - if (elements.length > 0) { + /** + * Check indentation for blocks + * @param {ASTNode} node node to check + * @returns {void} + */ + function blockIndentationCheck(node) { - // Skip last block line check if last item in same line - if (elements[elements.length - 1].loc.end.line === node.loc.end.line) { + // Skip inline blocks + if (isSingleLineNode(node)) { return; } - } - checkLastNodeLineIndent(node, elementsIndent - indentSize); - } + if (node.parent && ( + node.parent.type === "FunctionExpression" || + node.parent.type === "FunctionDeclaration" || + node.parent.type === "ArrowFunctionExpression" + )) { + checkIndentInFunctionBlock(node); + return; + } - /** - * Check if the node or node body is a BlockStatement or not - * @param {ASTNode} node node to test - * @returns {boolean} True if it or its body is a block statement - */ - function isNodeBodyBlock(node) { - return node.type === "BlockStatement" || node.type === "ClassBody" || (node.body && node.body.type === "BlockStatement") || - (node.consequent && node.consequent.type === "BlockStatement"); - } + var indent; + var nodesToCheck = []; - /** - * Check indentation for blocks - * @param {ASTNode} node node to check - * @returns {void} - */ - function blockIndentationCheck(node) { + /* + * For this statements we should check indent from statement beginning, + * not from the beginning of the block. + */ + var statementsWithProperties = [ + "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration" + ]; - // Skip inline blocks - if (isSingleLineNode(node)) { - return; - } + if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) { + indent = getNodeIndent(node.parent); + } else { + indent = getNodeIndent(node); + } - if (node.parent && ( - node.parent.type === "FunctionExpression" || - node.parent.type === "FunctionDeclaration" || - node.parent.type === "ArrowFunctionExpression" - )) { - checkIndentInFunctionBlock(node); - return; - } + if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") { + nodesToCheck = [node.consequent]; + } else if (util.isArray(node.body)) { + nodesToCheck = node.body; + } else { + nodesToCheck = [node.body]; + } - var indent; - var nodesToCheck = []; + if (nodesToCheck.length > 0) { + checkNodesIndent(nodesToCheck, indent + indentSize); + } - /* - * For this statements we should check indent from statement beginning, - * not from the beginning of the block. - */ - var statementsWithProperties = [ - "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration" - ]; - - if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) { - indent = getNodeIndent(node.parent); - } else { - indent = getNodeIndent(node); + if (node.type === "BlockStatement") { + checkLastNodeLineIndent(node, indent); + } } - if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") { - nodesToCheck = [node.consequent]; - } else if (util.isArray(node.body)) { - nodesToCheck = node.body; - } else { - nodesToCheck = [node.body]; - } + /** + * Filter out the elements which are on the same line of each other or the node. + * basically have only 1 elements from each line except the variable declaration line. + * @param {ASTNode} node Variable declaration node + * @returns {ASTNode[]} Filtered elements + */ + function filterOutSameLineVars(node) { + return node.declarations.reduce(function(finalCollection, elem) { + var lastElem = finalCollection[finalCollection.length - 1]; - if (nodesToCheck.length > 0) { - checkNodesIndent(nodesToCheck, indent + indentSize); - } + if ((elem.loc.start.line !== node.loc.start.line && !lastElem) || + (lastElem && lastElem.loc.start.line !== elem.loc.start.line)) { + finalCollection.push(elem); + } - if (node.type === "BlockStatement") { - checkLastNodeLineIndent(node, indent); + return finalCollection; + }, []); } - } - /** - * Filter out the elements which are on the same line of each other or the node. - * basically have only 1 elements from each line except the variable declaration line. - * @param {ASTNode} node Variable declaration node - * @returns {ASTNode[]} Filtered elements - */ - function filterOutSameLineVars(node) { - return node.declarations.reduce(function(finalCollection, elem) { - var lastElem = finalCollection[finalCollection.length - 1]; + /** + * Check indentation for variable declarations + * @param {ASTNode} node node to examine + * @returns {void} + */ + function checkIndentInVariableDeclarations(node) { + var elements = filterOutSameLineVars(node); + var nodeIndent = getNodeIndent(node); + var lastElement = elements[elements.length - 1]; - if ((elem.loc.start.line !== node.loc.start.line && !lastElem) || - (lastElem && lastElem.loc.start.line !== elem.loc.start.line)) { - finalCollection.push(elem); - } + var elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind]; - return finalCollection; - }, []); - } + // Comma can be placed before declaration + checkNodesIndent(elements, elementsIndent, true); - /** - * Check indentation for variable declarations - * @param {ASTNode} node node to examine - * @returns {void} - */ - function checkIndentInVariableDeclarations(node) { - var elements = filterOutSameLineVars(node); - var nodeIndent = getNodeIndent(node); - var lastElement = elements[elements.length - 1]; + // Only check the last line if there is any token after the last item + if (context.getLastToken(node).loc.end.line <= lastElement.loc.end.line) { + return; + } - var elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind]; + var tokenBeforeLastElement = context.getTokenBefore(lastElement); - // Comma can be placed before declaration - checkNodesIndent(elements, elementsIndent, true); + if (tokenBeforeLastElement.value === ",") { - // Only check the last line if there is any token after the last item - if (context.getLastToken(node).loc.end.line <= lastElement.loc.end.line) { - return; + // Special case for comma-first syntax where the semicolon is indented + checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement)); + } else { + checkLastNodeLineIndent(node, elementsIndent - indentSize); + } } - var tokenBeforeLastElement = context.getTokenBefore(lastElement); - - if (tokenBeforeLastElement.value === ",") { - - // Special case for comma-first syntax where the semicolon is indented - checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement)); - } else { - checkLastNodeLineIndent(node, elementsIndent - indentSize); + /** + * Check and decide whether to check for indentation for blockless nodes + * Scenarios are for or while statements without braces around them + * @param {ASTNode} node node to examine + * @returns {void} + */ + function blockLessNodes(node) { + if (node.body.type !== "BlockStatement") { + blockIndentationCheck(node); + } } - } - /** - * Check and decide whether to check for indentation for blockless nodes - * Scenarios are for or while statements without braces around them - * @param {ASTNode} node node to examine - * @returns {void} - */ - function blockLessNodes(node) { - if (node.body.type !== "BlockStatement") { - blockIndentationCheck(node); - } - } + /** + * Returns the expected indentation for the case statement + * @param {ASTNode} node node to examine + * @param {int} [switchIndent] indent for switch statement + * @returns {int} indent size + */ + function expectedCaseIndent(node, switchIndent) { + var switchNode = (node.type === "SwitchStatement") ? node : node.parent; + var caseIndent; - /** - * Returns the expected indentation for the case statement - * @param {ASTNode} node node to examine - * @param {int} [switchIndent] indent for switch statement - * @returns {int} indent size - */ - function expectedCaseIndent(node, switchIndent) { - var switchNode = (node.type === "SwitchStatement") ? node : node.parent; - var caseIndent; - - if (caseIndentStore[switchNode.loc.start.line]) { - return caseIndentStore[switchNode.loc.start.line]; - } else { - if (typeof switchIndent === "undefined") { - switchIndent = getNodeIndent(switchNode); - } - - if (switchNode.cases.length > 0 && options.SwitchCase === 0) { - caseIndent = switchIndent; + if (caseIndentStore[switchNode.loc.start.line]) { + return caseIndentStore[switchNode.loc.start.line]; } else { - caseIndent = switchIndent + (indentSize * options.SwitchCase); - } - - caseIndentStore[switchNode.loc.start.line] = caseIndent; - return caseIndent; - } - } + if (typeof switchIndent === "undefined") { + switchIndent = getNodeIndent(switchNode); + } - return { - "Program": function(node) { - if (node.body.length > 0) { + if (switchNode.cases.length > 0 && options.SwitchCase === 0) { + caseIndent = switchIndent; + } else { + caseIndent = switchIndent + (indentSize * options.SwitchCase); + } - // Root nodes should have no indent - checkNodesIndent(node.body, getNodeIndent(node)); + caseIndentStore[switchNode.loc.start.line] = caseIndent; + return caseIndent; } - }, + } - "ClassBody": blockIndentationCheck, + return { + Program: function(node) { + if (node.body.length > 0) { - "BlockStatement": blockIndentationCheck, + // Root nodes should have no indent + checkNodesIndent(node.body, getNodeIndent(node)); + } + }, - "WhileStatement": blockLessNodes, + ClassBody: blockIndentationCheck, - "ForStatement": blockLessNodes, + BlockStatement: blockIndentationCheck, - "ForInStatement": blockLessNodes, + WhileStatement: blockLessNodes, - "ForOfStatement": blockLessNodes, + ForStatement: blockLessNodes, - "DoWhileStatement": blockLessNodes, + ForInStatement: blockLessNodes, - "IfStatement": function(node) { - if (node.consequent.type !== "BlockStatement" && node.consequent.loc.start.line > node.loc.start.line) { - blockIndentationCheck(node); - } - }, + ForOfStatement: blockLessNodes, - "VariableDeclaration": function(node) { - if (node.declarations[node.declarations.length - 1].loc.start.line > node.declarations[0].loc.start.line) { - checkIndentInVariableDeclarations(node); - } - }, + DoWhileStatement: blockLessNodes, - "ObjectExpression": function(node) { - checkIndentInArrayOrObjectBlock(node); - }, + IfStatement: function(node) { + if (node.consequent.type !== "BlockStatement" && node.consequent.loc.start.line > node.loc.start.line) { + blockIndentationCheck(node); + } + }, - "ArrayExpression": function(node) { - checkIndentInArrayOrObjectBlock(node); - }, + VariableDeclaration: function(node) { + if (node.declarations[node.declarations.length - 1].loc.start.line > node.declarations[0].loc.start.line) { + checkIndentInVariableDeclarations(node); + } + }, - "SwitchStatement": function(node) { + ObjectExpression: function(node) { + checkIndentInArrayOrObjectBlock(node); + }, - // Switch is not a 'BlockStatement' - var switchIndent = getNodeIndent(node); - var caseIndent = expectedCaseIndent(node, switchIndent); + ArrayExpression: function(node) { + checkIndentInArrayOrObjectBlock(node); + }, - checkNodesIndent(node.cases, caseIndent); + SwitchStatement: function(node) { + // Switch is not a 'BlockStatement' + var switchIndent = getNodeIndent(node); + var caseIndent = expectedCaseIndent(node, switchIndent); - checkLastNodeLineIndent(node, switchIndent); - }, + checkNodesIndent(node.cases, caseIndent); - "SwitchCase": function(node) { - // Skip inline cases - if (isSingleLineNode(node)) { - return; - } - var caseIndent = expectedCaseIndent(node); + checkLastNodeLineIndent(node, switchIndent); + }, - checkNodesIndent(node.consequent, caseIndent + indentSize); - } - }; + SwitchCase: function(node) { -}; + // Skip inline cases + if (isSingleLineNode(node)) { + return; + } + var caseIndent = expectedCaseIndent(node); -module.exports.schema = [ - { - "oneOf": [ - { - "enum": ["tab"] - }, - { - "type": "integer", - "minimum": 0 + checkNodesIndent(node.consequent, caseIndent + indentSize); } - ] - }, - { - "type": "object", - "properties": { - "SwitchCase": { - "type": "integer", - "minimum": 0 - }, - "VariableDeclarator": { - "oneOf": [ - { - "type": "integer", - "minimum": 0 - }, - { - "type": "object", - "properties": { - "var": { - "type": "integer", - "minimum": 0 - }, - "let": { - "type": "integer", - "minimum": 0 - }, - "const": { - "type": "integer", - "minimum": 0 - } - } - } - ] - } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/init-declarations.js b/tools/eslint/lib/rules/init-declarations.js index 10fbf185c1..66b0a0aea4 100644 --- a/tools/eslint/lib/rules/init-declarations.js +++ b/tools/eslint/lib/rules/init-declarations.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to control the style of variable initializations. * @author Colin Ihrig - * @copyright 2015 Colin Ihrig. All rights reserved. */ "use strict"; @@ -43,74 +42,84 @@ function isInitialized(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require or disallow initialization in `var` declarations", + category: "Variables", + recommended: false + }, - var MODE_ALWAYS = "always", - MODE_NEVER = "never"; + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["never"] + }, + { + type: "object", + properties: { + ignoreForLoopInit: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + } + }, - var mode = context.options[0] || MODE_ALWAYS; - var params = context.options[1] || {}; + create: function(context) { - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + var MODE_ALWAYS = "always", + MODE_NEVER = "never"; - return { - "VariableDeclaration:exit": function(node) { + var mode = context.options[0] || MODE_ALWAYS; + var params = context.options[1] || {}; - var kind = node.kind, - declarations = node.declarations; + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - for (var i = 0; i < declarations.length; ++i) { - var declaration = declarations[i], - id = declaration.id, - initialized = isInitialized(declaration), - isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent); + return { + "VariableDeclaration:exit": function(node) { - if (id.type !== "Identifier") { - continue; - } + var kind = node.kind, + declarations = node.declarations; - if (mode === MODE_ALWAYS && !initialized) { - context.report(declaration, "Variable '" + id.name + "' should be initialized on declaration."); - } else if (mode === MODE_NEVER && kind !== "const" && initialized && !isIgnoredForLoop) { - context.report(declaration, "Variable '" + id.name + "' should not be initialized on declaration."); - } - } - } - }; -}; + for (var i = 0; i < declarations.length; ++i) { + var declaration = declarations[i], + id = declaration.id, + initialized = isInitialized(declaration), + isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent); -module.exports.schema = { - "anyOf": [ - { - "type": "array", - "items": [ - { - "enum": ["always"] - } - ], - "minItems": 0, - "maxItems": 1 - }, - { - "type": "array", - "items": [ - { - "enum": ["never"] - }, - { - "type": "object", - "properties": { - "ignoreForLoopInit": { - "type": "boolean" - } - }, - "additionalProperties": false + if (id.type !== "Identifier") { + continue; + } + + if (mode === MODE_ALWAYS && !initialized) { + context.report(declaration, "Variable '" + id.name + "' should be initialized on declaration."); + } else if (mode === MODE_NEVER && kind !== "const" && initialized && !isIgnoredForLoop) { + context.report(declaration, "Variable '" + id.name + "' should not be initialized on declaration."); + } } - ], - "minItems": 0, - "maxItems": 2 - } - ] + } + }; + } }; diff --git a/tools/eslint/lib/rules/jsx-quotes.js b/tools/eslint/lib/rules/jsx-quotes.js index 486aa81862..6b3a2efef7 100644 --- a/tools/eslint/lib/rules/jsx-quotes.js +++ b/tools/eslint/lib/rules/jsx-quotes.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to ensure consistent quotes used in jsx syntax. * @author Mathias Schreck - * @copyright 2015 Mathias Schreck */ "use strict"; @@ -37,39 +36,51 @@ var QUOTE_SETTINGS = { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var quoteOption = context.options[0] || "prefer-double", - setting = QUOTE_SETTINGS[quoteOption]; +module.exports = { + meta: { + docs: { + description: "enforce the consistent use of either double or single quotes in JSX attributes", + category: "Stylistic Issues", + recommended: false + }, - /** - * Checks if the given string literal node uses the expected quotes - * @param {ASTNode} node - A string literal node. - * @returns {boolean} Whether or not the string literal used the expected quotes. - * @public - */ - function usesExpectedQuotes(node) { - return node.value.indexOf(setting.quote) !== -1 || astUtils.isSurroundedBy(node.raw, setting.quote); - } - - return { - "JSXAttribute": function(node) { - var attributeValue = node.value; + fixable: "whitespace", - if (attributeValue && astUtils.isStringLiteral(attributeValue) && !usesExpectedQuotes(attributeValue)) { - context.report({ - node: attributeValue, - message: "Unexpected usage of " + setting.description + ".", - fix: function(fixer) { - return fixer.replaceText(attributeValue, setting.convert(attributeValue.raw)); - } - }); + schema: [ + { + enum: [ "prefer-single", "prefer-double" ] } + ] + }, + + create: function(context) { + var quoteOption = context.options[0] || "prefer-double", + setting = QUOTE_SETTINGS[quoteOption]; + + /** + * Checks if the given string literal node uses the expected quotes + * @param {ASTNode} node - A string literal node. + * @returns {boolean} Whether or not the string literal used the expected quotes. + * @public + */ + function usesExpectedQuotes(node) { + return node.value.indexOf(setting.quote) !== -1 || astUtils.isSurroundedBy(node.raw, setting.quote); } - }; -}; -module.exports.schema = [ - { - "enum": [ "prefer-single", "prefer-double" ] + return { + JSXAttribute: function(node) { + var attributeValue = node.value; + + if (attributeValue && astUtils.isStringLiteral(attributeValue) && !usesExpectedQuotes(attributeValue)) { + context.report({ + node: attributeValue, + message: "Unexpected usage of " + setting.description + ".", + fix: function(fixer) { + return fixer.replaceText(attributeValue, setting.convert(attributeValue.raw)); + } + }); + } + } + }; } -]; +}; diff --git a/tools/eslint/lib/rules/key-spacing.js b/tools/eslint/lib/rules/key-spacing.js index c317e4a2c8..1bc14aefec 100644 --- a/tools/eslint/lib/rules/key-spacing.js +++ b/tools/eslint/lib/rules/key-spacing.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to specify spacing of object literal keys and values * @author Brandon Mills - * @copyright 2014 Brandon Mills. All rights reserved. */ "use strict"; @@ -111,332 +110,341 @@ var messages = { value: "{{error}} space before value for {{computed}}key '{{key}}'." }; -module.exports = function(context) { - - /** - * OPTIONS - * "key-spacing": [2, { - * beforeColon: false, - * afterColon: true, - * align: "colon" // Optional, or "value" - * } - */ - - var options = context.options[0] || {}, - multiLineOptions = initOptions({}, (options.multiLine || options)), - singleLineOptions = initOptions({}, (options.singleLine || options)); - - /** - * Determines if the given property is key-value property. - * @param {ASTNode} property Property node to check. - * @returns {Boolean} Whether the property is a key-value property. - */ - function isKeyValueProperty(property) { - return !( - property.method || - property.shorthand || - property.kind !== "init" || - property.type !== "Property" // Could be "ExperimentalSpreadProperty" or "SpreadProperty" - ); - } +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing between keys and values in object literal properties", + category: "Stylistic Issues", + recommended: false + }, - /** - * Starting from the given a node (a property.key node here) looks forward - * until it finds the last token before a colon punctuator and returns it. - * @param {ASTNode} node The node to start looking from. - * @returns {ASTNode} The last token before a colon punctuator. - */ - function getLastTokenBeforeColon(node) { - var prevNode; - - while (node && (node.type !== "Punctuator" || node.value !== ":")) { - prevNode = node; - node = context.getTokenAfter(node); + schema: [{ + anyOf: [ + { + type: "object", + properties: { + align: { + enum: ["colon", "value"] + }, + mode: { + enum: ["strict", "minimum"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + }, + { + type: "object", + properties: { + singleLine: { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + }, + multiLine: { + type: "object", + properties: { + align: { + enum: ["colon", "value"] + }, + mode: { + enum: ["strict", "minimum"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + } + }, + additionalProperties: false + } + ] + }] + }, + + create: function(context) { + + /** + * OPTIONS + * "key-spacing": [2, { + * beforeColon: false, + * afterColon: true, + * align: "colon" // Optional, or "value" + * } + */ + + var options = context.options[0] || {}, + multiLineOptions = initOptions({}, (options.multiLine || options)), + singleLineOptions = initOptions({}, (options.singleLine || options)); + + /** + * Determines if the given property is key-value property. + * @param {ASTNode} property Property node to check. + * @returns {Boolean} Whether the property is a key-value property. + */ + function isKeyValueProperty(property) { + return !( + (property.method || + property.shorthand || + property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadProperty" + ); } - return prevNode; - } - - /** - * Starting from the given a node (a property.key node here) looks forward - * until it finds the colon punctuator and returns it. - * @param {ASTNode} node The node to start looking from. - * @returns {ASTNode} The colon punctuator. - */ - function getNextColon(node) { + /** + * Starting from the given a node (a property.key node here) looks forward + * until it finds the last token before a colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The last token before a colon punctuator. + */ + function getLastTokenBeforeColon(node) { + var prevNode; + + while (node && (node.type !== "Punctuator" || node.value !== ":")) { + prevNode = node; + node = context.getTokenAfter(node); + } - while (node && (node.type !== "Punctuator" || node.value !== ":")) { - node = context.getTokenAfter(node); + return prevNode; } - return node; - } + /** + * Starting from the given a node (a property.key node here) looks forward + * until it finds the colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The colon punctuator. + */ + function getNextColon(node) { - /** - * Gets an object literal property's key as the identifier name or string value. - * @param {ASTNode} property Property node whose key to retrieve. - * @returns {string} The property's key. - */ - function getKey(property) { - var key = property.key; + while (node && (node.type !== "Punctuator" || node.value !== ":")) { + node = context.getTokenAfter(node); + } - if (property.computed) { - return context.getSource().slice(key.range[0], key.range[1]); + return node; } - return property.key.name || property.key.value; - } + /** + * Gets an object literal property's key as the identifier name or string value. + * @param {ASTNode} property Property node whose key to retrieve. + * @returns {string} The property's key. + */ + function getKey(property) { + var key = property.key; - /** - * Reports an appropriately-formatted error if spacing is incorrect on one - * side of the colon. - * @param {ASTNode} property Key-value pair in an object literal. - * @param {string} side Side being verified - either "key" or "value". - * @param {string} whitespace Actual whitespace string. - * @param {int} expected Expected whitespace length. - * @param {string} mode Value of the mode as "strict" or "minimum" - * @returns {void} - */ - function report(property, side, whitespace, expected, mode) { - var diff = whitespace.length - expected, - key = property.key, - firstTokenAfterColon = context.getTokenAfter(getNextColon(key)), - location = side === "key" ? key.loc.start : firstTokenAfterColon.loc.start; - - if (( - diff && mode === "strict" || - diff < 0 && mode === "minimum" || - diff > 0 && !expected && mode === "minimum") && - !(expected && containsLineTerminator(whitespace)) - ) { - context.report(property[side], location, messages[side], { - error: diff > 0 ? "Extra" : "Missing", - computed: property.computed ? "computed " : "", - key: getKey(property) - }); + if (property.computed) { + return context.getSource().slice(key.range[0], key.range[1]); + } + + return property.key.name || property.key.value; } - } - /** - * Gets the number of characters in a key, including quotes around string - * keys and braces around computed property keys. - * @param {ASTNode} property Property of on object literal. - * @returns {int} Width of the key. - */ - function getKeyWidth(property) { - var startToken, endToken; + /** + * Reports an appropriately-formatted error if spacing is incorrect on one + * side of the colon. + * @param {ASTNode} property Key-value pair in an object literal. + * @param {string} side Side being verified - either "key" or "value". + * @param {string} whitespace Actual whitespace string. + * @param {int} expected Expected whitespace length. + * @param {string} mode Value of the mode as "strict" or "minimum" + * @returns {void} + */ + function report(property, side, whitespace, expected, mode) { + var diff = whitespace.length - expected, + key = property.key, + firstTokenAfterColon = context.getTokenAfter(getNextColon(key)), + location = side === "key" ? key.loc.start : firstTokenAfterColon.loc.start; + + if (( + diff && mode === "strict" || + diff < 0 && mode === "minimum" || + diff > 0 && !expected && mode === "minimum") && + !(expected && containsLineTerminator(whitespace)) + ) { + context.report(property[side], location, messages[side], { + error: diff > 0 ? "Extra" : "Missing", + computed: property.computed ? "computed " : "", + key: getKey(property) + }); + } + } - startToken = context.getFirstToken(property); - endToken = getLastTokenBeforeColon(property.key); + /** + * Gets the number of characters in a key, including quotes around string + * keys and braces around computed property keys. + * @param {ASTNode} property Property of on object literal. + * @returns {int} Width of the key. + */ + function getKeyWidth(property) { + var startToken, endToken; - return endToken.range[1] - startToken.range[0]; - } + startToken = context.getFirstToken(property); + endToken = getLastTokenBeforeColon(property.key); - /** - * Gets the whitespace around the colon in an object literal property. - * @param {ASTNode} property Property node from an object literal. - * @returns {Object} Whitespace before and after the property's colon. - */ - function getPropertyWhitespace(property) { - var whitespace = /(\s*):(\s*)/.exec(context.getSource().slice( - property.key.range[1], property.value.range[0] - )); - - if (whitespace) { - return { - beforeColon: whitespace[1], - afterColon: whitespace[2] - }; + return endToken.range[1] - startToken.range[0]; } - return null; - } - /** - * Creates groups of properties. - * @param {ASTNode} node ObjectExpression node being evaluated. - * @returns {Array.} Groups of property AST node lists. - */ - function createGroups(node) { - if (node.properties.length === 1) { - return [node.properties]; + /** + * Gets the whitespace around the colon in an object literal property. + * @param {ASTNode} property Property node from an object literal. + * @returns {Object} Whitespace before and after the property's colon. + */ + function getPropertyWhitespace(property) { + var whitespace = /(\s*):(\s*)/.exec(context.getSource().slice( + property.key.range[1], property.value.range[0] + )); + + if (whitespace) { + return { + beforeColon: whitespace[1], + afterColon: whitespace[2] + }; + } + return null; } - return node.properties.reduce(function(groups, property) { - var currentGroup = last(groups), - prev = last(currentGroup); - - if (!prev || continuesPropertyGroup(prev, property)) { - currentGroup.push(property); - } else { - groups.push([property]); + /** + * Creates groups of properties. + * @param {ASTNode} node ObjectExpression node being evaluated. + * @returns {Array.} Groups of property AST node lists. + */ + function createGroups(node) { + if (node.properties.length === 1) { + return [node.properties]; } - return groups; - }, [ - [] - ]); - } + return node.properties.reduce(function(groups, property) { + var currentGroup = last(groups), + prev = last(currentGroup); - /** - * Verifies correct vertical alignment of a group of properties. - * @param {ASTNode[]} properties List of Property AST nodes. - * @returns {void} - */ - function verifyGroupAlignment(properties) { - var length = properties.length, - widths = properties.map(getKeyWidth), // Width of keys, including quotes - targetWidth = Math.max.apply(null, widths), - i, property, whitespace, width, - align = multiLineOptions.align, - beforeColon = multiLineOptions.beforeColon, - afterColon = multiLineOptions.afterColon, - mode = multiLineOptions.mode; - - // Conditionally include one space before or after colon - targetWidth += (align === "colon" ? beforeColon : afterColon); - - for (i = 0; i < length; i++) { - property = properties[i]; - whitespace = getPropertyWhitespace(property); - if (whitespace) { // Object literal getters/setters lack a colon - width = widths[i]; - - if (align === "value") { - report(property, "key", whitespace.beforeColon, beforeColon, mode); - report(property, "value", whitespace.afterColon, targetWidth - width, mode); - } else { // align = "colon" - report(property, "key", whitespace.beforeColon, targetWidth - width, mode); - report(property, "value", whitespace.afterColon, afterColon, mode); + if (!prev || continuesPropertyGroup(prev, property)) { + currentGroup.push(property); + } else { + groups.push([property]); + } + + return groups; + }, [ + [] + ]); + } + + /** + * Verifies correct vertical alignment of a group of properties. + * @param {ASTNode[]} properties List of Property AST nodes. + * @returns {void} + */ + function verifyGroupAlignment(properties) { + var length = properties.length, + widths = properties.map(getKeyWidth), // Width of keys, including quotes + targetWidth = Math.max.apply(null, widths), + i, property, whitespace, width, + align = multiLineOptions.align, + beforeColon = multiLineOptions.beforeColon, + afterColon = multiLineOptions.afterColon, + mode = multiLineOptions.mode; + + // Conditionally include one space before or after colon + targetWidth += (align === "colon" ? beforeColon : afterColon); + + for (i = 0; i < length; i++) { + property = properties[i]; + whitespace = getPropertyWhitespace(property); + if (whitespace) { // Object literal getters/setters lack a colon + width = widths[i]; + + if (align === "value") { + report(property, "key", whitespace.beforeColon, beforeColon, mode); + report(property, "value", whitespace.afterColon, targetWidth - width, mode); + } else { // align = "colon" + report(property, "key", whitespace.beforeColon, targetWidth - width, mode); + report(property, "value", whitespace.afterColon, afterColon, mode); + } } } } - } - /** - * Verifies vertical alignment, taking into account groups of properties. - * @param {ASTNode} node ObjectExpression node being evaluated. - * @returns {void} - */ - function verifyAlignment(node) { - createGroups(node).forEach(function(group) { - verifyGroupAlignment(group.filter(isKeyValueProperty)); - }); - } + /** + * Verifies vertical alignment, taking into account groups of properties. + * @param {ASTNode} node ObjectExpression node being evaluated. + * @returns {void} + */ + function verifyAlignment(node) { + createGroups(node).forEach(function(group) { + verifyGroupAlignment(group.filter(isKeyValueProperty)); + }); + } - /** - * Verifies spacing of property conforms to specified options. - * @param {ASTNode} node Property node being evaluated. - * @param {Object} lineOptions Configured singleLine or multiLine options - * @returns {void} - */ - function verifySpacing(node, lineOptions) { - var actual = getPropertyWhitespace(node); - - if (actual) { // Object literal getters/setters lack colons - report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode); - report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode); + /** + * Verifies spacing of property conforms to specified options. + * @param {ASTNode} node Property node being evaluated. + * @param {Object} lineOptions Configured singleLine or multiLine options + * @returns {void} + */ + function verifySpacing(node, lineOptions) { + var actual = getPropertyWhitespace(node); + + if (actual) { // Object literal getters/setters lack colons + report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode); + report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode); + } } - } - /** - * Verifies spacing of each property in a list. - * @param {ASTNode[]} properties List of Property AST nodes. - * @returns {void} - */ - function verifyListSpacing(properties) { - var length = properties.length; + /** + * Verifies spacing of each property in a list. + * @param {ASTNode[]} properties List of Property AST nodes. + * @returns {void} + */ + function verifyListSpacing(properties) { + var length = properties.length; - for (var i = 0; i < length; i++) { - verifySpacing(properties[i], singleLineOptions); + for (var i = 0; i < length; i++) { + verifySpacing(properties[i], singleLineOptions); + } } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - if (multiLineOptions.align) { // Verify vertical alignment + if (multiLineOptions.align) { // Verify vertical alignment - return { - "ObjectExpression": function(node) { - if (isSingleLine(node)) { - verifyListSpacing(node.properties.filter(isKeyValueProperty)); - } else { - verifyAlignment(node); + return { + ObjectExpression: function(node) { + if (isSingleLine(node)) { + verifyListSpacing(node.properties.filter(isKeyValueProperty)); + } else { + verifyAlignment(node); + } } - } - }; + }; - } else { // Obey beforeColon and afterColon in each property as configured + } else { // Obey beforeColon and afterColon in each property as configured - return { - "Property": function(node) { - verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions); - } - }; + return { + Property: function(node) { + verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions); + } + }; - } + } + } }; - -module.exports.schema = [{ - "anyOf": [ - { - "type": "object", - "properties": { - "align": { - "enum": ["colon", "value"] - }, - "mode": { - "enum": ["strict", "minimum"] - }, - "beforeColon": { - "type": "boolean" - }, - "afterColon": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "singleLine": { - "type": "object", - "properties": { - "mode": { - "enum": ["strict", "minimum"] - }, - "beforeColon": { - "type": "boolean" - }, - "afterColon": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "multiLine": { - "type": "object", - "properties": { - "align": { - "enum": ["colon", "value"] - }, - "mode": { - "enum": ["strict", "minimum"] - }, - "beforeColon": { - "type": "boolean" - }, - "afterColon": { - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] -}]; diff --git a/tools/eslint/lib/rules/keyword-spacing.js b/tools/eslint/lib/rules/keyword-spacing.js index 2630b46f96..f771029a58 100644 --- a/tools/eslint/lib/rules/keyword-spacing.js +++ b/tools/eslint/lib/rules/keyword-spacing.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to enforce spacing before and after keywords. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -65,470 +63,482 @@ function isCloseParenOfTemplate(token) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var sourceCode = context.getSourceCode(); - - /** - * Reports a given token if there are not space(s) before the token. - * - * @param {Token} token - A token to report. - * @param {RegExp|undefined} pattern - Optional. A pattern of the previous - * token to check. - * @returns {void} - */ - function expectSpaceBefore(token, pattern) { - pattern = pattern || PREV_TOKEN; - - var prevToken = sourceCode.getTokenBefore(token); - - if (prevToken && - (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && - !isOpenParenOfTemplate(prevToken) && - astUtils.isTokenOnSameLine(prevToken, token) && - !sourceCode.isSpaceBetweenTokens(prevToken, token) - ) { - context.report({ - loc: token.loc.start, - message: "Expected space(s) before \"{{value}}\".", - data: token, - fix: function(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } - } +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing before and after keywords", + category: "Stylistic Issues", + recommended: false + }, - /** - * Reports a given token if there are space(s) before the token. - * - * @param {Token} token - A token to report. - * @param {RegExp|undefined} pattern - Optional. A pattern of the previous - * token to check. - * @returns {void} - */ - function unexpectSpaceBefore(token, pattern) { - pattern = pattern || PREV_TOKEN; - - var prevToken = sourceCode.getTokenBefore(token); - - if (prevToken && - (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && - !isOpenParenOfTemplate(prevToken) && - astUtils.isTokenOnSameLine(prevToken, token) && - sourceCode.isSpaceBetweenTokens(prevToken, token) - ) { - context.report({ - loc: token.loc.start, - message: "Unexpected space(s) before \"{{value}}\".", - data: token, - fix: function(fixer) { - return fixer.removeRange([prevToken.range[1], token.range[0]]); - } - }); + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: {type: "boolean"}, + after: {type: "boolean"}, + overrides: { + type: "object", + properties: KEYS.reduce(function(retv, key) { + retv[key] = { + type: "object", + properties: { + before: {type: "boolean"}, + after: {type: "boolean"} + }, + additionalProperties: false + }; + return retv; + }, {}), + additionalProperties: false + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var sourceCode = context.getSourceCode(); + + /** + * Reports a given token if there are not space(s) before the token. + * + * @param {Token} token - A token to report. + * @param {RegExp|undefined} pattern - Optional. A pattern of the previous + * token to check. + * @returns {void} + */ + function expectSpaceBefore(token, pattern) { + pattern = pattern || PREV_TOKEN; + + var prevToken = sourceCode.getTokenBefore(token); + + if (prevToken && + (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && + !isOpenParenOfTemplate(prevToken) && + astUtils.isTokenOnSameLine(prevToken, token) && + !sourceCode.isSpaceBetweenTokens(prevToken, token) + ) { + context.report({ + loc: token.loc.start, + message: "Expected space(s) before \"{{value}}\".", + data: token, + fix: function(fixer) { + return fixer.insertTextBefore(token, " "); + } + }); + } } - } - /** - * Reports a given token if there are not space(s) after the token. - * - * @param {Token} token - A token to report. - * @param {RegExp|undefined} pattern - Optional. A pattern of the next - * token to check. - * @returns {void} - */ - function expectSpaceAfter(token, pattern) { - pattern = pattern || NEXT_TOKEN; - - var nextToken = sourceCode.getTokenAfter(token); - - if (nextToken && - (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && - !isCloseParenOfTemplate(nextToken) && - astUtils.isTokenOnSameLine(token, nextToken) && - !sourceCode.isSpaceBetweenTokens(token, nextToken) - ) { - context.report({ - loc: token.loc.start, - message: "Expected space(s) after \"{{value}}\".", - data: token, - fix: function(fixer) { - return fixer.insertTextAfter(token, " "); - } - }); + /** + * Reports a given token if there are space(s) before the token. + * + * @param {Token} token - A token to report. + * @param {RegExp|undefined} pattern - Optional. A pattern of the previous + * token to check. + * @returns {void} + */ + function unexpectSpaceBefore(token, pattern) { + pattern = pattern || PREV_TOKEN; + + var prevToken = sourceCode.getTokenBefore(token); + + if (prevToken && + (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && + !isOpenParenOfTemplate(prevToken) && + astUtils.isTokenOnSameLine(prevToken, token) && + sourceCode.isSpaceBetweenTokens(prevToken, token) + ) { + context.report({ + loc: token.loc.start, + message: "Unexpected space(s) before \"{{value}}\".", + data: token, + fix: function(fixer) { + return fixer.removeRange([prevToken.range[1], token.range[0]]); + } + }); + } } - } - /** - * Reports a given token if there are space(s) after the token. - * - * @param {Token} token - A token to report. - * @param {RegExp|undefined} pattern - Optional. A pattern of the next - * token to check. - * @returns {void} - */ - function unexpectSpaceAfter(token, pattern) { - pattern = pattern || NEXT_TOKEN; - - var nextToken = sourceCode.getTokenAfter(token); - - if (nextToken && - (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && - !isCloseParenOfTemplate(nextToken) && - astUtils.isTokenOnSameLine(token, nextToken) && - sourceCode.isSpaceBetweenTokens(token, nextToken) - ) { - context.report({ - loc: token.loc.start, - message: "Unexpected space(s) after \"{{value}}\".", - data: token, - fix: function(fixer) { - return fixer.removeRange([token.range[1], nextToken.range[0]]); - } - }); + /** + * Reports a given token if there are not space(s) after the token. + * + * @param {Token} token - A token to report. + * @param {RegExp|undefined} pattern - Optional. A pattern of the next + * token to check. + * @returns {void} + */ + function expectSpaceAfter(token, pattern) { + pattern = pattern || NEXT_TOKEN; + + var nextToken = sourceCode.getTokenAfter(token); + + if (nextToken && + (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && + !isCloseParenOfTemplate(nextToken) && + astUtils.isTokenOnSameLine(token, nextToken) && + !sourceCode.isSpaceBetweenTokens(token, nextToken) + ) { + context.report({ + loc: token.loc.start, + message: "Expected space(s) after \"{{value}}\".", + data: token, + fix: function(fixer) { + return fixer.insertTextAfter(token, " "); + } + }); + } } - } - /** - * Parses the option object and determines check methods for each keyword. - * - * @param {object|undefined} options - The option object to parse. - * @returns {object} - Normalized option object. - * Keys are keywords (there are for every keyword). - * Values are instances of `{"before": function, "after": function}`. - */ - function parseOptions(options) { - var before = !options || options.before !== false; - var after = !options || options.after !== false; - var defaultValue = { - before: before ? expectSpaceBefore : unexpectSpaceBefore, - after: after ? expectSpaceAfter : unexpectSpaceAfter - }; - var overrides = (options && options.overrides) || {}; - var retv = Object.create(null); - - for (var i = 0; i < KEYS.length; ++i) { - var key = KEYS[i]; - var override = overrides[key]; - - if (override) { - var thisBefore = ("before" in override) ? override.before : before; - var thisAfter = ("after" in override) ? override.after : after; - - retv[key] = { - before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore, - after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter - }; - } else { - retv[key] = defaultValue; + /** + * Reports a given token if there are space(s) after the token. + * + * @param {Token} token - A token to report. + * @param {RegExp|undefined} pattern - Optional. A pattern of the next + * token to check. + * @returns {void} + */ + function unexpectSpaceAfter(token, pattern) { + pattern = pattern || NEXT_TOKEN; + + var nextToken = sourceCode.getTokenAfter(token); + + if (nextToken && + (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && + !isCloseParenOfTemplate(nextToken) && + astUtils.isTokenOnSameLine(token, nextToken) && + sourceCode.isSpaceBetweenTokens(token, nextToken) + ) { + context.report({ + loc: token.loc.start, + message: "Unexpected space(s) after \"{{value}}\".", + data: token, + fix: function(fixer) { + return fixer.removeRange([token.range[1], nextToken.range[0]]); + } + }); } } - return retv; - } + /** + * Parses the option object and determines check methods for each keyword. + * + * @param {object|undefined} options - The option object to parse. + * @returns {object} - Normalized option object. + * Keys are keywords (there are for every keyword). + * Values are instances of `{"before": function, "after": function}`. + */ + function parseOptions(options) { + var before = !options || options.before !== false; + var after = !options || options.after !== false; + var defaultValue = { + before: before ? expectSpaceBefore : unexpectSpaceBefore, + after: after ? expectSpaceAfter : unexpectSpaceAfter + }; + var overrides = (options && options.overrides) || {}; + var retv = Object.create(null); + + for (var i = 0; i < KEYS.length; ++i) { + var key = KEYS[i]; + var override = overrides[key]; + + if (override) { + var thisBefore = ("before" in override) ? override.before : before; + var thisAfter = ("after" in override) ? override.after : after; - var checkMethodMap = parseOptions(context.options[0]); - - /** - * Reports a given token if usage of spacing followed by the token is - * invalid. - * - * @param {Token} token - A token to report. - * @param {RegExp|undefined} pattern - Optional. A pattern of the previous - * token to check. - * @returns {void} - */ - function checkSpacingBefore(token, pattern) { - checkMethodMap[token.value].before(token, pattern); - } + retv[key] = { + before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore, + after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter + }; + } else { + retv[key] = defaultValue; + } + } - /** - * Reports a given token if usage of spacing preceded by the token is - * invalid. - * - * @param {Token} token - A token to report. - * @param {RegExp|undefined} pattern - Optional. A pattern of the next - * token to check. - * @returns {void} - */ - function checkSpacingAfter(token, pattern) { - checkMethodMap[token.value].after(token, pattern); - } + return retv; + } - /** - * Reports a given token if usage of spacing around the token is invalid. - * - * @param {Token} token - A token to report. - * @returns {void} - */ - function checkSpacingAround(token) { - checkSpacingBefore(token); - checkSpacingAfter(token); - } + var checkMethodMap = parseOptions(context.options[0]); + + /** + * Reports a given token if usage of spacing followed by the token is + * invalid. + * + * @param {Token} token - A token to report. + * @param {RegExp|undefined} pattern - Optional. A pattern of the previous + * token to check. + * @returns {void} + */ + function checkSpacingBefore(token, pattern) { + checkMethodMap[token.value].before(token, pattern); + } - /** - * Reports the first token of a given node if the first token is a keyword - * and usage of spacing around the token is invalid. - * - * @param {ASTNode|null} node - A node to report. - * @returns {void} - */ - function checkSpacingAroundFirstToken(node) { - var firstToken = node && sourceCode.getFirstToken(node); - - if (firstToken && firstToken.type === "Keyword") { - checkSpacingAround(firstToken); + /** + * Reports a given token if usage of spacing preceded by the token is + * invalid. + * + * @param {Token} token - A token to report. + * @param {RegExp|undefined} pattern - Optional. A pattern of the next + * token to check. + * @returns {void} + */ + function checkSpacingAfter(token, pattern) { + checkMethodMap[token.value].after(token, pattern); } - } - /** - * Reports the first token of a given node if the first token is a keyword - * and usage of spacing followed by the token is invalid. - * - * This is used for unary operators (e.g. `typeof`), `function`, and `super`. - * Other rules are handling usage of spacing preceded by those keywords. - * - * @param {ASTNode|null} node - A node to report. - * @returns {void} - */ - function checkSpacingBeforeFirstToken(node) { - var firstToken = node && sourceCode.getFirstToken(node); - - if (firstToken && firstToken.type === "Keyword") { - checkSpacingBefore(firstToken); + /** + * Reports a given token if usage of spacing around the token is invalid. + * + * @param {Token} token - A token to report. + * @returns {void} + */ + function checkSpacingAround(token) { + checkSpacingBefore(token); + checkSpacingAfter(token); } - } - /** - * Reports the previous token of a given node if the token is a keyword and - * usage of spacing around the token is invalid. - * - * @param {ASTNode|null} node - A node to report. - * @returns {void} - */ - function checkSpacingAroundTokenBefore(node) { - if (node) { - var token = sourceCode.getTokenBefore(node); - - while (token.type !== "Keyword") { - token = sourceCode.getTokenBefore(token); + /** + * Reports the first token of a given node if the first token is a keyword + * and usage of spacing around the token is invalid. + * + * @param {ASTNode|null} node - A node to report. + * @returns {void} + */ + function checkSpacingAroundFirstToken(node) { + var firstToken = node && sourceCode.getFirstToken(node); + + if (firstToken && firstToken.type === "Keyword") { + checkSpacingAround(firstToken); } - - checkSpacingAround(token); } - } - /** - * Reports `class` and `extends` keywords of a given node if usage of - * spacing around those keywords is invalid. - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function checkSpacingForClass(node) { - checkSpacingAroundFirstToken(node); - checkSpacingAroundTokenBefore(node.superClass); - } - - /** - * Reports `if` and `else` keywords of a given node if usage of spacing - * around those keywords is invalid. - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function checkSpacingForIfStatement(node) { - checkSpacingAroundFirstToken(node); - checkSpacingAroundTokenBefore(node.alternate); - } + /** + * Reports the first token of a given node if the first token is a keyword + * and usage of spacing followed by the token is invalid. + * + * This is used for unary operators (e.g. `typeof`), `function`, and `super`. + * Other rules are handling usage of spacing preceded by those keywords. + * + * @param {ASTNode|null} node - A node to report. + * @returns {void} + */ + function checkSpacingBeforeFirstToken(node) { + var firstToken = node && sourceCode.getFirstToken(node); + + if (firstToken && firstToken.type === "Keyword") { + checkSpacingBefore(firstToken); + } + } - /** - * Reports `try`, `catch`, and `finally` keywords of a given node if usage - * of spacing around those keywords is invalid. - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function checkSpacingForTryStatement(node) { - checkSpacingAroundFirstToken(node); - checkSpacingAroundFirstToken(node.handler); - checkSpacingAroundTokenBefore(node.finalizer); - } + /** + * Reports the previous token of a given node if the token is a keyword and + * usage of spacing around the token is invalid. + * + * @param {ASTNode|null} node - A node to report. + * @returns {void} + */ + function checkSpacingAroundTokenBefore(node) { + if (node) { + var token = sourceCode.getTokenBefore(node); + + while (token.type !== "Keyword") { + token = sourceCode.getTokenBefore(token); + } - /** - * Reports `do` and `while` keywords of a given node if usage of spacing - * around those keywords is invalid. - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function checkSpacingForDoWhileStatement(node) { - checkSpacingAroundFirstToken(node); - checkSpacingAroundTokenBefore(node.test); - } + checkSpacingAround(token); + } + } - /** - * Reports `for` and `in` keywords of a given node if usage of spacing - * around those keywords is invalid. - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function checkSpacingForForInStatement(node) { - checkSpacingAroundFirstToken(node); - checkSpacingAroundTokenBefore(node.right); - } + /** + * Reports `class` and `extends` keywords of a given node if usage of + * spacing around those keywords is invalid. + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function checkSpacingForClass(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.superClass); + } - /** - * Reports `for` and `of` keywords of a given node if usage of spacing - * around those keywords is invalid. - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function checkSpacingForForOfStatement(node) { - checkSpacingAroundFirstToken(node); - - // `of` is not a keyword token. - var token = sourceCode.getTokenBefore(node.right); - - while (token.value !== "of") { - token = sourceCode.getTokenBefore(token); + /** + * Reports `if` and `else` keywords of a given node if usage of spacing + * around those keywords is invalid. + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function checkSpacingForIfStatement(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.alternate); } - checkSpacingAround(token); - } - /** - * Reports `import`, `export`, `as`, and `from` keywords of a given node if - * usage of spacing around those keywords is invalid. - * - * This rule handles the `*` token in module declarations. - * - * import*as A from "./a"; /*error Expected space(s) after "import". - * error Expected space(s) before "as". - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function checkSpacingForModuleDeclaration(node) { - var firstToken = sourceCode.getFirstToken(node); - - checkSpacingBefore(firstToken, PREV_TOKEN_M); - checkSpacingAfter(firstToken, NEXT_TOKEN_M); - - if (node.source) { - var fromToken = sourceCode.getTokenBefore(node.source); - - checkSpacingBefore(fromToken, PREV_TOKEN_M); - checkSpacingAfter(fromToken, NEXT_TOKEN_M); + /** + * Reports `try`, `catch`, and `finally` keywords of a given node if usage + * of spacing around those keywords is invalid. + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function checkSpacingForTryStatement(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundFirstToken(node.handler); + checkSpacingAroundTokenBefore(node.finalizer); } - } - /** - * Reports `as` keyword of a given node if usage of spacing around this - * keyword is invalid. - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function checkSpacingForImportNamespaceSpecifier(node) { - var asToken = sourceCode.getFirstToken(node, 1); - - checkSpacingBefore(asToken, PREV_TOKEN_M); - } + /** + * Reports `do` and `while` keywords of a given node if usage of spacing + * around those keywords is invalid. + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function checkSpacingForDoWhileStatement(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.test); + } - /** - * Reports `static`, `get`, and `set` keywords of a given node if usage of - * spacing around those keywords is invalid. - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function checkSpacingForProperty(node) { - if (node.static) { + /** + * Reports `for` and `in` keywords of a given node if usage of spacing + * around those keywords is invalid. + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function checkSpacingForForInStatement(node) { checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.right); } - if (node.kind === "get" || node.kind === "set") { - var token = sourceCode.getFirstToken( - node, - node.static ? 1 : 0 - ); + /** + * Reports `for` and `of` keywords of a given node if usage of spacing + * around those keywords is invalid. + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function checkSpacingForForOfStatement(node) { + checkSpacingAroundFirstToken(node); + + // `of` is not a keyword token. + var token = sourceCode.getTokenBefore(node.right); + + while (token.value !== "of") { + token = sourceCode.getTokenBefore(token); + } checkSpacingAround(token); } - } - return { - - // Statements - DebuggerStatement: checkSpacingAroundFirstToken, - WithStatement: checkSpacingAroundFirstToken, - - // Statements - Control flow - BreakStatement: checkSpacingAroundFirstToken, - ContinueStatement: checkSpacingAroundFirstToken, - ReturnStatement: checkSpacingAroundFirstToken, - ThrowStatement: checkSpacingAroundFirstToken, - TryStatement: checkSpacingForTryStatement, - - // Statements - Choice - IfStatement: checkSpacingForIfStatement, - SwitchStatement: checkSpacingAroundFirstToken, - SwitchCase: checkSpacingAroundFirstToken, - - // Statements - Loops - DoWhileStatement: checkSpacingForDoWhileStatement, - ForInStatement: checkSpacingForForInStatement, - ForOfStatement: checkSpacingForForOfStatement, - ForStatement: checkSpacingAroundFirstToken, - WhileStatement: checkSpacingAroundFirstToken, - - // Statements - Declarations - ClassDeclaration: checkSpacingForClass, - ExportNamedDeclaration: checkSpacingForModuleDeclaration, - ExportDefaultDeclaration: checkSpacingAroundFirstToken, - ExportAllDeclaration: checkSpacingForModuleDeclaration, - FunctionDeclaration: checkSpacingBeforeFirstToken, - ImportDeclaration: checkSpacingForModuleDeclaration, - VariableDeclaration: checkSpacingAroundFirstToken, - - // Expressions - ClassExpression: checkSpacingForClass, - FunctionExpression: checkSpacingBeforeFirstToken, - NewExpression: checkSpacingBeforeFirstToken, - Super: checkSpacingBeforeFirstToken, - ThisExpression: checkSpacingBeforeFirstToken, - UnaryExpression: checkSpacingBeforeFirstToken, - YieldExpression: checkSpacingBeforeFirstToken, - - // Others - ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier, - MethodDefinition: checkSpacingForProperty, - Property: checkSpacingForProperty - }; -}; + /** + * Reports `import`, `export`, `as`, and `from` keywords of a given node if + * usage of spacing around those keywords is invalid. + * + * This rule handles the `*` token in module declarations. + * + * import*as A from "./a"; /*error Expected space(s) after "import". + * error Expected space(s) before "as". + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function checkSpacingForModuleDeclaration(node) { + var firstToken = sourceCode.getFirstToken(node); + + checkSpacingBefore(firstToken, PREV_TOKEN_M); + checkSpacingAfter(firstToken, NEXT_TOKEN_M); + + if (node.source) { + var fromToken = sourceCode.getTokenBefore(node.source); + + checkSpacingBefore(fromToken, PREV_TOKEN_M); + checkSpacingAfter(fromToken, NEXT_TOKEN_M); + } + } -module.exports.schema = [ - { - "type": "object", - "properties": { - "before": {"type": "boolean"}, - "after": {"type": "boolean"}, - "overrides": { - "type": "object", - "properties": KEYS.reduce(function(retv, key) { - retv[key] = { - "type": "object", - "properties": { - "before": {"type": "boolean"}, - "after": {"type": "boolean"} - }, - "additionalProperties": false - }; - return retv; - }, {}), - "additionalProperties": false + /** + * Reports `as` keyword of a given node if usage of spacing around this + * keyword is invalid. + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function checkSpacingForImportNamespaceSpecifier(node) { + var asToken = sourceCode.getFirstToken(node, 1); + + checkSpacingBefore(asToken, PREV_TOKEN_M); + } + + /** + * Reports `static`, `get`, and `set` keywords of a given node if usage of + * spacing around those keywords is invalid. + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function checkSpacingForProperty(node) { + if (node.static) { + checkSpacingAroundFirstToken(node); } - }, - "additionalProperties": false + if (node.kind === "get" || node.kind === "set") { + var token = sourceCode.getFirstToken( + node, + node.static ? 1 : 0 + ); + + checkSpacingAround(token); + } + } + + return { + + // Statements + DebuggerStatement: checkSpacingAroundFirstToken, + WithStatement: checkSpacingAroundFirstToken, + + // Statements - Control flow + BreakStatement: checkSpacingAroundFirstToken, + ContinueStatement: checkSpacingAroundFirstToken, + ReturnStatement: checkSpacingAroundFirstToken, + ThrowStatement: checkSpacingAroundFirstToken, + TryStatement: checkSpacingForTryStatement, + + // Statements - Choice + IfStatement: checkSpacingForIfStatement, + SwitchStatement: checkSpacingAroundFirstToken, + SwitchCase: checkSpacingAroundFirstToken, + + // Statements - Loops + DoWhileStatement: checkSpacingForDoWhileStatement, + ForInStatement: checkSpacingForForInStatement, + ForOfStatement: checkSpacingForForOfStatement, + ForStatement: checkSpacingAroundFirstToken, + WhileStatement: checkSpacingAroundFirstToken, + + // Statements - Declarations + ClassDeclaration: checkSpacingForClass, + ExportNamedDeclaration: checkSpacingForModuleDeclaration, + ExportDefaultDeclaration: checkSpacingAroundFirstToken, + ExportAllDeclaration: checkSpacingForModuleDeclaration, + FunctionDeclaration: checkSpacingBeforeFirstToken, + ImportDeclaration: checkSpacingForModuleDeclaration, + VariableDeclaration: checkSpacingAroundFirstToken, + + // Expressions + ClassExpression: checkSpacingForClass, + FunctionExpression: checkSpacingBeforeFirstToken, + NewExpression: checkSpacingBeforeFirstToken, + Super: checkSpacingBeforeFirstToken, + ThisExpression: checkSpacingBeforeFirstToken, + UnaryExpression: checkSpacingBeforeFirstToken, + YieldExpression: checkSpacingBeforeFirstToken, + + // Others + ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier, + MethodDefinition: checkSpacingForProperty, + Property: checkSpacingForProperty + }; } -]; +}; diff --git a/tools/eslint/lib/rules/linebreak-style.js b/tools/eslint/lib/rules/linebreak-style.js index 7643ea7710..d99d7e5857 100644 --- a/tools/eslint/lib/rules/linebreak-style.js +++ b/tools/eslint/lib/rules/linebreak-style.js @@ -1,9 +1,6 @@ /** * @fileoverview Rule to enforce a single linebreak style. * @author Erik Mueller - * @copyright 2015 Varun Verma. All rights reserverd. - * @copyright 2015 James Whitney. All rights reserved. - * @copyright 2015 Erik Mueller. All rights reserved. */ "use strict"; @@ -12,69 +9,81 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce consistent linebreak style", + category: "Stylistic Issues", + recommended: false + }, - var EXPECTED_LF_MSG = "Expected linebreaks to be 'LF' but found 'CRLF'.", - EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'."; + fixable: "whitespace", - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + schema: [ + { + enum: ["unix", "windows"] + } + ] + }, - /** - * Builds a fix function that replaces text at the specified range in the source text. - * @param {int[]} range The range to replace - * @param {string} text The text to insert. - * @returns {function} Fixer function - * @private - */ - function createFix(range, text) { - return function(fixer) { - return fixer.replaceTextRange(range, text); - }; - } + create: function(context) { - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + var EXPECTED_LF_MSG = "Expected linebreaks to be 'LF' but found 'CRLF'.", + EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'."; - return { - "Program": function checkForlinebreakStyle(node) { - var linebreakStyle = context.options[0] || "unix", - expectedLF = linebreakStyle === "unix", - expectedLFChars = expectedLF ? "\n" : "\r\n", - source = context.getSource(), - pattern = /\r\n|\r|\n|\u2028|\u2029/g, - match, - index, - range; + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - var i = 0; + /** + * Builds a fix function that replaces text at the specified range in the source text. + * @param {int[]} range The range to replace + * @param {string} text The text to insert. + * @returns {function} Fixer function + * @private + */ + function createFix(range, text) { + return function(fixer) { + return fixer.replaceTextRange(range, text); + }; + } - while ((match = pattern.exec(source)) !== null) { - i++; - if (match[0] === expectedLFChars) { - continue; - } + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - index = match.index; - range = [index, index + match[0].length]; - context.report({ - node: node, - loc: { - line: i, - column: context.getSourceLines()[i - 1].length - }, - message: expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG, - fix: createFix(range, expectedLFChars) - }); - } - } - }; -}; + return { + Program: function checkForlinebreakStyle(node) { + var linebreakStyle = context.options[0] || "unix", + expectedLF = linebreakStyle === "unix", + expectedLFChars = expectedLF ? "\n" : "\r\n", + source = context.getSource(), + pattern = /\r\n|\r|\n|\u2028|\u2029/g, + match, + index, + range; + + var i = 0; -module.exports.schema = [ - { - "enum": ["unix", "windows"] + while ((match = pattern.exec(source)) !== null) { + i++; + if (match[0] === expectedLFChars) { + continue; + } + + index = match.index; + range = [index, index + match[0].length]; + context.report({ + node: node, + loc: { + line: i, + column: context.getSourceLines()[i - 1].length + }, + message: expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG, + fix: createFix(range, expectedLFChars) + }); + } + } + }; } -]; +}; diff --git a/tools/eslint/lib/rules/lines-around-comment.js b/tools/eslint/lib/rules/lines-around-comment.js index 16d220c954..92fc3252ae 100644 --- a/tools/eslint/lib/rules/lines-around-comment.js +++ b/tools/eslint/lib/rules/lines-around-comment.js @@ -1,9 +1,6 @@ /** * @fileoverview Enforces empty lines around comments. * @author Jamund Ferguson - * @copyright 2015 Mathieu M-Gosselin. All rights reserved. - * @copyright 2015 Jamund Ferguson. All rights reserved. - * @copyright 2015 Gyandeep Singh. All rights reserved. */ "use strict"; @@ -68,272 +65,282 @@ function contains(val, array) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require empty lines around comments", + category: "Stylistic Issues", + recommended: false + }, - var options = context.options[0] ? lodash.assign({}, context.options[0]) : {}; + schema: [ + { + type: "object", + properties: { + beforeBlockComment: { + type: "boolean" + }, + afterBlockComment: { + type: "boolean" + }, + beforeLineComment: { + type: "boolean" + }, + afterLineComment: { + type: "boolean" + }, + allowBlockStart: { + type: "boolean" + }, + allowBlockEnd: { + type: "boolean" + }, + allowObjectStart: { + type: "boolean" + }, + allowObjectEnd: { + type: "boolean" + }, + allowArrayStart: { + type: "boolean" + }, + allowArrayEnd: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - options.beforeLineComment = options.beforeLineComment || false; - options.afterLineComment = options.afterLineComment || false; - options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true; - options.afterBlockComment = options.afterBlockComment || false; - options.allowBlockStart = options.allowBlockStart || false; - options.allowBlockEnd = options.allowBlockEnd || false; + create: function(context) { - var sourceCode = context.getSourceCode(); + var options = context.options[0] ? lodash.assign({}, context.options[0]) : {}; - /** - * Returns whether or not comments are on lines starting with or ending with code - * @param {ASTNode} node The comment node to check. - * @returns {boolean} True if the comment is not alone. - */ - function codeAroundComment(node) { - var token; + options.beforeLineComment = options.beforeLineComment || false; + options.afterLineComment = options.afterLineComment || false; + options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true; + options.afterBlockComment = options.afterBlockComment || false; + options.allowBlockStart = options.allowBlockStart || false; + options.allowBlockEnd = options.allowBlockEnd || false; - token = node; - do { - token = sourceCode.getTokenOrCommentBefore(token); - } while (token && (token.type === "Block" || token.type === "Line")); + var sourceCode = context.getSourceCode(); - if (token && token.loc.end.line === node.loc.start.line) { - return true; - } + /** + * Returns whether or not comments are on lines starting with or ending with code + * @param {ASTNode} node The comment node to check. + * @returns {boolean} True if the comment is not alone. + */ + function codeAroundComment(node) { + var token; - token = node; - do { - token = sourceCode.getTokenOrCommentAfter(token); - } while (token && (token.type === "Block" || token.type === "Line")); + token = node; + do { + token = sourceCode.getTokenOrCommentBefore(token); + } while (token && (token.type === "Block" || token.type === "Line")); - if (token && token.loc.start.line === node.loc.end.line) { - return true; - } + if (token && token.loc.end.line === node.loc.start.line) { + return true; + } - return false; - } + token = node; + do { + token = sourceCode.getTokenOrCommentAfter(token); + } while (token && (token.type === "Block" || token.type === "Line")); - /** - * Returns whether or not comments are inside a node type or not. - * @param {ASTNode} node The Comment node. - * @param {ASTNode} parent The Comment parent node. - * @param {string} nodeType The parent type to check against. - * @returns {boolean} True if the comment is inside nodeType. - */ - function isCommentInsideNodeType(node, parent, nodeType) { - return parent.type === nodeType || - (parent.body && parent.body.type === nodeType) || - (parent.consequent && parent.consequent.type === nodeType); - } + if (token && token.loc.start.line === node.loc.end.line) { + return true; + } - /** - * Returns whether or not comments are at the parent start or not. - * @param {ASTNode} node The Comment node. - * @param {string} nodeType The parent type to check against. - * @returns {boolean} True if the comment is at parent start. - */ - function isCommentAtParentStart(node, nodeType) { - var ancestors = context.getAncestors(); - var parent; - - if (ancestors.length) { - parent = ancestors.pop(); + return false; } - return parent && isCommentInsideNodeType(node, parent, nodeType) && - node.loc.start.line - parent.loc.start.line === 1; - } - - /** - * Returns whether or not comments are at the parent end or not. - * @param {ASTNode} node The Comment node. - * @param {string} nodeType The parent type to check against. - * @returns {boolean} True if the comment is at parent end. - */ - function isCommentAtParentEnd(node, nodeType) { - var ancestors = context.getAncestors(); - var parent; - - if (ancestors.length) { - parent = ancestors.pop(); + /** + * Returns whether or not comments are inside a node type or not. + * @param {ASTNode} node The Comment node. + * @param {ASTNode} parent The Comment parent node. + * @param {string} nodeType The parent type to check against. + * @returns {boolean} True if the comment is inside nodeType. + */ + function isCommentInsideNodeType(node, parent, nodeType) { + return parent.type === nodeType || + (parent.body && parent.body.type === nodeType) || + (parent.consequent && parent.consequent.type === nodeType); } - return parent && isCommentInsideNodeType(node, parent, nodeType) && - parent.loc.end.line - node.loc.end.line === 1; - } - - /** - * Returns whether or not comments are at the block start or not. - * @param {ASTNode} node The Comment node. - * @returns {boolean} True if the comment is at block start. - */ - function isCommentAtBlockStart(node) { - return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement") || isCommentAtParentStart(node, "SwitchCase"); - } - - /** - * Returns whether or not comments are at the block end or not. - * @param {ASTNode} node The Comment node. - * @returns {boolean} True if the comment is at block end. - */ - function isCommentAtBlockEnd(node) { - return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement") || isCommentAtParentEnd(node, "SwitchCase") || isCommentAtParentEnd(node, "SwitchStatement"); - } - - /** - * Returns whether or not comments are at the object start or not. - * @param {ASTNode} node The Comment node. - * @returns {boolean} True if the comment is at object start. - */ - function isCommentAtObjectStart(node) { - return isCommentAtParentStart(node, "ObjectExpression") || isCommentAtParentStart(node, "ObjectPattern"); - } + /** + * Returns whether or not comments are at the parent start or not. + * @param {ASTNode} node The Comment node. + * @param {string} nodeType The parent type to check against. + * @returns {boolean} True if the comment is at parent start. + */ + function isCommentAtParentStart(node, nodeType) { + var ancestors = context.getAncestors(); + var parent; + + if (ancestors.length) { + parent = ancestors.pop(); + } - /** - * Returns whether or not comments are at the object end or not. - * @param {ASTNode} node The Comment node. - * @returns {boolean} True if the comment is at object end. - */ - function isCommentAtObjectEnd(node) { - return isCommentAtParentEnd(node, "ObjectExpression") || isCommentAtParentEnd(node, "ObjectPattern"); - } + return parent && isCommentInsideNodeType(node, parent, nodeType) && + node.loc.start.line - parent.loc.start.line === 1; + } - /** - * Returns whether or not comments are at the array start or not. - * @param {ASTNode} node The Comment node. - * @returns {boolean} True if the comment is at array start. - */ - function isCommentAtArrayStart(node) { - return isCommentAtParentStart(node, "ArrayExpression") || isCommentAtParentStart(node, "ArrayPattern"); - } + /** + * Returns whether or not comments are at the parent end or not. + * @param {ASTNode} node The Comment node. + * @param {string} nodeType The parent type to check against. + * @returns {boolean} True if the comment is at parent end. + */ + function isCommentAtParentEnd(node, nodeType) { + var ancestors = context.getAncestors(); + var parent; + + if (ancestors.length) { + parent = ancestors.pop(); + } - /** - * Returns whether or not comments are at the array end or not. - * @param {ASTNode} node The Comment node. - * @returns {boolean} True if the comment is at array end. - */ - function isCommentAtArrayEnd(node) { - return isCommentAtParentEnd(node, "ArrayExpression") || isCommentAtParentEnd(node, "ArrayPattern"); - } + return parent && isCommentInsideNodeType(node, parent, nodeType) && + parent.loc.end.line - node.loc.end.line === 1; + } - /** - * Checks if a comment node has lines around it (ignores inline comments) - * @param {ASTNode} node The Comment node. - * @param {Object} opts Options to determine the newline. - * @param {Boolean} opts.after Should have a newline after this line. - * @param {Boolean} opts.before Should have a newline before this line. - * @returns {void} - */ - function checkForEmptyLine(node, opts) { - - var lines = context.getSourceLines(), - numLines = lines.length + 1, - comments = context.getAllComments(), - commentLines = getCommentLineNums(comments), - emptyLines = getEmptyLineNums(lines), - commentAndEmptyLines = commentLines.concat(emptyLines); - - var after = opts.after, - before = opts.before; - - var prevLineNum = node.loc.start.line - 1, - nextLineNum = node.loc.end.line + 1, - commentIsNotAlone = codeAroundComment(node); - - var blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(node), - blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(node), - objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(node), - objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(node), - arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(node), - arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(node); - - var exceptionStartAllowed = blockStartAllowed || objectStartAllowed || arrayStartAllowed; - var exceptionEndAllowed = blockEndAllowed || objectEndAllowed || arrayEndAllowed; - - // ignore top of the file and bottom of the file - if (prevLineNum < 1) { - before = false; + /** + * Returns whether or not comments are at the block start or not. + * @param {ASTNode} node The Comment node. + * @returns {boolean} True if the comment is at block start. + */ + function isCommentAtBlockStart(node) { + return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement") || isCommentAtParentStart(node, "SwitchCase"); } - if (nextLineNum >= numLines) { - after = false; + + /** + * Returns whether or not comments are at the block end or not. + * @param {ASTNode} node The Comment node. + * @returns {boolean} True if the comment is at block end. + */ + function isCommentAtBlockEnd(node) { + return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement") || isCommentAtParentEnd(node, "SwitchCase") || isCommentAtParentEnd(node, "SwitchStatement"); } - // we ignore all inline comments - if (commentIsNotAlone) { - return; + /** + * Returns whether or not comments are at the object start or not. + * @param {ASTNode} node The Comment node. + * @returns {boolean} True if the comment is at object start. + */ + function isCommentAtObjectStart(node) { + return isCommentAtParentStart(node, "ObjectExpression") || isCommentAtParentStart(node, "ObjectPattern"); } - // check for newline before - if (!exceptionStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) { - context.report(node, "Expected line before comment."); + /** + * Returns whether or not comments are at the object end or not. + * @param {ASTNode} node The Comment node. + * @returns {boolean} True if the comment is at object end. + */ + function isCommentAtObjectEnd(node) { + return isCommentAtParentEnd(node, "ObjectExpression") || isCommentAtParentEnd(node, "ObjectPattern"); } - // check for newline after - if (!exceptionEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) { - context.report(node, "Expected line after comment."); + /** + * Returns whether or not comments are at the array start or not. + * @param {ASTNode} node The Comment node. + * @returns {boolean} True if the comment is at array start. + */ + function isCommentAtArrayStart(node) { + return isCommentAtParentStart(node, "ArrayExpression") || isCommentAtParentStart(node, "ArrayPattern"); } - } + /** + * Returns whether or not comments are at the array end or not. + * @param {ASTNode} node The Comment node. + * @returns {boolean} True if the comment is at array end. + */ + function isCommentAtArrayEnd(node) { + return isCommentAtParentEnd(node, "ArrayExpression") || isCommentAtParentEnd(node, "ArrayPattern"); + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + /** + * Checks if a comment node has lines around it (ignores inline comments) + * @param {ASTNode} node The Comment node. + * @param {Object} opts Options to determine the newline. + * @param {Boolean} opts.after Should have a newline after this line. + * @param {Boolean} opts.before Should have a newline before this line. + * @returns {void} + */ + function checkForEmptyLine(node, opts) { + + var lines = context.getSourceLines(), + numLines = lines.length + 1, + comments = context.getAllComments(), + commentLines = getCommentLineNums(comments), + emptyLines = getEmptyLineNums(lines), + commentAndEmptyLines = commentLines.concat(emptyLines); + + var after = opts.after, + before = opts.before; + + var prevLineNum = node.loc.start.line - 1, + nextLineNum = node.loc.end.line + 1, + commentIsNotAlone = codeAroundComment(node); + + var blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(node), + blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(node), + objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(node), + objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(node), + arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(node), + arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(node); + + var exceptionStartAllowed = blockStartAllowed || objectStartAllowed || arrayStartAllowed; + var exceptionEndAllowed = blockEndAllowed || objectEndAllowed || arrayEndAllowed; + + // ignore top of the file and bottom of the file + if (prevLineNum < 1) { + before = false; + } + if (nextLineNum >= numLines) { + after = false; + } - return { + // we ignore all inline comments + if (commentIsNotAlone) { + return; + } - "LineComment": function(node) { - if (options.beforeLineComment || options.afterLineComment) { - checkForEmptyLine(node, { - after: options.afterLineComment, - before: options.beforeLineComment - }); + // check for newline before + if (!exceptionStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) { + context.report(node, "Expected line before comment."); } - }, - "BlockComment": function(node) { - if (options.beforeBlockComment || options.afterBlockComment) { - checkForEmptyLine(node, { - after: options.afterBlockComment, - before: options.beforeBlockComment - }); + // check for newline after + if (!exceptionEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) { + context.report(node, "Expected line after comment."); } + } - }; -}; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- -module.exports.schema = [ - { - "type": "object", - "properties": { - "beforeBlockComment": { - "type": "boolean" - }, - "afterBlockComment": { - "type": "boolean" - }, - "beforeLineComment": { - "type": "boolean" - }, - "afterLineComment": { - "type": "boolean" - }, - "allowBlockStart": { - "type": "boolean" - }, - "allowBlockEnd": { - "type": "boolean" - }, - "allowObjectStart": { - "type": "boolean" - }, - "allowObjectEnd": { - "type": "boolean" - }, - "allowArrayStart": { - "type": "boolean" + return { + + LineComment: function(node) { + if (options.beforeLineComment || options.afterLineComment) { + checkForEmptyLine(node, { + after: options.afterLineComment, + before: options.beforeLineComment + }); + } }, - "allowArrayEnd": { - "type": "boolean" + + BlockComment: function(node) { + if (options.beforeBlockComment || options.afterBlockComment) { + checkForEmptyLine(node, { + after: options.afterBlockComment, + before: options.beforeBlockComment + }); + } } - }, - "additionalProperties": false + + }; } -]; +}; diff --git a/tools/eslint/lib/rules/max-depth.js b/tools/eslint/lib/rules/max-depth.js index 22fa2f5f0b..317f06c68f 100644 --- a/tools/eslint/lib/rules/max-depth.js +++ b/tools/eslint/lib/rules/max-depth.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to set the maximum depth block can be nested in a function. * @author Ian Christian Myers - * @copyright 2013 Ian Christian Myers. All rights reserved. */ "use strict"; @@ -10,131 +9,141 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce a maximum depth that blocks can be nested", + category: "Stylistic Issues", + recommended: false + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0 + }, + max: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + ] + }, - var functionStack = [], - option = context.options[0], - maxDepth = 4; + create: function(context) { - if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { - maxDepth = option.maximum; - } - if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { - maxDepth = option.max; - } - if (typeof option === "number") { - maxDepth = option; - } + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * When parsing a new function, store it in our function stack - * @returns {void} - * @private - */ - function startFunction() { - functionStack.push(0); - } + var functionStack = [], + option = context.options[0], + maxDepth = 4; - /** - * When parsing is done then pop out the reference - * @returns {void} - * @private - */ - function endFunction() { - functionStack.pop(); - } + if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { + maxDepth = option.maximum; + } + if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { + maxDepth = option.max; + } + if (typeof option === "number") { + maxDepth = option; + } - /** - * Save the block and Evaluate the node - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function pushBlock(node) { - var len = ++functionStack[functionStack.length - 1]; - - if (len > maxDepth) { - context.report(node, "Blocks are nested too deeply ({{depth}}).", - { depth: len }); + /** + * When parsing a new function, store it in our function stack + * @returns {void} + * @private + */ + function startFunction() { + functionStack.push(0); } - } - /** - * Pop the saved block - * @returns {void} - * @private - */ - function popBlock() { - functionStack[functionStack.length - 1]--; - } + /** + * When parsing is done then pop out the reference + * @returns {void} + * @private + */ + function endFunction() { + functionStack.pop(); + } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + /** + * Save the block and Evaluate the node + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function pushBlock(node) { + var len = ++functionStack[functionStack.length - 1]; + + if (len > maxDepth) { + context.report(node, "Blocks are nested too deeply ({{depth}}).", + { depth: len }); + } + } - return { - "Program": startFunction, - "FunctionDeclaration": startFunction, - "FunctionExpression": startFunction, - "ArrowFunctionExpression": startFunction, + /** + * Pop the saved block + * @returns {void} + * @private + */ + function popBlock() { + functionStack[functionStack.length - 1]--; + } - "IfStatement": function(node) { - if (node.parent.type !== "IfStatement") { - pushBlock(node); - } - }, - "SwitchStatement": pushBlock, - "TryStatement": pushBlock, - "DoWhileStatement": pushBlock, - "WhileStatement": pushBlock, - "WithStatement": pushBlock, - "ForStatement": pushBlock, - "ForInStatement": pushBlock, - "ForOfStatement": pushBlock, - - "IfStatement:exit": popBlock, - "SwitchStatement:exit": popBlock, - "TryStatement:exit": popBlock, - "DoWhileStatement:exit": popBlock, - "WhileStatement:exit": popBlock, - "WithStatement:exit": popBlock, - "ForStatement:exit": popBlock, - "ForInStatement:exit": popBlock, - "ForOfStatement:exit": popBlock, - - "FunctionDeclaration:exit": endFunction, - "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction, - "Program:exit": endFunction - }; + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- -}; + return { + Program: startFunction, + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, -module.exports.schema = [ - { - "oneOf": [ - { - "type": "integer", - "minimum": 0 + IfStatement: function(node) { + if (node.parent.type !== "IfStatement") { + pushBlock(node); + } }, - { - "type": "object", - "properties": { - "maximum": { - "type": "integer", - "minimum": 0 - }, - "max": { - "type": "integer", - "minimum": 0 - } - }, - "additionalProperties": false - } - ] + SwitchStatement: pushBlock, + TryStatement: pushBlock, + DoWhileStatement: pushBlock, + WhileStatement: pushBlock, + WithStatement: pushBlock, + ForStatement: pushBlock, + ForInStatement: pushBlock, + ForOfStatement: pushBlock, + + "IfStatement:exit": popBlock, + "SwitchStatement:exit": popBlock, + "TryStatement:exit": popBlock, + "DoWhileStatement:exit": popBlock, + "WhileStatement:exit": popBlock, + "WithStatement:exit": popBlock, + "ForStatement:exit": popBlock, + "ForInStatement:exit": popBlock, + "ForOfStatement:exit": popBlock, + + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction, + "Program:exit": endFunction + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/max-len.js b/tools/eslint/lib/rules/max-len.js index 610cdf2bac..1ba539a119 100644 --- a/tools/eslint/lib/rules/max-len.js +++ b/tools/eslint/lib/rules/max-len.js @@ -1,245 +1,258 @@ /** * @fileoverview Rule to check for max length on a line. * @author Matt DuVall - * @copyright 2013 Matt DuVall. All rights reserved. */ "use strict"; //------------------------------------------------------------------------------ -// Rule Definition +// Constants //------------------------------------------------------------------------------ -module.exports = function(context) { - - /* - * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however: - * - They're matching an entire string that we know is a URI - * - We're matching part of a string where we think there *might* be a URL - * - We're only concerned about URLs, as picking out any URI would cause - * too many false positives - * - We don't care about matching the entire URL, any small segment is fine - */ - var URL_REGEXP = /[^:/?#]:\/\/[^?#]/; - - /** - * Computes the length of a line that may contain tabs. The width of each - * tab will be the number of spaces to the next tab stop. - * @param {string} line The line. - * @param {int} tabWidth The width of each tab stop in spaces. - * @returns {int} The computed line length. - * @private - */ - function computeLineLength(line, tabWidth) { - var extraCharacterCount = 0; - - line.replace(/\t/g, function(match, offset) { - var totalOffset = offset + extraCharacterCount, - previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0, - spaceCount = tabWidth - previousTabStopOffset; - - extraCharacterCount += spaceCount - 1; // -1 for the replaced tab - }); - return line.length + extraCharacterCount; - } - - // The options object must be the last option specified… - var lastOption = context.options[context.options.length - 1]; - var options = typeof lastOption === "object" ? Object.create(lastOption) : {}; - - // …but max code length… - if (typeof context.options[0] === "number") { - options.code = context.options[0]; - } +var OPTIONS_SCHEMA = { + type: "object", + properties: { + code: { + type: "integer", + minimum: 0 + }, + comments: { + type: "integer", + minimum: 0 + }, + tabWidth: { + type: "integer", + minimum: 0 + }, + ignorePattern: { + type: "string" + }, + ignoreComments: { + type: "boolean" + }, + ignoreUrls: { + type: "boolean" + }, + ignoreTrailingComments: { + type: "boolean" + } + }, + additionalProperties: false +}; - // …and tabWidth can be optionally specified directly as integers. - if (typeof context.options[1] === "number") { - options.tabWidth = context.options[1]; - } +var OPTIONS_OR_INTEGER_SCHEMA = { + anyOf: [ + OPTIONS_SCHEMA, + { + type: "integer", + minimum: 0 + } + ] +}; - var maxLength = options.code || 80, - tabWidth = options.tabWidth || 4, - ignorePattern = options.ignorePattern || null, - ignoreComments = options.ignoreComments || false, - ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false, - ignoreUrls = options.ignoreUrls || false, - maxCommentLength = options.comments; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ - if (ignorePattern) { - ignorePattern = new RegExp(ignorePattern); - } +module.exports = { + meta: { + docs: { + description: "enforce a maximum line length", + category: "Stylistic Issues", + recommended: false + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Tells if a given comment is trailing: it starts on the current line and - * extends to or past the end of the current line. - * @param {string} line The source line we want to check for a trailing comment on - * @param {number} lineNumber The one-indexed line number for line - * @param {ASTNode} comment The comment to inspect - * @returns {boolean} If the comment is trailing on the given line - */ - function isTrailingComment(line, lineNumber, comment) { - return comment && - (comment.loc.start.line === lineNumber && lineNumber <= comment.loc.end.line) && - (comment.loc.end.line > lineNumber || comment.loc.end.column === line.length); - } + schema: [ + OPTIONS_OR_INTEGER_SCHEMA, + OPTIONS_OR_INTEGER_SCHEMA, + OPTIONS_SCHEMA + ] + }, - /** - * Tells if a comment encompasses the entire line. - * @param {string} line The source line with a trailing comment - * @param {number} lineNumber The one-indexed line number this is on - * @param {ASTNode} comment The comment to remove - * @returns {boolean} If the comment covers the entire line - */ - function isFullLineComment(line, lineNumber, comment) { - var start = comment.loc.start, - end = comment.loc.end; - - return comment && - (start.line < lineNumber || (start.line === lineNumber && start.column === 0)) && - (end.line > lineNumber || end.column === line.length); - } + create: function(context) { + + /* + * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however: + * - They're matching an entire string that we know is a URI + * - We're matching part of a string where we think there *might* be a URL + * - We're only concerned about URLs, as picking out any URI would cause + * too many false positives + * - We don't care about matching the entire URL, any small segment is fine + */ + var URL_REGEXP = /[^:/?#]:\/\/[^?#]/; + + /** + * Computes the length of a line that may contain tabs. The width of each + * tab will be the number of spaces to the next tab stop. + * @param {string} line The line. + * @param {int} tabWidth The width of each tab stop in spaces. + * @returns {int} The computed line length. + * @private + */ + function computeLineLength(line, tabWidth) { + var extraCharacterCount = 0; + + line.replace(/\t/g, function(match, offset) { + var totalOffset = offset + extraCharacterCount, + previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0, + spaceCount = tabWidth - previousTabStopOffset; + + extraCharacterCount += spaceCount - 1; // -1 for the replaced tab + }); + return line.length + extraCharacterCount; + } - /** - * Gets the line after the comment and any remaining trailing whitespace is - * stripped. - * @param {string} line The source line with a trailing comment - * @param {number} lineNumber The one-indexed line number this is on - * @param {ASTNode} comment The comment to remove - * @returns {string} Line without comment and trailing whitepace - */ - function stripTrailingComment(line, lineNumber, comment) { - - // loc.column is zero-indexed - return line.slice(0, comment.loc.start.column).replace(/\s+$/, ""); - } + // The options object must be the last option specified… + var lastOption = context.options[context.options.length - 1]; + var options = typeof lastOption === "object" ? Object.create(lastOption) : {}; - /** - * Check the program for max length - * @param {ASTNode} node Node to examine - * @returns {void} - * @private - */ - function checkProgramForMaxLength(node) { + // …but max code length… + if (typeof context.options[0] === "number") { + options.code = context.options[0]; + } - // split (honors line-ending) - var lines = context.getSourceLines(), + // …and tabWidth can be optionally specified directly as integers. + if (typeof context.options[1] === "number") { + options.tabWidth = context.options[1]; + } - // list of comments to ignore - comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? context.getAllComments() : [], + var maxLength = options.code || 80, + tabWidth = options.tabWidth || 4, + ignorePattern = options.ignorePattern || null, + ignoreComments = options.ignoreComments || false, + ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false, + ignoreUrls = options.ignoreUrls || false, + maxCommentLength = options.comments; - // we iterate over comments in parallel with the lines - commentsIndex = 0; + if (ignorePattern) { + ignorePattern = new RegExp(ignorePattern); + } - lines.forEach(function(line, i) { + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tells if a given comment is trailing: it starts on the current line and + * extends to or past the end of the current line. + * @param {string} line The source line we want to check for a trailing comment on + * @param {number} lineNumber The one-indexed line number for line + * @param {ASTNode} comment The comment to inspect + * @returns {boolean} If the comment is trailing on the given line + */ + function isTrailingComment(line, lineNumber, comment) { + return comment && + (comment.loc.start.line === lineNumber && lineNumber <= comment.loc.end.line) && + (comment.loc.end.line > lineNumber || comment.loc.end.column === line.length); + } - // i is zero-indexed, line numbers are one-indexed - var lineNumber = i + 1; + /** + * Tells if a comment encompasses the entire line. + * @param {string} line The source line with a trailing comment + * @param {number} lineNumber The one-indexed line number this is on + * @param {ASTNode} comment The comment to remove + * @returns {boolean} If the comment covers the entire line + */ + function isFullLineComment(line, lineNumber, comment) { + var start = comment.loc.start, + end = comment.loc.end; + + return comment && + (start.line < lineNumber || (start.line === lineNumber && start.column === 0)) && + (end.line > lineNumber || end.column === line.length); + } - /* - * if we're checking comment length; we need to know whether this - * line is a comment - */ - var lineIsComment = false; + /** + * Gets the line after the comment and any remaining trailing whitespace is + * stripped. + * @param {string} line The source line with a trailing comment + * @param {number} lineNumber The one-indexed line number this is on + * @param {ASTNode} comment The comment to remove + * @returns {string} Line without comment and trailing whitepace + */ + function stripTrailingComment(line, lineNumber, comment) { + + // loc.column is zero-indexed + return line.slice(0, comment.loc.start.column).replace(/\s+$/, ""); + } - /* - * We can short-circuit the comment checks if we're already out of - * comments to check. - */ - if (commentsIndex < comments.length) { + /** + * Check the program for max length + * @param {ASTNode} node Node to examine + * @returns {void} + * @private + */ + function checkProgramForMaxLength(node) { + + // split (honors line-ending) + var lines = context.getSourceLines(), + + // list of comments to ignore + comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? context.getAllComments() : [], + + // we iterate over comments in parallel with the lines + commentsIndex = 0; + + lines.forEach(function(line, i) { + + // i is zero-indexed, line numbers are one-indexed + var lineNumber = i + 1; + + /* + * if we're checking comment length; we need to know whether this + * line is a comment + */ + var lineIsComment = false; + + /* + * We can short-circuit the comment checks if we're already out of + * comments to check. + */ + if (commentsIndex < comments.length) { + + // iterate over comments until we find one past the current line + do { + var comment = comments[++commentsIndex]; + } while (comment && comment.loc.start.line <= lineNumber); + + // and step back by one + comment = comments[--commentsIndex]; + + if (isFullLineComment(line, lineNumber, comment)) { + lineIsComment = true; + } else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) { + line = stripTrailingComment(line, lineNumber, comment); + } + } + if (ignorePattern && ignorePattern.test(line) || + ignoreUrls && URL_REGEXP.test(line)) { - // iterate over comments until we find one past the current line - do { - var comment = comments[++commentsIndex]; - } while (comment && comment.loc.start.line <= lineNumber); + // ignore this line + return; + } - // and step back by one - comment = comments[--commentsIndex]; + var lineLength = computeLineLength(line, tabWidth); - if (isFullLineComment(line, lineNumber, comment)) { - lineIsComment = true; - } else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) { - line = stripTrailingComment(line, lineNumber, comment); + if (lineIsComment && ignoreComments) { + return; } - } - if (ignorePattern && ignorePattern.test(line) || - ignoreUrls && URL_REGEXP.test(line)) { - - // ignore this line - return; - } - - var lineLength = computeLineLength(line, tabWidth); - - if (lineIsComment && ignoreComments) { - return; - } - - if (lineIsComment && lineLength > maxCommentLength) { - context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum comment line length of " + maxCommentLength + "."); - } else if (lineLength > maxLength) { - context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum line length of " + maxLength + "."); - } - }); - } + if (lineIsComment && lineLength > maxCommentLength) { + context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum comment line length of " + maxCommentLength + "."); + } else if (lineLength > maxLength) { + context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum line length of " + maxLength + "."); + } + }); + } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - return { - "Program": checkProgramForMaxLength - }; + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- -}; + return { + Program: checkProgramForMaxLength + }; -var OPTIONS_SCHEMA = { - "type": "object", - "properties": { - "code": { - "type": "integer", - "minimum": 0 - }, - "comments": { - "type": "integer", - "minimum": 0 - }, - "tabWidth": { - "type": "integer", - "minimum": 0 - }, - "ignorePattern": { - "type": "string" - }, - "ignoreComments": { - "type": "boolean" - }, - "ignoreUrls": { - "type": "boolean" - }, - "ignoreTrailingComments": { - "type": "boolean" - } - }, - "additionalProperties": false -}; - -var OPTIONS_OR_INTEGER_SCHEMA = { - "anyOf": [ - OPTIONS_SCHEMA, - { - "type": "integer", - "minimum": 0 - } - ] + } }; - -module.exports.schema = [ - OPTIONS_OR_INTEGER_SCHEMA, - OPTIONS_OR_INTEGER_SCHEMA, - OPTIONS_SCHEMA -]; diff --git a/tools/eslint/lib/rules/max-nested-callbacks.js b/tools/eslint/lib/rules/max-nested-callbacks.js index 21b411b251..06554127c6 100644 --- a/tools/eslint/lib/rules/max-nested-callbacks.js +++ b/tools/eslint/lib/rules/max-nested-callbacks.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to enforce a maximum number of nested callbacks. * @author Ian Christian Myers - * @copyright 2013 Ian Christian Myers. All rights reserved. */ "use strict"; @@ -10,94 +9,104 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce a maximum depth that callbacks can be nested", + category: "Stylistic Issues", + recommended: false + }, - //-------------------------------------------------------------------------- - // Constants - //-------------------------------------------------------------------------- - var option = context.options[0], - THRESHOLD = 10; + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0 + }, + max: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + ] + }, - if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { - THRESHOLD = option.maximum; - } - if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { - THRESHOLD = option.max; - } - if (typeof option === "number") { - THRESHOLD = option; - } + create: function(context) { - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Constants + //-------------------------------------------------------------------------- + var option = context.options[0], + THRESHOLD = 10; - var callbackStack = []; + if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { + THRESHOLD = option.maximum; + } + if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { + THRESHOLD = option.max; + } + if (typeof option === "number") { + THRESHOLD = option; + } - /** - * Checks a given function node for too many callbacks. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function checkFunction(node) { - var parent = node.parent; + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - if (parent.type === "CallExpression") { - callbackStack.push(node); - } + var callbackStack = []; - if (callbackStack.length > THRESHOLD) { - var opts = {num: callbackStack.length, max: THRESHOLD}; + /** + * Checks a given function node for too many callbacks. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkFunction(node) { + var parent = node.parent; - context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts); - } - } + if (parent.type === "CallExpression") { + callbackStack.push(node); + } - /** - * Pops the call stack. - * @returns {void} - * @private - */ - function popStack() { - callbackStack.pop(); - } + if (callbackStack.length > THRESHOLD) { + var opts = {num: callbackStack.length, max: THRESHOLD}; - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts); + } + } - return { - "ArrowFunctionExpression": checkFunction, - "ArrowFunctionExpression:exit": popStack, + /** + * Pops the call stack. + * @returns {void} + * @private + */ + function popStack() { + callbackStack.pop(); + } - "FunctionExpression": checkFunction, - "FunctionExpression:exit": popStack - }; + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- -}; + return { + ArrowFunctionExpression: checkFunction, + "ArrowFunctionExpression:exit": popStack, + + FunctionExpression: checkFunction, + "FunctionExpression:exit": popStack + }; -module.exports.schema = [ - { - "oneOf": [ - { - "type": "integer", - "minimum": 0 - }, - { - "type": "object", - "properties": { - "maximum": { - "type": "integer", - "minimum": 0 - }, - "max": { - "type": "integer", - "minimum": 0 - } - }, - "additionalProperties": false - } - ] } -]; +}; diff --git a/tools/eslint/lib/rules/max-params.js b/tools/eslint/lib/rules/max-params.js index d1cfe47092..5d9d64fac1 100644 --- a/tools/eslint/lib/rules/max-params.js +++ b/tools/eslint/lib/rules/max-params.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag when a function has too many parameters * @author Ilya Volodin - * @copyright 2014 Nicholas C. Zakas. All rights reserved. - * @copyright 2013 Ilya Volodin. All rights reserved. */ "use strict"; @@ -11,65 +9,75 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce a maximum number of parameters in `function` definitions", + category: "Stylistic Issues", + recommended: false + }, - var option = context.options[0], - numParams = 3; + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0 + }, + max: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + ] + }, - if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { - numParams = option.maximum; - } - if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { - numParams = option.max; - } - if (typeof option === "number") { - numParams = option; - } + create: function(context) { + + var option = context.options[0], + numParams = 3; - /** - * Checks a function to see if it has too many parameters. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function checkFunction(node) { - if (node.params.length > numParams) { - context.report(node, "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", { - count: node.params.length, - max: numParams - }); + if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { + numParams = option.maximum; + } + if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { + numParams = option.max; + } + if (typeof option === "number") { + numParams = option; } - } - return { - "FunctionDeclaration": checkFunction, - "ArrowFunctionExpression": checkFunction, - "FunctionExpression": checkFunction - }; + /** + * Checks a function to see if it has too many parameters. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkFunction(node) { + if (node.params.length > numParams) { + context.report(node, "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", { + count: node.params.length, + max: numParams + }); + } + } -}; + return { + FunctionDeclaration: checkFunction, + ArrowFunctionExpression: checkFunction, + FunctionExpression: checkFunction + }; -module.exports.schema = [ - { - "oneOf": [ - { - "type": "integer", - "minimum": 0 - }, - { - "type": "object", - "properties": { - "maximum": { - "type": "integer", - "minimum": 0 - }, - "max": { - "type": "integer", - "minimum": 0 - } - }, - "additionalProperties": false - } - ] } -]; +}; diff --git a/tools/eslint/lib/rules/max-statements-per-line.js b/tools/eslint/lib/rules/max-statements-per-line.js index 465c5a00b0..64058f0d30 100644 --- a/tools/eslint/lib/rules/max-statements-per-line.js +++ b/tools/eslint/lib/rules/max-statements-per-line.js @@ -1,9 +1,6 @@ /** * @fileoverview Specify the maximum number of statements allowed per line. * @author Kenneth Williams - * @copyright 2016 Kenneth Williams. All rights reserved. - * @copyright 2016 Michael Ficarra. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -11,96 +8,106 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce a maximum number of statements allowed per line", + category: "Stylistic Issues", + recommended: false + }, - var options = context.options[0] || {}, - lastStatementLine = 0, - numberOfStatementsOnThisLine = 0, - maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1; + schema: [ + { + type: "object", + properties: { + max: { + type: "integer" + } + }, + additionalProperties: false + } + ] + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + create: function(context) { - /** - * Reports a node - * @param {ASTNode} node The node to report - * @returns {void} - * @private - */ - function report(node) { - context.report( - node, - "This line has too many statements. Maximum allowed is {{max}}.", - { max: maxStatementsPerLine }); - } + var options = context.options[0] || {}, + lastStatementLine = 0, + numberOfStatementsOnThisLine = 0, + maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1; - /** - * Enforce a maximum number of statements per line - * @param {ASTNode} nodes Array of nodes to evaluate - * @returns {void} - * @private - */ - function enforceMaxStatementsPerLine(nodes) { - if (nodes.length < 1) { - return; - } + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - for (var i = 0, l = nodes.length; i < l; ++i) { - var currentStatement = nodes[i]; + /** + * Reports a node + * @param {ASTNode} node The node to report + * @returns {void} + * @private + */ + function report(node) { + context.report( + node, + "This line has too many statements. Maximum allowed is {{max}}.", + { max: maxStatementsPerLine }); + } - if (currentStatement.loc.start.line === lastStatementLine) { - ++numberOfStatementsOnThisLine; - } else { - numberOfStatementsOnThisLine = 1; - lastStatementLine = currentStatement.loc.end.line; + /** + * Enforce a maximum number of statements per line + * @param {ASTNode} nodes Array of nodes to evaluate + * @returns {void} + * @private + */ + function enforceMaxStatementsPerLine(nodes) { + if (nodes.length < 1) { + return; } - if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) { - report(currentStatement); + + for (var i = 0, l = nodes.length; i < l; ++i) { + var currentStatement = nodes[i]; + + if (currentStatement.loc.start.line === lastStatementLine) { + ++numberOfStatementsOnThisLine; + } else { + numberOfStatementsOnThisLine = 1; + lastStatementLine = currentStatement.loc.end.line; + } + if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) { + report(currentStatement); + } } } - } - /** - * Check each line in the body of a node - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkLinesInBody(node) { - enforceMaxStatementsPerLine(node.body); - } - - /** - * Check each line in the consequent of a switch case - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkLinesInConsequent(node) { - enforceMaxStatementsPerLine(node.consequent); - } + /** + * Check each line in the body of a node + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkLinesInBody(node) { + enforceMaxStatementsPerLine(node.body); + } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + /** + * Check each line in the consequent of a switch case + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkLinesInConsequent(node) { + enforceMaxStatementsPerLine(node.consequent); + } - return { - "Program": checkLinesInBody, - "BlockStatement": checkLinesInBody, - "SwitchCase": checkLinesInConsequent - }; + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- -}; + return { + Program: checkLinesInBody, + BlockStatement: checkLinesInBody, + SwitchCase: checkLinesInConsequent + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "max": { - "type": "integer" - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/max-statements.js b/tools/eslint/lib/rules/max-statements.js index e608958eba..72904c64ba 100644 --- a/tools/eslint/lib/rules/max-statements.js +++ b/tools/eslint/lib/rules/max-statements.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to set the maximum number of statements in a function. * @author Ian Christian Myers - * @copyright 2013 Ian Christian Myers. All rights reserved. */ "use strict"; @@ -10,141 +9,151 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce a maximum number of statements allowed in `function` blocks", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0 + }, + max: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + }, + { + type: "object", + properties: { + ignoreTopLevelFunctions: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + create: function(context) { - var functionStack = [], - option = context.options[0], - maxStatements = 10, - ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false, - topLevelFunctions = []; + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { - maxStatements = option.maximum; - } - if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { - maxStatements = option.max; - } - if (typeof option === "number") { - maxStatements = option; - } + var functionStack = [], + option = context.options[0], + maxStatements = 10, + ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false, + topLevelFunctions = []; - /** - * Reports a node if it has too many statements - * @param {ASTNode} node node to evaluate - * @param {int} count Number of statements in node - * @param {int} max Maximum number of statements allowed - * @returns {void} - * @private - */ - function reportIfTooManyStatements(node, count, max) { - if (count > max) { - context.report( - node, - "This function has too many statements ({{count}}). Maximum allowed is {{max}}.", - { count: count, max: max }); + if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { + maxStatements = option.maximum; + } + if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { + maxStatements = option.max; + } + if (typeof option === "number") { + maxStatements = option; } - } - - /** - * When parsing a new function, store it in our function stack - * @returns {void} - * @private - */ - function startFunction() { - functionStack.push(0); - } - /** - * Evaluate the node at the end of function - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function endFunction(node) { - var count = functionStack.pop(); - - if (ignoreTopLevelFunctions && functionStack.length === 0) { - topLevelFunctions.push({ node: node, count: count}); - } else { - reportIfTooManyStatements(node, count, maxStatements); + /** + * Reports a node if it has too many statements + * @param {ASTNode} node node to evaluate + * @param {int} count Number of statements in node + * @param {int} max Maximum number of statements allowed + * @returns {void} + * @private + */ + function reportIfTooManyStatements(node, count, max) { + if (count > max) { + context.report( + node, + "This function has too many statements ({{count}}). Maximum allowed is {{max}}.", + { count: count, max: max }); + } } - } - /** - * Increment the count of the functions - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function countStatements(node) { - functionStack[functionStack.length - 1] += node.body.length; - } + /** + * When parsing a new function, store it in our function stack + * @returns {void} + * @private + */ + function startFunction() { + functionStack.push(0); + } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + /** + * Evaluate the node at the end of function + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function endFunction(node) { + var count = functionStack.pop(); + + if (ignoreTopLevelFunctions && functionStack.length === 0) { + topLevelFunctions.push({ node: node, count: count}); + } else { + reportIfTooManyStatements(node, count, maxStatements); + } + } - return { - "FunctionDeclaration": startFunction, - "FunctionExpression": startFunction, - "ArrowFunctionExpression": startFunction, + /** + * Increment the count of the functions + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function countStatements(node) { + functionStack[functionStack.length - 1] += node.body.length; + } - "BlockStatement": countStatements, + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - "FunctionDeclaration:exit": endFunction, - "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction, + return { + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, - "Program:exit": function() { - if (topLevelFunctions.length === 1) { - return; - } + BlockStatement: countStatements, - topLevelFunctions.forEach(function(element) { - var count = element.count; - var node = element.node; + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction, - reportIfTooManyStatements(node, count, maxStatements); - }); - } - }; + "Program:exit": function() { + if (topLevelFunctions.length === 1) { + return; + } -}; + topLevelFunctions.forEach(function(element) { + var count = element.count; + var node = element.node; -module.exports.schema = [ - { - "oneOf": [ - { - "type": "integer", - "minimum": 0 - }, - { - "type": "object", - "properties": { - "maximum": { - "type": "integer", - "minimum": 0 - }, - "max": { - "type": "integer", - "minimum": 0 - } - }, - "additionalProperties": false + reportIfTooManyStatements(node, count, maxStatements); + }); } - ] - }, - { - "type": "object", - "properties": { - "ignoreTopLevelFunctions": { - "type": "boolean" - } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/new-cap.js b/tools/eslint/lib/rules/new-cap.js index f43c59fd99..697b60d0f1 100644 --- a/tools/eslint/lib/rules/new-cap.js +++ b/tools/eslint/lib/rules/new-cap.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag use of constructors without capital letters * @author Nicholas C. Zakas - * @copyright 2014 Jordan Harband. All rights reserved. - * @copyright 2013-2014 Nicholas C. Zakas. All rights reserved. */ "use strict"; @@ -76,174 +74,184 @@ function calculateCapIsNewExceptions(config) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require constructor `function` names to begin with a capital letter", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + type: "object", + properties: { + newIsCap: { + type: "boolean" + }, + capIsNew: { + type: "boolean" + }, + newIsCapExceptions: { + type: "array", + items: { + type: "string" + } + }, + capIsNewExceptions: { + type: "array", + items: { + type: "string" + } + }, + properties: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { - var config = context.options[0] ? lodash.assign({}, context.options[0]) : {}; + var config = context.options[0] ? lodash.assign({}, context.options[0]) : {}; - config.newIsCap = config.newIsCap !== false; - config.capIsNew = config.capIsNew !== false; - var skipProperties = config.properties === false; + config.newIsCap = config.newIsCap !== false; + config.capIsNew = config.capIsNew !== false; + var skipProperties = config.properties === false; - var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {}); + var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {}); - var capIsNewExceptions = calculateCapIsNewExceptions(config); + var capIsNewExceptions = calculateCapIsNewExceptions(config); - var listeners = {}; + var listeners = {}; - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * Get exact callee name from expression - * @param {ASTNode} node CallExpression or NewExpression node - * @returns {string} name - */ - function extractNameFromExpression(node) { + /** + * Get exact callee name from expression + * @param {ASTNode} node CallExpression or NewExpression node + * @returns {string} name + */ + function extractNameFromExpression(node) { - var name = "", - property; + var name = "", + property; - if (node.callee.type === "MemberExpression") { - property = node.callee.property; + if (node.callee.type === "MemberExpression") { + property = node.callee.property; - if (property.type === "Literal" && (typeof property.value === "string")) { - name = property.value; - } else if (property.type === "Identifier" && !node.callee.computed) { - name = property.name; + if (property.type === "Literal" && (typeof property.value === "string")) { + name = property.value; + } else if (property.type === "Identifier" && !node.callee.computed) { + name = property.name; + } + } else { + name = node.callee.name; } - } else { - name = node.callee.name; + return name; } - return name; - } - /** - * Returns the capitalization state of the string - - * Whether the first character is uppercase, lowercase, or non-alphabetic - * @param {string} str String - * @returns {string} capitalization state: "non-alpha", "lower", or "upper" - */ - function getCap(str) { - var firstChar = str.charAt(0); - - var firstCharLower = firstChar.toLowerCase(); - var firstCharUpper = firstChar.toUpperCase(); - - if (firstCharLower === firstCharUpper) { - - // char has no uppercase variant, so it's non-alphabetic - return "non-alpha"; - } else if (firstChar === firstCharLower) { - return "lower"; - } else { - return "upper"; + /** + * Returns the capitalization state of the string - + * Whether the first character is uppercase, lowercase, or non-alphabetic + * @param {string} str String + * @returns {string} capitalization state: "non-alpha", "lower", or "upper" + */ + function getCap(str) { + var firstChar = str.charAt(0); + + var firstCharLower = firstChar.toLowerCase(); + var firstCharUpper = firstChar.toUpperCase(); + + if (firstCharLower === firstCharUpper) { + + // char has no uppercase variant, so it's non-alphabetic + return "non-alpha"; + } else if (firstChar === firstCharLower) { + return "lower"; + } else { + return "upper"; + } } - } - /** - * Check if capitalization is allowed for a CallExpression - * @param {Object} allowedMap Object mapping calleeName to a Boolean - * @param {ASTNode} node CallExpression node - * @param {string} calleeName Capitalized callee name from a CallExpression - * @returns {Boolean} Returns true if the callee may be capitalized - */ - function isCapAllowed(allowedMap, node, calleeName) { - if (allowedMap[calleeName] || allowedMap[context.getSource(node.callee)]) { - return true; - } + /** + * Check if capitalization is allowed for a CallExpression + * @param {Object} allowedMap Object mapping calleeName to a Boolean + * @param {ASTNode} node CallExpression node + * @param {string} calleeName Capitalized callee name from a CallExpression + * @returns {Boolean} Returns true if the callee may be capitalized + */ + function isCapAllowed(allowedMap, node, calleeName) { + if (allowedMap[calleeName] || allowedMap[context.getSource(node.callee)]) { + return true; + } - if (calleeName === "UTC" && node.callee.type === "MemberExpression") { + if (calleeName === "UTC" && node.callee.type === "MemberExpression") { + + // allow if callee is Date.UTC + return node.callee.object.type === "Identifier" && + node.callee.object.name === "Date"; + } - // allow if callee is Date.UTC - return node.callee.object.type === "Identifier" && - node.callee.object.name === "Date"; + return skipProperties && node.callee.type === "MemberExpression"; } - return skipProperties && node.callee.type === "MemberExpression"; - } + /** + * Reports the given message for the given node. The location will be the start of the property or the callee. + * @param {ASTNode} node CallExpression or NewExpression node. + * @param {string} message The message to report. + * @returns {void} + */ + function report(node, message) { + var callee = node.callee; + + if (callee.type === "MemberExpression") { + callee = callee.property; + } - /** - * Reports the given message for the given node. The location will be the start of the property or the callee. - * @param {ASTNode} node CallExpression or NewExpression node. - * @param {string} message The message to report. - * @returns {void} - */ - function report(node, message) { - var callee = node.callee; - - if (callee.type === "MemberExpression") { - callee = callee.property; + context.report(node, callee.loc.start, message); } - context.report(node, callee.loc.start, message); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - if (config.newIsCap) { - listeners.NewExpression = function(node) { + if (config.newIsCap) { + listeners.NewExpression = function(node) { - var constructorName = extractNameFromExpression(node); + var constructorName = extractNameFromExpression(node); - if (constructorName) { - var capitalization = getCap(constructorName); - var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName); + if (constructorName) { + var capitalization = getCap(constructorName); + var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName); - if (!isAllowed) { - report(node, "A constructor name should not start with a lowercase letter."); + if (!isAllowed) { + report(node, "A constructor name should not start with a lowercase letter."); + } } - } - }; - } + }; + } - if (config.capIsNew) { - listeners.CallExpression = function(node) { + if (config.capIsNew) { + listeners.CallExpression = function(node) { - var calleeName = extractNameFromExpression(node); + var calleeName = extractNameFromExpression(node); - if (calleeName) { - var capitalization = getCap(calleeName); - var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName); + if (calleeName) { + var capitalization = getCap(calleeName); + var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName); - if (!isAllowed) { - report(node, "A function with a name starting with an uppercase letter should only be used as a constructor."); + if (!isAllowed) { + report(node, "A function with a name starting with an uppercase letter should only be used as a constructor."); + } } - } - }; - } - - return listeners; -}; + }; + } -module.exports.schema = [ - { - "type": "object", - "properties": { - "newIsCap": { - "type": "boolean" - }, - "capIsNew": { - "type": "boolean" - }, - "newIsCapExceptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "capIsNewExceptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "properties": { - "type": "boolean" - } - }, - "additionalProperties": false + return listeners; } -]; +}; diff --git a/tools/eslint/lib/rules/new-parens.js b/tools/eslint/lib/rules/new-parens.js index 13c8e3d824..0d7a84e3cc 100644 --- a/tools/eslint/lib/rules/new-parens.js +++ b/tools/eslint/lib/rules/new-parens.js @@ -9,22 +9,32 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - return { - - "NewExpression": function(node) { - var tokens = context.getTokens(node); - var prenticesTokens = tokens.filter(function(token) { - return token.value === "(" || token.value === ")"; - }); - - if (prenticesTokens.length < 2) { - context.report(node, "Missing '()' invoking a constructor"); +module.exports = { + meta: { + docs: { + description: "require parentheses when invoking a constructor with no arguments", + category: "Stylistic Issues", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + return { + + NewExpression: function(node) { + var tokens = context.getTokens(node); + var prenticesTokens = tokens.filter(function(token) { + return token.value === "(" || token.value === ")"; + }); + + if (prenticesTokens.length < 2) { + context.report(node, "Missing '()' invoking a constructor"); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/newline-after-var.js b/tools/eslint/lib/rules/newline-after-var.js index 34431e2c8c..fd80c8c542 100644 --- a/tools/eslint/lib/rules/newline-after-var.js +++ b/tools/eslint/lib/rules/newline-after-var.js @@ -1,9 +1,6 @@ /** * @fileoverview Rule to check empty newline after "var" statement * @author Gopal Venkatesan - * @copyright 2015 Gopal Venkatesan. All rights reserved. - * @copyright 2015 Casey Visco. All rights reserved. - * @copyright 2015 Ian VanSchooten. All rights reserved. */ "use strict"; @@ -12,159 +9,169 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var ALWAYS_MESSAGE = "Expected blank line after variable declarations.", - NEVER_MESSAGE = "Unexpected blank line after variable declarations."; - - var sourceCode = context.getSourceCode(); - - // Default `mode` to "always". - var mode = context.options[0] === "never" ? "never" : "always"; - - // Cache starting and ending line numbers of comments for faster lookup - var commentEndLine = context.getAllComments().reduce(function(result, token) { - result[token.loc.start.line] = token.loc.end.line; - return result; - }, {}); - - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Determine if provided keyword is a variable declaration - * @private - * @param {string} keyword - keyword to test - * @returns {boolean} True if `keyword` is a type of var - */ - function isVar(keyword) { - return keyword === "var" || keyword === "let" || keyword === "const"; - } - - /** - * Determine if provided keyword is a variant of for specifiers - * @private - * @param {string} keyword - keyword to test - * @returns {boolean} True if `keyword` is a variant of for specifier - */ - function isForTypeSpecifier(keyword) { - return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; - } - - /** - * Determine if provided keyword is an export specifiers - * @private - * @param {string} nodeType - nodeType to test - * @returns {boolean} True if `nodeType` is an export specifier - */ - function isExportSpecifier(nodeType) { - return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" || - nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration"; - } - - /** - * Determine if provided node is the last of their parent block. - * @private - * @param {ASTNode} node - node to test - * @returns {boolean} True if `node` is last of their parent block. - */ - function isLastNode(node) { - var token = sourceCode.getTokenAfter(node); - - return !token || (token.type === "Punctuator" && token.value === "}"); - } - - /** - * Determine if a token starts more than one line after a comment ends - * @param {token} token The token being checked - * @param {integer} commentStartLine The line number on which the comment starts - * @returns {boolean} True if `token` does not start immediately after a comment - */ - function hasBlankLineAfterComment(token, commentStartLine) { - var commentEnd = commentEndLine[commentStartLine]; - - // If there's another comment, repeat check for blank line - if (commentEndLine[commentEnd + 1]) { - return hasBlankLineAfterComment(token, commentEnd + 1); +module.exports = { + meta: { + docs: { + description: "require or disallow an empty line after `var` declarations", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + enum: ["never", "always"] + } + ] + }, + + create: function(context) { + + var ALWAYS_MESSAGE = "Expected blank line after variable declarations.", + NEVER_MESSAGE = "Unexpected blank line after variable declarations."; + + var sourceCode = context.getSourceCode(); + + // Default `mode` to "always". + var mode = context.options[0] === "never" ? "never" : "always"; + + // Cache starting and ending line numbers of comments for faster lookup + var commentEndLine = context.getAllComments().reduce(function(result, token) { + result[token.loc.start.line] = token.loc.end.line; + return result; + }, {}); + + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determine if provided keyword is a variable declaration + * @private + * @param {string} keyword - keyword to test + * @returns {boolean} True if `keyword` is a type of var + */ + function isVar(keyword) { + return keyword === "var" || keyword === "let" || keyword === "const"; } - return (token.loc.start.line > commentEndLine[commentStartLine] + 1); - } - - /** - * Checks that a blank line exists after a variable declaration when mode is - * set to "always", or checks that there is no blank line when mode is set - * to "never" - * @private - * @param {ASTNode} node - `VariableDeclaration` node to test - * @returns {void} - */ - function checkForBlankLine(node) { - var lastToken = sourceCode.getLastToken(node), - nextToken = sourceCode.getTokenAfter(node), - nextLineNum = lastToken.loc.end.line + 1, - noNextLineToken, - hasNextLineComment; - - // Ignore if there is no following statement - if (!nextToken) { - return; + /** + * Determine if provided keyword is a variant of for specifiers + * @private + * @param {string} keyword - keyword to test + * @returns {boolean} True if `keyword` is a variant of for specifier + */ + function isForTypeSpecifier(keyword) { + return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; } - // Ignore if parent of node is a for variant - if (isForTypeSpecifier(node.parent.type)) { - return; + /** + * Determine if provided keyword is an export specifiers + * @private + * @param {string} nodeType - nodeType to test + * @returns {boolean} True if `nodeType` is an export specifier + */ + function isExportSpecifier(nodeType) { + return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" || + nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration"; } - // Ignore if parent of node is an export specifier - if (isExportSpecifier(node.parent.type)) { - return; - } + /** + * Determine if provided node is the last of their parent block. + * @private + * @param {ASTNode} node - node to test + * @returns {boolean} True if `node` is last of their parent block. + */ + function isLastNode(node) { + var token = sourceCode.getTokenAfter(node); - // Some coding styles use multiple `var` statements, so do nothing if - // the next token is a `var` statement. - if (nextToken.type === "Keyword" && isVar(nextToken.value)) { - return; + return !token || (token.type === "Punctuator" && token.value === "}"); } - // Ignore if it is last statement in a block - if (isLastNode(node)) { - return; + /** + * Determine if a token starts more than one line after a comment ends + * @param {token} token The token being checked + * @param {integer} commentStartLine The line number on which the comment starts + * @returns {boolean} True if `token` does not start immediately after a comment + */ + function hasBlankLineAfterComment(token, commentStartLine) { + var commentEnd = commentEndLine[commentStartLine]; + + // If there's another comment, repeat check for blank line + if (commentEndLine[commentEnd + 1]) { + return hasBlankLineAfterComment(token, commentEnd + 1); + } + + return (token.loc.start.line > commentEndLine[commentStartLine] + 1); } - // Next statement is not a `var`... - noNextLineToken = nextToken.loc.start.line > nextLineNum; - hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined"); - - if (mode === "never" && noNextLineToken && !hasNextLineComment) { - context.report(node, NEVER_MESSAGE, { identifier: node.name }); + /** + * Checks that a blank line exists after a variable declaration when mode is + * set to "always", or checks that there is no blank line when mode is set + * to "never" + * @private + * @param {ASTNode} node - `VariableDeclaration` node to test + * @returns {void} + */ + function checkForBlankLine(node) { + var lastToken = sourceCode.getLastToken(node), + nextToken = sourceCode.getTokenAfter(node), + nextLineNum = lastToken.loc.end.line + 1, + noNextLineToken, + hasNextLineComment; + + // Ignore if there is no following statement + if (!nextToken) { + return; + } + + // Ignore if parent of node is a for variant + if (isForTypeSpecifier(node.parent.type)) { + return; + } + + // Ignore if parent of node is an export specifier + if (isExportSpecifier(node.parent.type)) { + return; + } + + // Some coding styles use multiple `var` statements, so do nothing if + // the next token is a `var` statement. + if (nextToken.type === "Keyword" && isVar(nextToken.value)) { + return; + } + + // Ignore if it is last statement in a block + if (isLastNode(node)) { + return; + } + + // Next statement is not a `var`... + noNextLineToken = nextToken.loc.start.line > nextLineNum; + hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined"); + + if (mode === "never" && noNextLineToken && !hasNextLineComment) { + context.report(node, NEVER_MESSAGE, { identifier: node.name }); + } + + // Token on the next line, or comment without blank line + if ( + mode === "always" && ( + !noNextLineToken || + hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum) + ) + ) { + context.report(node, ALWAYS_MESSAGE, { identifier: node.name }); + } } - // Token on the next line, or comment without blank line - if ( - mode === "always" && ( - !noNextLineToken || - hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum) - ) - ) { - context.report(node, ALWAYS_MESSAGE, { identifier: node.name }); - } - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { - "VariableDeclaration": checkForBlankLine - }; + return { + VariableDeclaration: checkForBlankLine + }; -}; - -module.exports.schema = [ - { - "enum": ["never", "always"] } -]; +}; diff --git a/tools/eslint/lib/rules/newline-before-return.js b/tools/eslint/lib/rules/newline-before-return.js index 6cc2944a43..77f3aedaa8 100644 --- a/tools/eslint/lib/rules/newline-before-return.js +++ b/tools/eslint/lib/rules/newline-before-return.js @@ -1,9 +1,6 @@ - /** * @fileoverview Rule to require newlines before `return` statement * @author Kai Cataldo - * @copyright 2016 Kai Cataldo. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -11,133 +8,159 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var sourceCode = context.getSourceCode(); - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Tests whether node is preceded by supplied tokens - * @param {ASTNode} node - node to check - * @param {array} testTokens - array of tokens to test against - * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens - * @private - */ - function isPrecededByTokens(node, testTokens) { - var tokenBefore = sourceCode.getTokenBefore(node); - - return testTokens.some(function(token) { - return tokenBefore.value === token; - }); - } - - /** - * Checks whether node is the first node after statement or in block - * @param {ASTNode} node - node to check - * @returns {boolean} Whether or not the node is the first node after statement or in block - * @private - */ - function isFirstNode(node) { - var parentType = node.parent.type; - - if (node.parent.body) { - return Array.isArray(node.parent.body) - ? node.parent.body[0] === node - : node.parent.body === node; +module.exports = { + meta: { + docs: { + description: "require an empty line before `return` statements", + category: "Stylistic Issues", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + var sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tests whether node is preceded by supplied tokens + * @param {ASTNode} node - node to check + * @param {array} testTokens - array of tokens to test against + * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens + * @private + */ + function isPrecededByTokens(node, testTokens) { + var tokenBefore = sourceCode.getTokenBefore(node); + + return testTokens.some(function(token) { + return tokenBefore.value === token; + }); } - if (parentType === "IfStatement") { - return isPrecededByTokens(node, ["else", ")"]); - } else if (parentType === "DoWhileStatement") { - return isPrecededByTokens(node, ["do"]); - } else if (parentType === "SwitchCase") { - return isPrecededByTokens(node, [":"]); - } else { - return isPrecededByTokens(node, [")"]); - } - } - - /** - * Returns the number of lines of comments that precede the node - * @param {ASTNode} node - node to check for overlapping comments - * @param {token} tokenBefore - previous token to check for overlapping comments - * @returns {number} Number of lines of comments that precede the node - * @private - */ - function calcCommentLines(node, tokenBefore) { - var comments = sourceCode.getComments(node).leading; - var numLinesComments = 0; - - if (!comments.length) { - return numLinesComments; - } - - comments.forEach(function(comment) { - numLinesComments++; - - if (comment.type === "Block") { - numLinesComments += comment.loc.end.line - comment.loc.start.line; + /** + * Checks whether node is the first node after statement or in block + * @param {ASTNode} node - node to check + * @returns {boolean} Whether or not the node is the first node after statement or in block + * @private + */ + function isFirstNode(node) { + var parentType = node.parent.type; + + if (node.parent.body) { + return Array.isArray(node.parent.body) + ? node.parent.body[0] === node + : node.parent.body === node; } - // avoid counting lines with inline comments twice - if (comment.loc.start.line === tokenBefore.loc.end.line) { - numLinesComments--; + if (parentType === "IfStatement") { + return isPrecededByTokens(node, ["else", ")"]); + } else if (parentType === "DoWhileStatement") { + return isPrecededByTokens(node, ["do"]); + } else if (parentType === "SwitchCase") { + return isPrecededByTokens(node, [":"]); + } else { + return isPrecededByTokens(node, [")"]); } + } - if (comment.loc.end.line === node.loc.start.line) { - numLinesComments--; + /** + * Returns the number of lines of comments that precede the node + * @param {ASTNode} node - node to check for overlapping comments + * @param {number} lineNumTokenBefore - line number of previous token, to check for overlapping comments + * @returns {number} Number of lines of comments that precede the node + * @private + */ + function calcCommentLines(node, lineNumTokenBefore) { + var comments = sourceCode.getComments(node).leading, + numLinesComments = 0; + + if (!comments.length) { + return numLinesComments; } - }); - return numLinesComments; - } + comments.forEach(function(comment) { + numLinesComments++; - /** - * Checks whether node is preceded by a newline - * @param {ASTNode} node - node to check - * @returns {boolean} Whether or not the node is preceded by a newline - * @private - */ - function hasNewlineBefore(node) { - var tokenBefore = sourceCode.getTokenBefore(node); - var lineNumTokenBefore = tokenBefore.loc.end.line; - var lineNumNode = node.loc.start.line; - var commentLines = calcCommentLines(node, tokenBefore); - - return (lineNumNode - lineNumTokenBefore - commentLines) > 1; - } + if (comment.type === "Block") { + numLinesComments += comment.loc.end.line - comment.loc.start.line; + } - /** - * Reports expected/unexpected newline before return statement - * @param {ASTNode} node - the node to report in the event of an error - * @param {boolean} isExpected - whether the newline is expected or not - * @returns {void} - * @private - */ - function reportError(node, isExpected) { - var expected = isExpected ? "Expected" : "Unexpected"; - - context.report({ - node: node, - message: expected + " newline before return statement." - }); - } + // avoid counting lines with inline comments twice + if (comment.loc.start.line === lineNumTokenBefore) { + numLinesComments--; + } + + if (comment.loc.end.line === node.loc.start.line) { + numLinesComments--; + } + }); - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + return numLinesComments; + } - return { - ReturnStatement: function(node) { - if (isFirstNode(node) && hasNewlineBefore(node)) { - reportError(node, false); - } else if (!isFirstNode(node) && !hasNewlineBefore(node)) { - reportError(node, true); + /** + * Checks whether node is preceded by a newline + * @param {ASTNode} node - node to check + * @returns {boolean} Whether or not the node is preceded by a newline + * @private + */ + function hasNewlineBefore(node) { + var tokenBefore = sourceCode.getTokenBefore(node), + lineNumNode = node.loc.start.line, + lineNumTokenBefore, + commentLines; + + /** + * Global return (at the beginning of a script) is a special case. + * If there is no token before `return`, then we expect no line + * break before the return. Comments are allowed to occupy lines + * before the global return, just no blank lines. + * Setting lineNumTokenBefore to zero in that case results in the + * desired behavior. + */ + if (tokenBefore) { + lineNumTokenBefore = tokenBefore.loc.end.line; + } else { + lineNumTokenBefore = 0; // global return at beginning of script } + + commentLines = calcCommentLines(node, lineNumTokenBefore); + + return (lineNumNode - lineNumTokenBefore - commentLines) > 1; + } + + /** + * Reports expected/unexpected newline before return statement + * @param {ASTNode} node - the node to report in the event of an error + * @param {boolean} isExpected - whether the newline is expected or not + * @returns {void} + * @private + */ + function reportError(node, isExpected) { + var expected = isExpected ? "Expected" : "Unexpected"; + + context.report({ + node: node, + message: expected + " newline before return statement." + }); } - }; -}; -module.exports.schema = []; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ReturnStatement: function(node) { + if (isFirstNode(node) && hasNewlineBefore(node)) { + reportError(node, false); + } else if (!isFirstNode(node) && !hasNewlineBefore(node)) { + reportError(node, true); + } + } + }; + } +}; diff --git a/tools/eslint/lib/rules/newline-per-chained-call.js b/tools/eslint/lib/rules/newline-per-chained-call.js index f7df4c29d6..80401415aa 100644 --- a/tools/eslint/lib/rules/newline-per-chained-call.js +++ b/tools/eslint/lib/rules/newline-per-chained-call.js @@ -2,8 +2,6 @@ * @fileoverview Rule to ensure newline per method call when chaining calls * @author Rajendra Patil * @author Burak Yigit Kaya - * @copyright 2016 Rajendra Patil. All rights reserved. - * @copyright 2016 Burak Yigit Kaya. All rights reserved. */ "use strict"; @@ -12,45 +10,55 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require a newline after each call in a method chain", + category: "Stylistic Issues", + recommended: false + }, - var options = context.options[0] || {}, - ignoreChainWithDepth = options.ignoreChainWithDepth || 2; + schema: [{ + type: "object", + properties: { + ignoreChainWithDepth: { + type: "integer", + minimum: 1, + maximum: 10 + } + }, + additionalProperties: false + }] + }, - return { - "CallExpression:exit": function(node) { - if (!node.callee || node.callee.type !== "MemberExpression") { - return; - } + create: function(context) { - var callee = node.callee; - var parent = callee.object; - var depth = 1; + var options = context.options[0] || {}, + ignoreChainWithDepth = options.ignoreChainWithDepth || 2; - while (parent && parent.callee) { - depth += 1; - parent = parent.callee.object; - } + return { + "CallExpression:exit": function(node) { + if (!node.callee || node.callee.type !== "MemberExpression") { + return; + } + + var callee = node.callee; + var parent = callee.object; + var depth = 1; - if (depth > ignoreChainWithDepth && callee.property.loc.start.line === callee.object.loc.end.line) { - context.report( - callee.property, - callee.property.loc.start, - "Expected line break after `" + context.getSource(callee.object).replace(/\r\n|\r|\n/g, "\\n") + "`." - ); + while (parent && parent.callee) { + depth += 1; + parent = parent.callee.object; + } + + if (depth > ignoreChainWithDepth && callee.property.loc.start.line === callee.object.loc.end.line) { + context.report( + callee.property, + callee.property.loc.start, + "Expected line break after `" + context.getSource(callee.object).replace(/\r\n|\r|\n/g, "\\n") + "`." + ); + } } - } - }; + }; + } }; - -module.exports.schema = [{ - "type": "object", - "properties": { - "ignoreChainWithDepth": { - "type": "integer", - "minimum": 1, - "maximum": 10 - } - }, - "additionalProperties": false -}]; diff --git a/tools/eslint/lib/rules/no-alert.js b/tools/eslint/lib/rules/no-alert.js index ea1e689544..e491dfefc5 100644 --- a/tools/eslint/lib/rules/no-alert.js +++ b/tools/eslint/lib/rules/no-alert.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag use of alert, confirm, prompt * @author Nicholas C. Zakas - * @copyright 2015 Mathias Schreck - * @copyright 2013 Nicholas C. Zakas */ "use strict"; @@ -98,39 +96,49 @@ function isGlobalThisReferenceOrGlobalWindow(scope, globalScope, node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var globalScope; +module.exports = { + meta: { + docs: { + description: "disallow the use of `alert`, `confirm`, and `prompt`", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "Program": function() { - globalScope = context.getScope(); - }, + create: function(context) { + var globalScope; - "CallExpression": function(node) { - var callee = node.callee, - identifierName, - currentScope = context.getScope(); + return { - // without window. - if (callee.type === "Identifier") { - identifierName = callee.name; + Program: function() { + globalScope = context.getScope(); + }, - if (!isShadowed(currentScope, globalScope, callee) && isProhibitedIdentifier(callee.name)) { - report(context, node, identifierName); - } + CallExpression: function(node) { + var callee = node.callee, + identifierName, + currentScope = context.getScope(); + + // without window. + if (callee.type === "Identifier") { + identifierName = callee.name; + + if (!isShadowed(currentScope, globalScope, callee) && isProhibitedIdentifier(callee.name)) { + report(context, node, identifierName); + } - } else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, globalScope, callee.object)) { - identifierName = getPropertyName(callee); + } else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, globalScope, callee.object)) { + identifierName = getPropertyName(callee); - if (isProhibitedIdentifier(identifierName)) { - report(context, node, identifierName); + if (isProhibitedIdentifier(identifierName)) { + report(context, node, identifierName); + } } - } - } - }; + } + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-array-constructor.js b/tools/eslint/lib/rules/no-array-constructor.js index ecf5837228..389725c62c 100644 --- a/tools/eslint/lib/rules/no-array-constructor.js +++ b/tools/eslint/lib/rules/no-array-constructor.js @@ -9,29 +9,39 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Disallow construction of dense arrays using the Array constructor - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function check(node) { - if ( - node.arguments.length !== 1 && - node.callee.type === "Identifier" && - node.callee.name === "Array" - ) { - context.report(node, "The array literal notation [] is preferrable."); +module.exports = { + meta: { + docs: { + description: "disallow `Array` constructors", + category: "Stylistic Issues", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + /** + * Disallow construction of dense arrays using the Array constructor + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function check(node) { + if ( + node.arguments.length !== 1 && + node.callee.type === "Identifier" && + node.callee.name === "Array" + ) { + context.report(node, "The array literal notation [] is preferrable."); + } } - } - return { - "CallExpression": check, - "NewExpression": check - }; + return { + CallExpression: check, + NewExpression: check + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-bitwise.js b/tools/eslint/lib/rules/no-bitwise.js index 9655525396..0294998ecc 100644 --- a/tools/eslint/lib/rules/no-bitwise.js +++ b/tools/eslint/lib/rules/no-bitwise.js @@ -18,82 +18,92 @@ var BITWISE_OPERATORS = [ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0] || {}; - var allowed = options.allow || []; - var int32Hint = options.int32Hint === true; - - /** - * Reports an unexpected use of a bitwise operator. - * @param {ASTNode} node Node which contains the bitwise operator. - * @returns {void} - */ - function report(node) { - context.report(node, "Unexpected use of '{{operator}}'.", { operator: node.operator }); - } +module.exports = { + meta: { + docs: { + description: "disallow bitwise operators", + category: "Stylistic Issues", + recommended: false + }, - /** - * Checks if the given node has a bitwise operator. - * @param {ASTNode} node The node to check. - * @returns {boolean} Whether or not the node has a bitwise operator. - */ - function hasBitwiseOperator(node) { - return BITWISE_OPERATORS.indexOf(node.operator) !== -1; - } + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { + enum: BITWISE_OPERATORS + }, + uniqueItems: true + }, + int32Hint: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - /** - * Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`. - * @param {ASTNode} node The node to check. - * @returns {boolean} Whether or not the node has a bitwise operator. - */ - function allowedOperator(node) { - return allowed.indexOf(node.operator) !== -1; - } + create: function(context) { + var options = context.options[0] || {}; + var allowed = options.allow || []; + var int32Hint = options.int32Hint === true; - /** - * Checks if the given bitwise operator is used for integer typecasting, i.e. "|0" - * @param {ASTNode} node The node to check. - * @returns {boolean} whether the node is used in integer typecasting. - */ - function isInt32Hint(node) { - return int32Hint && node.operator === "|" && node.right && - node.right.type === "Literal" && node.right.value === 0; - } + /** + * Reports an unexpected use of a bitwise operator. + * @param {ASTNode} node Node which contains the bitwise operator. + * @returns {void} + */ + function report(node) { + context.report(node, "Unexpected use of '{{operator}}'.", { operator: node.operator }); + } - /** - * Report if the given node contains a bitwise operator. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkNodeForBitwiseOperator(node) { - if (hasBitwiseOperator(node) && !allowedOperator(node) && !isInt32Hint(node)) { - report(node); + /** + * Checks if the given node has a bitwise operator. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node has a bitwise operator. + */ + function hasBitwiseOperator(node) { + return BITWISE_OPERATORS.indexOf(node.operator) !== -1; } - } - return { - "AssignmentExpression": checkNodeForBitwiseOperator, - "BinaryExpression": checkNodeForBitwiseOperator, - "UnaryExpression": checkNodeForBitwiseOperator - }; + /** + * Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node has a bitwise operator. + */ + function allowedOperator(node) { + return allowed.indexOf(node.operator) !== -1; + } -}; + /** + * Checks if the given bitwise operator is used for integer typecasting, i.e. "|0" + * @param {ASTNode} node The node to check. + * @returns {boolean} whether the node is used in integer typecasting. + */ + function isInt32Hint(node) { + return int32Hint && node.operator === "|" && node.right && + node.right.type === "Literal" && node.right.value === 0; + } -module.exports.schema = [ - { - "type": "object", - "properties": { - "allow": { - "type": "array", - "items": { - "enum": BITWISE_OPERATORS - }, - "uniqueItems": true - }, - "int32Hint": { - "type": "boolean" + /** + * Report if the given node contains a bitwise operator. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkNodeForBitwiseOperator(node) { + if (hasBitwiseOperator(node) && !allowedOperator(node) && !isInt32Hint(node)) { + report(node); } - }, - "additionalProperties": false + } + + return { + AssignmentExpression: checkNodeForBitwiseOperator, + BinaryExpression: checkNodeForBitwiseOperator, + UnaryExpression: checkNodeForBitwiseOperator + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/no-caller.js b/tools/eslint/lib/rules/no-caller.js index aacb3feffb..0405fdaeb9 100644 --- a/tools/eslint/lib/rules/no-caller.js +++ b/tools/eslint/lib/rules/no-caller.js @@ -9,21 +9,31 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `arguments.caller` or `arguments.callee`", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "MemberExpression": function(node) { - var objectName = node.object.name, - propertyName = node.property.name; + create: function(context) { - if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) { - context.report(node, "Avoid arguments.{{property}}.", { property: propertyName }); - } + return { - } - }; + MemberExpression: function(node) { + var objectName = node.object.name, + propertyName = node.property.name; -}; + if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) { + context.report(node, "Avoid arguments.{{property}}.", { property: propertyName }); + } -module.exports.schema = []; + } + }; + + } +}; diff --git a/tools/eslint/lib/rules/no-case-declarations.js b/tools/eslint/lib/rules/no-case-declarations.js index 4969d4f1bf..8ef202538e 100644 --- a/tools/eslint/lib/rules/no-case-declarations.js +++ b/tools/eslint/lib/rules/no-case-declarations.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag use of an lexical declarations inside a case clause * @author Erik Arvidsson - * @copyright 2015 Erik Arvidsson. All rights reserved. */ "use strict"; @@ -9,40 +8,50 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Checks whether or not a node is a lexical declaration. - * @param {ASTNode} node A direct child statement of a switch case. - * @returns {boolean} Whether or not the node is a lexical declaration. - */ - function isLexicalDeclaration(node) { - switch (node.type) { - case "FunctionDeclaration": - case "ClassDeclaration": - return true; - case "VariableDeclaration": - return node.kind !== "var"; - default: - return false; +module.exports = { + meta: { + docs: { + description: "disallow lexical declarations in case clauses", + category: "Best Practices", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Checks whether or not a node is a lexical declaration. + * @param {ASTNode} node A direct child statement of a switch case. + * @returns {boolean} Whether or not the node is a lexical declaration. + */ + function isLexicalDeclaration(node) { + switch (node.type) { + case "FunctionDeclaration": + case "ClassDeclaration": + return true; + case "VariableDeclaration": + return node.kind !== "var"; + default: + return false; + } } - } - return { - "SwitchCase": function(node) { - for (var i = 0; i < node.consequent.length; i++) { - var statement = node.consequent[i]; + return { + SwitchCase: function(node) { + for (var i = 0; i < node.consequent.length; i++) { + var statement = node.consequent[i]; - if (isLexicalDeclaration(statement)) { - context.report({ - node: node, - message: "Unexpected lexical declaration in case block." - }); + if (isLexicalDeclaration(statement)) { + context.report({ + node: node, + message: "Unexpected lexical declaration in case block." + }); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-catch-shadow.js b/tools/eslint/lib/rules/no-catch-shadow.js index 88eeb02fa6..4a206833c0 100644 --- a/tools/eslint/lib/rules/no-catch-shadow.js +++ b/tools/eslint/lib/rules/no-catch-shadow.js @@ -15,44 +15,54 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Check if the parameters are been shadowed - * @param {object} scope current scope - * @param {string} name parameter name - * @returns {boolean} True is its been shadowed - */ - function paramIsShadowing(scope, name) { - return astUtils.getVariableByName(scope, name) !== null; - } +module.exports = { + meta: { + docs: { + description: "disallow `catch` clause parameters from shadowing variables in the outer scope", + category: "Variables", + recommended: false + }, - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + schema: [] + }, - return { + create: function(context) { - "CatchClause": function(node) { - var scope = context.getScope(); + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - // When blockBindings is enabled, CatchClause creates its own scope - // so start from one upper scope to exclude the current node - if (scope.block === node) { - scope = scope.upper; - } + /** + * Check if the parameters are been shadowed + * @param {object} scope current scope + * @param {string} name parameter name + * @returns {boolean} True is its been shadowed + */ + function paramIsShadowing(scope, name) { + return astUtils.getVariableByName(scope, name) !== null; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { - if (paramIsShadowing(scope, node.param.name)) { - context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.", - { name: node.param.name }); + CatchClause: function(node) { + var scope = context.getScope(); + + // When blockBindings is enabled, CatchClause creates its own scope + // so start from one upper scope to exclude the current node + if (scope.block === node) { + scope = scope.upper; + } + + if (paramIsShadowing(scope, node.param.name)) { + context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.", + { name: node.param.name }); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-class-assign.js b/tools/eslint/lib/rules/no-class-assign.js index 82f8e31fc3..1e4d3243d8 100644 --- a/tools/eslint/lib/rules/no-class-assign.js +++ b/tools/eslint/lib/rules/no-class-assign.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to disallow modifying variables of class declarations * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -12,37 +11,47 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - astUtils.getModifyingReferences(variable.references).forEach(function(reference) { - context.report( - reference.identifier, - "'{{name}}' is a class.", - {name: reference.identifier.name}); - - }); - } +module.exports = { + meta: { + docs: { + description: "disallow reassigning class members", + category: "ECMAScript 6", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils.getModifyingReferences(variable.references).forEach(function(reference) { + context.report( + reference.identifier, + "'{{name}}' is a class.", + {name: reference.identifier.name}); + + }); + } + + /** + * Finds and reports references that are non initializer and writable. + * @param {ASTNode} node - A ClassDeclaration/ClassExpression node to check. + * @returns {void} + */ + function checkForClass(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } + + return { + ClassDeclaration: checkForClass, + ClassExpression: checkForClass + }; - /** - * Finds and reports references that are non initializer and writable. - * @param {ASTNode} node - A ClassDeclaration/ClassExpression node to check. - * @returns {void} - */ - function checkForClass(node) { - context.getDeclaredVariables(node).forEach(checkVariable); } - - return { - "ClassDeclaration": checkForClass, - "ClassExpression": checkForClass - }; - }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-cond-assign.js b/tools/eslint/lib/rules/no-cond-assign.js index f4bb8425cc..27b99c6b54 100644 --- a/tools/eslint/lib/rules/no-cond-assign.js +++ b/tools/eslint/lib/rules/no-cond-assign.js @@ -5,130 +5,140 @@ "use strict"; var NODE_DESCRIPTIONS = { - "DoWhileStatement": "a 'do...while' statement", - "ForStatement": "a 'for' statement", - "IfStatement": "an 'if' statement", - "WhileStatement": "a 'while' statement" + DoWhileStatement: "a 'do...while' statement", + ForStatement: "a 'for' statement", + IfStatement: "an 'if' statement", + WhileStatement: "a 'while' statement" }; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var prohibitAssign = (context.options[0] || "except-parens"); - - /** - * Check whether an AST node is the test expression for a conditional statement. - * @param {!Object} node The node to test. - * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`. - */ - function isConditionalTestExpression(node) { - return node.parent && - node.parent.test && - node === node.parent.test; - } - - /** - * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement. - * @param {!Object} node The node to use at the start of the search. - * @returns {?Object} The closest ancestor node that represents a conditional statement. - */ - function findConditionalAncestor(node) { - var currentAncestor = node; - - do { - if (isConditionalTestExpression(currentAncestor)) { - return currentAncestor.parent; +module.exports = { + meta: { + docs: { + description: "disallow assignment operators in conditional expressions", + category: "Possible Errors", + recommended: true + }, + + schema: [ + { + enum: ["except-parens", "always"] } - } while ((currentAncestor = currentAncestor.parent)); + ] + }, + + create: function(context) { + + var prohibitAssign = (context.options[0] || "except-parens"); + + /** + * Check whether an AST node is the test expression for a conditional statement. + * @param {!Object} node The node to test. + * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`. + */ + function isConditionalTestExpression(node) { + return node.parent && + node.parent.test && + node === node.parent.test; + } - return null; - } + /** + * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement. + * @param {!Object} node The node to use at the start of the search. + * @returns {?Object} The closest ancestor node that represents a conditional statement. + */ + function findConditionalAncestor(node) { + var currentAncestor = node; + + do { + if (isConditionalTestExpression(currentAncestor)) { + return currentAncestor.parent; + } + } while ((currentAncestor = currentAncestor.parent)); + + return null; + } - /** - * Check whether the code represented by an AST node is enclosed in parentheses. - * @param {!Object} node The node to test. - * @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`. - */ - function isParenthesised(node) { - var previousToken = context.getTokenBefore(node), - nextToken = context.getTokenAfter(node); - - return previousToken.value === "(" && previousToken.range[1] <= node.range[0] && - nextToken.value === ")" && nextToken.range[0] >= node.range[1]; - } + /** + * Check whether the code represented by an AST node is enclosed in parentheses. + * @param {!Object} node The node to test. + * @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`. + */ + function isParenthesised(node) { + var previousToken = context.getTokenBefore(node), + nextToken = context.getTokenAfter(node); + + return previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } - /** - * Check whether the code represented by an AST node is enclosed in two sets of parentheses. - * @param {!Object} node The node to test. - * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`. - */ - function isParenthesisedTwice(node) { - var previousToken = context.getTokenBefore(node, 1), - nextToken = context.getTokenAfter(node, 1); - - return isParenthesised(node) && - previousToken.value === "(" && previousToken.range[1] <= node.range[0] && - nextToken.value === ")" && nextToken.range[0] >= node.range[1]; - } + /** + * Check whether the code represented by an AST node is enclosed in two sets of parentheses. + * @param {!Object} node The node to test. + * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`. + */ + function isParenthesisedTwice(node) { + var previousToken = context.getTokenBefore(node, 1), + nextToken = context.getTokenAfter(node, 1); + + return isParenthesised(node) && + previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } - /** - * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses. - * @param {!Object} node The node for the conditional statement. - * @returns {void} - */ - function testForAssign(node) { - if (node.test && - (node.test.type === "AssignmentExpression") && - (node.type === "ForStatement" ? - !isParenthesised(node.test) : - !isParenthesisedTwice(node.test) - ) - ) { - - // must match JSHint's error message - context.report({ - node: node, - loc: node.test.loc.start, - message: "Expected a conditional expression and instead saw an assignment." - }); + /** + * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses. + * @param {!Object} node The node for the conditional statement. + * @returns {void} + */ + function testForAssign(node) { + if (node.test && + (node.test.type === "AssignmentExpression") && + (node.type === "ForStatement" ? + !isParenthesised(node.test) : + !isParenthesisedTwice(node.test) + ) + ) { + + // must match JSHint's error message + context.report({ + node: node, + loc: node.test.loc.start, + message: "Expected a conditional expression and instead saw an assignment." + }); + } + } + + /** + * Check whether an assignment expression is descended from a conditional statement's test expression. + * @param {!Object} node The node for the assignment expression. + * @returns {void} + */ + function testForConditionalAncestor(node) { + var ancestor = findConditionalAncestor(node); + + if (ancestor) { + context.report(ancestor, "Unexpected assignment within {{type}}.", { + type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type + }); + } } - } - /** - * Check whether an assignment expression is descended from a conditional statement's test expression. - * @param {!Object} node The node for the assignment expression. - * @returns {void} - */ - function testForConditionalAncestor(node) { - var ancestor = findConditionalAncestor(node); - - if (ancestor) { - context.report(ancestor, "Unexpected assignment within {{type}}.", { - type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type - }); + if (prohibitAssign === "always") { + return { + AssignmentExpression: testForConditionalAncestor + }; } - } - if (prohibitAssign === "always") { return { - "AssignmentExpression": testForConditionalAncestor + DoWhileStatement: testForAssign, + ForStatement: testForAssign, + IfStatement: testForAssign, + WhileStatement: testForAssign }; - } - - return { - "DoWhileStatement": testForAssign, - "ForStatement": testForAssign, - "IfStatement": testForAssign, - "WhileStatement": testForAssign - }; -}; - -module.exports.schema = [ - { - "enum": ["except-parens", "always"] } -]; +}; diff --git a/tools/eslint/lib/rules/no-confusing-arrow.js b/tools/eslint/lib/rules/no-confusing-arrow.js index 8756cf431d..d951a53cf9 100644 --- a/tools/eslint/lib/rules/no-confusing-arrow.js +++ b/tools/eslint/lib/rules/no-confusing-arrow.js @@ -2,28 +2,6 @@ * @fileoverview A rule to warn against using arrow functions when they could be * confused with comparisions * @author Jxck - * @copyright 2015 Luke Karrys. All rights reserved. - * The MIT License (MIT) - - * Copyright (c) 2015 Jxck - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. */ "use strict"; @@ -47,31 +25,41 @@ function isConditional(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var config = context.options[0] || {}; +module.exports = { + meta: { + docs: { + description: "disallow arrow functions where they could be confused with comparisons", + category: "ECMAScript 6", + recommended: false + }, + + schema: [{ + type: "object", + properties: { + allowParens: {type: "boolean"} + }, + additionalProperties: false + }] + }, - /** - * Reports if an arrow function contains an ambiguous conditional. - * @param {ASTNode} node - A node to check and report. - * @returns {void} - */ - function checkArrowFunc(node) { - var body = node.body; + create: function(context) { + var config = context.options[0] || {}; - if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(context, body))) { - context.report(node, "Arrow function used ambiguously with a conditional expression."); + /** + * Reports if an arrow function contains an ambiguous conditional. + * @param {ASTNode} node - A node to check and report. + * @returns {void} + */ + function checkArrowFunc(node) { + var body = node.body; + + if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(context, body))) { + context.report(node, "Arrow function used ambiguously with a conditional expression."); + } } - } - return { - "ArrowFunctionExpression": checkArrowFunc - }; + return { + ArrowFunctionExpression: checkArrowFunc + }; + } }; - -module.exports.schema = [{ - type: "object", - properties: { - allowParens: {type: "boolean"} - }, - additionalProperties: false -}]; diff --git a/tools/eslint/lib/rules/no-console.js b/tools/eslint/lib/rules/no-console.js index 3efbbd4f24..18a897409f 100644 --- a/tools/eslint/lib/rules/no-console.js +++ b/tools/eslint/lib/rules/no-console.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag use of console object * @author Nicholas C. Zakas - * @copyright 2016 Eric Correia. All rights reserved. */ "use strict"; @@ -10,47 +9,57 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `console`", + category: "Possible Errors", + recommended: true + }, - return { + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { + type: "string" + }, + minItems: 1, + uniqueItems: true + } + }, + additionalProperties: false + } + ] + }, - "MemberExpression": function(node) { + create: function(context) { - if (node.object.name === "console") { - var blockConsole = true; + return { - if (context.options.length > 0) { - var allowedProperties = context.options[0].allow; - var passedProperty = node.property.name; - var propertyIsAllowed = (allowedProperties.indexOf(passedProperty) > -1); + MemberExpression: function(node) { - if (propertyIsAllowed) { - blockConsole = false; + if (node.object.name === "console") { + var blockConsole = true; + + if (context.options.length > 0) { + var allowedProperties = context.options[0].allow; + var passedProperty = node.property.name; + var propertyIsAllowed = (allowedProperties.indexOf(passedProperty) > -1); + + if (propertyIsAllowed) { + blockConsole = false; + } } - } - if (blockConsole) { - context.report(node, "Unexpected console statement."); + if (blockConsole) { + context.report(node, "Unexpected console statement."); + } } } - } - }; - -}; + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "allow": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "uniqueItems": true - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/no-const-assign.js b/tools/eslint/lib/rules/no-const-assign.js index d10e1b26d6..344e05a644 100644 --- a/tools/eslint/lib/rules/no-const-assign.js +++ b/tools/eslint/lib/rules/no-const-assign.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to disallow modifying variables that are declared using `const` * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -12,30 +11,40 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - astUtils.getModifyingReferences(variable.references).forEach(function(reference) { - context.report( - reference.identifier, - "'{{name}}' is constant.", - {name: reference.identifier.name}); - }); - } +module.exports = { + meta: { + docs: { + description: "disallow reassigning `const` variables", + category: "ECMAScript 6", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils.getModifyingReferences(variable.references).forEach(function(reference) { + context.report( + reference.identifier, + "'{{name}}' is constant.", + {name: reference.identifier.name}); + }); + } - return { - "VariableDeclaration": function(node) { - if (node.kind === "const") { - context.getDeclaredVariables(node).forEach(checkVariable); + return { + VariableDeclaration: function(node) { + if (node.kind === "const") { + context.getDeclaredVariables(node).forEach(checkVariable); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-constant-condition.js b/tools/eslint/lib/rules/no-constant-condition.js index a8251d1f95..0072491bee 100644 --- a/tools/eslint/lib/rules/no-constant-condition.js +++ b/tools/eslint/lib/rules/no-constant-condition.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag use constant conditions * @author Christian Schulz - * @copyright 2014 Christian Schulz. All rights reserved. */ "use strict"; @@ -10,69 +9,110 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Checks if a node has a constant truthiness value. - * @param {ASTNode} node The AST node to check. - * @returns {Bool} true when node's truthiness is constant - * @private - */ - function isConstant(node) { - switch (node.type) { - case "Literal": - case "ArrowFunctionExpression": - case "FunctionExpression": - case "ObjectExpression": - case "ArrayExpression": - return true; - - case "UnaryExpression": - return isConstant(node.argument); - - case "BinaryExpression": - case "LogicalExpression": - return isConstant(node.left) && isConstant(node.right) && node.operator !== "in"; - - case "AssignmentExpression": - return (node.operator === "=") && isConstant(node.right); - - case "SequenceExpression": - return isConstant(node.expressions[node.expressions.length - 1]); - - // no default +module.exports = { + meta: { + docs: { + description: "disallow constant expressions in conditions", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + + /** + * Checks if a branch node of LogicalExpression short circuits the whole condition + * @param {ASTNode} node The branch of main condition which needs to be checked + * @param {string} operator The operator of the main LogicalExpression. + * @returns {boolean} true when condition short circuits whole condition + */ + function isLogicalIdentity(node, operator) { + switch (node.type) { + case "Literal": + return (operator === "||" && node.value === true) || + (operator === "&&" && node.value === false); + case "LogicalExpression": + return isLogicalIdentity(node.left, node.operator) || + isLogicalIdentity(node.right, node.operator); + + // no default + } + return false; } - return false; - } - /** - * Reports when the given node contains a constant condition. - * @param {ASTNode} node The AST node to check. - * @returns {void} - * @private - */ - function checkConstantCondition(node) { - if (node.test && isConstant(node.test)) { - context.report(node, "Unexpected constant condition."); + /** + * Checks if a node has a constant truthiness value. + * @param {ASTNode} node The AST node to check. + * @param {boolean} inBooleanPosition `false` if checking branch of a condition. + * `true` in all other cases + * @returns {Bool} true when node's truthiness is constant + * @private + */ + function isConstant(node, inBooleanPosition) { + switch (node.type) { + case "Literal": + case "ArrowFunctionExpression": + case "FunctionExpression": + case "ObjectExpression": + case "ArrayExpression": + return true; + + case "UnaryExpression": + return (node.operator === "typeof" && inBooleanPosition) || + isConstant(node.argument, true); + + case "BinaryExpression": + return isConstant(node.left, false) && + isConstant(node.right, false) && + node.operator !== "in"; + case "LogicalExpression": + var isLeftConstant = isConstant(node.left, inBooleanPosition); + var isRightConstant = isConstant(node.right, inBooleanPosition); + var isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator)); + var isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator)); + + return (isLeftConstant && isRightConstant) || isLeftShortCircuit || isRightShortCircuit; + case "AssignmentExpression": + return (node.operator === "=") && isConstant(node.right, inBooleanPosition); + + case "SequenceExpression": + return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition); + + // no default + } + return false; } - } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + /** + * Reports when the given node contains a constant condition. + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function checkConstantCondition(node) { + if (node.test && isConstant(node.test, true)) { + context.report(node, "Unexpected constant condition."); + } + } - return { - "ConditionalExpression": checkConstantCondition, - "IfStatement": checkConstantCondition, - "WhileStatement": checkConstantCondition, - "DoWhileStatement": checkConstantCondition, - "ForStatement": checkConstantCondition - }; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- -}; + return { + ConditionalExpression: checkConstantCondition, + IfStatement: checkConstantCondition, + WhileStatement: checkConstantCondition, + DoWhileStatement: checkConstantCondition, + ForStatement: checkConstantCondition + }; -module.exports.schema = []; + } +}; diff --git a/tools/eslint/lib/rules/no-continue.js b/tools/eslint/lib/rules/no-continue.js index 89fa1848e9..246df89ebe 100644 --- a/tools/eslint/lib/rules/no-continue.js +++ b/tools/eslint/lib/rules/no-continue.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag use of continue statement * @author Borislav Zhivkov - * @copyright 2015 Borislav Zhivkov. All rights reserved. */ "use strict"; @@ -10,14 +9,24 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `continue` statements", + category: "Stylistic Issues", + recommended: false + }, - return { - "ContinueStatement": function(node) { - context.report(node, "Unexpected use of continue statement"); - } - }; + schema: [] + }, -}; + create: function(context) { + + return { + ContinueStatement: function(node) { + context.report(node, "Unexpected use of continue statement"); + } + }; -module.exports.schema = []; + } +}; diff --git a/tools/eslint/lib/rules/no-control-regex.js b/tools/eslint/lib/rules/no-control-regex.js index 98d2284019..74e03f03c5 100644 --- a/tools/eslint/lib/rules/no-control-regex.js +++ b/tools/eslint/lib/rules/no-control-regex.js @@ -9,51 +9,87 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Get the regex expression - * @param {ASTNode} node node to evaluate - * @returns {*} Regex if found else null - * @private - */ - function getRegExp(node) { - if (node.value instanceof RegExp) { - return node.value; - } else if (typeof node.value === "string") { - - var parent = context.getAncestors().pop(); - - if ((parent.type === "NewExpression" || parent.type === "CallExpression") && - parent.callee.type === "Identifier" && parent.callee.name === "RegExp" - ) { - - // there could be an invalid regular expression string - try { - return new RegExp(node.value); - } catch (ex) { - return null; +module.exports = { + meta: { + docs: { + description: "disallow control characters in regular expressions", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Get the regex expression + * @param {ASTNode} node node to evaluate + * @returns {*} Regex if found else null + * @private + */ + function getRegExp(node) { + if (node.value instanceof RegExp) { + return node.value; + } else if (typeof node.value === "string") { + + var parent = context.getAncestors().pop(); + + if ((parent.type === "NewExpression" || parent.type === "CallExpression") && + parent.callee.type === "Identifier" && parent.callee.name === "RegExp" + ) { + + // there could be an invalid regular expression string + try { + return new RegExp(node.value); + } catch (ex) { + return null; + } } } + + return null; } - return null; - } + /** + * Check if given regex string has control characters in it + * @param {String} regexStr regex as string to check + * @returns {Boolean} returns true if finds control characters on given string + * @private + */ + function hasControlCharacters(regexStr) { - return { - "Literal": function(node) { - var computedValue, - regex = getRegExp(node); + // check control characters, if RegExp object used + var hasControlChars = /[\x00-\x1f]/.test(regexStr); // eslint-disable-line no-control-regex - if (regex) { - computedValue = regex.toString(); - if (/[\x00-\x1f]/.test(computedValue)) { - context.report(node, "Unexpected control character in regular expression."); - } + // check substr, if regex literal used + var subStrIndex = regexStr.search(/\\x[01][0-9a-f]/i); + + if (!hasControlChars && subStrIndex > -1) { + + // is it escaped, check backslash count + var possibleEscapeCharacters = regexStr.substr(0, subStrIndex).match(/\\+$/gi); + + hasControlChars = possibleEscapeCharacters === null || !(possibleEscapeCharacters[0].length % 2); } + + return hasControlChars; } - }; -}; + return { + Literal: function(node) { + var computedValue, + regex = getRegExp(node); + + if (regex) { + computedValue = regex.toString(); -module.exports.schema = []; + if (hasControlCharacters(computedValue)) { + context.report(node, "Unexpected control character in regular expression."); + } + } + } + }; + + } +}; diff --git a/tools/eslint/lib/rules/no-debugger.js b/tools/eslint/lib/rules/no-debugger.js index 7d86e76d4f..552294bd67 100644 --- a/tools/eslint/lib/rules/no-debugger.js +++ b/tools/eslint/lib/rules/no-debugger.js @@ -9,14 +9,24 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `debugger`", + category: "Possible Errors", + recommended: true + }, - return { - "DebuggerStatement": function(node) { - context.report(node, "Unexpected 'debugger' statement."); - } - }; + schema: [] + }, -}; + create: function(context) { + + return { + DebuggerStatement: function(node) { + context.report(node, "Unexpected 'debugger' statement."); + } + }; -module.exports.schema = []; + } +}; diff --git a/tools/eslint/lib/rules/no-delete-var.js b/tools/eslint/lib/rules/no-delete-var.js index d6ffbd107a..c1d717a15b 100644 --- a/tools/eslint/lib/rules/no-delete-var.js +++ b/tools/eslint/lib/rules/no-delete-var.js @@ -9,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow deleting variables", + category: "Variables", + recommended: true + }, - return { + schema: [] + }, - "UnaryExpression": function(node) { - if (node.operator === "delete" && node.argument.type === "Identifier") { - context.report(node, "Variables should not be deleted."); + create: function(context) { + + return { + + UnaryExpression: function(node) { + if (node.operator === "delete" && node.argument.type === "Identifier") { + context.report(node, "Variables should not be deleted."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-div-regex.js b/tools/eslint/lib/rules/no-div-regex.js index 61e7a1c4d8..58c44662cb 100644 --- a/tools/eslint/lib/rules/no-div-regex.js +++ b/tools/eslint/lib/rules/no-div-regex.js @@ -9,19 +9,29 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow division operators explicitly at the beginning of regular expressions", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "Literal": function(node) { - var token = context.getFirstToken(node); + create: function(context) { - if (token.type === "RegularExpression" && token.value[1] === "=") { - context.report(node, "A regular expression literal can be confused with '/='."); + return { + + Literal: function(node) { + var token = context.getFirstToken(node); + + if (token.type === "RegularExpression" && token.value[1] === "=") { + context.report(node, "A regular expression literal can be confused with '/='."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-dupe-args.js b/tools/eslint/lib/rules/no-dupe-args.js index 5de9b500b6..e927ce2b3a 100644 --- a/tools/eslint/lib/rules/no-dupe-args.js +++ b/tools/eslint/lib/rules/no-dupe-args.js @@ -1,9 +1,6 @@ /** * @fileoverview Rule to flag duplicate arguments * @author Jamund Ferguson - * @copyright 2015 Jamund Ferguson. All rights reserved. - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -12,64 +9,65 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow duplicate arguments in `function` definitions", + category: "Possible Errors", + recommended: true + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + schema: [] + }, - /** - * Checks whether or not a given definition is a parameter's. - * @param {escope.DefEntry} def - A definition to check. - * @returns {boolean} `true` if the definition is a parameter's. - */ - function isParameter(def) { - return def.type === "Parameter"; - } + create: function(context) { - /** - * Determines if a given node has duplicate parameters. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function checkParams(node) { - var variables = context.getDeclaredVariables(node); - var keyMap = Object.create(null); + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - for (var i = 0; i < variables.length; ++i) { - var variable = variables[i]; + /** + * Checks whether or not a given definition is a parameter's. + * @param {escope.DefEntry} def - A definition to check. + * @returns {boolean} `true` if the definition is a parameter's. + */ + function isParameter(def) { + return def.type === "Parameter"; + } - // TODO(nagashima): Remove this duplication check after https://github.com/estools/escope/pull/79 - var key = "$" + variable.name; // to avoid __proto__. + /** + * Determines if a given node has duplicate parameters. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkParams(node) { + var variables = context.getDeclaredVariables(node); - if (!isParameter(variable.defs[0]) || keyMap[key]) { - continue; - } - keyMap[key] = true; + for (var i = 0; i < variables.length; ++i) { + var variable = variables[i]; - // Checks and reports duplications. - var defs = variable.defs.filter(isParameter); + // Checks and reports duplications. + var defs = variable.defs.filter(isParameter); - if (defs.length >= 2) { - context.report({ - node: node, - message: "Duplicate param '{{name}}'.", - data: {name: variable.name} - }); + if (defs.length >= 2) { + context.report({ + node: node, + message: "Duplicate param '{{name}}'.", + data: {name: variable.name} + }); + } } } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - return { - "FunctionDeclaration": checkParams, - "FunctionExpression": checkParams - }; + return { + FunctionDeclaration: checkParams, + FunctionExpression: checkParams + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-dupe-class-members.js b/tools/eslint/lib/rules/no-dupe-class-members.js index d61b05136d..883020bdfe 100644 --- a/tools/eslint/lib/rules/no-dupe-class-members.js +++ b/tools/eslint/lib/rules/no-dupe-class-members.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to disallow duplicate name in class members. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -10,91 +9,101 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var stack = []; - - /** - * Gets state of a given member name. - * @param {string} name - A name of a member. - * @param {boolean} isStatic - A flag which specifies that is a static member. - * @returns {object} A state of a given member name. - * - retv.init {boolean} A flag which shows the name is declared as normal member. - * - retv.get {boolean} A flag which shows the name is declared as getter. - * - retv.set {boolean} A flag which shows the name is declared as setter. - */ - function getState(name, isStatic) { - var stateMap = stack[stack.length - 1]; - var key = "$" + name; // to avoid "__proto__". - - if (!stateMap[key]) { - stateMap[key] = { - nonStatic: {init: false, get: false, set: false}, - static: {init: false, get: false, set: false} - }; - } - - return stateMap[key][isStatic ? "static" : "nonStatic"]; - } - - /** - * Gets the name text of a given node. - * - * @param {ASTNode} node - A node to get the name. - * @returns {string} The name text of the node. - */ - function getName(node) { - switch (node.type) { - case "Identifier": return node.name; - case "Literal": return String(node.value); - - /* istanbul ignore next: syntax error */ - default: return ""; - } - } - - return { - - // Initializes the stack of state of member declarations. - "Program": function() { - stack = []; - }, - - // Initializes state of member declarations for the class. - "ClassBody": function() { - stack.push(Object.create(null)); - }, - - // Disposes the state for the class. - "ClassBody:exit": function() { - stack.pop(); +module.exports = { + meta: { + docs: { + description: "disallow duplicate class members", + category: "ECMAScript 6", + recommended: true }, - // Reports the node if its name has been declared already. - "MethodDefinition": function(node) { - if (node.computed) { - return; + schema: [] + }, + + create: function(context) { + var stack = []; + + /** + * Gets state of a given member name. + * @param {string} name - A name of a member. + * @param {boolean} isStatic - A flag which specifies that is a static member. + * @returns {object} A state of a given member name. + * - retv.init {boolean} A flag which shows the name is declared as normal member. + * - retv.get {boolean} A flag which shows the name is declared as getter. + * - retv.set {boolean} A flag which shows the name is declared as setter. + */ + function getState(name, isStatic) { + var stateMap = stack[stack.length - 1]; + var key = "$" + name; // to avoid "__proto__". + + if (!stateMap[key]) { + stateMap[key] = { + nonStatic: {init: false, get: false, set: false}, + static: {init: false, get: false, set: false} + }; } - var name = getName(node.key); - var state = getState(name, node.static); - var isDuplicate = false; - - if (node.kind === "get") { - isDuplicate = (state.init || state.get); - state.get = true; - } else if (node.kind === "set") { - isDuplicate = (state.init || state.set); - state.set = true; - } else { - isDuplicate = (state.init || state.get || state.set); - state.init = true; - } + return stateMap[key][isStatic ? "static" : "nonStatic"]; + } - if (isDuplicate) { - context.report(node, "Duplicate name '{{name}}'.", {name: name}); + /** + * Gets the name text of a given node. + * + * @param {ASTNode} node - A node to get the name. + * @returns {string} The name text of the node. + */ + function getName(node) { + switch (node.type) { + case "Identifier": return node.name; + case "Literal": return String(node.value); + + /* istanbul ignore next: syntax error */ + default: return ""; } } - }; -}; -module.exports.schema = []; + return { + + // Initializes the stack of state of member declarations. + Program: function() { + stack = []; + }, + + // Initializes state of member declarations for the class. + ClassBody: function() { + stack.push(Object.create(null)); + }, + + // Disposes the state for the class. + "ClassBody:exit": function() { + stack.pop(); + }, + + // Reports the node if its name has been declared already. + MethodDefinition: function(node) { + if (node.computed) { + return; + } + + var name = getName(node.key); + var state = getState(name, node.static); + var isDuplicate = false; + + if (node.kind === "get") { + isDuplicate = (state.init || state.get); + state.get = true; + } else if (node.kind === "set") { + isDuplicate = (state.init || state.set); + state.set = true; + } else { + isDuplicate = (state.init || state.get || state.set); + state.init = true; + } + + if (isDuplicate) { + context.report(node, "Duplicate name '{{name}}'.", {name: name}); + } + } + }; + } +}; diff --git a/tools/eslint/lib/rules/no-dupe-keys.js b/tools/eslint/lib/rules/no-dupe-keys.js index e07f081b4b..26f009b3bf 100644 --- a/tools/eslint/lib/rules/no-dupe-keys.js +++ b/tools/eslint/lib/rules/no-dupe-keys.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag use of duplicate keys in an object. * @author Ian Christian Myers - * @copyright 2013 Ian Christian Myers. All rights reserved. - * @copyright 2013 Nicholas C. Zakas. All rights reserved. */ "use strict"; @@ -11,38 +9,48 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow duplicate keys in object literals", + category: "Possible Errors", + recommended: true + }, - return { + schema: [] + }, - "ObjectExpression": function(node) { + create: function(context) { - // Object that will be a map of properties--safe because we will - // prefix all of the keys. - var nodeProps = Object.create(null); + return { - node.properties.forEach(function(property) { + ObjectExpression: function(node) { - if (property.type !== "Property") { - return; - } + // Object that will be a map of properties--safe because we will + // prefix all of the keys. + var nodeProps = Object.create(null); - var keyName = property.key.name || property.key.value, - key = property.kind + "-" + keyName, - checkProperty = (!property.computed || property.key.type === "Literal"); + node.properties.forEach(function(property) { - if (checkProperty) { - if (nodeProps[key]) { - context.report(node, property.loc.start, "Duplicate key '{{key}}'.", { key: keyName }); - } else { - nodeProps[key] = true; + if (property.type !== "Property") { + return; } - } - }); - } - }; + var keyName = property.key.name || property.key.value, + key = property.kind + "-" + keyName, + checkProperty = (!property.computed || property.key.type === "Literal"); -}; + if (checkProperty) { + if (nodeProps[key]) { + context.report(node, property.loc.start, "Duplicate key '{{key}}'.", { key: keyName }); + } else { + nodeProps[key] = true; + } + } + }); -module.exports.schema = []; + } + }; + + } +}; diff --git a/tools/eslint/lib/rules/no-duplicate-case.js b/tools/eslint/lib/rules/no-duplicate-case.js index d56fca7696..1308730a63 100644 --- a/tools/eslint/lib/rules/no-duplicate-case.js +++ b/tools/eslint/lib/rules/no-duplicate-case.js @@ -1,9 +1,7 @@ /** * @fileoverview Rule to disallow a duplicate case label. - * @author Dieter Oberkofler + * @author Dieter Oberkofler * @author Burak Yigit Kaya - * @copyright 2015 Dieter Oberkofler. All rights reserved. - * @copyright 2015 Burak Yigit Kaya. All rights reserved. */ "use strict"; @@ -12,23 +10,33 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow duplicate case labels", + category: "Possible Errors", + recommended: true + }, - return { - "SwitchStatement": function(node) { - var mapping = {}; + schema: [] + }, - node.cases.forEach(function(switchCase) { - var key = context.getSource(switchCase.test); + create: function(context) { - if (mapping[key]) { - context.report(switchCase, "Duplicate case label."); - } else { - mapping[key] = switchCase; - } - }); - } - }; -}; + return { + SwitchStatement: function(node) { + var mapping = {}; + + node.cases.forEach(function(switchCase) { + var key = context.getSource(switchCase.test); -module.exports.schema = []; + if (mapping[key]) { + context.report(switchCase, "Duplicate case label."); + } else { + mapping[key] = switchCase; + } + }); + } + }; + } +}; diff --git a/tools/eslint/lib/rules/no-duplicate-imports.js b/tools/eslint/lib/rules/no-duplicate-imports.js index 6c0edbb999..44432f4863 100644 --- a/tools/eslint/lib/rules/no-duplicate-imports.js +++ b/tools/eslint/lib/rules/no-duplicate-imports.js @@ -1,8 +1,6 @@ /** * @fileoverview Restrict usage of duplicate imports. * @author Simen Bekkhus - * @copyright 2016 Simen Bekkhus. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -98,29 +96,39 @@ function handleExports(context, importsInFile, exportsInFile) { }; } -module.exports = function(context) { - var includeExports = (context.options[0] || {}).includeExports, - importsInFile = [], - exportsInFile = []; +module.exports = { + meta: { + docs: { + description: "disallow duplicate module imports", + category: "ECMAScript 6", + recommended: false + }, + + schema: [{ + type: "object", + properties: { + includeExports: { + type: "boolean" + } + }, + additionalProperties: false + }] + }, - var handlers = { - "ImportDeclaration": handleImports(context, includeExports, importsInFile, exportsInFile) - }; + create: function(context) { + var includeExports = (context.options[0] || {}).includeExports, + importsInFile = [], + exportsInFile = []; - if (includeExports) { - handlers.ExportNamedDeclaration = handleExports(context, importsInFile, exportsInFile); - handlers.ExportAllDeclaration = handleExports(context, importsInFile, exportsInFile); - } - - return handlers; -}; + var handlers = { + ImportDeclaration: handleImports(context, includeExports, importsInFile, exportsInFile) + }; -module.exports.schema = [{ - "type": "object", - "properties": { - "includeExports": { - "type": "boolean" + if (includeExports) { + handlers.ExportNamedDeclaration = handleExports(context, importsInFile, exportsInFile); + handlers.ExportAllDeclaration = handleExports(context, importsInFile, exportsInFile); } - }, - "additionalProperties": false -}]; + + return handlers; + } +}; diff --git a/tools/eslint/lib/rules/no-else-return.js b/tools/eslint/lib/rules/no-else-return.js index 33a519e4e0..4678d320af 100644 --- a/tools/eslint/lib/rules/no-else-return.js +++ b/tools/eslint/lib/rules/no-else-return.js @@ -9,140 +9,150 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Display the context report if rule is violated - * - * @param {Node} node The 'else' node - * @returns {void} - */ - function displayReport(node) { - context.report(node, "Unexpected 'else' after 'return'."); - } +module.exports = { + meta: { + docs: { + description: "disallow `else` blocks after `return` statements in `if` statements", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Display the context report if rule is violated + * + * @param {Node} node The 'else' node + * @returns {void} + */ + function displayReport(node) { + context.report(node, "Unexpected 'else' after 'return'."); + } - /** - * Check to see if the node is a ReturnStatement - * - * @param {Node} node The node being evaluated - * @returns {boolean} True if node is a return - */ - function checkForReturn(node) { - return node.type === "ReturnStatement"; - } + /** + * Check to see if the node is a ReturnStatement + * + * @param {Node} node The node being evaluated + * @returns {boolean} True if node is a return + */ + function checkForReturn(node) { + return node.type === "ReturnStatement"; + } - /** - * Naive return checking, does not iterate through the whole - * BlockStatement because we make the assumption that the ReturnStatement - * will be the last node in the body of the BlockStatement. - * - * @param {Node} node The consequent/alternate node - * @returns {boolean} True if it has a return - */ - function naiveHasReturn(node) { - if (node.type === "BlockStatement") { - var body = node.body, - lastChildNode = body[body.length - 1]; - - return lastChildNode && checkForReturn(lastChildNode); + /** + * Naive return checking, does not iterate through the whole + * BlockStatement because we make the assumption that the ReturnStatement + * will be the last node in the body of the BlockStatement. + * + * @param {Node} node The consequent/alternate node + * @returns {boolean} True if it has a return + */ + function naiveHasReturn(node) { + if (node.type === "BlockStatement") { + var body = node.body, + lastChildNode = body[body.length - 1]; + + return lastChildNode && checkForReturn(lastChildNode); + } + return checkForReturn(node); } - return checkForReturn(node); - } - /** - * Check to see if the node is valid for evaluation, - * meaning it has an else and not an else-if - * - * @param {Node} node The node being evaluated - * @returns {boolean} True if the node is valid - */ - function hasElse(node) { - return node.alternate && node.consequent && node.alternate.type !== "IfStatement"; - } + /** + * Check to see if the node is valid for evaluation, + * meaning it has an else and not an else-if + * + * @param {Node} node The node being evaluated + * @returns {boolean} True if the node is valid + */ + function hasElse(node) { + return node.alternate && node.consequent && node.alternate.type !== "IfStatement"; + } - /** - * If the consequent is an IfStatement, check to see if it has an else - * and both its consequent and alternate path return, meaning this is - * a nested case of rule violation. If-Else not considered currently. - * - * @param {Node} node The consequent node - * @returns {boolean} True if this is a nested rule violation - */ - function checkForIf(node) { - return node.type === "IfStatement" && hasElse(node) && - naiveHasReturn(node.alternate) && naiveHasReturn(node.consequent); - } + /** + * If the consequent is an IfStatement, check to see if it has an else + * and both its consequent and alternate path return, meaning this is + * a nested case of rule violation. If-Else not considered currently. + * + * @param {Node} node The consequent node + * @returns {boolean} True if this is a nested rule violation + */ + function checkForIf(node) { + return node.type === "IfStatement" && hasElse(node) && + naiveHasReturn(node.alternate) && naiveHasReturn(node.consequent); + } - /** - * Check the consequent/body node to make sure it is not - * a ReturnStatement or an IfStatement that returns on both - * code paths. - * - * @param {Node} node The consequent or body node - * @param {Node} alternate The alternate node - * @returns {boolean} `true` if it is a Return/If node that always returns. - */ - function checkForReturnOrIf(node) { - return checkForReturn(node) || checkForIf(node); - } + /** + * Check the consequent/body node to make sure it is not + * a ReturnStatement or an IfStatement that returns on both + * code paths. + * + * @param {Node} node The consequent or body node + * @param {Node} alternate The alternate node + * @returns {boolean} `true` if it is a Return/If node that always returns. + */ + function checkForReturnOrIf(node) { + return checkForReturn(node) || checkForIf(node); + } - /** - * Check whether a node returns in every codepath. - * @param {Node} node The node to be checked - * @returns {boolean} `true` if it returns on every codepath. - */ - function alwaysReturns(node) { - if (node.type === "BlockStatement") { + /** + * Check whether a node returns in every codepath. + * @param {Node} node The node to be checked + * @returns {boolean} `true` if it returns on every codepath. + */ + function alwaysReturns(node) { + if (node.type === "BlockStatement") { - // If we have a BlockStatement, check each consequent body node. - return node.body.some(checkForReturnOrIf); - } else { + // If we have a BlockStatement, check each consequent body node. + return node.body.some(checkForReturnOrIf); + } else { - /* - * If not a block statement, make sure the consequent isn't a - * ReturnStatement or an IfStatement with returns on both paths. - */ - return checkForReturnOrIf(node); + /* + * If not a block statement, make sure the consequent isn't a + * ReturnStatement or an IfStatement with returns on both paths. + */ + return checkForReturnOrIf(node); + } } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - return { + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - "IfStatement": function(node) { - var parent = context.getAncestors().pop(), - consequents, - alternate; + return { - // Only "top-level" if statements are checked, meaning the first `if` - // in a `if-else-if-...` chain. - if (parent.type === "IfStatement" && parent.alternate === node) { - return; - } + IfStatement: function(node) { + var parent = context.getAncestors().pop(), + consequents, + alternate; - for (consequents = []; node.type === "IfStatement"; node = node.alternate) { - if (!node.alternate) { + // Only "top-level" if statements are checked, meaning the first `if` + // in a `if-else-if-...` chain. + if (parent.type === "IfStatement" && parent.alternate === node) { return; } - consequents.push(node.consequent); - alternate = node.alternate; - } - if (consequents.every(alwaysReturns)) { - displayReport(alternate); + for (consequents = []; node.type === "IfStatement"; node = node.alternate) { + if (!node.alternate) { + return; + } + consequents.push(node.consequent); + alternate = node.alternate; + } + + if (consequents.every(alwaysReturns)) { + displayReport(alternate); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-empty-character-class.js b/tools/eslint/lib/rules/no-empty-character-class.js index d6341a124a..e015e0cc06 100644 --- a/tools/eslint/lib/rules/no-empty-character-class.js +++ b/tools/eslint/lib/rules/no-empty-character-class.js @@ -27,20 +27,30 @@ var regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+\])*\/[gimuy]*$/; // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow empty character classes in regular expressions", + category: "Possible Errors", + recommended: true + }, - return { + schema: [] + }, - "Literal": function(node) { - var token = context.getFirstToken(node); + create: function(context) { - if (token.type === "RegularExpression" && !regex.test(token.value)) { - context.report(node, "Empty class."); + return { + + Literal: function(node) { + var token = context.getFirstToken(node); + + if (token.type === "RegularExpression" && !regex.test(token.value)) { + context.report(node, "Empty class."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-empty-function.js b/tools/eslint/lib/rules/no-empty-function.js index 9ff304d4d7..412614b501 100644 --- a/tools/eslint/lib/rules/no-empty-function.js +++ b/tools/eslint/lib/rules/no-empty-function.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to disallow empty functions. * @author Toru Nagashima - * @copyright 2016 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -96,55 +94,65 @@ function getKind(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0] || {}; - var allowed = options.allow || []; - - /** - * Reports a given function node if the node matches the following patterns. - * - * - Not allowed by options. - * - The body is empty. - * - The body doesn't have any comments. - * - * @param {ASTNode} node - A function node to report. This is one of - * an ArrowFunctionExpression, a FunctionDeclaration, or a - * FunctionExpression. - * @returns {void} - */ - function reportIfEmpty(node) { - var kind = getKind(node); - - if (allowed.indexOf(kind) === -1 && - node.body.type === "BlockStatement" && - node.body.body.length === 0 && - context.getComments(node.body).trailing.length === 0 - ) { - context.report({ - node: node, - loc: node.body.loc.start, - message: "Unexpected empty " + SHOW_KIND[kind] + "." - }); - } - } - - return { - ArrowFunctionExpression: reportIfEmpty, - FunctionDeclaration: reportIfEmpty, - FunctionExpression: reportIfEmpty - }; -}; +module.exports = { + meta: { + docs: { + description: "disallow empty functions", + category: "Best Practices", + recommended: false + }, -module.exports.schema = [ - { - type: "object", - properties: { - allow: { - type: "array", - items: {enum: ALLOW_OPTIONS}, - uniqueItems: true + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: {enum: ALLOW_OPTIONS}, + uniqueItems: true + } + }, + additionalProperties: false } - }, - additionalProperties: false + ] + }, + + create: function(context) { + var options = context.options[0] || {}; + var allowed = options.allow || []; + + /** + * Reports a given function node if the node matches the following patterns. + * + * - Not allowed by options. + * - The body is empty. + * - The body doesn't have any comments. + * + * @param {ASTNode} node - A function node to report. This is one of + * an ArrowFunctionExpression, a FunctionDeclaration, or a + * FunctionExpression. + * @returns {void} + */ + function reportIfEmpty(node) { + var kind = getKind(node); + + if (allowed.indexOf(kind) === -1 && + node.body.type === "BlockStatement" && + node.body.body.length === 0 && + context.getComments(node.body).trailing.length === 0 + ) { + context.report({ + node: node, + loc: node.body.loc.start, + message: "Unexpected empty " + SHOW_KIND[kind] + "." + }); + } + } + + return { + ArrowFunctionExpression: reportIfEmpty, + FunctionDeclaration: reportIfEmpty, + FunctionExpression: reportIfEmpty + }; } -]; +}; diff --git a/tools/eslint/lib/rules/no-empty-pattern.js b/tools/eslint/lib/rules/no-empty-pattern.js index aa8515ad19..afd8b1e393 100644 --- a/tools/eslint/lib/rules/no-empty-pattern.js +++ b/tools/eslint/lib/rules/no-empty-pattern.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to disallow an empty pattern * @author Alberto Rodríguez - * @copyright 2015 Alberto Rodríguez. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -10,19 +8,29 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - return { - "ObjectPattern": function(node) { - if (node.properties.length === 0) { - context.report(node, "Unexpected empty object pattern."); - } +module.exports = { + meta: { + docs: { + description: "disallow empty destructuring patterns", + category: "Best Practices", + recommended: true }, - "ArrayPattern": function(node) { - if (node.elements.length === 0) { - context.report(node, "Unexpected empty array pattern."); + + schema: [] + }, + + create: function(context) { + return { + ObjectPattern: function(node) { + if (node.properties.length === 0) { + context.report(node, "Unexpected empty object pattern."); + } + }, + ArrayPattern: function(node) { + if (node.elements.length === 0) { + context.report(node, "Unexpected empty array pattern."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-empty.js b/tools/eslint/lib/rules/no-empty.js index df40433d7b..8f32428304 100644 --- a/tools/eslint/lib/rules/no-empty.js +++ b/tools/eslint/lib/rules/no-empty.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag use of an empty block statement * @author Nicholas C. Zakas - * @copyright Nicholas C. Zakas. All rights reserved. - * @copyright 2015 Dieter Oberkofler. All rights reserved. */ "use strict"; @@ -12,36 +10,63 @@ var FUNCTION_TYPE = /^(?:ArrowFunctionExpression|Function(?:Declaration|Expression))$/; -module.exports = function(context) { - return { - "BlockStatement": function(node) { +module.exports = { + meta: { + docs: { + description: "disallow empty block statements", + category: "Possible Errors", + recommended: true + }, - // if the body is not empty, we can just return immediately - if (node.body.length !== 0) { - return; + schema: [ + { + type: "object", + properties: { + allowEmptyCatch: { + type: "boolean" + } + }, + additionalProperties: false } + ] + }, - // a function is generally allowed to be empty - if (FUNCTION_TYPE.test(node.parent.type)) { - return; - } + create: function(context) { + var options = context.options[0] || {}, + allowEmptyCatch = options.allowEmptyCatch || false; - // any other block is only allowed to be empty, if it contains a comment - if (context.getComments(node).trailing.length > 0) { - return; - } + return { + BlockStatement: function(node) { - context.report(node, "Empty block statement."); - }, + // if the body is not empty, we can just return immediately + if (node.body.length !== 0) { + return; + } + + // a function is generally allowed to be empty + if (FUNCTION_TYPE.test(node.parent.type)) { + return; + } - "SwitchStatement": function(node) { + if (allowEmptyCatch && node.parent.type === "CatchClause") { + return; + } - if (typeof node.cases === "undefined" || node.cases.length === 0) { - context.report(node, "Empty switch statement."); + // any other block is only allowed to be empty, if it contains a comment + if (context.getComments(node).trailing.length > 0) { + return; + } + + context.report(node, "Empty block statement."); + }, + + SwitchStatement: function(node) { + + if (typeof node.cases === "undefined" || node.cases.length === 0) { + context.report(node, "Empty switch statement."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-eq-null.js b/tools/eslint/lib/rules/no-eq-null.js index 92d88920ae..da039bb9d7 100644 --- a/tools/eslint/lib/rules/no-eq-null.js +++ b/tools/eslint/lib/rules/no-eq-null.js @@ -10,20 +10,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `null` comparisons without type-checking operators", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "BinaryExpression": function(node) { - var badOperator = node.operator === "==" || node.operator === "!="; + create: function(context) { - if (node.right.type === "Literal" && node.right.raw === "null" && badOperator || - node.left.type === "Literal" && node.left.raw === "null" && badOperator) { - context.report(node, "Use ‘===’ to compare with ‘null’."); + return { + + BinaryExpression: function(node) { + var badOperator = node.operator === "==" || node.operator === "!="; + + if (node.right.type === "Literal" && node.right.raw === "null" && badOperator || + node.left.type === "Literal" && node.left.raw === "null" && badOperator) { + context.report(node, "Use ‘===’ to compare with ‘null’."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-eval.js b/tools/eslint/lib/rules/no-eval.js index 7c47c892f9..04db4b96b4 100644 --- a/tools/eslint/lib/rules/no-eval.js +++ b/tools/eslint/lib/rules/no-eval.js @@ -1,9 +1,6 @@ /** * @fileoverview Rule to flag use of eval() statement * @author Nicholas C. Zakas - * @copyright 2015 Toru Nagashima. All rights reserved. - * @copyright 2015 Mathias Schreck. All rights reserved. - * @copyright 2013 Nicholas C. Zakas. All rights reserved. */ "use strict"; @@ -77,140 +74,170 @@ function isMember(node, name) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var allowIndirect = Boolean( - context.options[0] && - context.options[0].allowIndirect - ); - var sourceCode = context.getSourceCode(); - var funcInfo = null; - - /** - * Pushs a variable scope (Program or Function) information to the stack. - * - * This is used in order to check whether or not `this` binding is a - * reference to the global object. - * - * @param {ASTNode} node - A node of the scope. This is one of Program, - * FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression. - * @returns {void} - */ - function enterVarScope(node) { - var strict = context.getScope().isStrict; - - funcInfo = { - upper: funcInfo, - node: node, - strict: strict, - defaultThis: false, - initialized: strict - }; - } +module.exports = { + meta: { + docs: { + description: "disallow the use of `eval()`", + category: "Best Practices", + recommended: false + }, - /** - * Pops a variable scope from the stack. - * - * @returns {void} - */ - function exitVarScope() { - funcInfo = funcInfo.upper; - } + schema: [ + { + type: "object", + properties: { + allowIndirect: {type: "boolean"} + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var allowIndirect = Boolean( + context.options[0] && + context.options[0].allowIndirect + ); + var sourceCode = context.getSourceCode(); + var funcInfo = null; + + /** + * Pushs a variable scope (Program or Function) information to the stack. + * + * This is used in order to check whether or not `this` binding is a + * reference to the global object. + * + * @param {ASTNode} node - A node of the scope. This is one of Program, + * FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression. + * @returns {void} + */ + function enterVarScope(node) { + var strict = context.getScope().isStrict; - /** - * Reports a given node. - * - * `node` is `Identifier` or `MemberExpression`. - * The parent of `node` might be `CallExpression`. - * - * The location of the report is always `eval` `Identifier` (or possibly - * `Literal`). The type of the report is `CallExpression` if the parent is - * `CallExpression`. Otherwise, it's the given node type. - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function report(node) { - var locationNode = node; - var parent = node.parent; - - if (node.type === "MemberExpression") { - locationNode = node.property; + funcInfo = { + upper: funcInfo, + node: node, + strict: strict, + defaultThis: false, + initialized: strict + }; } - if (parent.type === "CallExpression" && parent.callee === node) { - node = parent; + + /** + * Pops a variable scope from the stack. + * + * @returns {void} + */ + function exitVarScope() { + funcInfo = funcInfo.upper; } - context.report({ - node: node, - loc: locationNode.loc.start, - message: "eval can be harmful." - }); - } + /** + * Reports a given node. + * + * `node` is `Identifier` or `MemberExpression`. + * The parent of `node` might be `CallExpression`. + * + * The location of the report is always `eval` `Identifier` (or possibly + * `Literal`). The type of the report is `CallExpression` if the parent is + * `CallExpression`. Otherwise, it's the given node type. + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function report(node) { + var locationNode = node; + var parent = node.parent; + + if (node.type === "MemberExpression") { + locationNode = node.property; + } + if (parent.type === "CallExpression" && parent.callee === node) { + node = parent; + } - /** - * Reports accesses of `eval` via the global object. - * - * @param {escope.Scope} globalScope - The global scope. - * @returns {void} - */ - function reportAccessingEvalViaGlobalObject(globalScope) { - for (var i = 0; i < candidatesOfGlobalObject.length; ++i) { - var name = candidatesOfGlobalObject[i]; - var variable = astUtils.getVariableByName(globalScope, name); + context.report({ + node: node, + loc: locationNode.loc.start, + message: "eval can be harmful." + }); + } - if (!variable) { - continue; - } + /** + * Reports accesses of `eval` via the global object. + * + * @param {escope.Scope} globalScope - The global scope. + * @returns {void} + */ + function reportAccessingEvalViaGlobalObject(globalScope) { + for (var i = 0; i < candidatesOfGlobalObject.length; ++i) { + var name = candidatesOfGlobalObject[i]; + var variable = astUtils.getVariableByName(globalScope, name); + + if (!variable) { + continue; + } - var references = variable.references; + var references = variable.references; - for (var j = 0; j < references.length; ++j) { - var identifier = references[j].identifier; - var node = identifier.parent; + for (var j = 0; j < references.length; ++j) { + var identifier = references[j].identifier; + var node = identifier.parent; - // To detect code like `window.window.eval`. - while (isMember(node, name)) { - node = node.parent; - } + // To detect code like `window.window.eval`. + while (isMember(node, name)) { + node = node.parent; + } - // Reports. - if (isMember(node, "eval")) { - report(node); + // Reports. + if (isMember(node, "eval")) { + report(node); + } } } } - } - /** - * Reports all accesses of `eval` (excludes direct calls to eval). - * - * @param {escope.Scope} globalScope - The global scope. - * @returns {void} - */ - function reportAccessingEval(globalScope) { - var variable = astUtils.getVariableByName(globalScope, "eval"); - - if (!variable) { - return; - } + /** + * Reports all accesses of `eval` (excludes direct calls to eval). + * + * @param {escope.Scope} globalScope - The global scope. + * @returns {void} + */ + function reportAccessingEval(globalScope) { + var variable = astUtils.getVariableByName(globalScope, "eval"); + + if (!variable) { + return; + } - var references = variable.references; + var references = variable.references; - for (var i = 0; i < references.length; ++i) { - var reference = references[i]; - var id = reference.identifier; + for (var i = 0; i < references.length; ++i) { + var reference = references[i]; + var id = reference.identifier; - if (id.name === "eval" && !astUtils.isCallee(id)) { + if (id.name === "eval" && !astUtils.isCallee(id)) { - // Is accessing to eval (excludes direct calls to eval) - report(id); + // Is accessing to eval (excludes direct calls to eval) + report(id); + } } } - } - if (allowIndirect) { + if (allowIndirect) { + + // Checks only direct calls to eval. It's simple! + return { + "CallExpression:exit": function(node) { + var callee = node.callee; + + if (isIdentifier(callee, "eval")) { + report(callee); + } + } + }; + } - // Checks only direct calls to eval. It's simple! return { "CallExpression:exit": function(node) { var callee = node.callee; @@ -218,84 +245,64 @@ module.exports = function(context) { if (isIdentifier(callee, "eval")) { report(callee); } - } - }; - } - - return { - "CallExpression:exit": function(node) { - var callee = node.callee; - - if (isIdentifier(callee, "eval")) { - report(callee); - } - }, - - "Program": function(node) { - var scope = context.getScope(), - features = context.parserOptions.ecmaFeatures || {}, - strict = - scope.isStrict || - node.sourceType === "module" || - (features.globalReturn && scope.childScopes[0].isStrict); - - funcInfo = { - upper: null, - node: node, - strict: strict, - defaultThis: true, - initialized: true - }; - }, - - "Program:exit": function() { - var globalScope = context.getScope(); - - exitVarScope(); - reportAccessingEval(globalScope); - reportAccessingEvalViaGlobalObject(globalScope); - }, - - "FunctionDeclaration": enterVarScope, - "FunctionDeclaration:exit": exitVarScope, - "FunctionExpression": enterVarScope, - "FunctionExpression:exit": exitVarScope, - "ArrowFunctionExpression": enterVarScope, - "ArrowFunctionExpression:exit": exitVarScope, - - "ThisExpression": function(node) { - if (!isMember(node.parent, "eval")) { - return; - } + }, + + Program: function(node) { + var scope = context.getScope(), + features = context.parserOptions.ecmaFeatures || {}, + strict = + scope.isStrict || + node.sourceType === "module" || + (features.globalReturn && scope.childScopes[0].isStrict); + + funcInfo = { + upper: null, + node: node, + strict: strict, + defaultThis: true, + initialized: true + }; + }, + + "Program:exit": function() { + var globalScope = context.getScope(); + + exitVarScope(); + reportAccessingEval(globalScope); + reportAccessingEvalViaGlobalObject(globalScope); + }, + + FunctionDeclaration: enterVarScope, + "FunctionDeclaration:exit": exitVarScope, + FunctionExpression: enterVarScope, + "FunctionExpression:exit": exitVarScope, + ArrowFunctionExpression: enterVarScope, + "ArrowFunctionExpression:exit": exitVarScope, + + ThisExpression: function(node) { + if (!isMember(node.parent, "eval")) { + return; + } - /* - * `this.eval` is found. - * Checks whether or not the value of `this` is the global object. - */ - if (!funcInfo.initialized) { - funcInfo.initialized = true; - funcInfo.defaultThis = astUtils.isDefaultThisBinding( - funcInfo.node, - sourceCode - ); - } + /* + * `this.eval` is found. + * Checks whether or not the value of `this` is the global object. + */ + if (!funcInfo.initialized) { + funcInfo.initialized = true; + funcInfo.defaultThis = astUtils.isDefaultThisBinding( + funcInfo.node, + sourceCode + ); + } - if (!funcInfo.strict && funcInfo.defaultThis) { + if (!funcInfo.strict && funcInfo.defaultThis) { - // `this.eval` is possible built-in `eval`. - report(node.parent); + // `this.eval` is possible built-in `eval`. + report(node.parent); + } } - } - }; - -}; + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "allowIndirect": {"type": "boolean"} - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/no-ex-assign.js b/tools/eslint/lib/rules/no-ex-assign.js index e658e475b4..bf3afc6cd3 100644 --- a/tools/eslint/lib/rules/no-ex-assign.js +++ b/tools/eslint/lib/rules/no-ex-assign.js @@ -11,27 +11,37 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - astUtils.getModifyingReferences(variable.references).forEach(function(reference) { - context.report( - reference.identifier, - "Do not assign to the exception parameter."); - }); - } - - return { - "CatchClause": function(node) { - context.getDeclaredVariables(node).forEach(checkVariable); +module.exports = { + meta: { + docs: { + description: "disallow reassigning exceptions in `catch` clauses", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils.getModifyingReferences(variable.references).forEach(function(reference) { + context.report( + reference.identifier, + "Do not assign to the exception parameter."); + }); } - }; -}; + return { + CatchClause: function(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } + }; -module.exports.schema = []; + } +}; diff --git a/tools/eslint/lib/rules/no-extend-native.js b/tools/eslint/lib/rules/no-extend-native.js index cf2cf33f3a..69d4931ab6 100644 --- a/tools/eslint/lib/rules/no-extend-native.js +++ b/tools/eslint/lib/rules/no-extend-native.js @@ -15,87 +15,97 @@ var globals = require("globals"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var config = context.options[0] || {}; - var exceptions = config.exceptions || []; - var modifiedBuiltins = Object.keys(globals.builtin).filter(function(builtin) { - return builtin[0].toUpperCase() === builtin[0]; - }); - - if (exceptions.length) { - modifiedBuiltins = modifiedBuiltins.filter(function(builtIn) { - return exceptions.indexOf(builtIn) === -1; - }); - } - - return { - - // handle the Array.prototype.extra style case - "AssignmentExpression": function(node) { - var lhs = node.left, - affectsProto; +module.exports = { + meta: { + docs: { + description: "disallow extending native types", + category: "Best Practices", + recommended: false + }, - if (lhs.type !== "MemberExpression" || lhs.object.type !== "MemberExpression") { - return; + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, + additionalProperties: false } + ] + }, - affectsProto = lhs.object.computed ? - lhs.object.property.type === "Literal" && lhs.object.property.value === "prototype" : - lhs.object.property.name === "prototype"; + create: function(context) { - if (!affectsProto) { - return; - } + var config = context.options[0] || {}; + var exceptions = config.exceptions || []; + var modifiedBuiltins = Object.keys(globals.builtin).filter(function(builtin) { + return builtin[0].toUpperCase() === builtin[0]; + }); - modifiedBuiltins.forEach(function(builtin) { - if (lhs.object.object.name === builtin) { - context.report(node, builtin + " prototype is read only, properties should not be added."); - } + if (exceptions.length) { + modifiedBuiltins = modifiedBuiltins.filter(function(builtIn) { + return exceptions.indexOf(builtIn) === -1; }); - }, + } - // handle the Object.definePropert[y|ies](Array.prototype) case - "CallExpression": function(node) { + return { - var callee = node.callee, - subject, - object; + // handle the Array.prototype.extra style case + AssignmentExpression: function(node) { + var lhs = node.left, + affectsProto; - // only worry about Object.definePropert[y|ies] - if (callee.type === "MemberExpression" && - callee.object.name === "Object" && - (callee.property.name === "defineProperty" || callee.property.name === "defineProperties")) { + if (lhs.type !== "MemberExpression" || lhs.object.type !== "MemberExpression") { + return; + } - // verify the object being added to is a native prototype - subject = node.arguments[0]; - object = subject && subject.object; - if (object && - object.type === "Identifier" && - (modifiedBuiltins.indexOf(object.name) > -1) && - subject.property.name === "prototype") { + affectsProto = lhs.object.computed ? + lhs.object.property.type === "Literal" && lhs.object.property.value === "prototype" : + lhs.object.property.name === "prototype"; - context.report(node, object.name + " prototype is read only, properties should not be added."); + if (!affectsProto) { + return; } - } - } - }; - -}; + modifiedBuiltins.forEach(function(builtin) { + if (lhs.object.object.name === builtin) { + context.report(node, builtin + " prototype is read only, properties should not be added."); + } + }); + }, + + // handle the Object.definePropert[y|ies](Array.prototype) case + CallExpression: function(node) { + + var callee = node.callee, + subject, + object; + + // only worry about Object.definePropert[y|ies] + if (callee.type === "MemberExpression" && + callee.object.name === "Object" && + (callee.property.name === "defineProperty" || callee.property.name === "defineProperties")) { + + // verify the object being added to is a native prototype + subject = node.arguments[0]; + object = subject && subject.object; + if (object && + object.type === "Identifier" && + (modifiedBuiltins.indexOf(object.name) > -1) && + subject.property.name === "prototype") { + + context.report(node, object.name + " prototype is read only, properties should not be added."); + } + } -module.exports.schema = [ - { - "type": "object", - "properties": { - "exceptions": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/no-extra-bind.js b/tools/eslint/lib/rules/no-extra-bind.js index cd800eabd7..f75e2fcc25 100644 --- a/tools/eslint/lib/rules/no-extra-bind.js +++ b/tools/eslint/lib/rules/no-extra-bind.js @@ -1,9 +1,6 @@ /** * @fileoverview Rule to flag unnecessary bind calls * @author Bence Dányi - * @copyright 2014 Bence Dányi. All rights reserved. - * @copyright 2016 Toru Nagashima. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; @@ -11,139 +8,149 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var scopeInfo = null; +module.exports = { + meta: { + docs: { + description: "disallow unnecessary calls to `.bind()`", + category: "Best Practices", + recommended: false + }, - /** - * Reports a given function node. - * - * @param {ASTNode} node - A node to report. This is a FunctionExpression or - * an ArrowFunctionExpression. - * @returns {void} - */ - function report(node) { - context.report({ - node: node.parent.parent, - message: "The function binding is unnecessary.", - loc: node.parent.property.loc.start - }); - } + schema: [] + }, - /** - * Gets the property name of a given node. - * If the property name is dynamic, this returns an empty string. - * - * @param {ASTNode} node - A node to check. This is a MemberExpression. - * @returns {string} The property name of the node. - */ - function getPropertyName(node) { - if (node.computed) { - switch (node.property.type) { - case "Literal": - return String(node.property.value); - case "TemplateLiteral": - if (node.property.expressions.length === 0) { - return node.property.quasis[0].value.cooked; - } + create: function(context) { + var scopeInfo = null; - // fallthrough - default: - return false; - } + /** + * Reports a given function node. + * + * @param {ASTNode} node - A node to report. This is a FunctionExpression or + * an ArrowFunctionExpression. + * @returns {void} + */ + function report(node) { + context.report({ + node: node.parent.parent, + message: "The function binding is unnecessary.", + loc: node.parent.property.loc.start + }); } - return node.property.name; - } - /** - * Checks whether or not a given function node is the callee of `.bind()` - * method. - * - * e.g. `(function() {}.bind(foo))` - * - * @param {ASTNode} node - A node to report. This is a FunctionExpression or - * an ArrowFunctionExpression. - * @returns {boolean} `true` if the node is the callee of `.bind()` method. - */ - function isCalleeOfBindMethod(node) { - var parent = node.parent; - var grandparent = parent.parent; + /** + * Gets the property name of a given node. + * If the property name is dynamic, this returns an empty string. + * + * @param {ASTNode} node - A node to check. This is a MemberExpression. + * @returns {string} The property name of the node. + */ + function getPropertyName(node) { + if (node.computed) { + switch (node.property.type) { + case "Literal": + return String(node.property.value); + case "TemplateLiteral": + if (node.property.expressions.length === 0) { + return node.property.quasis[0].value.cooked; + } - return ( - grandparent && - grandparent.type === "CallExpression" && - grandparent.callee === parent && - grandparent.arguments.length === 1 && - parent.type === "MemberExpression" && - parent.object === node && - getPropertyName(parent) === "bind" - ); - } + // fallthrough + default: + return false; + } + } + return node.property.name; + } - /** - * Adds a scope information object to the stack. - * - * @param {ASTNode} node - A node to add. This node is a FunctionExpression - * or a FunctionDeclaration node. - * @returns {void} - */ - function enterFunction(node) { - scopeInfo = { - isBound: isCalleeOfBindMethod(node), - thisFound: false, - upper: scopeInfo - }; - } + /** + * Checks whether or not a given function node is the callee of `.bind()` + * method. + * + * e.g. `(function() {}.bind(foo))` + * + * @param {ASTNode} node - A node to report. This is a FunctionExpression or + * an ArrowFunctionExpression. + * @returns {boolean} `true` if the node is the callee of `.bind()` method. + */ + function isCalleeOfBindMethod(node) { + var parent = node.parent; + var grandparent = parent.parent; - /** - * Removes the scope information object from the top of the stack. - * At the same time, this reports the function node if the function has - * `.bind()` and the `this` keywords found. - * - * @param {ASTNode} node - A node to remove. This node is a - * FunctionExpression or a FunctionDeclaration node. - * @returns {void} - */ - function exitFunction(node) { - if (scopeInfo.isBound && !scopeInfo.thisFound) { - report(node); + return ( + grandparent && + grandparent.type === "CallExpression" && + grandparent.callee === parent && + grandparent.arguments.length === 1 && + parent.type === "MemberExpression" && + parent.object === node && + getPropertyName(parent) === "bind" + ); } - scopeInfo = scopeInfo.upper; - } + /** + * Adds a scope information object to the stack. + * + * @param {ASTNode} node - A node to add. This node is a FunctionExpression + * or a FunctionDeclaration node. + * @returns {void} + */ + function enterFunction(node) { + scopeInfo = { + isBound: isCalleeOfBindMethod(node), + thisFound: false, + upper: scopeInfo + }; + } - /** - * Reports a given arrow function if the function is callee of `.bind()` - * method. - * - * @param {ASTNode} node - A node to report. This node is an - * ArrowFunctionExpression. - * @returns {void} - */ - function exitArrowFunction(node) { - if (isCalleeOfBindMethod(node)) { - report(node); + /** + * Removes the scope information object from the top of the stack. + * At the same time, this reports the function node if the function has + * `.bind()` and the `this` keywords found. + * + * @param {ASTNode} node - A node to remove. This node is a + * FunctionExpression or a FunctionDeclaration node. + * @returns {void} + */ + function exitFunction(node) { + if (scopeInfo.isBound && !scopeInfo.thisFound) { + report(node); + } + + scopeInfo = scopeInfo.upper; } - } - /** - * Set the mark as the `this` keyword was found in this scope. - * - * @returns {void} - */ - function markAsThisFound() { - if (scopeInfo) { - scopeInfo.thisFound = true; + /** + * Reports a given arrow function if the function is callee of `.bind()` + * method. + * + * @param {ASTNode} node - A node to report. This node is an + * ArrowFunctionExpression. + * @returns {void} + */ + function exitArrowFunction(node) { + if (isCalleeOfBindMethod(node)) { + report(node); + } } - } - return { - "ArrowFunctionExpression:exit": exitArrowFunction, - "FunctionDeclaration": enterFunction, - "FunctionDeclaration:exit": exitFunction, - "FunctionExpression": enterFunction, - "FunctionExpression:exit": exitFunction, - "ThisExpression": markAsThisFound - }; -}; + /** + * Set the mark as the `this` keyword was found in this scope. + * + * @returns {void} + */ + function markAsThisFound() { + if (scopeInfo) { + scopeInfo.thisFound = true; + } + } -module.exports.schema = []; + return { + "ArrowFunctionExpression:exit": exitArrowFunction, + FunctionDeclaration: enterFunction, + "FunctionDeclaration:exit": exitFunction, + FunctionExpression: enterFunction, + "FunctionExpression:exit": exitFunction, + ThisExpression: markAsThisFound + }; + } +}; diff --git a/tools/eslint/lib/rules/no-extra-boolean-cast.js b/tools/eslint/lib/rules/no-extra-boolean-cast.js index c7ee1adc6b..f14da0821d 100644 --- a/tools/eslint/lib/rules/no-extra-boolean-cast.js +++ b/tools/eslint/lib/rules/no-extra-boolean-cast.js @@ -9,72 +9,82 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - // Node types which have a test which will coerce values to booleans. - var BOOLEAN_NODE_TYPES = [ - "IfStatement", - "DoWhileStatement", - "WhileStatement", - "ConditionalExpression", - "ForStatement" - ]; - - /** - * Check if a node is in a context where its value would be coerced to a boolean at runtime. - * - * @param {Object} node The node - * @param {Object} parent Its parent - * @returns {Boolean} If it is in a boolean context - */ - function isInBooleanContext(node, parent) { - return ( - (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 && - node === parent.test) || - - // ! - (parent.type === "UnaryExpression" && - parent.operator === "!") - ); - } +module.exports = { + meta: { + docs: { + description: "disallow unnecessary boolean casts", + category: "Possible Errors", + recommended: true + }, + schema: [] + }, - return { - "UnaryExpression": function(node) { - var ancestors = context.getAncestors(), - parent = ancestors.pop(), - grandparent = ancestors.pop(); + create: function(context) { - // Exit early if it's guaranteed not to match - if (node.operator !== "!" || - parent.type !== "UnaryExpression" || - parent.operator !== "!") { - return; - } + // Node types which have a test which will coerce values to booleans. + var BOOLEAN_NODE_TYPES = [ + "IfStatement", + "DoWhileStatement", + "WhileStatement", + "ConditionalExpression", + "ForStatement" + ]; - if (isInBooleanContext(parent, grandparent) || + /** + * Check if a node is in a context where its value would be coerced to a boolean at runtime. + * + * @param {Object} node The node + * @param {Object} parent Its parent + * @returns {Boolean} If it is in a boolean context + */ + function isInBooleanContext(node, parent) { + return ( + (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 && + node === parent.test) || - // Boolean() and new Boolean() - ((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") && - grandparent.callee.type === "Identifier" && - grandparent.callee.name === "Boolean") - ) { - context.report(node, "Redundant double negation."); - } - }, - "CallExpression": function(node) { - var parent = node.parent; + // ! + (parent.type === "UnaryExpression" && + parent.operator === "!") + ); + } - if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") { - return; - } - if (isInBooleanContext(node, parent)) { - context.report(node, "Redundant Boolean call."); + return { + UnaryExpression: function(node) { + var ancestors = context.getAncestors(), + parent = ancestors.pop(), + grandparent = ancestors.pop(); + + // Exit early if it's guaranteed not to match + if (node.operator !== "!" || + parent.type !== "UnaryExpression" || + parent.operator !== "!") { + return; + } + + if (isInBooleanContext(parent, grandparent) || + + // Boolean() and new Boolean() + ((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") && + grandparent.callee.type === "Identifier" && + grandparent.callee.name === "Boolean") + ) { + context.report(node, "Redundant double negation."); + } + }, + CallExpression: function(node) { + var parent = node.parent; + + if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") { + return; + } + + if (isInBooleanContext(node, parent)) { + context.report(node, "Redundant Boolean call."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-extra-label.js b/tools/eslint/lib/rules/no-extra-label.js index c11a703576..f1a48e3688 100644 --- a/tools/eslint/lib/rules/no-extra-label.js +++ b/tools/eslint/lib/rules/no-extra-label.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to disallow unnecessary labels * @author Toru Nagashima - * @copyright 2016 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -17,116 +15,126 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var scopeInfo = null; - - /** - * Creates a new scope with a breakable statement. - * - * @param {ASTNode} node - A node to create. This is a BreakableStatement. - * @returns {void} - */ - function enterBreakableStatement(node) { - scopeInfo = { - label: astUtils.getLabel(node), - breakable: true, - upper: scopeInfo - }; - } +module.exports = { + meta: { + docs: { + description: "disallow unnecessary labels", + category: "Best Practices", + recommended: false + }, - /** - * Removes the top scope of the stack. - * - * @returns {void} - */ - function exitBreakableStatement() { - scopeInfo = scopeInfo.upper; - } + schema: [] + }, - /** - * Creates a new scope with a labeled statement. - * - * This ignores it if the body is a breakable statement. - * In this case it's handled in the `enterBreakableStatement` function. - * - * @param {ASTNode} node - A node to create. This is a LabeledStatement. - * @returns {void} - */ - function enterLabeledStatement(node) { - if (!astUtils.isBreakableStatement(node.body)) { + create: function(context) { + var scopeInfo = null; + + /** + * Creates a new scope with a breakable statement. + * + * @param {ASTNode} node - A node to create. This is a BreakableStatement. + * @returns {void} + */ + function enterBreakableStatement(node) { scopeInfo = { - label: node.label.name, - breakable: false, + label: astUtils.getLabel(node), + breakable: true, upper: scopeInfo }; } - } - /** - * Removes the top scope of the stack. - * - * This ignores it if the body is a breakable statement. - * In this case it's handled in the `exitBreakableStatement` function. - * - * @param {ASTNode} node - A node. This is a LabeledStatement. - * @returns {void} - */ - function exitLabeledStatement(node) { - if (!astUtils.isBreakableStatement(node.body)) { + /** + * Removes the top scope of the stack. + * + * @returns {void} + */ + function exitBreakableStatement() { scopeInfo = scopeInfo.upper; } - } - /** - * Reports a given control node if it's unnecessary. - * - * @param {ASTNode} node - A node. This is a BreakStatement or a - * ContinueStatement. - * @returns {void} - */ - function reportIfUnnecessary(node) { - if (!node.label) { - return; + /** + * Creates a new scope with a labeled statement. + * + * This ignores it if the body is a breakable statement. + * In this case it's handled in the `enterBreakableStatement` function. + * + * @param {ASTNode} node - A node to create. This is a LabeledStatement. + * @returns {void} + */ + function enterLabeledStatement(node) { + if (!astUtils.isBreakableStatement(node.body)) { + scopeInfo = { + label: node.label.name, + breakable: false, + upper: scopeInfo + }; + } } - var labelNode = node.label; - var label = labelNode.name; - var info = scopeInfo; - - while (info) { - if (info.breakable || info.label === label) { - if (info.breakable && info.label === label) { - context.report({ - node: labelNode, - message: "This label '{{name}}' is unnecessary.", - data: labelNode - }); - } + /** + * Removes the top scope of the stack. + * + * This ignores it if the body is a breakable statement. + * In this case it's handled in the `exitBreakableStatement` function. + * + * @param {ASTNode} node - A node. This is a LabeledStatement. + * @returns {void} + */ + function exitLabeledStatement(node) { + if (!astUtils.isBreakableStatement(node.body)) { + scopeInfo = scopeInfo.upper; + } + } + + /** + * Reports a given control node if it's unnecessary. + * + * @param {ASTNode} node - A node. This is a BreakStatement or a + * ContinueStatement. + * @returns {void} + */ + function reportIfUnnecessary(node) { + if (!node.label) { return; } - info = info.upper; + var labelNode = node.label; + var label = labelNode.name; + var info = scopeInfo; + + while (info) { + if (info.breakable || info.label === label) { + if (info.breakable && info.label === label) { + context.report({ + node: labelNode, + message: "This label '{{name}}' is unnecessary.", + data: labelNode + }); + } + return; + } + + info = info.upper; + } } - } - return { - "WhileStatement": enterBreakableStatement, - "WhileStatement:exit": exitBreakableStatement, - "DoWhileStatement": enterBreakableStatement, - "DoWhileStatement:exit": exitBreakableStatement, - "ForStatement": enterBreakableStatement, - "ForStatement:exit": exitBreakableStatement, - "ForInStatement": enterBreakableStatement, - "ForInStatement:exit": exitBreakableStatement, - "ForOfStatement": enterBreakableStatement, - "ForOfStatement:exit": exitBreakableStatement, - "SwitchStatement": enterBreakableStatement, - "SwitchStatement:exit": exitBreakableStatement, - "LabeledStatement": enterLabeledStatement, - "LabeledStatement:exit": exitLabeledStatement, - "BreakStatement": reportIfUnnecessary, - "ContinueStatement": reportIfUnnecessary - }; + return { + WhileStatement: enterBreakableStatement, + "WhileStatement:exit": exitBreakableStatement, + DoWhileStatement: enterBreakableStatement, + "DoWhileStatement:exit": exitBreakableStatement, + ForStatement: enterBreakableStatement, + "ForStatement:exit": exitBreakableStatement, + ForInStatement: enterBreakableStatement, + "ForInStatement:exit": exitBreakableStatement, + ForOfStatement: enterBreakableStatement, + "ForOfStatement:exit": exitBreakableStatement, + SwitchStatement: enterBreakableStatement, + "SwitchStatement:exit": exitBreakableStatement, + LabeledStatement: enterLabeledStatement, + "LabeledStatement:exit": exitLabeledStatement, + BreakStatement: reportIfUnnecessary, + ContinueStatement: reportIfUnnecessary + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-extra-parens.js b/tools/eslint/lib/rules/no-extra-parens.js index 50f5160b20..cf129394ba 100644 --- a/tools/eslint/lib/rules/no-extra-parens.js +++ b/tools/eslint/lib/rules/no-extra-parens.js @@ -1,8 +1,6 @@ /** * @fileoverview Disallow parenthesising higher precedence subexpressions. * @author Michael Ficarra - * @copyright 2014 Michael Ficarra. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -12,569 +10,583 @@ var astUtils = require("../ast-utils.js"); -module.exports = function(context) { - var isParenthesised = astUtils.isParenthesised.bind(astUtils, context); - var ALL_NODES = context.options[0] !== "functions"; - var EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false; - var sourceCode = context.getSourceCode(); - - /** - * Determines if this rule should be enforced for a node given the current configuration. - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the rule should be enforced for this node. - * @private - */ - function ruleApplies(node) { - return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression"; - } +module.exports = { + meta: { + docs: { + description: "disallow unnecessary parentheses", + category: "Possible Errors", + recommended: false + }, - /** - * Determines if a node is surrounded by parentheses twice. - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the node is doubly parenthesised. - * @private - */ - function isParenthesisedTwice(node) { - var previousToken = context.getTokenBefore(node, 1), - nextToken = context.getTokenAfter(node, 1); - - return isParenthesised(node) && previousToken && nextToken && - previousToken.value === "(" && previousToken.range[1] <= node.range[0] && - nextToken.value === ")" && nextToken.range[0] >= node.range[1]; - } + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["functions"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["all"] + }, + { + type: "object", + properties: { + conditionalAssign: {type: "boolean"}, + nestedBinaryExpressions: {type: "boolean"} + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + } + }, + + create: function(context) { + var isParenthesised = astUtils.isParenthesised.bind(astUtils, context); + var ALL_NODES = context.options[0] !== "functions"; + var EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false; + var NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false; + var sourceCode = context.getSourceCode(); + + /** + * Determines if this rule should be enforced for a node given the current configuration. + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the rule should be enforced for this node. + * @private + */ + function ruleApplies(node) { + return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression"; + } - /** - * Determines if a node is surrounded by (potentially) invalid parentheses. - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the node is incorrectly parenthesised. - * @private - */ - function hasExcessParens(node) { - return ruleApplies(node) && isParenthesised(node); - } + /** + * Determines if a node is surrounded by parentheses twice. + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the node is doubly parenthesised. + * @private + */ + function isParenthesisedTwice(node) { + var previousToken = context.getTokenBefore(node, 1), + nextToken = context.getTokenAfter(node, 1); + + return isParenthesised(node) && previousToken && nextToken && + previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } - /** - * Determines if a node that is expected to be parenthesised is surrounded by - * (potentially) invalid extra parentheses. - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. - * @private - */ - function hasDoubleExcessParens(node) { - return ruleApplies(node) && isParenthesisedTwice(node); - } + /** + * Determines if a node is surrounded by (potentially) invalid parentheses. + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the node is incorrectly parenthesised. + * @private + */ + function hasExcessParens(node) { + return ruleApplies(node) && isParenthesised(node); + } - /** - * Determines if a node test expression is allowed to have a parenthesised assignment - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the assignment can be parenthesised. - * @private - */ - function isCondAssignException(node) { - return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression"; - } + /** + * Determines if a node that is expected to be parenthesised is surrounded by + * (potentially) invalid extra parentheses. + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. + * @private + */ + function hasDoubleExcessParens(node) { + return ruleApplies(node) && isParenthesisedTwice(node); + } - /** - * Determines if a node following a [no LineTerminator here] restriction is - * surrounded by (potentially) invalid extra parentheses. - * @param {Token} token - The token preceding the [no LineTerminator here] restriction. - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the node is incorrectly parenthesised. - * @private - */ - function hasExcessParensNoLineTerminator(token, node) { - if (token.loc.end.line === node.loc.start.line) { - return hasExcessParens(node); + /** + * Determines if a node test expression is allowed to have a parenthesised assignment + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the assignment can be parenthesised. + * @private + */ + function isCondAssignException(node) { + return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression"; } - return hasDoubleExcessParens(node); - } + /** + * Determines if a node following a [no LineTerminator here] restriction is + * surrounded by (potentially) invalid extra parentheses. + * @param {Token} token - The token preceding the [no LineTerminator here] restriction. + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the node is incorrectly parenthesised. + * @private + */ + function hasExcessParensNoLineTerminator(token, node) { + if (token.loc.end.line === node.loc.start.line) { + return hasExcessParens(node); + } - /** - * Checks whether or not a given node is located at the head of ExpressionStatement. - * @param {ASTNode} node - A node to check. - * @returns {boolean} `true` if the node is located at the head of ExpressionStatement. - */ - function isHeadOfExpressionStatement(node) { - var parent = node.parent; + return hasDoubleExcessParens(node); + } - while (parent) { - switch (parent.type) { - case "SequenceExpression": - if (parent.expressions[0] !== node || isParenthesised(node)) { + /** + * Checks whether or not a given node is located at the head of ExpressionStatement. + * @param {ASTNode} node - A node to check. + * @returns {boolean} `true` if the node is located at the head of ExpressionStatement. + */ + function isHeadOfExpressionStatement(node) { + var parent = node.parent; + + while (parent) { + switch (parent.type) { + case "SequenceExpression": + if (parent.expressions[0] !== node || isParenthesised(node)) { + return false; + } + break; + + case "UnaryExpression": + case "UpdateExpression": + if (parent.prefix || isParenthesised(node)) { + return false; + } + break; + + case "BinaryExpression": + case "LogicalExpression": + if (parent.left !== node || isParenthesised(node)) { + return false; + } + break; + + case "ConditionalExpression": + if (parent.test !== node || isParenthesised(node)) { + return false; + } + break; + + case "CallExpression": + if (parent.callee !== node || isParenthesised(node)) { + return false; + } + break; + + case "MemberExpression": + if (parent.object !== node || isParenthesised(node)) { + return false; + } + break; + + case "ExpressionStatement": + return true; + + default: return false; - } - break; + } - case "UnaryExpression": - case "UpdateExpression": - if (parent.prefix || isParenthesised(node)) { - return false; - } - break; + node = parent; + parent = parent.parent; + } - case "BinaryExpression": - case "LogicalExpression": - if (parent.left !== node || isParenthesised(node)) { - return false; - } - break; + /* istanbul ignore next */ + throw new Error("unreachable"); + } - case "ConditionalExpression": - if (parent.test !== node || isParenthesised(node)) { - return false; - } - break; + /** + * Get the precedence level based on the node type + * @param {ASTNode} node node to evaluate + * @returns {int} precedence level + * @private + */ + function precedence(node) { - case "CallExpression": - if (parent.callee !== node || isParenthesised(node)) { - return false; - } - break; + switch (node.type) { + case "SequenceExpression": + return 0; - case "MemberExpression": - if (parent.object !== node || isParenthesised(node)) { - return false; - } - break; + case "AssignmentExpression": + case "ArrowFunctionExpression": + case "YieldExpression": + return 1; - case "ExpressionStatement": - return true; + case "ConditionalExpression": + return 3; - default: - return false; - } + case "LogicalExpression": + switch (node.operator) { + case "||": + return 4; + case "&&": + return 5; - node = parent; - parent = parent.parent; - } + // no default + } - /* istanbul ignore next */ - throw new Error("unreachable"); - } + /* falls through */ - /** - * Get the precedence level based on the node type - * @param {ASTNode} node node to evaluate - * @returns {int} precedence level - * @private - */ - function precedence(node) { - - switch (node.type) { - case "SequenceExpression": - return 0; - - case "AssignmentExpression": - case "ArrowFunctionExpression": - case "YieldExpression": - return 1; - - case "ConditionalExpression": - return 3; - - case "LogicalExpression": - switch (node.operator) { - case "||": - return 4; - case "&&": - return 5; - - // no default - } + case "BinaryExpression": - /* falls through */ - - case "BinaryExpression": - - switch (node.operator) { - case "|": - return 6; - case "^": - return 7; - case "&": - return 8; - case "==": - case "!=": - case "===": - case "!==": - return 9; - case "<": - case "<=": - case ">": - case ">=": - case "in": - case "instanceof": - return 10; - case "<<": - case ">>": - case ">>>": - return 11; - case "+": - case "-": - return 12; - case "*": - case "/": - case "%": - return 13; - - // no default - } + switch (node.operator) { + case "|": + return 6; + case "^": + return 7; + case "&": + return 8; + case "==": + case "!=": + case "===": + case "!==": + return 9; + case "<": + case "<=": + case ">": + case ">=": + case "in": + case "instanceof": + return 10; + case "<<": + case ">>": + case ">>>": + return 11; + case "+": + case "-": + return 12; + case "*": + case "/": + case "%": + return 13; + + // no default + } - /* falls through */ + /* falls through */ - case "UnaryExpression": - return 14; + case "UnaryExpression": + return 14; - case "UpdateExpression": - return 15; + case "UpdateExpression": + return 15; - case "CallExpression": + case "CallExpression": - // IIFE is allowed to have parens in any position (#655) - if (node.callee.type === "FunctionExpression") { - return -1; - } - return 16; + // IIFE is allowed to have parens in any position (#655) + if (node.callee.type === "FunctionExpression") { + return -1; + } + return 16; - case "NewExpression": - return 17; + case "NewExpression": + return 17; - // no default + // no default + } + return 18; } - return 18; - } - /** - * Report the node - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function report(node) { - var previousToken = context.getTokenBefore(node); - - context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression."); - } + /** + * Report the node + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function report(node) { + var previousToken = context.getTokenBefore(node); - /** - * Evaluate Unary update - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function dryUnaryUpdate(node) { - if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) { - report(node.argument); + context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression."); } - } - /** - * Evaluate a new call - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function dryCallNew(node) { - if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !( - node.type === "CallExpression" && - node.callee.type === "FunctionExpression" && - - // One set of parentheses are allowed for a function expression - !hasDoubleExcessParens(node.callee) - )) { - report(node.callee); + /** + * Evaluate Unary update + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function dryUnaryUpdate(node) { + if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) { + report(node.argument); + } } - if (node.arguments.length === 1) { - if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) { - report(node.arguments[0]); + + /** + * Evaluate a new call + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function dryCallNew(node) { + if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !( + node.type === "CallExpression" && + node.callee.type === "FunctionExpression" && + + // One set of parentheses are allowed for a function expression + !hasDoubleExcessParens(node.callee) + )) { + report(node.callee); } - } else { - [].forEach.call(node.arguments, function(arg) { - if (hasExcessParens(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) { - report(arg); + if (node.arguments.length === 1) { + if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) { + report(node.arguments[0]); } - }); + } else { + [].forEach.call(node.arguments, function(arg) { + if (hasExcessParens(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) { + report(arg); + } + }); + } } - } - /** - * Evaluate binary logicals - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function dryBinaryLogical(node) { - var prec = precedence(node); - - if (hasExcessParens(node.left) && precedence(node.left) >= prec) { - report(node.left); - } - if (hasExcessParens(node.right) && precedence(node.right) > prec) { - report(node.right); + /** + * Evaluate binary logicals + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function dryBinaryLogical(node) { + if (!NESTED_BINARY) { + var prec = precedence(node); + + if (hasExcessParens(node.left) && precedence(node.left) >= prec) { + report(node.left); + } + if (hasExcessParens(node.right) && precedence(node.right) > prec) { + report(node.right); + } + } } - } - return { - "ArrayExpression": function(node) { - [].forEach.call(node.elements, function(e) { - if (e && hasExcessParens(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) { - report(e); + return { + ArrayExpression: function(node) { + [].forEach.call(node.elements, function(e) { + if (e && hasExcessParens(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) { + report(e); + } + }); + }, + + ArrowFunctionExpression: function(node) { + if (node.body.type !== "BlockStatement") { + if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) { + report(node.body); + return; + } + + // Object literals *must* be parenthesised + if (node.body.type === "ObjectExpression" && hasDoubleExcessParens(node.body)) { + report(node.body); + return; + } } - }); - }, + }, - "ArrowFunctionExpression": function(node) { - if (node.body.type !== "BlockStatement") { - if (node.body.type !== "ObjectExpression" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) { - report(node.body); - return; + AssignmentExpression: function(node) { + if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) { + report(node.right); } + }, + + BinaryExpression: dryBinaryLogical, + CallExpression: dryCallNew, - // Object literals *must* be parenthesised - if (node.body.type === "ObjectExpression" && hasDoubleExcessParens(node.body)) { - report(node.body); - return; + ConditionalExpression: function(node) { + if (hasExcessParens(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) { + report(node.test); } - } - }, + if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) { + report(node.consequent); + } + if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) { + report(node.alternate); + } + }, - "AssignmentExpression": function(node) { - if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) { - report(node.right); - } - }, + DoWhileStatement: function(node) { + if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + }, + + ExpressionStatement: function(node) { + var firstToken, secondToken, firstTokens; + + if (hasExcessParens(node.expression)) { + firstTokens = context.getFirstTokens(node.expression, 2); + firstToken = firstTokens[0]; + secondToken = firstTokens[1]; + + if ( + !firstToken || + firstToken.value !== "{" && + firstToken.value !== "function" && + firstToken.value !== "class" && + ( + firstToken.value !== "let" || + !secondToken || + secondToken.value !== "[" + ) + ) { + report(node.expression); + } + } + }, - "BinaryExpression": dryBinaryLogical, - "CallExpression": dryCallNew, + ForInStatement: function(node) { + if (hasExcessParens(node.right)) { + report(node.right); + } + }, - "ConditionalExpression": function(node) { - if (hasExcessParens(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) { - report(node.test); - } - if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) { - report(node.consequent); - } - if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) { - report(node.alternate); - } - }, + ForOfStatement: function(node) { + if (hasExcessParens(node.right)) { + report(node.right); + } + }, - "DoWhileStatement": function(node) { - if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } - }, + ForStatement: function(node) { + if (node.init && hasExcessParens(node.init)) { + report(node.init); + } + + if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + + if (node.update && hasExcessParens(node.update)) { + report(node.update); + } + }, - "ExpressionStatement": function(node) { - var firstToken, secondToken, firstTokens; + IfStatement: function(node) { + if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + }, - if (hasExcessParens(node.expression)) { - firstTokens = context.getFirstTokens(node.expression, 2); - firstToken = firstTokens[0]; - secondToken = firstTokens[1]; + LogicalExpression: dryBinaryLogical, + MemberExpression: function(node) { if ( - !firstToken || - firstToken.value !== "{" && - firstToken.value !== "function" && - firstToken.value !== "class" && + hasExcessParens(node.object) && + precedence(node.object) >= precedence(node) && ( - firstToken.value !== "let" || - !secondToken || - secondToken.value !== "[" + node.computed || + !( + (node.object.type === "Literal" && + typeof node.object.value === "number" && + /^[0-9]+$/.test(context.getFirstToken(node.object).value)) + || + + // RegExp literal is allowed to have parens (#1589) + (node.object.type === "Literal" && node.object.regex) + ) + ) && + !( + (node.object.type === "FunctionExpression" || node.object.type === "ClassExpression") && + isHeadOfExpressionStatement(node) && + !hasDoubleExcessParens(node.object) ) ) { - report(node.expression); + report(node.object); } - } - }, - - "ForInStatement": function(node) { - if (hasExcessParens(node.right)) { - report(node.right); - } - }, - - "ForOfStatement": function(node) { - if (hasExcessParens(node.right)) { - report(node.right); - } - }, + if (node.computed && hasExcessParens(node.property)) { + report(node.property); + } + }, - "ForStatement": function(node) { - if (node.init && hasExcessParens(node.init)) { - report(node.init); - } + NewExpression: dryCallNew, - if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } + ObjectExpression: function(node) { + [].forEach.call(node.properties, function(e) { + var v = e.value; - if (node.update && hasExcessParens(node.update)) { - report(node.update); - } - }, + if (v && hasExcessParens(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) { + report(v); + } + }); + }, - "IfStatement": function(node) { - if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } - }, + ReturnStatement: function(node) { + var returnToken = sourceCode.getFirstToken(node); - "LogicalExpression": dryBinaryLogical, - - "MemberExpression": function(node) { - if ( - hasExcessParens(node.object) && - precedence(node.object) >= precedence(node) && - ( - node.computed || - !( - (node.object.type === "Literal" && - typeof node.object.value === "number" && - /^[0-9]+$/.test(context.getFirstToken(node.object).value)) - || + if (node.argument && + hasExcessParensNoLineTerminator(returnToken, node.argument) && // RegExp literal is allowed to have parens (#1589) - (node.object.type === "Literal" && node.object.regex) - ) - ) && - !( - (node.object.type === "FunctionExpression" || node.object.type === "ClassExpression") && - isHeadOfExpressionStatement(node) && - !hasDoubleExcessParens(node.object) - ) - ) { - report(node.object); - } - if (node.computed && hasExcessParens(node.property)) { - report(node.property); - } - }, - - "NewExpression": dryCallNew, - - "ObjectExpression": function(node) { - [].forEach.call(node.properties, function(e) { - var v = e.value; - - if (v && hasExcessParens(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) { - report(v); + !(node.argument.type === "Literal" && node.argument.regex)) { + report(node.argument); } - }); - }, + }, - "ReturnStatement": function(node) { - var returnToken = sourceCode.getFirstToken(node); - - if (node.argument && - hasExcessParensNoLineTerminator(returnToken, node.argument) && - - // RegExp literal is allowed to have parens (#1589) - !(node.argument.type === "Literal" && node.argument.regex)) { - report(node.argument); - } - }, + SequenceExpression: function(node) { + [].forEach.call(node.expressions, function(e) { + if (hasExcessParens(e) && precedence(e) >= precedence(node)) { + report(e); + } + }); + }, - "SequenceExpression": function(node) { - [].forEach.call(node.expressions, function(e) { - if (hasExcessParens(e) && precedence(e) >= precedence(node)) { - report(e); + SwitchCase: function(node) { + if (node.test && hasExcessParens(node.test)) { + report(node.test); } - }); - }, + }, - "SwitchCase": function(node) { - if (node.test && hasExcessParens(node.test)) { - report(node.test); - } - }, - - "SwitchStatement": function(node) { - if (hasDoubleExcessParens(node.discriminant)) { - report(node.discriminant); - } - }, + SwitchStatement: function(node) { + if (hasDoubleExcessParens(node.discriminant)) { + report(node.discriminant); + } + }, - "ThrowStatement": function(node) { - var throwToken = sourceCode.getFirstToken(node); + ThrowStatement: function(node) { + var throwToken = sourceCode.getFirstToken(node); - if (hasExcessParensNoLineTerminator(throwToken, node.argument)) { - report(node.argument); - } - }, + if (hasExcessParensNoLineTerminator(throwToken, node.argument)) { + report(node.argument); + } + }, - "UnaryExpression": dryUnaryUpdate, - "UpdateExpression": dryUnaryUpdate, + UnaryExpression: dryUnaryUpdate, + UpdateExpression: dryUnaryUpdate, - "VariableDeclarator": function(node) { - if (node.init && hasExcessParens(node.init) && - precedence(node.init) >= precedence({type: "AssignmentExpression"}) && + VariableDeclarator: function(node) { + if (node.init && hasExcessParens(node.init) && + precedence(node.init) >= precedence({type: "AssignmentExpression"}) && - // RegExp literal is allowed to have parens (#1589) - !(node.init.type === "Literal" && node.init.regex)) { - report(node.init); - } - }, + // RegExp literal is allowed to have parens (#1589) + !(node.init.type === "Literal" && node.init.regex)) { + report(node.init); + } + }, - "WhileStatement": function(node) { - if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } - }, + WhileStatement: function(node) { + if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + }, - "WithStatement": function(node) { - if (hasDoubleExcessParens(node.object)) { - report(node.object); - } - }, + WithStatement: function(node) { + if (hasDoubleExcessParens(node.object)) { + report(node.object); + } + }, - "YieldExpression": function(node) { - var yieldToken; + YieldExpression: function(node) { + var yieldToken; - if (node.argument) { - yieldToken = sourceCode.getFirstToken(node); + if (node.argument) { + yieldToken = sourceCode.getFirstToken(node); - if ((precedence(node.argument) >= precedence(node) && - hasExcessParensNoLineTerminator(yieldToken, node.argument)) || - hasDoubleExcessParens(node.argument)) { - report(node.argument); + if ((precedence(node.argument) >= precedence(node) && + hasExcessParensNoLineTerminator(yieldToken, node.argument)) || + hasDoubleExcessParens(node.argument)) { + report(node.argument); + } } } - } - }; + }; -}; - -module.exports.schema = { - "anyOf": [ - { - "type": "array", - "items": [ - { - "enum": ["functions"] - } - ], - "minItems": 0, - "maxItems": 1 - }, - { - "type": "array", - "items": [ - { - "enum": ["all"] - }, - { - "type": "object", - "properties": { - "conditionalAssign": {"type": "boolean"} - }, - "additionalProperties": false - } - ], - "minItems": 0, - "maxItems": 2 - } - ] + } }; diff --git a/tools/eslint/lib/rules/no-extra-semi.js b/tools/eslint/lib/rules/no-extra-semi.js index 9639ae178e..e451b9e42e 100644 --- a/tools/eslint/lib/rules/no-extra-semi.js +++ b/tools/eslint/lib/rules/no-extra-semi.js @@ -9,76 +9,87 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Reports an unnecessary semicolon error. - * @param {Node|Token} nodeOrToken - A node or a token to be reported. - * @returns {void} - */ - function report(nodeOrToken) { - context.report({ - node: nodeOrToken, - message: "Unnecessary semicolon.", - fix: function(fixer) { - return fixer.remove(nodeOrToken); - } - }); - } +module.exports = { + meta: { + docs: { + description: "disallow unnecessary semicolons", + category: "Possible Errors", + recommended: true + }, - /** - * Checks for a part of a class body. - * This checks tokens from a specified token to a next MethodDefinition or the end of class body. - * - * @param {Token} firstToken - The first token to check. - * @returns {void} - */ - function checkForPartOfClassBody(firstToken) { - for (var token = firstToken; - token.type === "Punctuator" && token.value !== "}"; - token = context.getTokenAfter(token) - ) { - if (token.value === ";") { - report(token); - } - } - } + fixable: "code", + schema: [] + }, - return { + create: function(context) { /** - * Reports this empty statement, except if the parent node is a loop. - * @param {Node} node - A EmptyStatement node to be reported. + * Reports an unnecessary semicolon error. + * @param {Node|Token} nodeOrToken - A node or a token to be reported. * @returns {void} */ - "EmptyStatement": function(node) { - var parent = node.parent, - allowedParentTypes = ["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement"]; - - if (allowedParentTypes.indexOf(parent.type) === -1) { - report(node); - } - }, - - /** - * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body. - * @param {Node} node - A ClassBody node to check. - * @returns {void} - */ - "ClassBody": function(node) { - checkForPartOfClassBody(context.getFirstToken(node, 1)); // 0 is `{`. - }, + function report(nodeOrToken) { + context.report({ + node: nodeOrToken, + message: "Unnecessary semicolon.", + fix: function(fixer) { + return fixer.remove(nodeOrToken); + } + }); + } /** - * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body. - * @param {Node} node - A MethodDefinition node of the start point. + * Checks for a part of a class body. + * This checks tokens from a specified token to a next MethodDefinition or the end of class body. + * + * @param {Token} firstToken - The first token to check. * @returns {void} */ - "MethodDefinition": function(node) { - checkForPartOfClassBody(context.getTokenAfter(node)); + function checkForPartOfClassBody(firstToken) { + for (var token = firstToken; + token.type === "Punctuator" && token.value !== "}"; + token = context.getTokenAfter(token) + ) { + if (token.value === ";") { + report(token); + } + } } - }; -}; + return { + + /** + * Reports this empty statement, except if the parent node is a loop. + * @param {Node} node - A EmptyStatement node to be reported. + * @returns {void} + */ + EmptyStatement: function(node) { + var parent = node.parent, + allowedParentTypes = ["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement"]; -module.exports.schema = []; + if (allowedParentTypes.indexOf(parent.type) === -1) { + report(node); + } + }, + + /** + * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body. + * @param {Node} node - A ClassBody node to check. + * @returns {void} + */ + ClassBody: function(node) { + checkForPartOfClassBody(context.getFirstToken(node, 1)); // 0 is `{`. + }, + + /** + * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body. + * @param {Node} node - A MethodDefinition node of the start point. + * @returns {void} + */ + MethodDefinition: function(node) { + checkForPartOfClassBody(context.getTokenAfter(node)); + } + }; + + } +}; diff --git a/tools/eslint/lib/rules/no-fallthrough.js b/tools/eslint/lib/rules/no-fallthrough.js index 1e5d40c7ff..2edb4972ba 100644 --- a/tools/eslint/lib/rules/no-fallthrough.js +++ b/tools/eslint/lib/rules/no-fallthrough.js @@ -14,19 +14,20 @@ var lodash = require("lodash"); // Helpers //------------------------------------------------------------------------------ -var FALLTHROUGH_COMMENT = /falls?\s?through/i; +var DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/i; /** * Checks whether or not a given node has a fallthrough comment. * @param {ASTNode} node - A SwitchCase node to get comments. * @param {RuleContext} context - A rule context which stores comments. - * @returns {boolean} `true` if the node has a fallthrough comment. + * @param {RegExp} fallthroughCommentPattern - A pattern to match comment to. + * @returns {boolean} `true` if the node has a valid fallthrough comment. */ -function hasFallthroughComment(node, context) { +function hasFallthroughComment(node, context, fallthroughCommentPattern) { var sourceCode = context.getSourceCode(); var comment = lodash.last(sourceCode.getComments(node).leading); - return Boolean(comment && FALLTHROUGH_COMMENT.test(comment.value)); + return Boolean(comment && fallthroughCommentPattern.test(comment.value)); } /** @@ -38,58 +39,97 @@ function isReachable(segment) { return segment.reachable; } +/** + * Checks whether a node and a token are separated by blank lines + * @param {ASTNode} node - The node to check + * @param {Token} token - The token to compare against + * @returns {boolean} `true` if there are blank lines between node and token + */ +function hasBlankLinesBetween(node, token) { + return token.loc.start.line > node.loc.end.line + 1; +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var currentCodePath = null; - - /* - * We need to use leading comments of the next SwitchCase node because - * trailing comments is wrong if semicolons are omitted. - */ - var fallthroughCase = null; - - return { - "onCodePathStart": function(codePath) { - currentCodePath = codePath; - }, - "onCodePathEnd": function() { - currentCodePath = currentCodePath.upper; +module.exports = { + meta: { + docs: { + description: "disallow fallthrough of `case` statements", + category: "Best Practices", + recommended: true }, - "SwitchCase": function(node) { - - /* - * Checks whether or not there is a fallthrough comment. - * And reports the previous fallthrough node if that does not exist. - */ - if (fallthroughCase && !hasFallthroughComment(node, context)) { - context.report({ - message: "Expected a 'break' statement before '{{type}}'.", - data: {type: node.test ? "case" : "default"}, - node: node - }); + schema: [ + { + type: "object", + properties: { + commentPattern: { + type: "string" + } + }, + additionalProperties: false } - fallthroughCase = null; - }, + ] + }, + + create: function(context) { + var options = context.options[0] || {}; + var currentCodePath = null; + var sourceCode = context.getSourceCode(); + + /* + * We need to use leading comments of the next SwitchCase node because + * trailing comments is wrong if semicolons are omitted. + */ + var fallthroughCase = null; + var fallthroughCommentPattern = null; + + if (options.commentPattern) { + fallthroughCommentPattern = new RegExp(options.commentPattern); + } else { + fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT; + } - "SwitchCase:exit": function(node) { - - /* - * `reachable` meant fall through because statements preceded by - * `break`, `return`, or `throw` are unreachable. - * And allows empty cases and the last case. - */ - if (currentCodePath.currentSegments.some(isReachable) && - node.consequent.length > 0 && - lodash.last(node.parent.cases) !== node - ) { - fallthroughCase = node; + return { + onCodePathStart: function(codePath) { + currentCodePath = codePath; + }, + onCodePathEnd: function() { + currentCodePath = currentCodePath.upper; + }, + + SwitchCase: function(node) { + + /* + * Checks whether or not there is a fallthrough comment. + * And reports the previous fallthrough node if that does not exist. + */ + if (fallthroughCase && !hasFallthroughComment(node, context, fallthroughCommentPattern)) { + context.report({ + message: "Expected a 'break' statement before '{{type}}'.", + data: {type: node.test ? "case" : "default"}, + node: node + }); + } + fallthroughCase = null; + }, + + "SwitchCase:exit": function(node) { + var nextToken = sourceCode.getTokenAfter(node); + + /* + * `reachable` meant fall through because statements preceded by + * `break`, `return`, or `throw` are unreachable. + * And allows empty cases and the last case. + */ + if (currentCodePath.currentSegments.some(isReachable) && + (node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) && + lodash.last(node.parent.cases) !== node) { + fallthroughCase = node; + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-floating-decimal.js b/tools/eslint/lib/rules/no-floating-decimal.js index 61a84f5e46..56ebd3989a 100644 --- a/tools/eslint/lib/rules/no-floating-decimal.js +++ b/tools/eslint/lib/rules/no-floating-decimal.js @@ -9,22 +9,32 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow leading or trailing decimal points in numeric literals", + category: "Best Practices", + recommended: false + }, - return { - "Literal": function(node) { + schema: [] + }, - if (typeof node.value === "number") { - if (node.raw.indexOf(".") === 0) { - context.report(node, "A leading decimal point can be confused with a dot."); - } - if (node.raw.indexOf(".") === node.raw.length - 1) { - context.report(node, "A trailing decimal point can be confused with a dot."); + create: function(context) { + + return { + Literal: function(node) { + + if (typeof node.value === "number") { + if (node.raw.indexOf(".") === 0) { + context.report(node, "A leading decimal point can be confused with a dot."); + } + if (node.raw.indexOf(".") === node.raw.length - 1) { + context.report(node, "A trailing decimal point can be confused with a dot."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-func-assign.js b/tools/eslint/lib/rules/no-func-assign.js index f72c8c0150..ac3afe55c0 100644 --- a/tools/eslint/lib/rules/no-func-assign.js +++ b/tools/eslint/lib/rules/no-func-assign.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag use of function declaration identifiers as variables. * @author Ian Christian Myers - * @copyright 2013 Ian Christian Myers. All rights reserved. */ "use strict"; @@ -12,46 +11,56 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Reports a reference if is non initializer and writable. - * @param {References} references - Collection of reference to check. - * @returns {void} - */ - function checkReference(references) { - astUtils.getModifyingReferences(references).forEach(function(reference) { - context.report( - reference.identifier, - "'{{name}}' is a function.", - {name: reference.identifier.name}); - }); - } +module.exports = { + meta: { + docs: { + description: "disallow reassigning `function` declarations", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - if (variable.defs[0].type === "FunctionName") { - checkReference(variable.references); + /** + * Reports a reference if is non initializer and writable. + * @param {References} references - Collection of reference to check. + * @returns {void} + */ + function checkReference(references) { + astUtils.getModifyingReferences(references).forEach(function(reference) { + context.report( + reference.identifier, + "'{{name}}' is a function.", + {name: reference.identifier.name}); + }); } - } - /** - * Checks parameters of a given function node. - * @param {ASTNode} node - A function node to check. - * @returns {void} - */ - function checkForFunction(node) { - context.getDeclaredVariables(node).forEach(checkVariable); - } + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.defs[0].type === "FunctionName") { + checkReference(variable.references); + } + } - return { - "FunctionDeclaration": checkForFunction, - "FunctionExpression": checkForFunction - }; -}; + /** + * Checks parameters of a given function node. + * @param {ASTNode} node - A function node to check. + * @returns {void} + */ + function checkForFunction(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } -module.exports.schema = []; + return { + FunctionDeclaration: checkForFunction, + FunctionExpression: checkForFunction + }; + } +}; diff --git a/tools/eslint/lib/rules/no-implicit-coercion.js b/tools/eslint/lib/rules/no-implicit-coercion.js index 2bc96485c3..058d9f3572 100644 --- a/tools/eslint/lib/rules/no-implicit-coercion.js +++ b/tools/eslint/lib/rules/no-implicit-coercion.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to disallow the type conversions with shorter notations. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -144,104 +143,114 @@ function getOtherOperand(node, value) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = parseOptions(context.options[0]), - operatorAllowed = false; +module.exports = { + meta: { + docs: { + description: "disallow shorthand type conversions", + category: "Best Practices", + recommended: false + }, - return { - "UnaryExpression": function(node) { - - // !!foo - operatorAllowed = options.allow.indexOf("!!") >= 0; - if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) { - context.report( - node, - "use `Boolean({{code}})` instead.", { - code: context.getSource(node.argument.argument) - }); - } + schema: [{ + type: "object", + properties: { + boolean: { + type: "boolean" + }, + number: { + type: "boolean" + }, + string: { + type: "boolean" + }, + allow: { + type: "array", + items: { + enum: ALLOWABLE_OPERATORS + }, + uniqueItems: true + } + }, + additionalProperties: false + }] + }, - // ~foo.indexOf(bar) - operatorAllowed = options.allow.indexOf("~") >= 0; - if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) { - context.report( - node, - "use `{{code}} !== -1` instead.", { - code: context.getSource(node.argument) - }); - } + create: function(context) { + var options = parseOptions(context.options[0]), + operatorAllowed = false; - // +foo - operatorAllowed = options.allow.indexOf("+") >= 0; - if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) { - context.report( - node, - "use `Number({{code}})` instead.", { - code: context.getSource(node.argument) - }); - } - }, + return { + UnaryExpression: function(node) { - // Use `:exit` to prevent double reporting - "BinaryExpression:exit": function(node) { + // !!foo + operatorAllowed = options.allow.indexOf("!!") >= 0; + if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) { + context.report( + node, + "use `Boolean({{code}})` instead.", { + code: context.getSource(node.argument.argument) + }); + } - // 1 * foo - operatorAllowed = options.allow.indexOf("*") >= 0; - var nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && getNonNumericOperand(node); + // ~foo.indexOf(bar) + operatorAllowed = options.allow.indexOf("~") >= 0; + if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) { + context.report( + node, + "use `{{code}} !== -1` instead.", { + code: context.getSource(node.argument) + }); + } - if (nonNumericOperand) { - context.report( - node, - "use `Number({{code}})` instead.", { - code: context.getSource(nonNumericOperand) - }); - } + // +foo + operatorAllowed = options.allow.indexOf("+") >= 0; + if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) { + context.report( + node, + "use `Number({{code}})` instead.", { + code: context.getSource(node.argument) + }); + } + }, - // "" + foo - operatorAllowed = options.allow.indexOf("+") >= 0; - if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) { - context.report( - node, - "use `String({{code}})` instead.", { - code: context.getSource(getOtherOperand(node, "")) - }); - } - }, + // Use `:exit` to prevent double reporting + "BinaryExpression:exit": function(node) { - "AssignmentExpression": function(node) { + // 1 * foo + operatorAllowed = options.allow.indexOf("*") >= 0; + var nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && getNonNumericOperand(node); - // foo += "" - operatorAllowed = options.allow.indexOf("+") >= 0; - if (options.string && isAppendEmptyString(node)) { - context.report( - node, - "use `{{code}} = String({{code}})` instead.", { - code: context.getSource(getOtherOperand(node, "")) - }); - } - } - }; -}; + if (nonNumericOperand) { + context.report( + node, + "use `Number({{code}})` instead.", { + code: context.getSource(nonNumericOperand) + }); + } -module.exports.schema = [{ - "type": "object", - "properties": { - "boolean": { - "type": "boolean" - }, - "number": { - "type": "boolean" - }, - "string": { - "type": "boolean" - }, - "allow": { - "type": "array", - "items": { - "enum": ALLOWABLE_OPERATORS + // "" + foo + operatorAllowed = options.allow.indexOf("+") >= 0; + if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) { + context.report( + node, + "use `String({{code}})` instead.", { + code: context.getSource(getOtherOperand(node, "")) + }); + } }, - "uniqueItems": true - } - }, - "additionalProperties": false -}]; + + AssignmentExpression: function(node) { + + // foo += "" + operatorAllowed = options.allow.indexOf("+") >= 0; + if (options.string && isAppendEmptyString(node)) { + context.report( + node, + "use `{{code}} = String({{code}})` instead.", { + code: context.getSource(getOtherOperand(node, "")) + }); + } + } + }; + } +}; diff --git a/tools/eslint/lib/rules/no-implicit-globals.js b/tools/eslint/lib/rules/no-implicit-globals.js index 045ebffed2..c2768ea1a0 100644 --- a/tools/eslint/lib/rules/no-implicit-globals.js +++ b/tools/eslint/lib/rules/no-implicit-globals.js @@ -1,9 +1,7 @@ /** * @fileoverview Rule to check for implicit global variables and functions. * @author Joshua Peek - * @copyright 2015 Joshua Peek. All rights reserved. - * See LICENSE file in root directory for full license. -*/ + */ "use strict"; @@ -11,37 +9,47 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - return { - "Program": function() { - var scope = context.getScope(); - - scope.variables.forEach(function(variable) { - if (variable.writeable) { - return; - } - - variable.defs.forEach(function(def) { - if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) { - context.report(def.node, "Implicit global variable, assign as global property instead."); +module.exports = { + meta: { + docs: { + description: "disallow `var` and named `function` declarations in the global scope", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + return { + Program: function() { + var scope = context.getScope(); + + scope.variables.forEach(function(variable) { + if (variable.writeable) { + return; } + + variable.defs.forEach(function(def) { + if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) { + context.report(def.node, "Implicit global variable, assign as global property instead."); + } + }); }); - }); - scope.implicit.variables.forEach(function(variable) { - var scopeVariable = scope.set.get(variable.name); + scope.implicit.variables.forEach(function(variable) { + var scopeVariable = scope.set.get(variable.name); - if (scopeVariable && scopeVariable.writeable) { - return; - } + if (scopeVariable && scopeVariable.writeable) { + return; + } - variable.defs.forEach(function(def) { - context.report(def.node, "Implicit global variable, assign as global property instead."); + variable.defs.forEach(function(def) { + context.report(def.node, "Implicit global variable, assign as global property instead."); + }); }); - }); - } - }; + } + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-implied-eval.js b/tools/eslint/lib/rules/no-implied-eval.js index eea0f7fe41..7c1ed2fb6e 100644 --- a/tools/eslint/lib/rules/no-implied-eval.js +++ b/tools/eslint/lib/rules/no-implied-eval.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag use of implied eval via setTimeout and setInterval * @author James Allardice - * @copyright 2015 Mathias Schreck. All rights reserved. - * @copyright 2013 James Allardice. All rights reserved. */ "use strict"; @@ -11,142 +9,152 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var CALLEE_RE = /set(?:Timeout|Interval)|execScript/; - - /* - * Figures out if we should inspect a given binary expression. Is a stack - * of stacks, where the first element in each substack is a CallExpression. - */ - var impliedEvalAncestorsStack = []; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Get the last element of an array, without modifying arr, like pop(), but non-destructive. - * @param {array} arr What to inspect - * @returns {*} The last element of arr - * @private - */ - function last(arr) { - return arr ? arr[arr.length - 1] : null; - } - - /** - * Checks if the given MemberExpression node is a potentially implied eval identifier on window. - * @param {ASTNode} node The MemberExpression node to check. - * @returns {boolean} Whether or not the given node is potentially an implied eval. - * @private - */ - function isImpliedEvalMemberExpression(node) { - var object = node.object, - property = node.property, - hasImpliedEvalName = CALLEE_RE.test(property.name) || CALLEE_RE.test(property.value); - - return object.name === "window" && hasImpliedEvalName; - } - - /** - * Determines if a node represents a call to a potentially implied eval. - * - * This checks the callee name and that there's an argument, but not the type of the argument. - * - * @param {ASTNode} node The CallExpression to check. - * @returns {boolean} True if the node matches, false if not. - * @private - */ - function isImpliedEvalCallExpression(node) { - var isMemberExpression = (node.callee.type === "MemberExpression"), - isIdentifier = (node.callee.type === "Identifier"), - isImpliedEvalCallee = - (isIdentifier && CALLEE_RE.test(node.callee.name)) || - (isMemberExpression && isImpliedEvalMemberExpression(node.callee)); - - return isImpliedEvalCallee && node.arguments.length; - } - - /** - * Checks that the parent is a direct descendent of an potential implied eval CallExpression, and if the parent is a CallExpression, that we're the first argument. - * @param {ASTNode} node The node to inspect the parent of. - * @returns {boolean} Was the parent a direct descendent, and is the child therefore potentially part of a dangerous argument? - * @private - */ - function hasImpliedEvalParent(node) { - - // make sure our parent is marked - return node.parent === last(last(impliedEvalAncestorsStack)) && - - // if our parent is a CallExpression, make sure we're the first argument - (node.parent.type !== "CallExpression" || node === node.parent.arguments[0]); - } +module.exports = { + meta: { + docs: { + description: "disallow the use of `eval()`-like methods", + category: "Best Practices", + recommended: false + }, - /** - * Checks if our parent is marked as part of an implied eval argument. If - * so, collapses the top of impliedEvalAncestorsStack and reports on the - * original CallExpression. - * @param {ASTNode} node The CallExpression to check. - * @returns {boolean} True if the node matches, false if not. - * @private - */ - function checkString(node) { - if (hasImpliedEvalParent(node)) { - - // remove the entire substack, to avoid duplicate reports - var substack = impliedEvalAncestorsStack.pop(); - - context.report(substack[0], "Implied eval. Consider passing a function instead of a string."); + schema: [] + }, + + create: function(context) { + var CALLEE_RE = /set(?:Timeout|Interval)|execScript/; + + /* + * Figures out if we should inspect a given binary expression. Is a stack + * of stacks, where the first element in each substack is a CallExpression. + */ + var impliedEvalAncestorsStack = []; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Get the last element of an array, without modifying arr, like pop(), but non-destructive. + * @param {array} arr What to inspect + * @returns {*} The last element of arr + * @private + */ + function last(arr) { + return arr ? arr[arr.length - 1] : null; } - } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "CallExpression": function(node) { - if (isImpliedEvalCallExpression(node)) { + /** + * Checks if the given MemberExpression node is a potentially implied eval identifier on window. + * @param {ASTNode} node The MemberExpression node to check. + * @returns {boolean} Whether or not the given node is potentially an implied eval. + * @private + */ + function isImpliedEvalMemberExpression(node) { + var object = node.object, + property = node.property, + hasImpliedEvalName = CALLEE_RE.test(property.name) || CALLEE_RE.test(property.value); + + return object.name === "window" && hasImpliedEvalName; + } - // call expressions create a new substack - impliedEvalAncestorsStack.push([node]); - } - }, + /** + * Determines if a node represents a call to a potentially implied eval. + * + * This checks the callee name and that there's an argument, but not the type of the argument. + * + * @param {ASTNode} node The CallExpression to check. + * @returns {boolean} True if the node matches, false if not. + * @private + */ + function isImpliedEvalCallExpression(node) { + var isMemberExpression = (node.callee.type === "MemberExpression"), + isIdentifier = (node.callee.type === "Identifier"), + isImpliedEvalCallee = + (isIdentifier && CALLEE_RE.test(node.callee.name)) || + (isMemberExpression && isImpliedEvalMemberExpression(node.callee)); + + return isImpliedEvalCallee && node.arguments.length; + } - "CallExpression:exit": function(node) { - if (node === last(last(impliedEvalAncestorsStack))) { + /** + * Checks that the parent is a direct descendent of an potential implied eval CallExpression, and if the parent is a CallExpression, that we're the first argument. + * @param {ASTNode} node The node to inspect the parent of. + * @returns {boolean} Was the parent a direct descendent, and is the child therefore potentially part of a dangerous argument? + * @private + */ + function hasImpliedEvalParent(node) { - /* Destroys the entire sub-stack, rather than just using - * last(impliedEvalAncestorsStack).pop(), as a CallExpression is - * always the bottom of a impliedEvalAncestorsStack substack. - */ - impliedEvalAncestorsStack.pop(); - } - }, + // make sure our parent is marked + return node.parent === last(last(impliedEvalAncestorsStack)) && - "BinaryExpression": function(node) { - if (node.operator === "+" && hasImpliedEvalParent(node)) { - last(impliedEvalAncestorsStack).push(node); - } - }, + // if our parent is a CallExpression, make sure we're the first argument + (node.parent.type !== "CallExpression" || node === node.parent.arguments[0]); + } - "BinaryExpression:exit": function(node) { - if (node === last(last(impliedEvalAncestorsStack))) { - last(impliedEvalAncestorsStack).pop(); + /** + * Checks if our parent is marked as part of an implied eval argument. If + * so, collapses the top of impliedEvalAncestorsStack and reports on the + * original CallExpression. + * @param {ASTNode} node The CallExpression to check. + * @returns {boolean} True if the node matches, false if not. + * @private + */ + function checkString(node) { + if (hasImpliedEvalParent(node)) { + + // remove the entire substack, to avoid duplicate reports + var substack = impliedEvalAncestorsStack.pop(); + + context.report(substack[0], "Implied eval. Consider passing a function instead of a string."); } - }, + } - "Literal": function(node) { - if (typeof node.value === "string") { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + CallExpression: function(node) { + if (isImpliedEvalCallExpression(node)) { + + // call expressions create a new substack + impliedEvalAncestorsStack.push([node]); + } + }, + + "CallExpression:exit": function(node) { + if (node === last(last(impliedEvalAncestorsStack))) { + + /* Destroys the entire sub-stack, rather than just using + * last(impliedEvalAncestorsStack).pop(), as a CallExpression is + * always the bottom of a impliedEvalAncestorsStack substack. + */ + impliedEvalAncestorsStack.pop(); + } + }, + + BinaryExpression: function(node) { + if (node.operator === "+" && hasImpliedEvalParent(node)) { + last(impliedEvalAncestorsStack).push(node); + } + }, + + "BinaryExpression:exit": function(node) { + if (node === last(last(impliedEvalAncestorsStack))) { + last(impliedEvalAncestorsStack).pop(); + } + }, + + Literal: function(node) { + if (typeof node.value === "string") { + checkString(node); + } + }, + + TemplateLiteral: function(node) { checkString(node); } - }, - - "TemplateLiteral": function(node) { - checkString(node); - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-inline-comments.js b/tools/eslint/lib/rules/no-inline-comments.js index 4048802bc8..7835ed30fc 100644 --- a/tools/eslint/lib/rules/no-inline-comments.js +++ b/tools/eslint/lib/rules/no-inline-comments.js @@ -1,7 +1,6 @@ /** * @fileoverview Enforces or disallows inline comments. * @author Greg Cochard - * @copyright 2014 Greg Cochard. All rights reserved. */ "use strict"; @@ -11,44 +10,54 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow inline comments after code", + category: "Stylistic Issues", + recommended: false + }, - /** - * Will check that comments are not on lines starting with or ending with code - * @param {ASTNode} node The comment node to check - * @private - * @returns {void} - */ - function testCodeAroundComment(node) { + schema: [] + }, - // Get the whole line and cut it off at the start of the comment - var startLine = String(context.getSourceLines()[node.loc.start.line - 1]); - var endLine = String(context.getSourceLines()[node.loc.end.line - 1]); + create: function(context) { - var preamble = startLine.slice(0, node.loc.start.column).trim(); + /** + * Will check that comments are not on lines starting with or ending with code + * @param {ASTNode} node The comment node to check + * @private + * @returns {void} + */ + function testCodeAroundComment(node) { - // Also check after the comment - var postamble = endLine.slice(node.loc.end.column).trim(); + // Get the whole line and cut it off at the start of the comment + var startLine = String(context.getSourceLines()[node.loc.start.line - 1]); + var endLine = String(context.getSourceLines()[node.loc.end.line - 1]); - // Check that this comment isn't an ESLint directive - var isDirective = astUtils.isDirectiveComment(node); + var preamble = startLine.slice(0, node.loc.start.column).trim(); - // Should be empty if there was only whitespace around the comment - if (!isDirective && (preamble || postamble)) { - context.report(node, "Unexpected comment inline with code."); + // Also check after the comment + var postamble = endLine.slice(node.loc.end.column).trim(); + + // Check that this comment isn't an ESLint directive + var isDirective = astUtils.isDirectiveComment(node); + + // Should be empty if there was only whitespace around the comment + if (!isDirective && (preamble || postamble)) { + context.report(node, "Unexpected comment inline with code."); + } } - } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { + return { - "LineComment": testCodeAroundComment, - "BlockComment": testCodeAroundComment + LineComment: testCodeAroundComment, + BlockComment: testCodeAroundComment - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-inner-declarations.js b/tools/eslint/lib/rules/no-inner-declarations.js index 8dcd3e827c..3471ce8cc7 100644 --- a/tools/eslint/lib/rules/no-inner-declarations.js +++ b/tools/eslint/lib/rules/no-inner-declarations.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to enforce declarations in program or function body root. * @author Brandon Mills - * @copyright 2014 Brandon Mills. All rights reserved. */ "use strict"; @@ -10,71 +9,81 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Find the nearest Program or Function ancestor node. - * @returns {Object} Ancestor's type and distance from node. - */ - function nearestBody() { - var ancestors = context.getAncestors(), - ancestor = ancestors.pop(), - generation = 1; - - while (ancestor && ["Program", "FunctionDeclaration", - "FunctionExpression", "ArrowFunctionExpression" - ].indexOf(ancestor.type) < 0) { - generation += 1; - ancestor = ancestors.pop(); - } +module.exports = { + meta: { + docs: { + description: "disallow `function` or `var` declarations in nested blocks", + category: "Possible Errors", + recommended: true + }, - return { + schema: [ + { + enum: ["functions", "both"] + } + ] + }, - // Type of containing ancestor - type: ancestor.type, + create: function(context) { - // Separation between ancestor and node - distance: generation - }; - } + /** + * Find the nearest Program or Function ancestor node. + * @returns {Object} Ancestor's type and distance from node. + */ + function nearestBody() { + var ancestors = context.getAncestors(), + ancestor = ancestors.pop(), + generation = 1; - /** - * Ensure that a given node is at a program or function body's root. - * @param {ASTNode} node Declaration node to check. - * @returns {void} - */ - function check(node) { - var body = nearestBody(node), - valid = ((body.type === "Program" && body.distance === 1) || - body.distance === 2); - - if (!valid) { - context.report(node, "Move {{type}} declaration to {{body}} root.", - { - type: (node.type === "FunctionDeclaration" ? - "function" : "variable"), - body: (body.type === "Program" ? - "program" : "function body") - } - ); + while (ancestor && ["Program", "FunctionDeclaration", + "FunctionExpression", "ArrowFunctionExpression" + ].indexOf(ancestor.type) < 0) { + generation += 1; + ancestor = ancestors.pop(); + } + + return { + + // Type of containing ancestor + type: ancestor.type, + + // Separation between ancestor and node + distance: generation + }; } - } - return { + /** + * Ensure that a given node is at a program or function body's root. + * @param {ASTNode} node Declaration node to check. + * @returns {void} + */ + function check(node) { + var body = nearestBody(node), + valid = ((body.type === "Program" && body.distance === 1) || + body.distance === 2); - "FunctionDeclaration": check, - "VariableDeclaration": function(node) { - if (context.options[0] === "both" && node.kind === "var") { - check(node); + if (!valid) { + context.report(node, "Move {{type}} declaration to {{body}} root.", + { + type: (node.type === "FunctionDeclaration" ? + "function" : "variable"), + body: (body.type === "Program" ? + "program" : "function body") + } + ); } } - }; + return { -}; + FunctionDeclaration: check, + VariableDeclaration: function(node) { + if (context.options[0] === "both" && node.kind === "var") { + check(node); + } + } + + }; -module.exports.schema = [ - { - "enum": ["functions", "both"] } -]; +}; diff --git a/tools/eslint/lib/rules/no-invalid-regexp.js b/tools/eslint/lib/rules/no-invalid-regexp.js index 014e705838..6f8b867378 100644 --- a/tools/eslint/lib/rules/no-invalid-regexp.js +++ b/tools/eslint/lib/rules/no-invalid-regexp.js @@ -1,7 +1,6 @@ /** * @fileoverview Validate strings passed to the RegExp constructor * @author Michael Ficarra - * @copyright 2014 Michael Ficarra. All rights reserved. */ "use strict"; @@ -15,73 +14,83 @@ var espree = require("espree"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var options = context.options[0], - allowedFlags = ""; +module.exports = { + meta: { + docs: { + description: "disallow invalid regular expression strings in `RegExp` constructors", + category: "Possible Errors", + recommended: true + }, + + schema: [{ + type: "object", + properties: { + allowConstructorFlags: { + type: "array", + items: { + type: "string" + } + } + }, + additionalProperties: false + }] + }, - if (options && options.allowConstructorFlags) { - allowedFlags = options.allowConstructorFlags.join(""); - } + create: function(context) { - /** - * Check if node is a string - * @param {ASTNode} node node to evaluate - * @returns {boolean} True if its a string - * @private - */ - function isString(node) { - return node && node.type === "Literal" && typeof node.value === "string"; - } + var options = context.options[0], + allowedFlags = ""; - /** - * Validate strings passed to the RegExp constructor - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function check(node) { - if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0])) { - var flags = isString(node.arguments[1]) ? node.arguments[1].value : ""; - - if (allowedFlags) { - flags = flags.replace(new RegExp("[" + allowedFlags + "]", "gi"), ""); - } + if (options && options.allowConstructorFlags) { + allowedFlags = options.allowConstructorFlags.join(""); + } - try { - void new RegExp(node.arguments[0].value); - } catch (e) { - context.report(node, e.message); - } + /** + * Check if node is a string + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if its a string + * @private + */ + function isString(node) { + return node && node.type === "Literal" && typeof node.value === "string"; + } - if (flags) { + /** + * Validate strings passed to the RegExp constructor + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function check(node) { + if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0])) { + var flags = isString(node.arguments[1]) ? node.arguments[1].value : ""; + + if (allowedFlags) { + flags = flags.replace(new RegExp("[" + allowedFlags + "]", "gi"), ""); + } try { - espree.parse("/./" + flags, context.parserOptions); - } catch (ex) { - context.report(node, "Invalid flags supplied to RegExp constructor '" + flags + "'"); + void new RegExp(node.arguments[0].value); + } catch (e) { + context.report(node, e.message); } - } - } - } - - return { - "CallExpression": check, - "NewExpression": check - }; + if (flags) { -}; + try { + espree.parse("/./" + flags, context.parserOptions); + } catch (ex) { + context.report(node, "Invalid flags supplied to RegExp constructor '" + flags + "'"); + } + } -module.exports.schema = [{ - "type": "object", - "properties": { - "allowConstructorFlags": { - "type": "array", - "items": { - "type": "string" } } - }, - "additionalProperties": false -}]; + + return { + CallExpression: check, + NewExpression: check + }; + + } +}; diff --git a/tools/eslint/lib/rules/no-invalid-this.js b/tools/eslint/lib/rules/no-invalid-this.js index 0a9e535542..198bfd706a 100644 --- a/tools/eslint/lib/rules/no-invalid-this.js +++ b/tools/eslint/lib/rules/no-invalid-this.js @@ -1,8 +1,6 @@ /** * @fileoverview A rule to disallow `this` keywords outside of classes or class-like objects. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -17,98 +15,108 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var stack = [], - sourceCode = context.getSourceCode(); - - /** - * Gets the current checking context. - * - * The return value has a flag that whether or not `this` keyword is valid. - * The flag is initialized when got at the first time. - * - * @returns {{valid: boolean}} - * an object which has a flag that whether or not `this` keyword is valid. - */ - stack.getCurrent = function() { - var current = this[this.length - 1]; - - if (!current.init) { - current.init = true; - current.valid = !astUtils.isDefaultThisBinding( - current.node, - sourceCode); - } - return current; - }; - - /** - * Pushs new checking context into the stack. - * - * The checking context is not initialized yet. - * Because most functions don't have `this` keyword. - * When `this` keyword was found, the checking context is initialized. - * - * @param {ASTNode} node - A function node that was entered. - * @returns {void} - */ - function enterFunction(node) { - - // `this` can be invalid only under strict mode. - stack.push({ - init: !context.getScope().isStrict, - node: node, - valid: true - }); - } - - /** - * Pops the current checking context from the stack. - * @returns {void} - */ - function exitFunction() { - stack.pop(); - } - - return { +module.exports = { + meta: { + docs: { + description: "disallow `this` keywords outside of classes or class-like objects", + category: "Best Practices", + recommended: false + }, - /* - * `this` is invalid only under strict mode. - * Modules is always strict mode. + schema: [] + }, + + create: function(context) { + var stack = [], + sourceCode = context.getSourceCode(); + + /** + * Gets the current checking context. + * + * The return value has a flag that whether or not `this` keyword is valid. + * The flag is initialized when got at the first time. + * + * @returns {{valid: boolean}} + * an object which has a flag that whether or not `this` keyword is valid. + */ + stack.getCurrent = function() { + var current = this[this.length - 1]; + + if (!current.init) { + current.init = true; + current.valid = !astUtils.isDefaultThisBinding( + current.node, + sourceCode); + } + return current; + }; + + /** + * Pushs new checking context into the stack. + * + * The checking context is not initialized yet. + * Because most functions don't have `this` keyword. + * When `this` keyword was found, the checking context is initialized. + * + * @param {ASTNode} node - A function node that was entered. + * @returns {void} */ - "Program": function(node) { - var scope = context.getScope(), - features = context.parserOptions.ecmaFeatures || {}; + function enterFunction(node) { + // `this` can be invalid only under strict mode. stack.push({ - init: true, + init: !context.getScope().isStrict, node: node, - valid: !( - scope.isStrict || - node.sourceType === "module" || - (features.globalReturn && scope.childScopes[0].isStrict) - ) + valid: true }); - }, + } - "Program:exit": function() { + /** + * Pops the current checking context from the stack. + * @returns {void} + */ + function exitFunction() { stack.pop(); - }, - - "FunctionDeclaration": enterFunction, - "FunctionDeclaration:exit": exitFunction, - "FunctionExpression": enterFunction, - "FunctionExpression:exit": exitFunction, - - // Reports if `this` of the current context is invalid. - "ThisExpression": function(node) { - var current = stack.getCurrent(); + } - if (current && !current.valid) { - context.report(node, "Unexpected 'this'."); + return { + + /* + * `this` is invalid only under strict mode. + * Modules is always strict mode. + */ + Program: function(node) { + var scope = context.getScope(), + features = context.parserOptions.ecmaFeatures || {}; + + stack.push({ + init: true, + node: node, + valid: !( + scope.isStrict || + node.sourceType === "module" || + (features.globalReturn && scope.childScopes[0].isStrict) + ) + }); + }, + + "Program:exit": function() { + stack.pop(); + }, + + FunctionDeclaration: enterFunction, + "FunctionDeclaration:exit": exitFunction, + FunctionExpression: enterFunction, + "FunctionExpression:exit": exitFunction, + + // Reports if `this` of the current context is invalid. + ThisExpression: function(node) { + var current = stack.getCurrent(); + + if (current && !current.valid) { + context.report(node, "Unexpected 'this'."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-irregular-whitespace.js b/tools/eslint/lib/rules/no-irregular-whitespace.js index a849f060bd..1dbea8f5a9 100644 --- a/tools/eslint/lib/rules/no-irregular-whitespace.js +++ b/tools/eslint/lib/rules/no-irregular-whitespace.js @@ -2,7 +2,6 @@ * @fileoverview Rule to disalow whitespace that is not a tab or space, whitespace inside strings and comments are allowed * @author Jonathan Kingston * @author Christophe Porteneuve - * @copyright 2014 Jonathan Kingston. All rights reserved. */ "use strict"; @@ -11,187 +10,197 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow irregular whitespace outside of strings and comments", + category: "Possible Errors", + recommended: true + }, + + schema: [ + { + type: "object", + properties: { + skipComments: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { - var irregularWhitespace = /[\u0085\u00A0\ufeff\f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg, - irregularLineTerminators = /[\u2028\u2029]/mg; + var irregularWhitespace = /[\u0085\u00A0\ufeff\f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg, + irregularLineTerminators = /[\u2028\u2029]/mg; - // Module store of errors that we have found - var errors = []; + // Module store of errors that we have found + var errors = []; - // Comment nodes. We accumulate these as we go, so we can be sure to trigger them after the whole `Program` entity is parsed, even for top-of-file comments. - var commentNodes = []; + // Comment nodes. We accumulate these as we go, so we can be sure to trigger them after the whole `Program` entity is parsed, even for top-of-file comments. + var commentNodes = []; - // Lookup the `skipComments` option, which defaults to `false`. - var options = context.options[0] || {}; - var skipComments = !!options.skipComments; + // Lookup the `skipComments` option, which defaults to `false`. + var options = context.options[0] || {}; + var skipComments = !!options.skipComments; - /** - * Removes errors that occur inside a string node - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeWhitespaceError(node) { - var locStart = node.loc.start; - var locEnd = node.loc.end; + /** + * Removes errors that occur inside a string node + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeWhitespaceError(node) { + var locStart = node.loc.start; + var locEnd = node.loc.end; - errors = errors.filter(function(error) { - var errorLoc = error[1]; + errors = errors.filter(function(error) { + var errorLoc = error[1]; + + if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) { + if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) { + return false; + } + } + return true; + }); + } - if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) { - if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) { - return false; + /** + * Checks identifier or literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInIdentifierOrLiteral(node) { + if (typeof node.value === "string") { + + // If we have irregular characters remove them from the errors list + if (node.raw.match(irregularWhitespace) || node.raw.match(irregularLineTerminators)) { + removeWhitespaceError(node); } } - return true; - }); - } + } - /** - * Checks identifier or literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeInvalidNodeErrorsInIdentifierOrLiteral(node) { - if (typeof node.value === "string") { - - // If we have irregular characters remove them from the errors list - if (node.raw.match(irregularWhitespace) || node.raw.match(irregularLineTerminators)) { + /** + * Checks comment nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInComment(node) { + if (node.value.match(irregularWhitespace) || node.value.match(irregularLineTerminators)) { removeWhitespaceError(node); } } - } - /** - * Checks comment nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeInvalidNodeErrorsInComment(node) { - if (node.value.match(irregularWhitespace) || node.value.match(irregularLineTerminators)) { - removeWhitespaceError(node); + /** + * Checks the program source for irregular whitespace + * @param {ASTNode} node The program node + * @returns {void} + * @private + */ + function checkForIrregularWhitespace(node) { + var sourceLines = context.getSourceLines(); + + sourceLines.forEach(function(sourceLine, lineIndex) { + var lineNumber = lineIndex + 1, + location, + match; + + while ((match = irregularWhitespace.exec(sourceLine)) !== null) { + location = { + line: lineNumber, + column: match.index + }; + + errors.push([node, location, "Irregular whitespace not allowed"]); + } + }); } - } - /** - * Checks the program source for irregular whitespace - * @param {ASTNode} node The program node - * @returns {void} - * @private - */ - function checkForIrregularWhitespace(node) { - var sourceLines = context.getSourceLines(); - - sourceLines.forEach(function(sourceLine, lineIndex) { - var lineNumber = lineIndex + 1, + /** + * Checks the program source for irregular line terminators + * @param {ASTNode} node The program node + * @returns {void} + * @private + */ + function checkForIrregularLineTerminators(node) { + var source = context.getSource(), + sourceLines = context.getSourceLines(), + linebreaks = source.match(/\r\n|\r|\n|\u2028|\u2029/g), + lastLineIndex = -1, + lineIndex, location, match; - while ((match = irregularWhitespace.exec(sourceLine)) !== null) { + while ((match = irregularLineTerminators.exec(source)) !== null) { + lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0; + location = { - line: lineNumber, - column: match.index + line: lineIndex + 1, + column: sourceLines[lineIndex].length }; errors.push([node, location, "Irregular whitespace not allowed"]); + lastLineIndex = lineIndex; } - }); - } - - /** - * Checks the program source for irregular line terminators - * @param {ASTNode} node The program node - * @returns {void} - * @private - */ - function checkForIrregularLineTerminators(node) { - var source = context.getSource(), - sourceLines = context.getSourceLines(), - linebreaks = source.match(/\r\n|\r|\n|\u2028|\u2029/g), - lastLineIndex = -1, - lineIndex, - location, - match; - - while ((match = irregularLineTerminators.exec(source)) !== null) { - lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0; - - location = { - line: lineIndex + 1, - column: sourceLines[lineIndex].length - }; - - errors.push([node, location, "Irregular whitespace not allowed"]); - lastLineIndex = lineIndex; } - } - - /** - * Stores a comment node (`LineComment` or `BlockComment`) for later stripping of errors within; a necessary deferring of processing to deal with top-of-file comments. - * @param {ASTNode} node The comment node - * @returns {void} - * @private - */ - function rememberCommentNode(node) { - commentNodes.push(node); - } - - /** - * A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`. - * @returns {void} - * @private - */ - function noop() {} - - return { - "Program": function(node) { - - /* - * As we can easily fire warnings for all white space issues with - * all the source its simpler to fire them here. - * This means we can check all the application code without having - * to worry about issues caused in the parser tokens. - * When writing this code also evaluating per node was missing out - * connecting tokens in some cases. - * We can later filter the errors when they are found to be not an - * issue in nodes we don't care about. - */ - - checkForIrregularWhitespace(node); - checkForIrregularLineTerminators(node); - }, - - "Identifier": removeInvalidNodeErrorsInIdentifierOrLiteral, - "Literal": removeInvalidNodeErrorsInIdentifierOrLiteral, - "LineComment": skipComments ? rememberCommentNode : noop, - "BlockComment": skipComments ? rememberCommentNode : noop, - "Program:exit": function() { - - if (skipComments) { - // First strip errors occurring in comment nodes. We have to do this post-`Program` to deal with top-of-file comments. - commentNodes.forEach(removeInvalidNodeErrorsInComment); - } - - // If we have any errors remaining report on them - errors.forEach(function(error) { - context.report.apply(context, error); - }); + /** + * Stores a comment node (`LineComment` or `BlockComment`) for later stripping of errors within; a necessary deferring of processing to deal with top-of-file comments. + * @param {ASTNode} node The comment node + * @returns {void} + * @private + */ + function rememberCommentNode(node) { + commentNodes.push(node); } - }; -}; -module.exports.schema = [ - { - "type": "object", - "properties": { - "skipComments": { - "type": "boolean" + /** + * A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`. + * @returns {void} + * @private + */ + function noop() {} + + return { + Program: function(node) { + + /* + * As we can easily fire warnings for all white space issues with + * all the source its simpler to fire them here. + * This means we can check all the application code without having + * to worry about issues caused in the parser tokens. + * When writing this code also evaluating per node was missing out + * connecting tokens in some cases. + * We can later filter the errors when they are found to be not an + * issue in nodes we don't care about. + */ + + checkForIrregularWhitespace(node); + checkForIrregularLineTerminators(node); + }, + + Identifier: removeInvalidNodeErrorsInIdentifierOrLiteral, + Literal: removeInvalidNodeErrorsInIdentifierOrLiteral, + LineComment: skipComments ? rememberCommentNode : noop, + BlockComment: skipComments ? rememberCommentNode : noop, + "Program:exit": function() { + + if (skipComments) { + + // First strip errors occurring in comment nodes. We have to do this post-`Program` to deal with top-of-file comments. + commentNodes.forEach(removeInvalidNodeErrorsInComment); + } + + // If we have any errors remaining report on them + errors.forEach(function(error) { + context.report.apply(context, error); + }); } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/no-iterator.js b/tools/eslint/lib/rules/no-iterator.js index 817980c99f..67c1c1bb2a 100644 --- a/tools/eslint/lib/rules/no-iterator.js +++ b/tools/eslint/lib/rules/no-iterator.js @@ -9,20 +9,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of the `__iterator__` property", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "MemberExpression": function(node) { + create: function(context) { - if (node.property && - (node.property.type === "Identifier" && node.property.name === "__iterator__" && !node.computed) || - (node.property.type === "Literal" && node.property.value === "__iterator__")) { - context.report(node, "Reserved name '__iterator__'."); + return { + + MemberExpression: function(node) { + + if (node.property && + (node.property.type === "Identifier" && node.property.name === "__iterator__" && !node.computed) || + (node.property.type === "Literal" && node.property.value === "__iterator__")) { + context.report(node, "Reserved name '__iterator__'."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-label-var.js b/tools/eslint/lib/rules/no-label-var.js index 20fbfc182d..7c6d56f355 100644 --- a/tools/eslint/lib/rules/no-label-var.js +++ b/tools/eslint/lib/rules/no-label-var.js @@ -15,43 +15,53 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Check if the identifier is present inside current scope - * @param {object} scope current scope - * @param {string} name To evaluate - * @returns {boolean} True if its present - * @private - */ - function findIdentifier(scope, name) { - return astUtils.getVariableByName(scope, name) !== null; - } +module.exports = { + meta: { + docs: { + description: "disallow labels that share a name with a variable", + category: "Variables", + recommended: false + }, + + schema: [] + }, + + create: function(context) { - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Check if the identifier is present inside current scope + * @param {object} scope current scope + * @param {string} name To evaluate + * @returns {boolean} True if its present + * @private + */ + function findIdentifier(scope, name) { + return astUtils.getVariableByName(scope, name) !== null; + } - return { + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - "LabeledStatement": function(node) { + return { - // Fetch the innermost scope. - var scope = context.getScope(); + LabeledStatement: function(node) { - // Recursively find the identifier walking up the scope, starting - // with the innermost scope. - if (findIdentifier(scope, node.label.name)) { - context.report(node, "Found identifier with same name as label."); + // Fetch the innermost scope. + var scope = context.getScope(); + + // Recursively find the identifier walking up the scope, starting + // with the innermost scope. + if (findIdentifier(scope, node.label.name)) { + context.report(node, "Found identifier with same name as label."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-labels.js b/tools/eslint/lib/rules/no-labels.js index fe42c3d886..da0cd8e742 100644 --- a/tools/eslint/lib/rules/no-labels.js +++ b/tools/eslint/lib/rules/no-labels.js @@ -1,8 +1,6 @@ /** * @fileoverview Disallow Labeled Statements * @author Nicholas C. Zakas - * @copyright 2014 Nicholas C. Zakas. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; @@ -16,120 +14,130 @@ var LOOP_TYPES = /^(?:While|DoWhile|For|ForIn|ForOf)Statement$/; // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0]; - var allowLoop = Boolean(options && options.allowLoop); - var allowSwitch = Boolean(options && options.allowSwitch); - var scopeInfo = null; - - /** - * Gets the kind of a given node. - * - * @param {ASTNode} node - A node to get. - * @returns {string} The kind of the node. - */ - function getBodyKind(node) { - var type = node.type; - - if (LOOP_TYPES.test(type)) { - return "loop"; - } - if (type === "SwitchStatement") { - return "switch"; - } - return "other"; - } +module.exports = { + meta: { + docs: { + description: "disallow labeled statements", + category: "Best Practices", + recommended: false + }, - /** - * Checks whether the label of a given kind is allowed or not. - * - * @param {string} kind - A kind to check. - * @returns {boolean} `true` if the kind is allowed. - */ - function isAllowed(kind) { - switch (kind) { - case "loop": return allowLoop; - case "switch": return allowSwitch; - default: return false; + schema: [ + { + type: "object", + properties: { + allowLoop: { + type: "boolean" + }, + allowSwitch: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var options = context.options[0]; + var allowLoop = Boolean(options && options.allowLoop); + var allowSwitch = Boolean(options && options.allowSwitch); + var scopeInfo = null; + + /** + * Gets the kind of a given node. + * + * @param {ASTNode} node - A node to get. + * @returns {string} The kind of the node. + */ + function getBodyKind(node) { + var type = node.type; + + if (LOOP_TYPES.test(type)) { + return "loop"; + } + if (type === "SwitchStatement") { + return "switch"; + } + return "other"; } - } - /** - * Checks whether a given name is a label of a loop or not. - * - * @param {string} label - A name of a label to check. - * @returns {boolean} `true` if the name is a label of a loop. - */ - function getKind(label) { - var info = scopeInfo; - - while (info) { - if (info.label === label) { - return info.kind; + /** + * Checks whether the label of a given kind is allowed or not. + * + * @param {string} kind - A kind to check. + * @returns {boolean} `true` if the kind is allowed. + */ + function isAllowed(kind) { + switch (kind) { + case "loop": return allowLoop; + case "switch": return allowSwitch; + default: return false; } - info = info.upper; } - /* istanbul ignore next: syntax error */ - return "other"; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "LabeledStatement": function(node) { - scopeInfo = { - label: node.label.name, - kind: getBodyKind(node.body), - upper: scopeInfo - }; - }, - - "LabeledStatement:exit": function(node) { - if (!isAllowed(scopeInfo.kind)) { - context.report({ - node: node, - message: "Unexpected labeled statement." - }); + /** + * Checks whether a given name is a label of a loop or not. + * + * @param {string} label - A name of a label to check. + * @returns {boolean} `true` if the name is a label of a loop. + */ + function getKind(label) { + var info = scopeInfo; + + while (info) { + if (info.label === label) { + return info.kind; + } + info = info.upper; } - scopeInfo = scopeInfo.upper; - }, + /* istanbul ignore next: syntax error */ + return "other"; + } - "BreakStatement": function(node) { - if (node.label && !isAllowed(getKind(node.label.name))) { - context.report({ - node: node, - message: "Unexpected label in break statement." - }); - } - }, + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + LabeledStatement: function(node) { + scopeInfo = { + label: node.label.name, + kind: getBodyKind(node.body), + upper: scopeInfo + }; + }, - "ContinueStatement": function(node) { - if (node.label && !isAllowed(getKind(node.label.name))) { - context.report({ - node: node, - message: "Unexpected label in continue statement." - }); - } - } - }; + "LabeledStatement:exit": function(node) { + if (!isAllowed(scopeInfo.kind)) { + context.report({ + node: node, + message: "Unexpected labeled statement." + }); + } -}; + scopeInfo = scopeInfo.upper; + }, -module.exports.schema = [ - { - type: "object", - properties: { - allowLoop: { - type: "boolean" + BreakStatement: function(node) { + if (node.label && !isAllowed(getKind(node.label.name))) { + context.report({ + node: node, + message: "Unexpected label in break statement." + }); + } }, - allowSwitch: { - type: "boolean" + + ContinueStatement: function(node) { + if (node.label && !isAllowed(getKind(node.label.name))) { + context.report({ + node: node, + message: "Unexpected label in continue statement." + }); + } } - }, - additionalProperties: false + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/no-lone-blocks.js b/tools/eslint/lib/rules/no-lone-blocks.js index 976a817b5e..113cd89b93 100644 --- a/tools/eslint/lib/rules/no-lone-blocks.js +++ b/tools/eslint/lib/rules/no-lone-blocks.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag blocks with no reason to exist * @author Brandon Mills - * @copyright 2015 Roberto Vidal. All rights reserved. - * @copyright 2014 Brandon Mills. All rights reserved. */ "use strict"; @@ -11,94 +9,104 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - // A stack of lone blocks to be checked for block-level bindings - var loneBlocks = [], - ruleDef; - - /** - * Reports a node as invalid. - * @param {ASTNode} node - The node to be reported. - * @returns {void} - */ - function report(node) { - var parent = context.getAncestors().pop(); - - context.report(node, parent.type === "Program" ? - "Block is redundant." : - "Nested block is redundant." - ); - } - - /** - * Checks for any ocurrence of BlockStatement > BlockStatement or Program > BlockStatement - * @returns {boolean} True if the current node is a lone block. - */ - function isLoneBlock() { - var parent = context.getAncestors().pop(); +module.exports = { + meta: { + docs: { + description: "disallow unnecessary nested blocks", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + // A stack of lone blocks to be checked for block-level bindings + var loneBlocks = [], + ruleDef; + + /** + * Reports a node as invalid. + * @param {ASTNode} node - The node to be reported. + * @returns {void} + */ + function report(node) { + var parent = context.getAncestors().pop(); + + context.report(node, parent.type === "Program" ? + "Block is redundant." : + "Nested block is redundant." + ); + } - return parent.type === "BlockStatement" || parent.type === "Program"; - } + /** + * Checks for any ocurrence of BlockStatement > BlockStatement or Program > BlockStatement + * @returns {boolean} True if the current node is a lone block. + */ + function isLoneBlock() { + var parent = context.getAncestors().pop(); - /** - * Checks the enclosing block of the current node for block-level bindings, - * and "marks it" as valid if any. - * @returns {void} - */ - function markLoneBlock() { - if (loneBlocks.length === 0) { - return; + return parent.type === "BlockStatement" || parent.type === "Program"; } - var block = context.getAncestors().pop(); + /** + * Checks the enclosing block of the current node for block-level bindings, + * and "marks it" as valid if any. + * @returns {void} + */ + function markLoneBlock() { + if (loneBlocks.length === 0) { + return; + } - if (loneBlocks[loneBlocks.length - 1] === block) { - loneBlocks.pop(); - } - } + var block = context.getAncestors().pop(); - // Default rule definition: report all lone blocks - ruleDef = { - BlockStatement: function(node) { - if (isLoneBlock(node)) { - report(node); + if (loneBlocks[loneBlocks.length - 1] === block) { + loneBlocks.pop(); } } - }; - // ES6: report blocks without block-level bindings - if (context.parserOptions.ecmaVersion >= 6) { + // Default rule definition: report all lone blocks ruleDef = { - "BlockStatement": function(node) { + BlockStatement: function(node) { if (isLoneBlock(node)) { - loneBlocks.push(node); - } - }, - "BlockStatement:exit": function(node) { - if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) { - loneBlocks.pop(); report(node); } } }; - ruleDef.VariableDeclaration = function(node) { - if (node.kind === "let" || node.kind === "const") { - markLoneBlock(node); - } - }; + // ES6: report blocks without block-level bindings + if (context.parserOptions.ecmaVersion >= 6) { + ruleDef = { + BlockStatement: function(node) { + if (isLoneBlock(node)) { + loneBlocks.push(node); + } + }, + "BlockStatement:exit": function(node) { + if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) { + loneBlocks.pop(); + report(node); + } + } + }; - ruleDef.FunctionDeclaration = function(node) { - if (context.getScope().isStrict) { - markLoneBlock(node); - } - }; + ruleDef.VariableDeclaration = function(node) { + if (node.kind === "let" || node.kind === "const") { + markLoneBlock(node); + } + }; - ruleDef.ClassDeclaration = markLoneBlock; - } + ruleDef.FunctionDeclaration = function(node) { + if (context.getScope().isStrict) { + markLoneBlock(node); + } + }; - return ruleDef; -}; + ruleDef.ClassDeclaration = markLoneBlock; + } -module.exports.schema = []; + return ruleDef; + } +}; diff --git a/tools/eslint/lib/rules/no-lonely-if.js b/tools/eslint/lib/rules/no-lonely-if.js index 0d8ab9124a..1efd1acc01 100644 --- a/tools/eslint/lib/rules/no-lonely-if.js +++ b/tools/eslint/lib/rules/no-lonely-if.js @@ -8,23 +8,33 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `if` statements as the only statement in `else` blocks", + category: "Stylistic Issues", + recommended: false + }, - return { - "IfStatement": function(node) { - var ancestors = context.getAncestors(), - parent = ancestors.pop(), - grandparent = ancestors.pop(); + schema: [] + }, - if (parent && parent.type === "BlockStatement" && - parent.body.length === 1 && grandparent && - grandparent.type === "IfStatement" && - parent === grandparent.alternate) { - context.report(node, "Unexpected if as the only statement in an else block."); + create: function(context) { + + return { + IfStatement: function(node) { + var ancestors = context.getAncestors(), + parent = ancestors.pop(), + grandparent = ancestors.pop(); + + if (parent && parent.type === "BlockStatement" && + parent.body.length === 1 && grandparent && + grandparent.type === "IfStatement" && + parent === grandparent.alternate) { + context.report(node, "Unexpected if as the only statement in an else block."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-loop-func.js b/tools/eslint/lib/rules/no-loop-func.js index 0ab2fb853a..2b76093cce 100644 --- a/tools/eslint/lib/rules/no-loop-func.js +++ b/tools/eslint/lib/rules/no-loop-func.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag creation of function inside a loop * @author Ilya Volodin - * @copyright 2013 Ilya Volodin. All rights reserved. */ "use strict"; @@ -152,38 +151,48 @@ function isSafe(funcNode, loopNode, reference) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Reports functions which match the following condition: - * - * - has a loop node in ancestors. - * - has any references which refers to an unsafe variable. - * - * @param {ASTNode} node The AST node to check. - * @returns {boolean} Whether or not the node is within a loop. - */ - function checkForLoops(node) { - var loopNode = getContainingLoopNode(node); - - if (!loopNode) { - return; +module.exports = { + meta: { + docs: { + description: "disallow `function` declarations and expressions inside loop statements", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + /** + * Reports functions which match the following condition: + * + * - has a loop node in ancestors. + * - has any references which refers to an unsafe variable. + * + * @param {ASTNode} node The AST node to check. + * @returns {boolean} Whether or not the node is within a loop. + */ + function checkForLoops(node) { + var loopNode = getContainingLoopNode(node); + + if (!loopNode) { + return; + } + + var references = context.getScope().through; + + if (references.length > 0 && + !references.every(isSafe.bind(null, node, loopNode)) + ) { + context.report(node, "Don't make functions within a loop"); + } } - var references = context.getScope().through; - - if (references.length > 0 && - !references.every(isSafe.bind(null, node, loopNode)) - ) { - context.report(node, "Don't make functions within a loop"); - } + return { + ArrowFunctionExpression: checkForLoops, + FunctionExpression: checkForLoops, + FunctionDeclaration: checkForLoops + }; } - - return { - "ArrowFunctionExpression": checkForLoops, - "FunctionExpression": checkForLoops, - "FunctionDeclaration": checkForLoops - }; }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-magic-numbers.js b/tools/eslint/lib/rules/no-magic-numbers.js index e7532bd51e..2e7434c1a5 100644 --- a/tools/eslint/lib/rules/no-magic-numbers.js +++ b/tools/eslint/lib/rules/no-magic-numbers.js @@ -1,32 +1,6 @@ /** * @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js) * @author Vincent Lemeunier - * @copyright 2015 Vincent Lemeunier. All rights reserved. - * - * This rule was adapted from danielstjules/buddy.js - * The MIT License (MIT) - * - * Copyright (c) 2014 Daniel St. Jules - * - * 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. - * - * See LICENSE file in root directory for full license. */ "use strict"; @@ -35,125 +9,136 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var config = context.options[0] || {}, - detectObjects = !!config.detectObjects, - enforceConst = !!config.enforceConst, - ignore = config.ignore || [], - ignoreArrayIndexes = !!config.ignoreArrayIndexes; +module.exports = { + meta: { + docs: { + description: "disallow magic numbers", + category: "Best Practices", + recommended: false + }, - /** - * Returns whether the node is number literal - * @param {Node} node - the node literal being evaluated - * @returns {boolean} true if the node is a number literal - */ - function isNumber(node) { - return typeof node.value === "number"; - } + schema: [{ + type: "object", + properties: { + detectObjects: { + type: "boolean" + }, + enforceConst: { + type: "boolean" + }, + ignore: { + type: "array", + items: { + type: "number" + }, + uniqueItems: true + }, + ignoreArrayIndexes: { + type: "boolean" + } + }, + additionalProperties: false + }] + }, - /** - * Returns whether the number should be ignored - * @param {number} num - the number - * @returns {boolean} true if the number should be ignored - */ - function shouldIgnoreNumber(num) { - return ignore.indexOf(num) !== -1; - } + create: function(context) { + var config = context.options[0] || {}, + detectObjects = !!config.detectObjects, + enforceConst = !!config.enforceConst, + ignore = config.ignore || [], + ignoreArrayIndexes = !!config.ignoreArrayIndexes; + + /** + * Returns whether the node is number literal + * @param {Node} node - the node literal being evaluated + * @returns {boolean} true if the node is a number literal + */ + function isNumber(node) { + return typeof node.value === "number"; + } - /** - * Returns whether the number should be ignored when used as a radix within parseInt() or Number.parseInt() - * @param {ASTNode} parent - the non-"UnaryExpression" parent - * @param {ASTNode} node - the node literal being evaluated - * @returns {boolean} true if the number should be ignored - */ - function shouldIgnoreParseInt(parent, node) { - return parent.type === "CallExpression" && node === parent.arguments[1] && - (parent.callee.name === "parseInt" || - parent.callee.type === "MemberExpression" && - parent.callee.object.name === "Number" && - parent.callee.property.name === "parseInt"); - } + /** + * Returns whether the number should be ignored + * @param {number} num - the number + * @returns {boolean} true if the number should be ignored + */ + function shouldIgnoreNumber(num) { + return ignore.indexOf(num) !== -1; + } - /** - * Returns whether the number should be ignored when used to define a JSX prop - * @param {ASTNode} parent - the non-"UnaryExpression" parent - * @returns {boolean} true if the number should be ignored - */ - function shouldIgnoreJSXNumbers(parent) { - return parent.type.indexOf("JSX") === 0; - } + /** + * Returns whether the number should be ignored when used as a radix within parseInt() or Number.parseInt() + * @param {ASTNode} parent - the non-"UnaryExpression" parent + * @param {ASTNode} node - the node literal being evaluated + * @returns {boolean} true if the number should be ignored + */ + function shouldIgnoreParseInt(parent, node) { + return parent.type === "CallExpression" && node === parent.arguments[1] && + (parent.callee.name === "parseInt" || + parent.callee.type === "MemberExpression" && + parent.callee.object.name === "Number" && + parent.callee.property.name === "parseInt"); + } - /** - * Returns whether the number should be ignored when used as an array index with enabled 'ignoreArrayIndexes' option. - * @param {ASTNode} parent - the non-"UnaryExpression" parent. - * @returns {boolean} true if the number should be ignored - */ - function shouldIgnoreArrayIndexes(parent) { - return parent.type === "MemberExpression" && ignoreArrayIndexes; - } + /** + * Returns whether the number should be ignored when used to define a JSX prop + * @param {ASTNode} parent - the non-"UnaryExpression" parent + * @returns {boolean} true if the number should be ignored + */ + function shouldIgnoreJSXNumbers(parent) { + return parent.type.indexOf("JSX") === 0; + } + + /** + * Returns whether the number should be ignored when used as an array index with enabled 'ignoreArrayIndexes' option. + * @param {ASTNode} parent - the non-"UnaryExpression" parent. + * @returns {boolean} true if the number should be ignored + */ + function shouldIgnoreArrayIndexes(parent) { + return parent.type === "MemberExpression" && ignoreArrayIndexes; + } - return { - "Literal": function(node) { - var parent = node.parent, - value = node.value, - raw = node.raw, - okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"]; + return { + Literal: function(node) { + var parent = node.parent, + value = node.value, + raw = node.raw, + okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"]; - if (!isNumber(node)) { - return; - } + if (!isNumber(node)) { + return; + } - // For negative magic numbers: update the value and parent node - if (parent.type === "UnaryExpression" && parent.operator === "-") { - node = parent; - parent = node.parent; - value = -value; - raw = "-" + raw; - } + // For negative magic numbers: update the value and parent node + if (parent.type === "UnaryExpression" && parent.operator === "-") { + node = parent; + parent = node.parent; + value = -value; + raw = "-" + raw; + } - if (shouldIgnoreNumber(value) || - shouldIgnoreParseInt(parent, node) || - shouldIgnoreArrayIndexes(parent) || - shouldIgnoreJSXNumbers(parent)) { - return; - } + if (shouldIgnoreNumber(value) || + shouldIgnoreParseInt(parent, node) || + shouldIgnoreArrayIndexes(parent) || + shouldIgnoreJSXNumbers(parent)) { + return; + } - if (parent.type === "VariableDeclarator") { - if (enforceConst && parent.parent.kind !== "const") { + if (parent.type === "VariableDeclarator") { + if (enforceConst && parent.parent.kind !== "const") { + context.report({ + node: node, + message: "Number constants declarations must use 'const'" + }); + } + } else if (okTypes.indexOf(parent.type) === -1 || + (parent.type === "AssignmentExpression" && parent.operator !== "=")) { context.report({ node: node, - message: "Number constants declarations must use 'const'" + message: "No magic number: " + raw }); } - } else if (okTypes.indexOf(parent.type) === -1) { - context.report({ - node: node, - message: "No magic number: " + raw - }); } - } - }; + }; + } }; - -module.exports.schema = [{ - "type": "object", - "properties": { - "detectObjects": { - "type": "boolean" - }, - "enforceConst": { - "type": "boolean" - }, - "ignore": { - "type": "array", - "items": { - "type": "number" - }, - "uniqueItems": true - }, - "ignoreArrayIndexes": { - "type": "boolean" - } - }, - "additionalProperties": false -}]; diff --git a/tools/eslint/lib/rules/no-mixed-requires.js b/tools/eslint/lib/rules/no-mixed-requires.js index 63494d9525..b6d365a9db 100644 --- a/tools/eslint/lib/rules/no-mixed-requires.js +++ b/tools/eslint/lib/rules/no-mixed-requires.js @@ -9,204 +9,214 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `require` calls to be mixed with regular `var` declarations", + category: "Node.js and CommonJS", + recommended: false + }, + + schema: [ + { + oneOf: [ + { + type: "boolean" + }, + { + type: "object", + properties: { + grouping: { + type: "boolean" + }, + allowCall: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + } + ] + }, - var grouping = false, - allowCall = false, - options = context.options[0]; + create: function(context) { - if (typeof options === "object") { - grouping = options.grouping; - allowCall = options.allowCall; - } else { - grouping = !!options; - } + var grouping = false, + allowCall = false, + options = context.options[0]; - /** - * Returns the list of built-in modules. - * - * @returns {string[]} An array of built-in Node.js modules. - */ - function getBuiltinModules() { - - /* - * This list is generated using: - * `require("repl")._builtinLibs.concat('repl').sort()` - * This particular list is as per nodejs v0.12.2 and iojs v0.7.1 + if (typeof options === "object") { + grouping = options.grouping; + allowCall = options.allowCall; + } else { + grouping = !!options; + } + + /** + * Returns the list of built-in modules. + * + * @returns {string[]} An array of built-in Node.js modules. */ - return [ - "assert", "buffer", "child_process", "cluster", "crypto", - "dgram", "dns", "domain", "events", "fs", "http", "https", - "net", "os", "path", "punycode", "querystring", "readline", - "repl", "smalloc", "stream", "string_decoder", "tls", "tty", - "url", "util", "v8", "vm", "zlib" - ]; - } + function getBuiltinModules() { + + /* + * This list is generated using: + * `require("repl")._builtinLibs.concat('repl').sort()` + * This particular list is as per nodejs v0.12.2 and iojs v0.7.1 + */ + return [ + "assert", "buffer", "child_process", "cluster", "crypto", + "dgram", "dns", "domain", "events", "fs", "http", "https", + "net", "os", "path", "punycode", "querystring", "readline", + "repl", "smalloc", "stream", "string_decoder", "tls", "tty", + "url", "util", "v8", "vm", "zlib" + ]; + } - var BUILTIN_MODULES = getBuiltinModules(); + var BUILTIN_MODULES = getBuiltinModules(); - var DECL_REQUIRE = "require", - DECL_UNINITIALIZED = "uninitialized", - DECL_OTHER = "other"; + var DECL_REQUIRE = "require", + DECL_UNINITIALIZED = "uninitialized", + DECL_OTHER = "other"; - var REQ_CORE = "core", - REQ_FILE = "file", - REQ_MODULE = "module", - REQ_COMPUTED = "computed"; + var REQ_CORE = "core", + REQ_FILE = "file", + REQ_MODULE = "module", + REQ_COMPUTED = "computed"; - /** - * Determines the type of a declaration statement. - * @param {ASTNode} initExpression The init node of the VariableDeclarator. - * @returns {string} The type of declaration represented by the expression. - */ - function getDeclarationType(initExpression) { - if (!initExpression) { + /** + * Determines the type of a declaration statement. + * @param {ASTNode} initExpression The init node of the VariableDeclarator. + * @returns {string} The type of declaration represented by the expression. + */ + function getDeclarationType(initExpression) { + if (!initExpression) { - // "var x;" - return DECL_UNINITIALIZED; - } + // "var x;" + return DECL_UNINITIALIZED; + } - if (initExpression.type === "CallExpression" && - initExpression.callee.type === "Identifier" && - initExpression.callee.name === "require" - ) { + if (initExpression.type === "CallExpression" && + initExpression.callee.type === "Identifier" && + initExpression.callee.name === "require" + ) { - // "var x = require('util');" - return DECL_REQUIRE; - } else if (allowCall && - initExpression.type === "CallExpression" && - initExpression.callee.type === "CallExpression" - ) { + // "var x = require('util');" + return DECL_REQUIRE; + } else if (allowCall && + initExpression.type === "CallExpression" && + initExpression.callee.type === "CallExpression" + ) { - // "var x = require('diagnose')('sub-module');" - return getDeclarationType(initExpression.callee); - } else if (initExpression.type === "MemberExpression") { + // "var x = require('diagnose')('sub-module');" + return getDeclarationType(initExpression.callee); + } else if (initExpression.type === "MemberExpression") { - // "var x = require('glob').Glob;" - return getDeclarationType(initExpression.object); - } + // "var x = require('glob').Glob;" + return getDeclarationType(initExpression.object); + } - // "var x = 42;" - return DECL_OTHER; - } + // "var x = 42;" + return DECL_OTHER; + } - /** - * Determines the type of module that is loaded via require. - * @param {ASTNode} initExpression The init node of the VariableDeclarator. - * @returns {string} The module type. - */ - function inferModuleType(initExpression) { - if (initExpression.type === "MemberExpression") { + /** + * Determines the type of module that is loaded via require. + * @param {ASTNode} initExpression The init node of the VariableDeclarator. + * @returns {string} The module type. + */ + function inferModuleType(initExpression) { + if (initExpression.type === "MemberExpression") { - // "var x = require('glob').Glob;" - return inferModuleType(initExpression.object); - } else if (initExpression.arguments.length === 0) { + // "var x = require('glob').Glob;" + return inferModuleType(initExpression.object); + } else if (initExpression.arguments.length === 0) { - // "var x = require();" - return REQ_COMPUTED; - } + // "var x = require();" + return REQ_COMPUTED; + } - var arg = initExpression.arguments[0]; + var arg = initExpression.arguments[0]; - if (arg.type !== "Literal" || typeof arg.value !== "string") { + if (arg.type !== "Literal" || typeof arg.value !== "string") { - // "var x = require(42);" - return REQ_COMPUTED; - } + // "var x = require(42);" + return REQ_COMPUTED; + } - if (BUILTIN_MODULES.indexOf(arg.value) !== -1) { + if (BUILTIN_MODULES.indexOf(arg.value) !== -1) { - // "var fs = require('fs');" - return REQ_CORE; - } else if (/^\.{0,2}\//.test(arg.value)) { + // "var fs = require('fs');" + return REQ_CORE; + } else if (/^\.{0,2}\//.test(arg.value)) { - // "var utils = require('./utils');" - return REQ_FILE; - } else { + // "var utils = require('./utils');" + return REQ_FILE; + } else { - // "var async = require('async');" - return REQ_MODULE; + // "var async = require('async');" + return REQ_MODULE; + } } - } - /** - * Check if the list of variable declarations is mixed, i.e. whether it - * contains both require and other declarations. - * @param {ASTNode} declarations The list of VariableDeclarators. - * @returns {boolean} True if the declarations are mixed, false if not. - */ - function isMixed(declarations) { - var contains = {}; - - declarations.forEach(function(declaration) { - var type = getDeclarationType(declaration.init); - - contains[type] = true; - }); - - return !!( - contains[DECL_REQUIRE] && - (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER]) - ); - } + /** + * Check if the list of variable declarations is mixed, i.e. whether it + * contains both require and other declarations. + * @param {ASTNode} declarations The list of VariableDeclarators. + * @returns {boolean} True if the declarations are mixed, false if not. + */ + function isMixed(declarations) { + var contains = {}; - /** - * Check if all require declarations in the given list are of the same - * type. - * @param {ASTNode} declarations The list of VariableDeclarators. - * @returns {boolean} True if the declarations are grouped, false if not. - */ - function isGrouped(declarations) { - var found = {}; - - declarations.forEach(function(declaration) { - if (getDeclarationType(declaration.init) === DECL_REQUIRE) { - found[inferModuleType(declaration.init)] = true; - } - }); + declarations.forEach(function(declaration) { + var type = getDeclarationType(declaration.init); - return Object.keys(found).length <= 1; - } + contains[type] = true; + }); + return !!( + contains[DECL_REQUIRE] && + (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER]) + ); + } - return { + /** + * Check if all require declarations in the given list are of the same + * type. + * @param {ASTNode} declarations The list of VariableDeclarators. + * @returns {boolean} True if the declarations are grouped, false if not. + */ + function isGrouped(declarations) { + var found = {}; - "VariableDeclaration": function(node) { + declarations.forEach(function(declaration) { + if (getDeclarationType(declaration.init) === DECL_REQUIRE) { + found[inferModuleType(declaration.init)] = true; + } + }); - if (isMixed(node.declarations)) { - context.report( - node, - "Do not mix 'require' and other declarations." - ); - } else if (grouping && !isGrouped(node.declarations)) { - context.report( - node, - "Do not mix core, module, file and computed requires." - ); - } + return Object.keys(found).length <= 1; } - }; -}; -module.exports.schema = [ - { - "oneOf": [ - { - "type": "boolean" - }, - { - "type": "object", - "properties": { - "grouping": { - "type": "boolean" - }, - "allowCall": { - "type": "boolean" - } - }, - "additionalProperties": false + return { + + VariableDeclaration: function(node) { + + if (isMixed(node.declarations)) { + context.report( + node, + "Do not mix 'require' and other declarations." + ); + } else if (grouping && !isGrouped(node.declarations)) { + context.report( + node, + "Do not mix core, module, file and computed requires." + ); + } } - ] + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js b/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js index b7b191cf02..b3cdd38a60 100644 --- a/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js +++ b/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js @@ -1,9 +1,6 @@ /** * @fileoverview Disallow mixed spaces and tabs for indentation * @author Jary Niebur - * @copyright 2014 Nicholas C. Zakas. All rights reserved. - * @copyright 2014 Jary Niebur. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; @@ -11,126 +8,136 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow mixed spaces and tabs for indentation", + category: "Stylistic Issues", + recommended: true + }, - var smartTabs, - ignoredLocs = []; + schema: [ + { + enum: ["smart-tabs", true, false] + } + ] + }, - switch (context.options[0]) { - case true: // Support old syntax, maybe add deprecation warning here - case "smart-tabs": - smartTabs = true; - break; - default: - smartTabs = false; - } + create: function(context) { - /** - * Determines if a given line and column are before a location. - * @param {Location} loc The location object from an AST node. - * @param {int} line The line to check. - * @param {int} column The column to check. - * @returns {boolean} True if the line and column are before the location, false if not. - * @private - */ - function beforeLoc(loc, line, column) { - if (line < loc.start.line) { - return true; - } - return line === loc.start.line && column < loc.start.column; - } + var smartTabs, + ignoredLocs = []; - /** - * Determines if a given line and column are after a location. - * @param {Location} loc The location object from an AST node. - * @param {int} line The line to check. - * @param {int} column The column to check. - * @returns {boolean} True if the line and column are after the location, false if not. - * @private - */ - function afterLoc(loc, line, column) { - if (line > loc.end.line) { - return true; + switch (context.options[0]) { + case true: // Support old syntax, maybe add deprecation warning here + case "smart-tabs": + smartTabs = true; + break; + default: + smartTabs = false; } - return line === loc.end.line && column > loc.end.column; - } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { + /** + * Determines if a given line and column are before a location. + * @param {Location} loc The location object from an AST node. + * @param {int} line The line to check. + * @param {int} column The column to check. + * @returns {boolean} True if the line and column are before the location, false if not. + * @private + */ + function beforeLoc(loc, line, column) { + if (line < loc.start.line) { + return true; + } + return line === loc.start.line && column < loc.start.column; + } - "TemplateElement": function(node) { - ignoredLocs.push(node.loc); - }, + /** + * Determines if a given line and column are after a location. + * @param {Location} loc The location object from an AST node. + * @param {int} line The line to check. + * @param {int} column The column to check. + * @returns {boolean} True if the line and column are after the location, false if not. + * @private + */ + function afterLoc(loc, line, column) { + if (line > loc.end.line) { + return true; + } + return line === loc.end.line && column > loc.end.column; + } - "Program:exit": function(node) { - - /* - * At least one space followed by a tab - * or the reverse before non-tab/-space - * characters begin. - */ - var regex = /^(?=[\t ]*(\t | \t))/, - match, - lines = context.getSourceLines(), - comments = context.getAllComments(); - - comments.forEach(function(comment) { - ignoredLocs.push(comment.loc); - }); - - ignoredLocs.sort(function(first, second) { - if (beforeLoc(first, second.start.line, second.start.column)) { - return 1; - } + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - if (beforeLoc(second, first.start.line, second.start.column)) { - return -1; - } + return { - return 0; - }); + TemplateElement: function(node) { + ignoredLocs.push(node.loc); + }, - if (smartTabs) { + "Program:exit": function(node) { /* * At least one space followed by a tab - * before non-tab/-space characters begin. + * or the reverse before non-tab/-space + * characters begin. */ - regex = /^(?=[\t ]* \t)/; - } - - lines.forEach(function(line, i) { - match = regex.exec(line); + var regex = /^(?=[\t ]*(\t | \t))/, + match, + lines = context.getSourceLines(), + comments = context.getAllComments(); + + comments.forEach(function(comment) { + ignoredLocs.push(comment.loc); + }); + + ignoredLocs.sort(function(first, second) { + if (beforeLoc(first, second.start.line, second.start.column)) { + return 1; + } - if (match) { - var lineNumber = i + 1, - column = match.index + 1; + if (beforeLoc(second, first.start.line, second.start.column)) { + return -1; + } - for (var j = 0; j < ignoredLocs.length; j++) { - if (beforeLoc(ignoredLocs[j], lineNumber, column)) { - continue; - } - if (afterLoc(ignoredLocs[j], lineNumber, column)) { - continue; - } + return 0; + }); - return; - } + if (smartTabs) { - context.report(node, { line: lineNumber, column: column }, "Mixed spaces and tabs."); + /* + * At least one space followed by a tab + * before non-tab/-space characters begin. + */ + regex = /^(?=[\t ]* \t)/; } - }); - } - }; + lines.forEach(function(line, i) { + match = regex.exec(line); -}; + if (match) { + var lineNumber = i + 1, + column = match.index + 1; + + for (var j = 0; j < ignoredLocs.length; j++) { + if (beforeLoc(ignoredLocs[j], lineNumber, column)) { + continue; + } + if (afterLoc(ignoredLocs[j], lineNumber, column)) { + continue; + } + + return; + } + + context.report(node, { line: lineNumber, column: column }, "Mixed spaces and tabs."); + } + }); + } + + }; -module.exports.schema = [ - { - "enum": ["smart-tabs", true, false] } -]; +}; diff --git a/tools/eslint/lib/rules/no-multi-spaces.js b/tools/eslint/lib/rules/no-multi-spaces.js index e57bdd3910..d6c0e4198d 100644 --- a/tools/eslint/lib/rules/no-multi-spaces.js +++ b/tools/eslint/lib/rules/no-multi-spaces.js @@ -1,8 +1,6 @@ /** * @fileoverview Disallow use of multiple spaces. * @author Nicholas C. Zakas - * @copyright 2015 Brandon Mills. All rights reserved. - * @copyright 2015 Nicholas C. Zakas. All rights reserved. */ "use strict"; @@ -11,129 +9,141 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - // the index of the last comment that was checked - var exceptions = { "Property": true }, - hasExceptions = true, - options = context.options[0], - lastCommentIndex = 0; +module.exports = { + meta: { + docs: { + description: "disallow multiple spaces", + category: "Best Practices", + recommended: false + }, - if (options && options.exceptions) { - Object.keys(options.exceptions).forEach(function(key) { - if (options.exceptions[key]) { - exceptions[key] = true; - } else { - delete exceptions[key]; + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "object", + patternProperties: { + "^([A-Z][a-z]*)+$": { + type: "boolean" + } + }, + additionalProperties: false + } + }, + additionalProperties: false } - }); - hasExceptions = Object.keys(exceptions).length > 0; - } + ] + }, + + create: function(context) { + + // the index of the last comment that was checked + var exceptions = { Property: true }, + hasExceptions = true, + options = context.options[0], + lastCommentIndex = 0; + + if (options && options.exceptions) { + Object.keys(options.exceptions).forEach(function(key) { + if (options.exceptions[key]) { + exceptions[key] = true; + } else { + delete exceptions[key]; + } + }); + hasExceptions = Object.keys(exceptions).length > 0; + } + + /** + * Determines if a given source index is in a comment or not by checking + * the index against the comment range. Since the check goes straight + * through the file, once an index is passed a certain comment, we can + * go to the next comment to check that. + * @param {int} index The source index to check. + * @param {ASTNode[]} comments An array of comment nodes. + * @returns {boolean} True if the index is within a comment, false if not. + * @private + */ + function isIndexInComment(index, comments) { + + var comment; + + while (lastCommentIndex < comments.length) { + + comment = comments[lastCommentIndex]; + + if (comment.range[0] <= index && index < comment.range[1]) { + return true; + } else if (index > comment.range[1]) { + lastCommentIndex++; + } else { + break; + } - /** - * Determines if a given source index is in a comment or not by checking - * the index against the comment range. Since the check goes straight - * through the file, once an index is passed a certain comment, we can - * go to the next comment to check that. - * @param {int} index The source index to check. - * @param {ASTNode[]} comments An array of comment nodes. - * @returns {boolean} True if the index is within a comment, false if not. - * @private - */ - function isIndexInComment(index, comments) { - - var comment; - - while (lastCommentIndex < comments.length) { - - comment = comments[lastCommentIndex]; - - if (comment.range[0] <= index && index < comment.range[1]) { - return true; - } else if (index > comment.range[1]) { - lastCommentIndex++; - } else { - break; } + return false; } - return false; - } + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: function() { + + var source = context.getSource(), + allComments = context.getAllComments(), + pattern = /[^\n\r\u2028\u2029\t ].? {2,}/g, // note: repeating space + token, + previousToken, + parent; + + + /** + * Creates a fix function that removes the multiple spaces between the two tokens + * @param {RuleFixer} leftToken left token + * @param {RuleFixer} rightToken right token + * @returns {function} fix function + * @private + */ + function createFix(leftToken, rightToken) { + return function(fixer) { + return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " "); + }; + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "Program": function() { - - var source = context.getSource(), - allComments = context.getAllComments(), - pattern = /[^\n\r\u2028\u2029\t ].? {2,}/g, // note: repeating space - token, - previousToken, - parent; - - - /** - * Creates a fix function that removes the multiple spaces between the two tokens - * @param {RuleFixer} leftToken left token - * @param {RuleFixer} rightToken right token - * @returns {function} fix function - * @private - */ - function createFix(leftToken, rightToken) { - return function(fixer) { - return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " "); - }; - } + while (pattern.test(source)) { - while (pattern.test(source)) { + // do not flag anything inside of comments + if (!isIndexInComment(pattern.lastIndex, allComments)) { - // do not flag anything inside of comments - if (!isIndexInComment(pattern.lastIndex, allComments)) { + token = context.getTokenByRangeStart(pattern.lastIndex); + if (token) { + previousToken = context.getTokenBefore(token); - token = context.getTokenByRangeStart(pattern.lastIndex); - if (token) { - previousToken = context.getTokenBefore(token); + if (hasExceptions) { + parent = context.getNodeByRangeIndex(pattern.lastIndex - 1); + } - if (hasExceptions) { - parent = context.getNodeByRangeIndex(pattern.lastIndex - 1); + if (!parent || !exceptions[parent.type]) { + context.report({ + node: token, + loc: token.loc.start, + message: "Multiple spaces found before '{{value}}'.", + data: { value: token.value }, + fix: createFix(previousToken, token) + }); + } } - if (!parent || !exceptions[parent.type]) { - context.report({ - node: token, - loc: token.loc.start, - message: "Multiple spaces found before '{{value}}'.", - data: { value: token.value }, - fix: createFix(previousToken, token) - }); - } } - } } - } - }; - -}; + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "exceptions": { - "type": "object", - "patternProperties": { - "^([A-Z][a-z]*)+$": { - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/no-multi-str.js b/tools/eslint/lib/rules/no-multi-str.js index 470c65871e..fe7fee4a60 100644 --- a/tools/eslint/lib/rules/no-multi-str.js +++ b/tools/eslint/lib/rules/no-multi-str.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag when using multiline strings * @author Ilya Volodin - * @copyright 2014 Nicholas C. Zakas. All rights reserved. - * @copyright 2013 Ilya Volodin. All rights reserved. */ "use strict"; @@ -11,33 +9,43 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Determines if a given node is part of JSX syntax. - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node is a JSX node, false if not. - * @private - */ - function isJSXElement(node) { - return node.type.indexOf("JSX") === 0; - } +module.exports = { + meta: { + docs: { + description: "disallow multiline strings", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + /** + * Determines if a given node is part of JSX syntax. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a JSX node, false if not. + * @private + */ + function isJSXElement(node) { + return node.type.indexOf("JSX") === 0; + } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - return { + return { - "Literal": function(node) { - var lineBreak = /\n/; + Literal: function(node) { + var lineBreak = /\n/; - if (lineBreak.test(node.raw) && !isJSXElement(node.parent)) { - context.report(node, "Multiline support is limited to browsers supporting ES5 only."); + if (lineBreak.test(node.raw) && !isJSXElement(node.parent)) { + context.report(node, "Multiline support is limited to browsers supporting ES5 only."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-multiple-empty-lines.js b/tools/eslint/lib/rules/no-multiple-empty-lines.js index 34b9b8eff9..02a41fdbe3 100644 --- a/tools/eslint/lib/rules/no-multiple-empty-lines.js +++ b/tools/eslint/lib/rules/no-multiple-empty-lines.js @@ -2,7 +2,6 @@ * @fileoverview Disallows multiple blank lines. * implementation adapted from the no-trailing-spaces rule. * @author Greg Cochard - * @copyright 2014 Greg Cochard. All rights reserved. */ "use strict"; @@ -10,152 +9,162 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow multiple empty lines", + category: "Stylistic Issues", + recommended: false + }, - // Use options.max or 2 as default - var max = 2, - maxEOF, - maxBOF; + schema: [ + { + type: "object", + properties: { + max: { + type: "integer", + minimum: 0 + }, + maxEOF: { + type: "integer", + minimum: 0 + }, + maxBOF: { + type: "integer", + minimum: 0 + } + }, + required: ["max"], + additionalProperties: false + } + ] + }, - // store lines that appear empty but really aren't - var notEmpty = []; + create: function(context) { - if (context.options.length) { - max = context.options[0].max; - maxEOF = context.options[0].maxEOF; - maxBOF = context.options[0].maxBOF; - } + // Use options.max or 2 as default + var max = 2, + maxEOF, + maxBOF; - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + // store lines that appear empty but really aren't + var notEmpty = []; - return { + if (context.options.length) { + max = context.options[0].max; + maxEOF = context.options[0].maxEOF; + maxBOF = context.options[0].maxBOF; + } - "TemplateLiteral": function(node) { - var start = node.loc.start.line; - var end = node.loc.end.line; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - while (start <= end) { - notEmpty.push(start); - start++; - } - }, + return { + + TemplateLiteral: function(node) { + var start = node.loc.start.line; + var end = node.loc.end.line; - "Program:exit": function checkBlankLines(node) { - var lines = context.getSourceLines(), - currentLocation = -1, - lastLocation, - blankCounter = 0, - location, - firstOfEndingBlankLines, - firstNonBlankLine = -1, - trimmedLines = []; - - lines.forEach(function(str, i) { - var trimmed = str.trim(); - - if ((firstNonBlankLine === -1) && (trimmed !== "")) { - firstNonBlankLine = i; + while (start <= end) { + notEmpty.push(start); + start++; } + }, + + "Program:exit": function checkBlankLines(node) { + var lines = context.getSourceLines(), + currentLocation = -1, + lastLocation, + blankCounter = 0, + location, + firstOfEndingBlankLines, + firstNonBlankLine = -1, + trimmedLines = []; + + lines.forEach(function(str, i) { + var trimmed = str.trim(); + + if ((firstNonBlankLine === -1) && (trimmed !== "")) { + firstNonBlankLine = i; + } - trimmedLines.push(trimmed); - }); + trimmedLines.push(trimmed); + }); - // add the notEmpty lines in there with a placeholder - notEmpty.forEach(function(x, i) { - trimmedLines[i] = x; - }); + // add the notEmpty lines in there with a placeholder + notEmpty.forEach(function(x, i) { + trimmedLines[i] = x; + }); - if (typeof maxEOF === "undefined") { + if (typeof maxEOF === "undefined") { - /* - * Swallow the final newline, as some editors add it - * automatically and we don't want it to cause an issue - */ - if (trimmedLines[trimmedLines.length - 1] === "") { - trimmedLines = trimmedLines.slice(0, -1); - } + /* + * Swallow the final newline, as some editors add it + * automatically and we don't want it to cause an issue + */ + if (trimmedLines[trimmedLines.length - 1] === "") { + trimmedLines = trimmedLines.slice(0, -1); + } - firstOfEndingBlankLines = trimmedLines.length; - } else { + firstOfEndingBlankLines = trimmedLines.length; + } else { - // save the number of the first of the last blank lines - firstOfEndingBlankLines = trimmedLines.length; - while (trimmedLines[firstOfEndingBlankLines - 1] === "" - && firstOfEndingBlankLines > 0) { - firstOfEndingBlankLines--; + // save the number of the first of the last blank lines + firstOfEndingBlankLines = trimmedLines.length; + while (trimmedLines[firstOfEndingBlankLines - 1] === "" + && firstOfEndingBlankLines > 0) { + firstOfEndingBlankLines--; + } } - } - // Aggregate and count blank lines - if (firstNonBlankLine > maxBOF) { - context.report(node, 0, - "Too many blank lines at the beginning of file. Max of " + maxBOF + " allowed."); - } + // Aggregate and count blank lines + if (firstNonBlankLine > maxBOF) { + context.report(node, 0, + "Too many blank lines at the beginning of file. Max of " + maxBOF + " allowed."); + } - lastLocation = currentLocation; - currentLocation = trimmedLines.indexOf("", currentLocation + 1); - while (currentLocation !== -1) { lastLocation = currentLocation; currentLocation = trimmedLines.indexOf("", currentLocation + 1); - if (lastLocation === currentLocation - 1) { - blankCounter++; - } else { - location = { - line: lastLocation + 1, - column: 1 - }; - - if (lastLocation < firstOfEndingBlankLines) { - - // within the file, not at the end - if (blankCounter >= max) { - context.report({ - node: node, - loc: location, - message: "More than " + max + " blank " + (max === 1 ? "line" : "lines") + " not allowed." - }); - } + while (currentLocation !== -1) { + lastLocation = currentLocation; + currentLocation = trimmedLines.indexOf("", currentLocation + 1); + if (lastLocation === currentLocation - 1) { + blankCounter++; } else { - - // inside the last blank lines - if (blankCounter > maxEOF) { - context.report({ - node: node, - loc: location, - message: "Too many blank lines at the end of file. Max of " + maxEOF + " allowed." - }); + location = { + line: lastLocation + 1, + column: 1 + }; + + if (lastLocation < firstOfEndingBlankLines) { + + // within the file, not at the end + if (blankCounter >= max) { + context.report({ + node: node, + loc: location, + message: "More than " + max + " blank " + (max === 1 ? "line" : "lines") + " not allowed." + }); + } + } else { + + // inside the last blank lines + if (blankCounter > maxEOF) { + context.report({ + node: node, + loc: location, + message: "Too many blank lines at the end of file. Max of " + maxEOF + " allowed." + }); + } } - } - // Finally, reset the blank counter - blankCounter = 0; + // Finally, reset the blank counter + blankCounter = 0; + } } } - } - }; + }; -}; - -module.exports.schema = [ - { - "type": "object", - "properties": { - "max": { - "type": "integer", - "minimum": 0 - }, - "maxEOF": { - "type": "integer", - "minimum": 0 - }, - "maxBOF": { - "type": "integer", - "minimum": 0 - } - }, - "required": ["max"], - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/no-native-reassign.js b/tools/eslint/lib/rules/no-native-reassign.js index b4323369e4..7d45e277d5 100644 --- a/tools/eslint/lib/rules/no-native-reassign.js +++ b/tools/eslint/lib/rules/no-native-reassign.js @@ -9,65 +9,75 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var config = context.options[0]; - var exceptions = (config && config.exceptions) || []; +module.exports = { + meta: { + docs: { + description: "disallow reassigning native objects", + category: "Best Practices", + recommended: false + }, - /** - * Reports write references. - * @param {Reference} reference - A reference to check. - * @param {int} index - The index of the reference in the references. - * @param {Reference[]} references - The array that the reference belongs to. - * @returns {void} - */ - function checkReference(reference, index, references) { - var identifier = reference.identifier; + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: {type: "string"}, + uniqueItems: true + } + }, + additionalProperties: false + } + ] + }, - if (reference.init === false && - reference.isWrite() && + create: function(context) { + var config = context.options[0]; + var exceptions = (config && config.exceptions) || []; - // Destructuring assignments can have multiple default value, - // so possibly there are multiple writeable references for the same identifier. - (index === 0 || references[index - 1].identifier !== identifier) - ) { - context.report({ - node: identifier, - message: "{{name}} is a read-only native object.", - data: identifier - }); - } - } + /** + * Reports write references. + * @param {Reference} reference - A reference to check. + * @param {int} index - The index of the reference in the references. + * @param {Reference[]} references - The array that the reference belongs to. + * @returns {void} + */ + function checkReference(reference, index, references) { + var identifier = reference.identifier; - /** - * Reports write references if a given variable is readonly builtin. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - if (variable.writeable === false && exceptions.indexOf(variable.name) === -1) { - variable.references.forEach(checkReference); - } - } + if (reference.init === false && + reference.isWrite() && - return { - "Program": function() { - var globalScope = context.getScope(); + // Destructuring assignments can have multiple default value, + // so possibly there are multiple writeable references for the same identifier. + (index === 0 || references[index - 1].identifier !== identifier) + ) { + context.report({ + node: identifier, + message: "{{name}} is a read-only native object.", + data: identifier + }); + } + } - globalScope.variables.forEach(checkVariable); + /** + * Reports write references if a given variable is readonly builtin. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.writeable === false && exceptions.indexOf(variable.name) === -1) { + variable.references.forEach(checkReference); + } } - }; -}; -module.exports.schema = [ - { - "type": "object", - "properties": { - "exceptions": { - "type": "array", - "items": {"type": "string"}, - "uniqueItems": true + return { + Program: function() { + var globalScope = context.getScope(); + + globalScope.variables.forEach(checkVariable); } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/no-negated-condition.js b/tools/eslint/lib/rules/no-negated-condition.js index 0d5b283e54..5f1037472c 100644 --- a/tools/eslint/lib/rules/no-negated-condition.js +++ b/tools/eslint/lib/rules/no-negated-condition.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to disallow a negated condition * @author Alberto Rodríguez - * @copyright 2015 Alberto Rodríguez. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -10,65 +8,75 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow negated conditions", + category: "Stylistic Issues", + recommended: false + }, - /** - * Determines if a given node is an if-else without a condition on the else - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node has an else without an if. - * @private - */ - function hasElseWithoutCondition(node) { - return node.alternate && node.alternate.type !== "IfStatement"; - } + schema: [] + }, - /** - * Determines if a given node is a negated unary expression - * @param {Object} test The test object to check. - * @returns {boolean} True if the node is a negated unary expression. - * @private - */ - function isNegatedUnaryExpression(test) { - return test.type === "UnaryExpression" && test.operator === "!"; - } + create: function(context) { - /** - * Determines if a given node is a negated binary expression - * @param {Test} test The test to check. - * @returns {boolean} True if the node is a negated binary expression. - * @private - */ - function isNegatedBinaryExpression(test) { - return test.type === "BinaryExpression" && - (test.operator === "!=" || test.operator === "!=="); - } + /** + * Determines if a given node is an if-else without a condition on the else + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node has an else without an if. + * @private + */ + function hasElseWithoutCondition(node) { + return node.alternate && node.alternate.type !== "IfStatement"; + } - /** - * Determines if a given node has a negated if expression - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node has a negated if expression. - * @private - */ - function isNegatedIf(node) { - return isNegatedUnaryExpression(node.test) || isNegatedBinaryExpression(node.test); - } + /** + * Determines if a given node is a negated unary expression + * @param {Object} test The test object to check. + * @returns {boolean} True if the node is a negated unary expression. + * @private + */ + function isNegatedUnaryExpression(test) { + return test.type === "UnaryExpression" && test.operator === "!"; + } - return { - "IfStatement": function(node) { - if (!hasElseWithoutCondition(node)) { - return; - } + /** + * Determines if a given node is a negated binary expression + * @param {Test} test The test to check. + * @returns {boolean} True if the node is a negated binary expression. + * @private + */ + function isNegatedBinaryExpression(test) { + return test.type === "BinaryExpression" && + (test.operator === "!=" || test.operator === "!=="); + } - if (isNegatedIf(node)) { - context.report(node, "Unexpected negated condition."); - } - }, - "ConditionalExpression": function(node) { - if (isNegatedIf(node)) { - context.report(node, "Unexpected negated condition."); - } + /** + * Determines if a given node has a negated if expression + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node has a negated if expression. + * @private + */ + function isNegatedIf(node) { + return isNegatedUnaryExpression(node.test) || isNegatedBinaryExpression(node.test); } - }; -}; -module.exports.schema = []; + return { + IfStatement: function(node) { + if (!hasElseWithoutCondition(node)) { + return; + } + + if (isNegatedIf(node)) { + context.report(node, "Unexpected negated condition."); + } + }, + ConditionalExpression: function(node) { + if (isNegatedIf(node)) { + context.report(node, "Unexpected negated condition."); + } + } + }; + } +}; diff --git a/tools/eslint/lib/rules/no-negated-in-lhs.js b/tools/eslint/lib/rules/no-negated-in-lhs.js index 67be9bb06d..891b75dd2f 100644 --- a/tools/eslint/lib/rules/no-negated-in-lhs.js +++ b/tools/eslint/lib/rules/no-negated-in-lhs.js @@ -9,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow negating the left operand in `in` expressions", + category: "Possible Errors", + recommended: true + }, - return { + schema: [] + }, - "BinaryExpression": function(node) { - if (node.operator === "in" && node.left.type === "UnaryExpression" && node.left.operator === "!") { - context.report(node, "The 'in' expression's left operand is negated"); + create: function(context) { + + return { + + BinaryExpression: function(node) { + if (node.operator === "in" && node.left.type === "UnaryExpression" && node.left.operator === "!") { + context.report(node, "The 'in' expression's left operand is negated"); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-nested-ternary.js b/tools/eslint/lib/rules/no-nested-ternary.js index 2686ebd981..34f9eaaa8d 100644 --- a/tools/eslint/lib/rules/no-nested-ternary.js +++ b/tools/eslint/lib/rules/no-nested-ternary.js @@ -9,16 +9,26 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow nested ternary expressions", + category: "Stylistic Issues", + recommended: false + }, - return { - "ConditionalExpression": function(node) { - if (node.alternate.type === "ConditionalExpression" || - node.consequent.type === "ConditionalExpression") { - context.report(node, "Do not nest ternary expressions"); + schema: [] + }, + + create: function(context) { + + return { + ConditionalExpression: function(node) { + if (node.alternate.type === "ConditionalExpression" || + node.consequent.type === "ConditionalExpression") { + context.report(node, "Do not nest ternary expressions"); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-new-func.js b/tools/eslint/lib/rules/no-new-func.js index cbc0248fb1..7b01db0cd2 100644 --- a/tools/eslint/lib/rules/no-new-func.js +++ b/tools/eslint/lib/rules/no-new-func.js @@ -9,29 +9,39 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Checks if the callee is the Function constructor, and if so, reports an issue. - * @param {ASTNode} node The node to check and report on - * @returns {void} - * @private - */ - function validateCallee(node) { - if (node.callee.name === "Function") { - context.report(node, "The Function constructor is eval."); +module.exports = { + meta: { + docs: { + description: "disallow `new` operators with the `Function` object", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks if the callee is the Function constructor, and if so, reports an issue. + * @param {ASTNode} node The node to check and report on + * @returns {void} + * @private + */ + function validateCallee(node) { + if (node.callee.name === "Function") { + context.report(node, "The Function constructor is eval."); + } } - } - return { - "NewExpression": validateCallee, - "CallExpression": validateCallee - }; + return { + NewExpression: validateCallee, + CallExpression: validateCallee + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-new-object.js b/tools/eslint/lib/rules/no-new-object.js index dd1cd10d75..4fc2a3bebb 100644 --- a/tools/eslint/lib/rules/no-new-object.js +++ b/tools/eslint/lib/rules/no-new-object.js @@ -9,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `Object` constructors", + category: "Stylistic Issues", + recommended: false + }, - return { + schema: [] + }, - "NewExpression": function(node) { - if (node.callee.name === "Object") { - context.report(node, "The object literal notation {} is preferrable."); + create: function(context) { + + return { + + NewExpression: function(node) { + if (node.callee.name === "Object") { + context.report(node, "The object literal notation {} is preferrable."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-new-require.js b/tools/eslint/lib/rules/no-new-require.js index cd2eec562f..b39d9c3e37 100644 --- a/tools/eslint/lib/rules/no-new-require.js +++ b/tools/eslint/lib/rules/no-new-require.js @@ -9,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `new` operators with calls to `require`", + category: "Node.js and CommonJS", + recommended: false + }, - return { + schema: [] + }, - "NewExpression": function(node) { - if (node.callee.type === "Identifier" && node.callee.name === "require") { - context.report(node, "Unexpected use of new with require."); + create: function(context) { + + return { + + NewExpression: function(node) { + if (node.callee.type === "Identifier" && node.callee.name === "require") { + context.report(node, "Unexpected use of new with require."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-new-symbol.js b/tools/eslint/lib/rules/no-new-symbol.js index 143deec0a3..c7c524641e 100644 --- a/tools/eslint/lib/rules/no-new-symbol.js +++ b/tools/eslint/lib/rules/no-new-symbol.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to disallow use of the new operator with the `Symbol` object * @author Alberto Rodríguez - * @copyright 2016 Alberto Rodríguez. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -11,25 +9,35 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - return { - "Program:exit": function() { - var globalScope = context.getScope(); - var variable = globalScope.set.get("Symbol"); - - if (variable && variable.defs.length === 0) { - variable.references.forEach(function(ref) { - var node = ref.identifier; - - if (node.parent && node.parent.type === "NewExpression") { - context.report(node, "`Symbol` cannot be called as a constructor."); - } - }); +module.exports = { + meta: { + docs: { + description: "disallow `new` operators with the `Symbol` object", + category: "ECMAScript 6", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + return { + "Program:exit": function() { + var globalScope = context.getScope(); + var variable = globalScope.set.get("Symbol"); + + if (variable && variable.defs.length === 0) { + variable.references.forEach(function(ref) { + var node = ref.identifier; + + if (node.parent && node.parent.type === "NewExpression") { + context.report(node, "`Symbol` cannot be called as a constructor."); + } + }); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-new-wrappers.js b/tools/eslint/lib/rules/no-new-wrappers.js index 88c22c13c4..eec774fdca 100644 --- a/tools/eslint/lib/rules/no-new-wrappers.js +++ b/tools/eslint/lib/rules/no-new-wrappers.js @@ -9,19 +9,29 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `new` operators with the `String`, `Number`, and `Boolean` objects", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "NewExpression": function(node) { - var wrapperObjects = ["String", "Number", "Boolean", "Math", "JSON"]; + create: function(context) { - if (wrapperObjects.indexOf(node.callee.name) > -1) { - context.report(node, "Do not use {{fn}} as a constructor.", { fn: node.callee.name }); + return { + + NewExpression: function(node) { + var wrapperObjects = ["String", "Number", "Boolean", "Math", "JSON"]; + + if (wrapperObjects.indexOf(node.callee.name) > -1) { + context.report(node, "Do not use {{fn}} as a constructor.", { fn: node.callee.name }); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-new.js b/tools/eslint/lib/rules/no-new.js index e431d4fb78..154bced9bc 100644 --- a/tools/eslint/lib/rules/no-new.js +++ b/tools/eslint/lib/rules/no-new.js @@ -10,18 +10,28 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `new` operators outside of assignments or comparisons", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "ExpressionStatement": function(node) { + create: function(context) { - if (node.expression.type === "NewExpression") { - context.report(node, "Do not use 'new' for side effects."); + return { + + ExpressionStatement: function(node) { + + if (node.expression.type === "NewExpression") { + context.report(node, "Do not use 'new' for side effects."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-obj-calls.js b/tools/eslint/lib/rules/no-obj-calls.js index e8f1c94611..0f58ab93db 100644 --- a/tools/eslint/lib/rules/no-obj-calls.js +++ b/tools/eslint/lib/rules/no-obj-calls.js @@ -9,21 +9,31 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow calling global object properties as functions", + category: "Possible Errors", + recommended: true + }, - return { - "CallExpression": function(node) { + schema: [] + }, - if (node.callee.type === "Identifier") { - var name = node.callee.name; + create: function(context) { - if (name === "Math" || name === "JSON") { - context.report(node, "'{{name}}' is not a function.", { name: name }); + return { + CallExpression: function(node) { + + if (node.callee.type === "Identifier") { + var name = node.callee.name; + + if (name === "Math" || name === "JSON") { + context.report(node, "'{{name}}' is not a function.", { name: name }); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-octal-escape.js b/tools/eslint/lib/rules/no-octal-escape.js index 16e58316dd..3ca01324b5 100644 --- a/tools/eslint/lib/rules/no-octal-escape.js +++ b/tools/eslint/lib/rules/no-octal-escape.js @@ -9,31 +9,41 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow octal escape sequences in string literals", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "Literal": function(node) { - if (typeof node.value !== "string") { - return; - } + create: function(context) { + + return { - var match = node.raw.match(/^([^\\]|\\[^0-7])*\\([0-3][0-7]{1,2}|[4-7][0-7]|[0-7])/), - octalDigit; + Literal: function(node) { + if (typeof node.value !== "string") { + return; + } - if (match) { - octalDigit = match[2]; + var match = node.raw.match(/^([^\\]|\\[^0-7])*\\([0-3][0-7]{1,2}|[4-7][0-7]|[0-7])/), + octalDigit; - // \0 is actually not considered an octal - if (match[2] !== "0" || typeof match[3] !== "undefined") { - context.report(node, "Don't use octal: '\\{{octalDigit}}'. Use '\\u....' instead.", - { octalDigit: octalDigit }); + if (match) { + octalDigit = match[2]; + + // \0 is actually not considered an octal + if (match[2] !== "0" || typeof match[3] !== "undefined") { + context.report(node, "Don't use octal: '\\{{octalDigit}}'. Use '\\u....' instead.", + { octalDigit: octalDigit }); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-octal.js b/tools/eslint/lib/rules/no-octal.js index 5a00c9506e..1332dde5e8 100644 --- a/tools/eslint/lib/rules/no-octal.js +++ b/tools/eslint/lib/rules/no-octal.js @@ -9,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow octal literals", + category: "Best Practices", + recommended: true + }, - return { + schema: [] + }, - "Literal": function(node) { - if (typeof node.value === "number" && /^0[0-7]/.test(node.raw)) { - context.report(node, "Octal literals should not be used."); + create: function(context) { + + return { + + Literal: function(node) { + if (typeof node.value === "number" && /^0[0-7]/.test(node.raw)) { + context.report(node, "Octal literals should not be used."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-param-reassign.js b/tools/eslint/lib/rules/no-param-reassign.js index 9c7dc0bff2..fad61fc0c1 100644 --- a/tools/eslint/lib/rules/no-param-reassign.js +++ b/tools/eslint/lib/rules/no-param-reassign.js @@ -1,7 +1,6 @@ /** * @fileoverview Disallow reassignment of function parameters. * @author Nat Burns - * @copyright 2014 Nat Burns. All rights reserved. */ "use strict"; @@ -11,128 +10,138 @@ var stopNodePattern = /(?:Statement|Declaration|Function(?:Expression)?|Program)$/; -module.exports = function(context) { - var props = context.options[0] && Boolean(context.options[0].props); - - /** - * Checks whether or not the reference modifies properties of its variable. - * @param {Reference} reference - A reference to check. - * @returns {boolean} Whether or not the reference modifies properties of its variable. - */ - function isModifyingProp(reference) { - var node = reference.identifier; - var parent = node.parent; - - while (parent && !stopNodePattern.test(parent.type)) { - switch (parent.type) { - - // e.g. foo.a = 0; - case "AssignmentExpression": - return parent.left === node; - - // e.g. ++foo.a; - case "UpdateExpression": - return true; +module.exports = { + meta: { + docs: { + description: "disallow reassigning `function` parameters", + category: "Best Practices", + recommended: false + }, - // e.g. delete foo.a; - case "UnaryExpression": - if (parent.operator === "delete") { + schema: [ + { + type: "object", + properties: { + props: {type: "boolean"} + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var props = context.options[0] && Boolean(context.options[0].props); + + /** + * Checks whether or not the reference modifies properties of its variable. + * @param {Reference} reference - A reference to check. + * @returns {boolean} Whether or not the reference modifies properties of its variable. + */ + function isModifyingProp(reference) { + var node = reference.identifier; + var parent = node.parent; + + while (parent && !stopNodePattern.test(parent.type)) { + switch (parent.type) { + + // e.g. foo.a = 0; + case "AssignmentExpression": + return parent.left === node; + + // e.g. ++foo.a; + case "UpdateExpression": return true; - } - break; - - // EXCLUDES: e.g. cache.get(foo.a).b = 0; - case "CallExpression": - if (parent.callee !== node) { - return false; - } - break; - - // EXCLUDES: e.g. cache[foo.a] = 0; - case "MemberExpression": - if (parent.property === node) { - return false; - } - break; - - default: - break; + + // e.g. delete foo.a; + case "UnaryExpression": + if (parent.operator === "delete") { + return true; + } + break; + + // EXCLUDES: e.g. cache.get(foo.a).b = 0; + case "CallExpression": + if (parent.callee !== node) { + return false; + } + break; + + // EXCLUDES: e.g. cache[foo.a] = 0; + case "MemberExpression": + if (parent.property === node) { + return false; + } + break; + + default: + break; + } + + node = parent; + parent = node.parent; } - node = parent; - parent = node.parent; + return false; } - return false; - } - - /** - * Reports a reference if is non initializer and writable. - * @param {Reference} reference - A reference to check. - * @param {int} index - The index of the reference in the references. - * @param {Reference[]} references - The array that the reference belongs to. - * @returns {void} - */ - function checkReference(reference, index, references) { - var identifier = reference.identifier; - - if (identifier && - !reference.init && - - // Destructuring assignments can have multiple default value, - // so possibly there are multiple writeable references for the same identifier. - (index === 0 || references[index - 1].identifier !== identifier) - ) { - if (reference.isWrite()) { - context.report( - identifier, - "Assignment to function parameter '{{name}}'.", - {name: identifier.name}); - } else if (props && isModifyingProp(reference)) { - context.report( - identifier, - "Assignment to property of function parameter '{{name}}'.", - {name: identifier.name}); + /** + * Reports a reference if is non initializer and writable. + * @param {Reference} reference - A reference to check. + * @param {int} index - The index of the reference in the references. + * @param {Reference[]} references - The array that the reference belongs to. + * @returns {void} + */ + function checkReference(reference, index, references) { + var identifier = reference.identifier; + + if (identifier && + !reference.init && + + // Destructuring assignments can have multiple default value, + // so possibly there are multiple writeable references for the same identifier. + (index === 0 || references[index - 1].identifier !== identifier) + ) { + if (reference.isWrite()) { + context.report( + identifier, + "Assignment to function parameter '{{name}}'.", + {name: identifier.name}); + } else if (props && isModifyingProp(reference)) { + context.report( + identifier, + "Assignment to property of function parameter '{{name}}'.", + {name: identifier.name}); + } } } - } - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - if (variable.defs[0].type === "Parameter") { - variable.references.forEach(checkReference); + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.defs[0].type === "Parameter") { + variable.references.forEach(checkReference); + } } - } - - /** - * Checks parameters of a given function node. - * @param {ASTNode} node - A function node to check. - * @returns {void} - */ - function checkForFunction(node) { - context.getDeclaredVariables(node).forEach(checkVariable); - } - return { + /** + * Checks parameters of a given function node. + * @param {ASTNode} node - A function node to check. + * @returns {void} + */ + function checkForFunction(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } - // `:exit` is needed for the `node.parent` property of identifier nodes. - "FunctionDeclaration:exit": checkForFunction, - "FunctionExpression:exit": checkForFunction, - "ArrowFunctionExpression:exit": checkForFunction - }; + return { -}; + // `:exit` is needed for the `node.parent` property of identifier nodes. + "FunctionDeclaration:exit": checkForFunction, + "FunctionExpression:exit": checkForFunction, + "ArrowFunctionExpression:exit": checkForFunction + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "props": {"type": "boolean"} - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/no-path-concat.js b/tools/eslint/lib/rules/no-path-concat.js index c6d512bc0e..1412c6c32e 100644 --- a/tools/eslint/lib/rules/no-path-concat.js +++ b/tools/eslint/lib/rules/no-path-concat.js @@ -8,32 +8,42 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow string concatenation with `__dirname` and `__filename`", + category: "Node.js and CommonJS", + recommended: false + }, - var MATCHER = /^__(?:dir|file)name$/; + schema: [] + }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + create: function(context) { - return { + var MATCHER = /^__(?:dir|file)name$/; - "BinaryExpression": function(node) { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - var left = node.left, - right = node.right; + return { - if (node.operator === "+" && - ((left.type === "Identifier" && MATCHER.test(left.name)) || - (right.type === "Identifier" && MATCHER.test(right.name))) - ) { + BinaryExpression: function(node) { - context.report(node, "Use path.join() or path.resolve() instead of + to create paths."); + var left = node.left, + right = node.right; + + if (node.operator === "+" && + ((left.type === "Identifier" && MATCHER.test(left.name)) || + (right.type === "Identifier" && MATCHER.test(right.name))) + ) { + + context.report(node, "Use path.join() or path.resolve() instead of + to create paths."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-plusplus.js b/tools/eslint/lib/rules/no-plusplus.js index 42df6a5f2d..159a42be2c 100644 --- a/tools/eslint/lib/rules/no-plusplus.js +++ b/tools/eslint/lib/rules/no-plusplus.js @@ -10,36 +10,46 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the unary operators `++` and `--`", + category: "Stylistic Issues", + recommended: false + }, - var config = context.options[0], - allowInForAfterthought = false; + schema: [ + { + type: "object", + properties: { + allowForLoopAfterthoughts: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - if (typeof config === "object") { - allowInForAfterthought = config.allowForLoopAfterthoughts === true; - } + create: function(context) { - return { + var config = context.options[0], + allowInForAfterthought = false; - "UpdateExpression": function(node) { - if (allowInForAfterthought && node.parent.type === "ForStatement") { - return; - } - context.report(node, "Unary operator '" + node.operator + "' used."); + if (typeof config === "object") { + allowInForAfterthought = config.allowForLoopAfterthoughts === true; } - }; - -}; + return { -module.exports.schema = [ - { - "type": "object", - "properties": { - "allowForLoopAfterthoughts": { - "type": "boolean" + UpdateExpression: function(node) { + if (allowInForAfterthought && node.parent.type === "ForStatement") { + return; + } + context.report(node, "Unary operator '" + node.operator + "' used."); } - }, - "additionalProperties": false + + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/no-process-env.js b/tools/eslint/lib/rules/no-process-env.js index 6a5395e579..af48c78029 100644 --- a/tools/eslint/lib/rules/no-process-env.js +++ b/tools/eslint/lib/rules/no-process-env.js @@ -1,7 +1,6 @@ /** * @fileoverview Disallow the use of process.env() * @author Vignesh Anand - * @copyright 2014 Vignesh Anand. All rights reserved. */ "use strict"; @@ -9,22 +8,32 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `process.env`", + category: "Node.js and CommonJS", + recommended: false + }, - return { + schema: [] + }, - "MemberExpression": function(node) { - var objectName = node.object.name, - propertyName = node.property.name; + create: function(context) { - if (objectName === "process" && !node.computed && propertyName && propertyName === "env") { - context.report(node, "Unexpected use of process.env."); - } + return { - } + MemberExpression: function(node) { + var objectName = node.object.name, + propertyName = node.property.name; - }; + if (objectName === "process" && !node.computed && propertyName && propertyName === "env") { + context.report(node, "Unexpected use of process.env."); + } -}; + } -module.exports.schema = []; + }; + + } +}; diff --git a/tools/eslint/lib/rules/no-process-exit.js b/tools/eslint/lib/rules/no-process-exit.js index a1fa3b29da..6d8674418b 100644 --- a/tools/eslint/lib/rules/no-process-exit.js +++ b/tools/eslint/lib/rules/no-process-exit.js @@ -8,26 +8,36 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `process.exit()`", + category: "Node.js and CommonJS", + recommended: false + }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + schema: [] + }, - return { + create: function(context) { - "CallExpression": function(node) { - var callee = node.callee; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - if (callee.type === "MemberExpression" && callee.object.name === "process" && - callee.property.name === "exit" - ) { - context.report(node, "Don't use process.exit(); throw an error instead."); + return { + + CallExpression: function(node) { + var callee = node.callee; + + if (callee.type === "MemberExpression" && callee.object.name === "process" && + callee.property.name === "exit" + ) { + context.report(node, "Don't use process.exit(); throw an error instead."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-proto.js b/tools/eslint/lib/rules/no-proto.js index f91a46afeb..325f3d1c01 100644 --- a/tools/eslint/lib/rules/no-proto.js +++ b/tools/eslint/lib/rules/no-proto.js @@ -9,20 +9,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of the `__proto__` property", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "MemberExpression": function(node) { + create: function(context) { - if (node.property && - (node.property.type === "Identifier" && node.property.name === "__proto__" && !node.computed) || - (node.property.type === "Literal" && node.property.value === "__proto__")) { - context.report(node, "The '__proto__' property is deprecated."); + return { + + MemberExpression: function(node) { + + if (node.property && + (node.property.type === "Identifier" && node.property.name === "__proto__" && !node.computed) || + (node.property.type === "Literal" && node.property.value === "__proto__")) { + context.report(node, "The '__proto__' property is deprecated."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-redeclare.js b/tools/eslint/lib/rules/no-redeclare.js index 83a6eed9f0..4253cc8533 100644 --- a/tools/eslint/lib/rules/no-redeclare.js +++ b/tools/eslint/lib/rules/no-redeclare.js @@ -9,88 +9,98 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = { - builtinGlobals: Boolean(context.options[0] && context.options[0].builtinGlobals) - }; +module.exports = { + meta: { + docs: { + description: "disallow `var` redeclaration", + category: "Best Practices", + recommended: true + }, + + schema: [ + { + type: "object", + properties: { + builtinGlobals: {type: "boolean"} + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var options = { + builtinGlobals: Boolean(context.options[0] && context.options[0].builtinGlobals) + }; - /** - * Find variables in a given scope and flag redeclared ones. - * @param {Scope} scope - An escope scope object. - * @returns {void} - * @private - */ - function findVariablesInScope(scope) { - scope.variables.forEach(function(variable) { - var hasBuiltin = options.builtinGlobals && "writeable" in variable; - var count = (hasBuiltin ? 1 : 0) + variable.identifiers.length; + /** + * Find variables in a given scope and flag redeclared ones. + * @param {Scope} scope - An escope scope object. + * @returns {void} + * @private + */ + function findVariablesInScope(scope) { + scope.variables.forEach(function(variable) { + var hasBuiltin = options.builtinGlobals && "writeable" in variable; + var count = (hasBuiltin ? 1 : 0) + variable.identifiers.length; - if (count >= 2) { - variable.identifiers.sort(function(a, b) { - return a.range[1] - b.range[1]; - }); + if (count >= 2) { + variable.identifiers.sort(function(a, b) { + return a.range[1] - b.range[1]; + }); - for (var i = (hasBuiltin ? 0 : 1), l = variable.identifiers.length; i < l; i++) { - context.report( - variable.identifiers[i], - "'{{a}}' is already defined", - {a: variable.name}); + for (var i = (hasBuiltin ? 0 : 1), l = variable.identifiers.length; i < l; i++) { + context.report( + variable.identifiers[i], + "'{{a}}' is already defined", + {a: variable.name}); + } } - } - }); + }); - } + } - /** - * Find variables in the current scope. - * @param {ASTNode} node - The Program node. - * @returns {void} - * @private - */ - function checkForGlobal(node) { - var scope = context.getScope(), - parserOptions = context.parserOptions, - ecmaFeatures = parserOptions.ecmaFeatures || {}; + /** + * Find variables in the current scope. + * @param {ASTNode} node - The Program node. + * @returns {void} + * @private + */ + function checkForGlobal(node) { + var scope = context.getScope(), + parserOptions = context.parserOptions, + ecmaFeatures = parserOptions.ecmaFeatures || {}; - // Nodejs env or modules has a special scope. - if (ecmaFeatures.globalReturn || node.sourceType === "module") { - findVariablesInScope(scope.childScopes[0]); - } else { - findVariablesInScope(scope); + // Nodejs env or modules has a special scope. + if (ecmaFeatures.globalReturn || node.sourceType === "module") { + findVariablesInScope(scope.childScopes[0]); + } else { + findVariablesInScope(scope); + } } - } - /** - * Find variables in the current scope. - * @returns {void} - * @private - */ - function checkForBlock() { - findVariablesInScope(context.getScope()); - } + /** + * Find variables in the current scope. + * @returns {void} + * @private + */ + function checkForBlock() { + findVariablesInScope(context.getScope()); + } - if (context.parserOptions.ecmaVersion >= 6) { - return { - "Program": checkForGlobal, - "BlockStatement": checkForBlock, - "SwitchStatement": checkForBlock - }; - } else { - return { - "Program": checkForGlobal, - "FunctionDeclaration": checkForBlock, - "FunctionExpression": checkForBlock, - "ArrowFunctionExpression": checkForBlock - }; + if (context.parserOptions.ecmaVersion >= 6) { + return { + Program: checkForGlobal, + BlockStatement: checkForBlock, + SwitchStatement: checkForBlock + }; + } else { + return { + Program: checkForGlobal, + FunctionDeclaration: checkForBlock, + FunctionExpression: checkForBlock, + ArrowFunctionExpression: checkForBlock + }; + } } }; - -module.exports.schema = [ - { - "type": "object", - "properties": { - "builtinGlobals": {"type": "boolean"} - }, - "additionalProperties": false - } -]; diff --git a/tools/eslint/lib/rules/no-regex-spaces.js b/tools/eslint/lib/rules/no-regex-spaces.js index 0de9a49f35..0584131924 100644 --- a/tools/eslint/lib/rules/no-regex-spaces.js +++ b/tools/eslint/lib/rules/no-regex-spaces.js @@ -9,27 +9,37 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - return { - - "Literal": function(node) { - var token = context.getFirstToken(node), - nodeType = token.type, - nodeValue = token.value, - multipleSpacesRegex = /( {2,})+?/, - regexResults; - - if (nodeType === "RegularExpression") { - regexResults = multipleSpacesRegex.exec(nodeValue); - - if (regexResults !== null) { - context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}."); +module.exports = { + meta: { + docs: { + description: "disallow multiple spaces in regular expression literals", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + return { + + Literal: function(node) { + var token = context.getFirstToken(node), + nodeType = token.type, + nodeValue = token.value, + multipleSpacesRegex = /( {2,})+?/, + regexResults; + + if (nodeType === "RegularExpression") { + regexResults = multipleSpacesRegex.exec(nodeValue); + + if (regexResults !== null) { + context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-restricted-globals.js b/tools/eslint/lib/rules/no-restricted-globals.js index 3cb64a485e..3292705b13 100644 --- a/tools/eslint/lib/rules/no-restricted-globals.js +++ b/tools/eslint/lib/rules/no-restricted-globals.js @@ -1,8 +1,6 @@ /** * @fileoverview Restrict usage of specified globals. * @author Benoît Zugmeyer - * @copyright 2016 Benoît Zugmeyer. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -10,62 +8,73 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var restrictedGlobals = context.options; +module.exports = { + meta: { + docs: { + description: "disallow specified global variables", + category: "Variables", + recommended: false + }, - // if no globals are restricted we don't need to check - if (restrictedGlobals.length === 0) { - return {}; - } - - /** - * Report a variable to be used as a restricted global. - * @param {Reference} reference the variable reference - * @returns {void} - * @private - */ - function reportReference(reference) { - context.report(reference.identifier, "Unexpected use of '{{name}}'", { - name: reference.identifier.name - }); - } - - /** - * Check if the given name is a restricted global name. - * @param {string} name name of a variable - * @returns {boolean} whether the variable is a restricted global or not - * @private - */ - function isRestricted(name) { - return restrictedGlobals.indexOf(name) >= 0; - } + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, - return { - "Program": function() { - var scope = context.getScope(); + create: function(context) { + var restrictedGlobals = context.options; - // Report variables declared elsewhere (ex: variables defined as "global" by eslint) - scope.variables.forEach(function(variable) { - if (!variable.defs.length && isRestricted(variable.name)) { - variable.references.forEach(reportReference); - } - }); + // if no globals are restricted we don't need to check + if (restrictedGlobals.length === 0) { + return {}; + } - // Report variables not declared at all - scope.through.forEach(function(reference) { - if (isRestricted(reference.identifier.name)) { - reportReference(reference); - } + /** + * Report a variable to be used as a restricted global. + * @param {Reference} reference the variable reference + * @returns {void} + * @private + */ + function reportReference(reference) { + context.report(reference.identifier, "Unexpected use of '{{name}}'", { + name: reference.identifier.name }); + } + /** + * Check if the given name is a restricted global name. + * @param {string} name name of a variable + * @returns {boolean} whether the variable is a restricted global or not + * @private + */ + function isRestricted(name) { + return restrictedGlobals.indexOf(name) >= 0; } - }; -}; -module.exports.schema = { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true + return { + Program: function() { + var scope = context.getScope(); + + // Report variables declared elsewhere (ex: variables defined as "global" by eslint) + scope.variables.forEach(function(variable) { + if (!variable.defs.length && isRestricted(variable.name)) { + variable.references.forEach(reportReference); + } + }); + + // Report variables not declared at all + scope.through.forEach(function(reference) { + if (isRestricted(reference.identifier.name)) { + reportReference(reference); + } + }); + + } + }; + } }; + diff --git a/tools/eslint/lib/rules/no-restricted-imports.js b/tools/eslint/lib/rules/no-restricted-imports.js index 0ecd2b3108..3129ce7278 100644 --- a/tools/eslint/lib/rules/no-restricted-imports.js +++ b/tools/eslint/lib/rules/no-restricted-imports.js @@ -1,8 +1,6 @@ /** * @fileoverview Restrict usage of specified node imports. * @author Guy Ellis - * @copyright 2015 Guy Ellis. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -10,34 +8,44 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var restrictedImports = context.options; +module.exports = { + meta: { + docs: { + description: "disallow specified modules when loaded by `import`", + category: "ECMAScript 6", + recommended: false + }, - // if no imports are restricted we don"t need to check - if (restrictedImports.length === 0) { - return {}; - } + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, + + create: function(context) { + var restrictedImports = context.options; + + // if no imports are restricted we don"t need to check + if (restrictedImports.length === 0) { + return {}; + } - return { - "ImportDeclaration": function(node) { - if (node && node.source && node.source.value) { + return { + ImportDeclaration: function(node) { + if (node && node.source && node.source.value) { - var value = node.source.value.trim(); + var value = node.source.value.trim(); - if (restrictedImports.indexOf(value) !== -1) { - context.report(node, "'{{importName}}' import is restricted from being used.", { - importName: value - }); + if (restrictedImports.indexOf(value) !== -1) { + context.report(node, "'{{importName}}' import is restricted from being used.", { + importName: value + }); + } } } - } - }; -}; - -module.exports.schema = { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true + }; + } }; diff --git a/tools/eslint/lib/rules/no-restricted-modules.js b/tools/eslint/lib/rules/no-restricted-modules.js index 97097c41b0..43e5391562 100644 --- a/tools/eslint/lib/rules/no-restricted-modules.js +++ b/tools/eslint/lib/rules/no-restricted-modules.js @@ -8,74 +8,84 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow specified modules when loaded by `require`", + category: "Node.js and CommonJS", + recommended: false + }, - // trim restricted module names - var restrictedModules = context.options; + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, - // if no modules are restricted we don't need to check the CallExpressions - if (restrictedModules.length === 0) { - return {}; - } + create: function(context) { - /** - * Function to check if a node is a string literal. - * @param {ASTNode} node The node to check. - * @returns {boolean} If the node is a string literal. - */ - function isString(node) { - return node && node.type === "Literal" && typeof node.value === "string"; - } + // trim restricted module names + var restrictedModules = context.options; - /** - * Function to check if a node is a require call. - * @param {ASTNode} node The node to check. - * @returns {boolean} If the node is a require call. - */ - function isRequireCall(node) { - return node.callee.type === "Identifier" && node.callee.name === "require"; - } - - /** - * Function to check if a node has an argument that is an restricted module and return its name. - * @param {ASTNode} node The node to check - * @returns {undefined|String} restricted module name or undefined if node argument isn't restricted. - */ - function getRestrictedModuleName(node) { - var moduleName; + // if no modules are restricted we don't need to check the CallExpressions + if (restrictedModules.length === 0) { + return {}; + } - // node has arguments and first argument is string - if (node.arguments.length && isString(node.arguments[0])) { - var argumentValue = node.arguments[0].value.trim(); + /** + * Function to check if a node is a string literal. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a string literal. + */ + function isString(node) { + return node && node.type === "Literal" && typeof node.value === "string"; + } - // check if argument value is in restricted modules array - if (restrictedModules.indexOf(argumentValue) !== -1) { - moduleName = argumentValue; - } + /** + * Function to check if a node is a require call. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a require call. + */ + function isRequireCall(node) { + return node.callee.type === "Identifier" && node.callee.name === "require"; } - return moduleName; - } + /** + * Function to check if a node has an argument that is an restricted module and return its name. + * @param {ASTNode} node The node to check + * @returns {undefined|String} restricted module name or undefined if node argument isn't restricted. + */ + function getRestrictedModuleName(node) { + var moduleName; - return { - "CallExpression": function(node) { - if (isRequireCall(node)) { - var restrictedModuleName = getRestrictedModuleName(node); + // node has arguments and first argument is string + if (node.arguments.length && isString(node.arguments[0])) { + var argumentValue = node.arguments[0].value.trim(); - if (restrictedModuleName) { - context.report(node, "'{{moduleName}}' module is restricted from being used.", { - moduleName: restrictedModuleName - }); + // check if argument value is in restricted modules array + if (restrictedModules.indexOf(argumentValue) !== -1) { + moduleName = argumentValue; } } + + return moduleName; } - }; -}; -module.exports.schema = { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true + return { + CallExpression: function(node) { + if (isRequireCall(node)) { + var restrictedModuleName = getRestrictedModuleName(node); + + if (restrictedModuleName) { + context.report(node, "'{{moduleName}}' module is restricted from being used.", { + moduleName: restrictedModuleName + }); + } + } + } + }; + } }; diff --git a/tools/eslint/lib/rules/no-restricted-syntax.js b/tools/eslint/lib/rules/no-restricted-syntax.js index 52175835c8..cd9eb603f9 100644 --- a/tools/eslint/lib/rules/no-restricted-syntax.js +++ b/tools/eslint/lib/rules/no-restricted-syntax.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag use of certain node types * @author Burak Yigit Kaya - * @copyright 2015 Burak Yigit Kaya. All rights reserved. */ "use strict"; @@ -11,34 +10,44 @@ var nodeTypes = require("espree").Syntax; -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow specified syntax", + category: "Stylistic Issues", + recommended: false + }, + + schema: { + type: "array", + items: [ + { + enum: Object.keys(nodeTypes).map(function(k) { + return nodeTypes[k]; + }) + } + ], + uniqueItems: true, + minItems: 0 + } + }, - /** - * Generates a warning from the provided node, saying that node type is not allowed. - * @param {ASTNode} node The node to warn on - * @returns {void} - */ - function warn(node) { - context.report(node, "Using '{{type}}' is not allowed.", node); - } + create: function(context) { - return context.options.reduce(function(result, nodeType) { - result[nodeType] = warn; + /** + * Generates a warning from the provided node, saying that node type is not allowed. + * @param {ASTNode} node The node to warn on + * @returns {void} + */ + function warn(node) { + context.report(node, "Using '{{type}}' is not allowed.", node); + } - return result; - }, {}); + return context.options.reduce(function(result, nodeType) { + result[nodeType] = warn; -}; + return result; + }, {}); -module.exports.schema = { - "type": "array", - "items": [ - { - "enum": Object.keys(nodeTypes).map(function(k) { - return nodeTypes[k]; - }) - } - ], - "uniqueItems": true, - "minItems": 0 + } }; diff --git a/tools/eslint/lib/rules/no-return-assign.js b/tools/eslint/lib/rules/no-return-assign.js index dc02897d41..0d9e0b4b07 100644 --- a/tools/eslint/lib/rules/no-return-assign.js +++ b/tools/eslint/lib/rules/no-return-assign.js @@ -34,41 +34,51 @@ function isEnclosedInParens(node, context) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var always = (context.options[0] || "except-parens") !== "except-parens"; - - /** - * Check whether return statement contains assignment - * @param {ASTNode} nodeToCheck node to check - * @param {ASTNode} nodeToReport node to report - * @param {string} message message to report - * @returns {void} - * @private - */ - function checkForAssignInReturn(nodeToCheck, nodeToReport, message) { - if (isAssignment(nodeToCheck) && (always || !isEnclosedInParens(nodeToCheck, context))) { - context.report(nodeToReport, message); - } - } +module.exports = { + meta: { + docs: { + description: "disallow assignment operators in `return` statements", + category: "Best Practices", + recommended: false + }, - return { - "ReturnStatement": function(node) { - var message = "Return statement should not contain assignment."; + schema: [ + { + enum: ["except-parens", "always"] + } + ] + }, - checkForAssignInReturn(node.argument, node, message); - }, - "ArrowFunctionExpression": function(node) { - if (node.body.type !== "BlockStatement") { - var message = "Arrow function should not return assignment."; + create: function(context) { + var always = (context.options[0] || "except-parens") !== "except-parens"; - checkForAssignInReturn(node.body, node, message); + /** + * Check whether return statement contains assignment + * @param {ASTNode} nodeToCheck node to check + * @param {ASTNode} nodeToReport node to report + * @param {string} message message to report + * @returns {void} + * @private + */ + function checkForAssignInReturn(nodeToCheck, nodeToReport, message) { + if (isAssignment(nodeToCheck) && (always || !isEnclosedInParens(nodeToCheck, context))) { + context.report(nodeToReport, message); } } - }; -}; -module.exports.schema = [ - { - "enum": ["except-parens", "always"] + return { + ReturnStatement: function(node) { + var message = "Return statement should not contain assignment."; + + checkForAssignInReturn(node.argument, node, message); + }, + ArrowFunctionExpression: function(node) { + if (node.body.type !== "BlockStatement") { + var message = "Arrow function should not return assignment."; + + checkForAssignInReturn(node.body, node, message); + } + } + }; } -]; +}; diff --git a/tools/eslint/lib/rules/no-script-url.js b/tools/eslint/lib/rules/no-script-url.js index 9526061b24..0605cd8642 100644 --- a/tools/eslint/lib/rules/no-script-url.js +++ b/tools/eslint/lib/rules/no-script-url.js @@ -11,24 +11,34 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `javascript", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "Literal": function(node) { + create: function(context) { - var value; + return { - if (node.value && typeof node.value === "string") { - value = node.value.toLowerCase(); + Literal: function(node) { - if (value.indexOf("javascript:") === 0) { - context.report(node, "Script URL is a form of eval."); + var value; + + if (node.value && typeof node.value === "string") { + value = node.value.toLowerCase(); + + if (value.indexOf("javascript:") === 0) { + context.report(node, "Script URL is a form of eval."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-self-assign.js b/tools/eslint/lib/rules/no-self-assign.js index 2a03cab407..bcccc3d838 100644 --- a/tools/eslint/lib/rules/no-self-assign.js +++ b/tools/eslint/lib/rules/no-self-assign.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to disallow assignments where both sides are exactly the same * @author Toru Nagashima - * @copyright 2016 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -97,29 +95,39 @@ function eachSelfAssignment(left, right, report) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Reports a given node as self assignments. - * - * @param {ASTNode} node - A node to report. This is an Identifier node. - * @returns {void} - */ - function report(node) { - context.report({ - node: node, - message: "'{{name}}' is assigned to itself.", - data: node - }); - } +module.exports = { + meta: { + docs: { + description: "disallow assignments where both sides are exactly the same", + category: "Best Practices", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Reports a given node as self assignments. + * + * @param {ASTNode} node - A node to report. This is an Identifier node. + * @returns {void} + */ + function report(node) { + context.report({ + node: node, + message: "'{{name}}' is assigned to itself.", + data: node + }); + } - return { - "AssignmentExpression": function(node) { - if (node.operator === "=") { - eachSelfAssignment(node.left, node.right, report); + return { + AssignmentExpression: function(node) { + if (node.operator === "=") { + eachSelfAssignment(node.left, node.right, report); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-self-compare.js b/tools/eslint/lib/rules/no-self-compare.js index 9ab99d48f8..eef05080b7 100644 --- a/tools/eslint/lib/rules/no-self-compare.js +++ b/tools/eslint/lib/rules/no-self-compare.js @@ -10,21 +10,31 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow comparisons where both sides are exactly the same", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "BinaryExpression": function(node) { - var operators = ["===", "==", "!==", "!=", ">", "<", ">=", "<="]; + create: function(context) { - if (operators.indexOf(node.operator) > -1 && - (node.left.type === "Identifier" && node.right.type === "Identifier" && node.left.name === node.right.name || - node.left.type === "Literal" && node.right.type === "Literal" && node.left.value === node.right.value)) { - context.report(node, "Comparing to itself is potentially pointless."); + return { + + BinaryExpression: function(node) { + var operators = ["===", "==", "!==", "!=", ">", "<", ">=", "<="]; + + if (operators.indexOf(node.operator) > -1 && + (node.left.type === "Identifier" && node.right.type === "Identifier" && node.left.name === node.right.name || + node.left.type === "Literal" && node.right.type === "Literal" && node.left.value === node.right.value)) { + context.report(node, "Comparing to itself is potentially pointless."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-sequences.js b/tools/eslint/lib/rules/no-sequences.js index 032c879f8e..ea20a4b955 100644 --- a/tools/eslint/lib/rules/no-sequences.js +++ b/tools/eslint/lib/rules/no-sequences.js @@ -9,89 +9,99 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Parts of the grammar that are required to have parens. - */ - var parenthesized = { - "DoWhileStatement": "test", - "IfStatement": "test", - "SwitchStatement": "discriminant", - "WhileStatement": "test", - "WithStatement": "object" - - // Omitting CallExpression - commas are parsed as argument separators - // Omitting NewExpression - commas are parsed as argument separators - // Omitting ForInStatement - parts aren't individually parenthesised - // Omitting ForStatement - parts aren't individually parenthesised - }; - - /** - * Determines whether a node is required by the grammar to be wrapped in - * parens, e.g. the test of an if statement. - * @param {ASTNode} node - The AST node - * @returns {boolean} True if parens around node belong to parent node. - */ - function requiresExtraParens(node) { - return node.parent && parenthesized[node.parent.type] && - node === node.parent[parenthesized[node.parent.type]]; - } - - /** - * Check if a node is wrapped in parens. - * @param {ASTNode} node - The AST node - * @returns {boolean} True if the node has a paren on each side. - */ - function isParenthesised(node) { - var previousToken = context.getTokenBefore(node), - nextToken = context.getTokenAfter(node); - - return previousToken && nextToken && - previousToken.value === "(" && previousToken.range[1] <= node.range[0] && - nextToken.value === ")" && nextToken.range[0] >= node.range[1]; - } +module.exports = { + meta: { + docs: { + description: "disallow comma operators", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + /** + * Parts of the grammar that are required to have parens. + */ + var parenthesized = { + DoWhileStatement: "test", + IfStatement: "test", + SwitchStatement: "discriminant", + WhileStatement: "test", + WithStatement: "object" + + // Omitting CallExpression - commas are parsed as argument separators + // Omitting NewExpression - commas are parsed as argument separators + // Omitting ForInStatement - parts aren't individually parenthesised + // Omitting ForStatement - parts aren't individually parenthesised + }; + + /** + * Determines whether a node is required by the grammar to be wrapped in + * parens, e.g. the test of an if statement. + * @param {ASTNode} node - The AST node + * @returns {boolean} True if parens around node belong to parent node. + */ + function requiresExtraParens(node) { + return node.parent && parenthesized[node.parent.type] && + node === node.parent[parenthesized[node.parent.type]]; + } - /** - * Check if a node is wrapped in two levels of parens. - * @param {ASTNode} node - The AST node - * @returns {boolean} True if two parens surround the node on each side. - */ - function isParenthesisedTwice(node) { - var previousToken = context.getTokenBefore(node, 1), - nextToken = context.getTokenAfter(node, 1); - - return isParenthesised(node) && previousToken && nextToken && - previousToken.value === "(" && previousToken.range[1] <= node.range[0] && - nextToken.value === ")" && nextToken.range[0] >= node.range[1]; - } + /** + * Check if a node is wrapped in parens. + * @param {ASTNode} node - The AST node + * @returns {boolean} True if the node has a paren on each side. + */ + function isParenthesised(node) { + var previousToken = context.getTokenBefore(node), + nextToken = context.getTokenAfter(node); + + return previousToken && nextToken && + previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } - return { - "SequenceExpression": function(node) { + /** + * Check if a node is wrapped in two levels of parens. + * @param {ASTNode} node - The AST node + * @returns {boolean} True if two parens surround the node on each side. + */ + function isParenthesisedTwice(node) { + var previousToken = context.getTokenBefore(node, 1), + nextToken = context.getTokenAfter(node, 1); + + return isParenthesised(node) && previousToken && nextToken && + previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } - // Always allow sequences in for statement update - if (node.parent.type === "ForStatement" && - (node === node.parent.init || node === node.parent.update)) { - return; - } + return { + SequenceExpression: function(node) { - // Wrapping a sequence in extra parens indicates intent - if (requiresExtraParens(node)) { - if (isParenthesisedTwice(node)) { + // Always allow sequences in for statement update + if (node.parent.type === "ForStatement" && + (node === node.parent.init || node === node.parent.update)) { return; } - } else { - if (isParenthesised(node)) { - return; + + // Wrapping a sequence in extra parens indicates intent + if (requiresExtraParens(node)) { + if (isParenthesisedTwice(node)) { + return; + } + } else { + if (isParenthesised(node)) { + return; + } } - } - var child = context.getTokenAfter(node.expressions[0]); + var child = context.getTokenAfter(node.expressions[0]); - context.report(node, child.loc.start, "Unexpected use of comma operator."); - } - }; + context.report(node, child.loc.start, "Unexpected use of comma operator."); + } + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-shadow-restricted-names.js b/tools/eslint/lib/rules/no-shadow-restricted-names.js index e39cd00f47..b7731d9d67 100644 --- a/tools/eslint/lib/rules/no-shadow-restricted-names.js +++ b/tools/eslint/lib/rules/no-shadow-restricted-names.js @@ -1,7 +1,6 @@ /** * @fileoverview Disallow shadowing of NaN, undefined, and Infinity (ES5 section 15.1.1) * @author Michael Ficarra - * @copyright 2013 Michael Ficarra. All rights reserved. */ "use strict"; @@ -9,46 +8,56 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow identifiers from shadowing restricted names", + category: "Variables", + recommended: false + }, - var RESTRICTED = ["undefined", "NaN", "Infinity", "arguments", "eval"]; + schema: [] + }, - /** - * Check if the node name is present inside the restricted list - * @param {ASTNode} id id to evaluate - * @returns {void} - * @private - */ - function checkForViolation(id) { - if (RESTRICTED.indexOf(id.name) > -1) { - context.report(id, "Shadowing of global property '" + id.name + "'."); - } - } + create: function(context) { - return { - "VariableDeclarator": function(node) { - checkForViolation(node.id); - }, - "ArrowFunctionExpression": function(node) { - [].map.call(node.params, checkForViolation); - }, - "FunctionExpression": function(node) { - if (node.id) { - checkForViolation(node.id); + var RESTRICTED = ["undefined", "NaN", "Infinity", "arguments", "eval"]; + + /** + * Check if the node name is present inside the restricted list + * @param {ASTNode} id id to evaluate + * @returns {void} + * @private + */ + function checkForViolation(id) { + if (RESTRICTED.indexOf(id.name) > -1) { + context.report(id, "Shadowing of global property '" + id.name + "'."); } - [].map.call(node.params, checkForViolation); - }, - "FunctionDeclaration": function(node) { - if (node.id) { + } + + return { + VariableDeclarator: function(node) { checkForViolation(node.id); + }, + ArrowFunctionExpression: function(node) { [].map.call(node.params, checkForViolation); + }, + FunctionExpression: function(node) { + if (node.id) { + checkForViolation(node.id); + } + [].map.call(node.params, checkForViolation); + }, + FunctionDeclaration: function(node) { + if (node.id) { + checkForViolation(node.id); + [].map.call(node.params, checkForViolation); + } + }, + CatchClause: function(node) { + checkForViolation(node.param); } - }, - "CatchClause": function(node) { - checkForViolation(node.param); - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-shadow.js b/tools/eslint/lib/rules/no-shadow.js index 16432d6b02..ee0dd69f3a 100644 --- a/tools/eslint/lib/rules/no-shadow.js +++ b/tools/eslint/lib/rules/no-shadow.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag on declaring variables already declared in the outer scope * @author Ilya Volodin - * @copyright 2013 Ilya Volodin. All rights reserved. */ "use strict"; @@ -16,164 +15,174 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var options = { - builtinGlobals: Boolean(context.options[0] && context.options[0].builtinGlobals), - hoist: (context.options[0] && context.options[0].hoist) || "functions", - allow: (context.options[0] && context.options[0].allow) || [] - }; - - /** - * Check if variable name is allowed. - * - * @param {ASTNode} variable The variable to check. - * @returns {boolean} Whether or not the variable name is allowed. - */ - function isAllowed(variable) { - return options.allow.indexOf(variable.name) !== -1; - } - - /** - * Checks if a variable of the class name in the class scope of ClassDeclaration. - * - * ClassDeclaration creates two variables of its name into its outer scope and its class scope. - * So we should ignore the variable in the class scope. - * - * @param {Object} variable The variable to check. - * @returns {boolean} Whether or not the variable of the class name in the class scope of ClassDeclaration. - */ - function isDuplicatedClassNameVariable(variable) { - var block = variable.scope.block; - - return block.type === "ClassDeclaration" && block.id === variable.identifiers[0]; - } +module.exports = { + meta: { + docs: { + description: "disallow `var` declarations from shadowing variables in the outer scope", + category: "Variables", + recommended: false + }, - /** - * Checks if a variable is inside the initializer of scopeVar. - * - * To avoid reporting at declarations such as `var a = function a() {};`. - * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`. - * - * @param {Object} variable The variable to check. - * @param {Object} scopeVar The scope variable to look for. - * @returns {boolean} Whether or not the variable is inside initializer of scopeVar. - */ - function isOnInitializer(variable, scopeVar) { - var outerScope = scopeVar.scope; - var outerDef = scopeVar.defs[0]; - var outer = outerDef && outerDef.parent && outerDef.parent.range; - var innerScope = variable.scope; - var innerDef = variable.defs[0]; - var inner = innerDef && innerDef.name.range; - - return ( - outer && - inner && - outer[0] < inner[0] && - inner[1] < outer[1] && - ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && - outerScope === innerScope.upper - ); - } + schema: [ + { + type: "object", + properties: { + builtinGlobals: {type: "boolean"}, + hoist: {enum: ["all", "functions", "never"]}, + allow: { + type: "array", + items: { + type: "string" + } + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + + var options = { + builtinGlobals: Boolean(context.options[0] && context.options[0].builtinGlobals), + hoist: (context.options[0] && context.options[0].hoist) || "functions", + allow: (context.options[0] && context.options[0].allow) || [] + }; + + /** + * Check if variable name is allowed. + * + * @param {ASTNode} variable The variable to check. + * @returns {boolean} Whether or not the variable name is allowed. + */ + function isAllowed(variable) { + return options.allow.indexOf(variable.name) !== -1; + } - /** - * Get a range of a variable's identifier node. - * @param {Object} variable The variable to get. - * @returns {Array|undefined} The range of the variable's identifier node. - */ - function getNameRange(variable) { - var def = variable.defs[0]; + /** + * Checks if a variable of the class name in the class scope of ClassDeclaration. + * + * ClassDeclaration creates two variables of its name into its outer scope and its class scope. + * So we should ignore the variable in the class scope. + * + * @param {Object} variable The variable to check. + * @returns {boolean} Whether or not the variable of the class name in the class scope of ClassDeclaration. + */ + function isDuplicatedClassNameVariable(variable) { + var block = variable.scope.block; + + return block.type === "ClassDeclaration" && block.id === variable.identifiers[0]; + } - return def && def.name.range; - } + /** + * Checks if a variable is inside the initializer of scopeVar. + * + * To avoid reporting at declarations such as `var a = function a() {};`. + * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`. + * + * @param {Object} variable The variable to check. + * @param {Object} scopeVar The scope variable to look for. + * @returns {boolean} Whether or not the variable is inside initializer of scopeVar. + */ + function isOnInitializer(variable, scopeVar) { + var outerScope = scopeVar.scope; + var outerDef = scopeVar.defs[0]; + var outer = outerDef && outerDef.parent && outerDef.parent.range; + var innerScope = variable.scope; + var innerDef = variable.defs[0]; + var inner = innerDef && innerDef.name.range; + + return ( + outer && + inner && + outer[0] < inner[0] && + inner[1] < outer[1] && + ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && + outerScope === innerScope.upper + ); + } - /** - * Checks if a variable is in TDZ of scopeVar. - * @param {Object} variable The variable to check. - * @param {Object} scopeVar The variable of TDZ. - * @returns {boolean} Whether or not the variable is in TDZ of scopeVar. - */ - function isInTdz(variable, scopeVar) { - var outerDef = scopeVar.defs[0]; - var inner = getNameRange(variable); - var outer = getNameRange(scopeVar); - - return ( - inner && - outer && - inner[1] < outer[0] && - - // Excepts FunctionDeclaration if is {"hoist":"function"}. - (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") - ); - } + /** + * Get a range of a variable's identifier node. + * @param {Object} variable The variable to get. + * @returns {Array|undefined} The range of the variable's identifier node. + */ + function getNameRange(variable) { + var def = variable.defs[0]; - /** - * Checks the current context for shadowed variables. - * @param {Scope} scope - Fixme - * @returns {void} - */ - function checkForShadows(scope) { - var variables = scope.variables; - - for (var i = 0; i < variables.length; ++i) { - var variable = variables[i]; - - // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. - if (variable.identifiers.length === 0 || - isDuplicatedClassNameVariable(variable) || - isAllowed(variable) - ) { - continue; - } + return def && def.name.range; + } - // Gets shadowed variable. - var shadowed = astUtils.getVariableByName(scope.upper, variable.name); - - if (shadowed && - (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && - !isOnInitializer(variable, shadowed) && - !(options.hoist !== "all" && isInTdz(variable, shadowed)) - ) { - context.report({ - node: variable.identifiers[0], - message: "'{{name}}' is already declared in the upper scope.", - data: variable - }); - } + /** + * Checks if a variable is in TDZ of scopeVar. + * @param {Object} variable The variable to check. + * @param {Object} scopeVar The variable of TDZ. + * @returns {boolean} Whether or not the variable is in TDZ of scopeVar. + */ + function isInTdz(variable, scopeVar) { + var outerDef = scopeVar.defs[0]; + var inner = getNameRange(variable); + var outer = getNameRange(scopeVar); + + return ( + inner && + outer && + inner[1] < outer[0] && + + // Excepts FunctionDeclaration if is {"hoist":"function"}. + (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") + ); } - } - return { - "Program:exit": function() { - var globalScope = context.getScope(); - var stack = globalScope.childScopes.slice(); - var scope; + /** + * Checks the current context for shadowed variables. + * @param {Scope} scope - Fixme + * @returns {void} + */ + function checkForShadows(scope) { + var variables = scope.variables; + + for (var i = 0; i < variables.length; ++i) { + var variable = variables[i]; + + // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. + if (variable.identifiers.length === 0 || + isDuplicatedClassNameVariable(variable) || + isAllowed(variable) + ) { + continue; + } - while (stack.length) { - scope = stack.pop(); - stack.push.apply(stack, scope.childScopes); - checkForShadows(scope); + // Gets shadowed variable. + var shadowed = astUtils.getVariableByName(scope.upper, variable.name); + + if (shadowed && + (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && + !isOnInitializer(variable, shadowed) && + !(options.hoist !== "all" && isInTdz(variable, shadowed)) + ) { + context.report({ + node: variable.identifiers[0], + message: "'{{name}}' is already declared in the upper scope.", + data: variable + }); + } } } - }; -}; + return { + "Program:exit": function() { + var globalScope = context.getScope(); + var stack = globalScope.childScopes.slice(); + var scope; -module.exports.schema = [ - { - "type": "object", - "properties": { - "builtinGlobals": {"type": "boolean"}, - "hoist": {"enum": ["all", "functions", "never"]}, - "allow": { - "type": "array", - "items": { - "type": "string" + while (stack.length) { + scope = stack.pop(); + stack.push.apply(stack, scope.childScopes); + checkForShadows(scope); } } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/no-spaced-func.js b/tools/eslint/lib/rules/no-spaced-func.js index 551a3c6097..f0a1612113 100644 --- a/tools/eslint/lib/rules/no-spaced-func.js +++ b/tools/eslint/lib/rules/no-spaced-func.js @@ -9,52 +9,63 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var sourceCode = context.getSourceCode(); - - /** - * Check if open space is present in a function name - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function detectOpenSpaces(node) { - var lastCalleeToken = sourceCode.getLastToken(node.callee), - prevToken = lastCalleeToken, - parenToken = sourceCode.getTokenAfter(lastCalleeToken); - - // advances to an open parenthesis. - while ( - parenToken && - parenToken.range[1] < node.range[1] && - parenToken.value !== "(" - ) { - prevToken = parenToken; - parenToken = sourceCode.getTokenAfter(parenToken); - } +module.exports = { + meta: { + docs: { + description: "disallow spacing between `function` identifiers and their applications", + category: "Stylistic Issues", + recommended: false + }, + + fixable: "whitespace", + schema: [] + }, + + create: function(context) { + + var sourceCode = context.getSourceCode(); + + /** + * Check if open space is present in a function name + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function detectOpenSpaces(node) { + var lastCalleeToken = sourceCode.getLastToken(node.callee), + prevToken = lastCalleeToken, + parenToken = sourceCode.getTokenAfter(lastCalleeToken); - // look for a space between the callee and the open paren - if (parenToken && - parenToken.range[1] < node.range[1] && - sourceCode.isSpaceBetweenTokens(prevToken, parenToken) - ) { - context.report({ - node: node, - loc: lastCalleeToken.loc.start, - message: "Unexpected space between function name and paren.", - fix: function(fixer) { - return fixer.removeRange([prevToken.range[1], parenToken.range[0]]); - } - }); + // advances to an open parenthesis. + while ( + parenToken && + parenToken.range[1] < node.range[1] && + parenToken.value !== "(" + ) { + prevToken = parenToken; + parenToken = sourceCode.getTokenAfter(parenToken); + } + + // look for a space between the callee and the open paren + if (parenToken && + parenToken.range[1] < node.range[1] && + sourceCode.isSpaceBetweenTokens(prevToken, parenToken) + ) { + context.report({ + node: node, + loc: lastCalleeToken.loc.start, + message: "Unexpected space between function name and paren.", + fix: function(fixer) { + return fixer.removeRange([prevToken.range[1], parenToken.range[0]]); + } + }); + } } - } - return { - "CallExpression": detectOpenSpaces, - "NewExpression": detectOpenSpaces - }; + return { + CallExpression: detectOpenSpaces, + NewExpression: detectOpenSpaces + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-sparse-arrays.js b/tools/eslint/lib/rules/no-sparse-arrays.js index 808ec99bfc..b1ae0ba740 100644 --- a/tools/eslint/lib/rules/no-sparse-arrays.js +++ b/tools/eslint/lib/rules/no-sparse-arrays.js @@ -8,26 +8,36 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow sparse arrays", + category: "Possible Errors", + recommended: true + }, + schema: [] + }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + create: function(context) { - return { - "ArrayExpression": function(node) { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - var emptySpot = node.elements.indexOf(null) > -1; + return { - if (emptySpot) { - context.report(node, "Unexpected comma in middle of array."); + ArrayExpression: function(node) { + + var emptySpot = node.elements.indexOf(null) > -1; + + if (emptySpot) { + context.report(node, "Unexpected comma in middle of array."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-sync.js b/tools/eslint/lib/rules/no-sync.js index 481514b83d..be6860e75a 100644 --- a/tools/eslint/lib/rules/no-sync.js +++ b/tools/eslint/lib/rules/no-sync.js @@ -11,20 +11,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow synchronous methods", + category: "Node.js and CommonJS", + recommended: false + }, - return { + schema: [] + }, - "MemberExpression": function(node) { - var propertyName = node.property.name, - syncRegex = /.*Sync$/; + create: function(context) { - if (syncRegex.exec(propertyName) !== null) { - context.report(node, "Unexpected sync method: '" + propertyName + "'."); + return { + + MemberExpression: function(node) { + var propertyName = node.property.name, + syncRegex = /.*Sync$/; + + if (syncRegex.exec(propertyName) !== null) { + context.report(node, "Unexpected sync method: '" + propertyName + "'."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-ternary.js b/tools/eslint/lib/rules/no-ternary.js index 79f8576b5f..fb986152bb 100644 --- a/tools/eslint/lib/rules/no-ternary.js +++ b/tools/eslint/lib/rules/no-ternary.js @@ -9,16 +9,26 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow ternary operators", + category: "Stylistic Issues", + recommended: false + }, - return { + schema: [] + }, - "ConditionalExpression": function(node) { - context.report(node, "Ternary operator used."); - } + create: function(context) { - }; + return { -}; + ConditionalExpression: function(node) { + context.report(node, "Ternary operator used."); + } + + }; -module.exports.schema = []; + } +}; diff --git a/tools/eslint/lib/rules/no-this-before-super.js b/tools/eslint/lib/rules/no-this-before-super.js index 4e6c47fda8..fb172f47f3 100644 --- a/tools/eslint/lib/rules/no-this-before-super.js +++ b/tools/eslint/lib/rules/no-this-before-super.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to disallow using `this`/`super` before `super()`. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -35,248 +34,266 @@ function isConstructorFunction(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /* - * Information for each constructor. - * - upper: Information of the upper constructor. - * - hasExtends: A flag which shows whether the owner class has a valid - * `extends` part. - * - scope: The scope of the owner class. - * - codePath: The code path of this constructor. - */ - var funcInfo = null; - - /* - * Information for each code path segment. - * Each key is the id of a code path segment. - * Each value is an object: - * - superCalled: The flag which shows `super()` called in all code paths. - * - invalidNodes: The array of invalid ThisExpression and Super nodes. - */ - var segInfoMap = Object.create(null); - - /** - * Gets whether or not `super()` is called in a given code path segment. - * @param {CodePathSegment} segment - A code path segment to get. - * @returns {boolean} `true` if `super()` is called. - */ - function isCalled(segment) { - return segInfoMap[segment.id].superCalled; - } +module.exports = { + meta: { + docs: { + description: "disallow `this`/`super` before calling `super()` in constructors", + category: "ECMAScript 6", + recommended: true + }, - /** - * Checks whether or not this is in a constructor. - * @returns {boolean} `true` if this is in a constructor. - */ - function isInConstructorOfDerivedClass() { - return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends); - } + schema: [] + }, - /** - * Checks whether or not this is before `super()` is called. - * @returns {boolean} `true` if this is before `super()` is called. - */ - function isBeforeCallOfSuper() { - return ( - isInConstructorOfDerivedClass(funcInfo) && - !funcInfo.codePath.currentSegments.every(isCalled) - ); - } + create: function(context) { - /** - * Sets a given node as invalid. - * @param {ASTNode} node - A node to set as invalid. This is one of - * a ThisExpression and a Super. - * @returns {void} - */ - function setInvalid(node) { - var segments = funcInfo.codePath.currentSegments; - - for (var i = 0; i < segments.length; ++i) { - segInfoMap[segments[i].id].invalidNodes.push(node); - } - } - - /** - * Sets the current segment as `super` was called. - * @returns {void} - */ - function setSuperCalled() { - var segments = funcInfo.codePath.currentSegments; + /* + * Information for each constructor. + * - upper: Information of the upper constructor. + * - hasExtends: A flag which shows whether the owner class has a valid + * `extends` part. + * - scope: The scope of the owner class. + * - codePath: The code path of this constructor. + */ + var funcInfo = null; + + /* + * Information for each code path segment. + * Each key is the id of a code path segment. + * Each value is an object: + * - superCalled: The flag which shows `super()` called in all code paths. + * - invalidNodes: The array of invalid ThisExpression and Super nodes. + */ + var segInfoMap = Object.create(null); - for (var i = 0; i < segments.length; ++i) { - segInfoMap[segments[i].id].superCalled = true; + /** + * Gets whether or not `super()` is called in a given code path segment. + * @param {CodePathSegment} segment - A code path segment to get. + * @returns {boolean} `true` if `super()` is called. + */ + function isCalled(segment) { + return !segment.reachable || segInfoMap[segment.id].superCalled; } - } - return { + /** + * Checks whether or not this is in a constructor. + * @returns {boolean} `true` if this is in a constructor. + */ + function isInConstructorOfDerivedClass() { + return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends); + } /** - * Adds information of a constructor into the stack. - * @param {CodePath} codePath - A code path which was started. - * @param {ASTNode} node - The current node. - * @returns {void} + * Checks whether or not this is before `super()` is called. + * @returns {boolean} `true` if this is before `super()` is called. */ - "onCodePathStart": function(codePath, node) { - if (isConstructorFunction(node)) { - - // Class > ClassBody > MethodDefinition > FunctionExpression - var classNode = node.parent.parent.parent; - - funcInfo = { - upper: funcInfo, - isConstructor: true, - hasExtends: Boolean( - classNode.superClass && - !astUtils.isNullOrUndefined(classNode.superClass) - ), - codePath: codePath - }; - } else { - funcInfo = { - upper: funcInfo, - isConstructor: false, - hasExtends: false, - codePath: codePath - }; - } - }, + function isBeforeCallOfSuper() { + return ( + isInConstructorOfDerivedClass(funcInfo) && + !funcInfo.codePath.currentSegments.every(isCalled) + ); + } /** - * Removes the top of stack item. - * - * And this treverses all segments of this code path then reports every - * invalid node. - * - * @param {CodePath} codePath - A code path which was ended. - * @param {ASTNode} node - The current node. + * Sets a given node as invalid. + * @param {ASTNode} node - A node to set as invalid. This is one of + * a ThisExpression and a Super. * @returns {void} */ - "onCodePathEnd": function(codePath) { - var isDerivedClass = funcInfo.hasExtends; - - funcInfo = funcInfo.upper; - if (!isDerivedClass) { - return; - } - - codePath.traverseSegments(function(segment, controller) { - var info = segInfoMap[segment.id]; + function setInvalid(node) { + var segments = funcInfo.codePath.currentSegments; - for (var i = 0; i < info.invalidNodes.length; ++i) { - var invalidNode = info.invalidNodes[i]; + for (var i = 0; i < segments.length; ++i) { + var segment = segments[i]; - context.report({ - message: "'{{kind}}' is not allowed before 'super()'.", - node: invalidNode, - data: { - kind: invalidNode.type === "Super" ? "super" : "this" - } - }); - } - - if (info.superCalled) { - controller.skip(); + if (segment.reachable) { + segInfoMap[segment.id].invalidNodes.push(node); } - }); - }, + } + } /** - * Initialize information of a given code path segment. - * @param {CodePathSegment} segment - A code path segment to initialize. + * Sets the current segment as `super` was called. * @returns {void} */ - "onCodePathSegmentStart": function(segment) { - if (!isInConstructorOfDerivedClass(funcInfo)) { - return; - } + function setSuperCalled() { + var segments = funcInfo.codePath.currentSegments; - // Initialize info. - segInfoMap[segment.id] = { - superCalled: ( - segment.prevSegments.length > 0 && - segment.prevSegments.every(isCalled) - ), - invalidNodes: [] - }; - }, + for (var i = 0; i < segments.length; ++i) { + var segment = segments[i]; - /** - * 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 (!isInConstructorOfDerivedClass(funcInfo)) { - return; + if (segment.reachable) { + segInfoMap[segment.id].superCalled = true; + } } + } - // Update information inside of the loop. - funcInfo.codePath.traverseSegments( - {first: toSegment, last: fromSegment}, - function(segment, controller) { + return { + + /** + * Adds information of a constructor into the stack. + * @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; + + 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 + }; + } + }, + + /** + * Removes the top of stack item. + * + * And this treverses all segments of this code path then reports every + * invalid node. + * + * @param {CodePath} codePath - A code path which was ended. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathEnd: function(codePath) { + var isDerivedClass = funcInfo.hasExtends; + + funcInfo = funcInfo.upper; + if (!isDerivedClass) { + return; + } + + codePath.traverseSegments(function(segment, controller) { var info = segInfoMap[segment.id]; + for (var i = 0; i < info.invalidNodes.length; ++i) { + var invalidNode = info.invalidNodes[i]; + + context.report({ + message: "'{{kind}}' is not allowed before 'super()'.", + node: invalidNode, + data: { + kind: invalidNode.type === "Super" ? "super" : "this" + } + }); + } + if (info.superCalled) { - info.invalidNodes = []; controller.skip(); - } else if ( - segment.prevSegments.length > 0 && - segment.prevSegments.every(isCalled) - ) { - info.superCalled = true; - info.invalidNodes = []; } + }); + }, + + /** + * Initialize information of a given code path segment. + * @param {CodePathSegment} segment - A code path segment to initialize. + * @returns {void} + */ + onCodePathSegmentStart: function(segment) { + if (!isInConstructorOfDerivedClass(funcInfo)) { + return; } - ); - }, - - /** - * Reports if this is before `super()`. - * @param {ASTNode} node - A target node. - * @returns {void} - */ - "ThisExpression": function(node) { - if (isBeforeCallOfSuper()) { - setInvalid(node); - } - }, - /** - * Reports if this is before `super()`. - * @param {ASTNode} node - A target node. - * @returns {void} - */ - "Super": function(node) { - if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) { - setInvalid(node); - } - }, + // Initialize info. + segInfoMap[segment.id] = { + superCalled: ( + segment.prevSegments.length > 0 && + segment.prevSegments.every(isCalled) + ), + invalidNodes: [] + }; + }, + + /** + * 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 (!isInConstructorOfDerivedClass(funcInfo)) { + return; + } - /** - * Marks `super()` called. - * @param {ASTNode} node - A target node. - * @returns {void} - */ - "CallExpression:exit": function(node) { - if (node.callee.type === "Super" && isBeforeCallOfSuper()) { - setSuperCalled(); + // Update information inside of the loop. + funcInfo.codePath.traverseSegments( + {first: toSegment, last: fromSegment}, + function(segment, controller) { + var info = segInfoMap[segment.id]; + + if (info.superCalled) { + info.invalidNodes = []; + controller.skip(); + } else if ( + segment.prevSegments.length > 0 && + segment.prevSegments.every(isCalled) + ) { + info.superCalled = true; + info.invalidNodes = []; + } + } + ); + }, + + /** + * Reports if this is before `super()`. + * @param {ASTNode} node - A target node. + * @returns {void} + */ + ThisExpression: function(node) { + if (isBeforeCallOfSuper()) { + setInvalid(node); + } + }, + + /** + * Reports if this is before `super()`. + * @param {ASTNode} node - A target node. + * @returns {void} + */ + Super: function(node) { + if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) { + setInvalid(node); + } + }, + + /** + * Marks `super()` called. + * @param {ASTNode} node - A target node. + * @returns {void} + */ + "CallExpression:exit": function(node) { + if (node.callee.type === "Super" && isBeforeCallOfSuper()) { + setSuperCalled(); + } + }, + + /** + * Resets state. + * @returns {void} + */ + "Program:exit": function() { + segInfoMap = Object.create(null); } - }, - - /** - * Resets state. - * @returns {void} - */ - "Program:exit": function() { - segInfoMap = Object.create(null); - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-throw-literal.js b/tools/eslint/lib/rules/no-throw-literal.js index 745850dc8d..bedf94379e 100644 --- a/tools/eslint/lib/rules/no-throw-literal.js +++ b/tools/eslint/lib/rules/no-throw-literal.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to restrict what can be thrown as an exception. * @author Dieter Oberkofler - * @copyright 2015 Ian VanSchooten. All rights reserved. - * @copyright 2015 Dieter Oberkofler. All rights reserved. */ "use strict"; @@ -49,23 +47,33 @@ function couldBeError(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow throwing literals as exceptions", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "ThrowStatement": function(node) { - if (!couldBeError(node.argument)) { - context.report(node, "Expected an object to be thrown."); - } else if (node.argument.type === "Identifier") { - if (node.argument.name === "undefined") { - context.report(node, "Do not throw undefined."); + create: function(context) { + + return { + + ThrowStatement: function(node) { + if (!couldBeError(node.argument)) { + context.report(node, "Expected an object to be thrown."); + } else if (node.argument.type === "Identifier") { + if (node.argument.name === "undefined") { + context.report(node, "Do not throw undefined."); + } } - } - } + } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-trailing-spaces.js b/tools/eslint/lib/rules/no-trailing-spaces.js index af30610f94..a08907fc95 100644 --- a/tools/eslint/lib/rules/no-trailing-spaces.js +++ b/tools/eslint/lib/rules/no-trailing-spaces.js @@ -1,7 +1,6 @@ /** * @fileoverview Disallow trailing spaces at the end of lines. * @author Nodeca Team - * @copyright 2015 Greg Cochard */ "use strict"; @@ -9,103 +8,128 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u2028\u2029\u3000]", - SKIP_BLANK = "^" + BLANK_CLASS + "*$", - NONBLANK = BLANK_CLASS + "+$"; - - var options = context.options[0] || {}, - skipBlankLines = options.skipBlankLines || false; - - /** - * Report the error message - * @param {ASTNode} node node to report - * @param {int[]} location range information - * @param {int[]} fixRange Range based on the whole program - * @returns {void} - */ - function report(node, location, fixRange) { - - /* - * Passing node is a bit dirty, because message data will contain big - * text in `source`. But... who cares :) ? - * One more kludge will not make worse the bloody wizardry of this - * plugin. - */ - context.report({ - node: node, - loc: location, - message: "Trailing spaces not allowed.", - fix: function(fixer) { - return fixer.removeRange(fixRange); - } - }); - } - - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- +module.exports = { + meta: { + docs: { + description: "disallow trailing whitespace at the end of lines", + category: "Stylistic Issues", + recommended: false + }, - return { + fixable: "whitespace", - "Program": function checkTrailingSpaces(node) { + schema: [ + { + type: "object", + properties: { + skipBlankLines: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - // Let's hack. Since Espree does not return whitespace nodes, - // fetch the source code and do matching via regexps. + create: function(context) { + var sourceCode = context.getSourceCode(); - var src = context.getSource(), - re = new RegExp(NONBLANK), - skipMatch = new RegExp(SKIP_BLANK), - matches, - lines = src.split(/\r?\n/), - linebreaks = context.getSource().match(/\r\n|\r|\n|\u2028|\u2029/g), - location, - totalLength = 0, - fixRange = []; + var BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u2028\u2029\u3000]", + SKIP_BLANK = "^" + BLANK_CLASS + "*$", + NONBLANK = BLANK_CLASS + "+$"; - for (var i = 0, ii = lines.length; i < ii; i++) { - matches = re.exec(lines[i]); + var options = context.options[0] || {}, + skipBlankLines = options.skipBlankLines || false; - // Always add linebreak length to line length to accommodate for line break (\n or \r\n) - // Because during the fix time they also reserve one spot in the array. - // Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF) - var linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1; - var lineLength = lines[i].length + linebreakLength; + /** + * Report the error message + * @param {ASTNode} node node to report + * @param {int[]} location range information + * @param {int[]} fixRange Range based on the whole program + * @returns {void} + */ + function report(node, location, fixRange) { + + /* + * Passing node is a bit dirty, because message data will contain big + * text in `source`. But... who cares :) ? + * One more kludge will not make worse the bloody wizardry of this + * plugin. + */ + context.report({ + node: node, + loc: location, + message: "Trailing spaces not allowed.", + fix: function(fixer) { + return fixer.removeRange(fixRange); + } + }); + } - if (matches) { - // If the line has only whitespace, and skipBlankLines - // is true, don't report it - if (skipBlankLines && skipMatch.test(lines[i])) { - continue; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + Program: function checkTrailingSpaces(node) { + + // Let's hack. Since Espree does not return whitespace nodes, + // fetch the source code and do matching via regexps. + + var re = new RegExp(NONBLANK), + skipMatch = new RegExp(SKIP_BLANK), + matches, + lines = sourceCode.lines, + linebreaks = sourceCode.getText().match(/\r\n|\r|\n|\u2028|\u2029/g), + location, + totalLength = 0, + rangeStart, + rangeEnd, + fixRange = [], + containingNode; + + for (var i = 0, ii = lines.length; i < ii; i++) { + matches = re.exec(lines[i]); + + // Always add linebreak length to line length to accommodate for line break (\n or \r\n) + // Because during the fix time they also reserve one spot in the array. + // Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF) + var linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1; + var lineLength = lines[i].length + linebreakLength; + + if (matches) { + location = { + line: i + 1, + column: matches.index + }; + + rangeStart = totalLength + location.column; + rangeEnd = totalLength + lineLength - linebreakLength; + containingNode = sourceCode.getNodeByRangeIndex(rangeStart); + + if (containingNode && containingNode.type === "TemplateElement" && + rangeStart > containingNode.parent.range[0] && + rangeEnd < containingNode.parent.range[1]) { + totalLength += lineLength; + continue; + } + + // If the line has only whitespace, and skipBlankLines + // is true, don't report it + if (skipBlankLines && skipMatch.test(lines[i])) { + continue; + } + + fixRange = [rangeStart, rangeEnd]; + report(node, location, fixRange); } - location = { - line: i + 1, - column: matches.index - }; - - fixRange = [totalLength + location.column, totalLength + lineLength - linebreakLength]; - report(node, location, fixRange); + totalLength += lineLength; } - - totalLength += lineLength; } - } - }; -}; - -module.exports.schema = [ - { - "type": "object", - "properties": { - "skipBlankLines": { - "type": "boolean" - } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/no-undef-init.js b/tools/eslint/lib/rules/no-undef-init.js index 1348c641e3..8622e45701 100644 --- a/tools/eslint/lib/rules/no-undef-init.js +++ b/tools/eslint/lib/rules/no-undef-init.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag when initializing to undefined * @author Ilya Volodin - * @copyright 2013 Ilya Volodin. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; @@ -11,20 +9,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow initializing variables to `undefined`", + category: "Variables", + recommended: false + }, - return { + schema: [] + }, - "VariableDeclarator": function(node) { - var name = node.id.name, - init = node.init && node.init.name; + create: function(context) { - if (init === "undefined" && node.parent.kind !== "const") { - context.report(node, "It's not necessary to initialize '{{name}}' to undefined.", { name: name }); + return { + + VariableDeclarator: function(node) { + var name = node.id.name, + init = node.init && node.init.name; + + if (init === "undefined" && node.parent.kind !== "const") { + context.report(node, "It's not necessary to initialize '{{name}}' to undefined.", { name: name }); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-undef.js b/tools/eslint/lib/rules/no-undef.js index c3a8872567..b76ce4bb5b 100644 --- a/tools/eslint/lib/rules/no-undef.js +++ b/tools/eslint/lib/rules/no-undef.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag references to undeclared variables. * @author Mark Macdonald - * @copyright 2015 Nicholas C. Zakas. All rights reserved. - * @copyright 2013 Mark Macdonald. All rights reserved. */ "use strict"; @@ -25,39 +23,49 @@ function hasTypeOfOperator(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0]; - var considerTypeOf = options && options.typeof === true || false; +module.exports = { + meta: { + docs: { + description: "disallow the use of undeclared variables unless mentioned in `/*global */` comments", + category: "Variables", + recommended: true + }, - return { - "Program:exit": function(/* node */) { - var globalScope = context.getScope(); + schema: [ + { + type: "object", + properties: { + typeof: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - globalScope.through.forEach(function(ref) { - var identifier = ref.identifier; + create: function(context) { + var options = context.options[0]; + var considerTypeOf = options && options.typeof === true || false; - if (!considerTypeOf && hasTypeOfOperator(identifier)) { - return; - } + return { + "Program:exit": function(/* node */) { + var globalScope = context.getScope(); - context.report({ - node: identifier, - message: "'{{name}}' is not defined.", - data: identifier - }); - }); - } - }; -}; + globalScope.through.forEach(function(ref) { + var identifier = ref.identifier; -module.exports.schema = [ - { - "type": "object", - "properties": { - "typeof": { - "type": "boolean" + if (!considerTypeOf && hasTypeOfOperator(identifier)) { + return; + } + + context.report({ + node: identifier, + message: "'{{name}}' is not defined.", + data: identifier + }); + }); } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/no-undefined.js b/tools/eslint/lib/rules/no-undefined.js index a44eabf1b1..3ad2128b4e 100644 --- a/tools/eslint/lib/rules/no-undefined.js +++ b/tools/eslint/lib/rules/no-undefined.js @@ -8,21 +8,31 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `undefined` as an identifier", + category: "Variables", + recommended: false + }, - return { + schema: [] + }, - "Identifier": function(node) { - if (node.name === "undefined") { - var parent = context.getAncestors().pop(); + create: function(context) { - if (!parent || parent.type !== "MemberExpression" || node !== parent.property || parent.computed) { - context.report(node, "Unexpected use of undefined."); + return { + + Identifier: function(node) { + if (node.name === "undefined") { + var parent = context.getAncestors().pop(); + + if (!parent || parent.type !== "MemberExpression" || node !== parent.property || parent.computed) { + context.report(node, "Unexpected use of undefined."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-underscore-dangle.js b/tools/eslint/lib/rules/no-underscore-dangle.js index 6401e93e37..4217f8adc5 100644 --- a/tools/eslint/lib/rules/no-underscore-dangle.js +++ b/tools/eslint/lib/rules/no-underscore-dangle.js @@ -9,136 +9,146 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var options = context.options[0] || {}; - var ALLOWED_VARIABLES = options.allow ? options.allow : []; - var allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false; - - //------------------------------------------------------------------------- - // Helpers - //------------------------------------------------------------------------- - - /** - * Check if identifier is present inside the allowed option - * @param {string} identifier name of the node - * @returns {boolean} true if its is present - * @private - */ - function isAllowed(identifier) { - return ALLOWED_VARIABLES.some(function(ident) { - return ident === identifier; - }); - } +module.exports = { + meta: { + docs: { + description: "disallow dangling underscores in identifiers", + category: "Stylistic Issues", + recommended: false + }, - /** - * Check if identifier has a underscore at the end - * @param {ASTNode} identifier node to evaluate - * @returns {boolean} true if its is present - * @private - */ - function hasTrailingUnderscore(identifier) { - var len = identifier.length; + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { + type: "string" + } + }, + allowAfterThis: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + + var options = context.options[0] || {}; + var ALLOWED_VARIABLES = options.allow ? options.allow : []; + var allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false; + + //------------------------------------------------------------------------- + // Helpers + //------------------------------------------------------------------------- + + /** + * Check if identifier is present inside the allowed option + * @param {string} identifier name of the node + * @returns {boolean} true if its is present + * @private + */ + function isAllowed(identifier) { + return ALLOWED_VARIABLES.some(function(ident) { + return ident === identifier; + }); + } - return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_"); - } + /** + * Check if identifier has a underscore at the end + * @param {ASTNode} identifier node to evaluate + * @returns {boolean} true if its is present + * @private + */ + function hasTrailingUnderscore(identifier) { + var len = identifier.length; - /** - * Check if identifier is a special case member expression - * @param {ASTNode} identifier node to evaluate - * @returns {boolean} true if its is a special case - * @private - */ - function isSpecialCaseIdentifierForMemberExpression(identifier) { - return identifier === "__proto__"; - } + return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_"); + } - /** - * Check if identifier is a special case variable expression - * @param {ASTNode} identifier node to evaluate - * @returns {boolean} true if its is a special case - * @private - */ - function isSpecialCaseIdentifierInVariableExpression(identifier) { + /** + * Check if identifier is a special case member expression + * @param {ASTNode} identifier node to evaluate + * @returns {boolean} true if its is a special case + * @private + */ + function isSpecialCaseIdentifierForMemberExpression(identifier) { + return identifier === "__proto__"; + } - // Checks for the underscore library usage here - return identifier === "_"; - } + /** + * Check if identifier is a special case variable expression + * @param {ASTNode} identifier node to evaluate + * @returns {boolean} true if its is a special case + * @private + */ + function isSpecialCaseIdentifierInVariableExpression(identifier) { - /** - * Check if function has a underscore at the end - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForTrailingUnderscoreInFunctionDeclaration(node) { - if (node.id) { - var identifier = node.id.name; + // Checks for the underscore library usage here + return identifier === "_"; + } - if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) { - context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); + /** + * Check if function has a underscore at the end + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForTrailingUnderscoreInFunctionDeclaration(node) { + if (node.id) { + var identifier = node.id.name; + + if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) { + context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); + } } } - } - /** - * Check if variable expression has a underscore at the end - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForTrailingUnderscoreInVariableExpression(node) { - var identifier = node.id.name; - - if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && - !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) { - context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); - } - } + /** + * Check if variable expression has a underscore at the end + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForTrailingUnderscoreInVariableExpression(node) { + var identifier = node.id.name; - /** - * Check if member expression has a underscore at the end - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForTrailingUnderscoreInMemberExpression(node) { - var identifier = node.property.name, - isMemberOfThis = node.object.type === "ThisExpression"; - - if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && - !(isMemberOfThis && allowAfterThis) && - !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) { - context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); + if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && + !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) { + context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); + } } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + /** + * Check if member expression has a underscore at the end + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForTrailingUnderscoreInMemberExpression(node) { + var identifier = node.property.name, + isMemberOfThis = node.object.type === "ThisExpression"; + + if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && + !(isMemberOfThis && allowAfterThis) && + !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) { + context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); + } + } - return { - "FunctionDeclaration": checkForTrailingUnderscoreInFunctionDeclaration, - "VariableDeclarator": checkForTrailingUnderscoreInVariableExpression, - "MemberExpression": checkForTrailingUnderscoreInMemberExpression - }; + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- -}; + return { + FunctionDeclaration: checkForTrailingUnderscoreInFunctionDeclaration, + VariableDeclarator: checkForTrailingUnderscoreInVariableExpression, + MemberExpression: checkForTrailingUnderscoreInMemberExpression + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "allow": { - "type": "array", - "items": { - "type": "string" - } - }, - "allowAfterThis": { - "type": "boolean" - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/no-unexpected-multiline.js b/tools/eslint/lib/rules/no-unexpected-multiline.js index 312376a334..c066673301 100644 --- a/tools/eslint/lib/rules/no-unexpected-multiline.js +++ b/tools/eslint/lib/rules/no-unexpected-multiline.js @@ -1,70 +1,79 @@ /** * @fileoverview Rule to spot scenarios where a newline looks like it is ending a statement, but is not. * @author Glen Mailer - * @copyright 2015 Glen Mailer */ "use strict"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow confusing multiline expressions", + category: "Possible Errors", + recommended: true + }, - var FUNCTION_MESSAGE = "Unexpected newline between function and ( of function call."; - var PROPERTY_MESSAGE = "Unexpected newline between object and [ of property access."; - var TAGGED_TEMPLATE_MESSAGE = "Unexpected newline between template tag and template literal."; + schema: [] + }, - /** - * Check to see if there is a newline between the node and the following open bracket - * line's expression - * @param {ASTNode} node The node to check. - * @param {string} msg The error message to use. - * @returns {void} - * @private - */ - function checkForBreakAfter(node, msg) { - var nodeExpressionEnd = node; - var openParen = context.getTokenAfter(node); + create: function(context) { - // Move along until the end of the wrapped expression - while (openParen.value === ")") { - nodeExpressionEnd = openParen; - openParen = context.getTokenAfter(nodeExpressionEnd); - } + var FUNCTION_MESSAGE = "Unexpected newline between function and ( of function call."; + var PROPERTY_MESSAGE = "Unexpected newline between object and [ of property access."; + var TAGGED_TEMPLATE_MESSAGE = "Unexpected newline between template tag and template literal."; - if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) { - context.report(node, openParen.loc.start, msg, { char: openParen.value }); + /** + * Check to see if there is a newline between the node and the following open bracket + * line's expression + * @param {ASTNode} node The node to check. + * @param {string} msg The error message to use. + * @returns {void} + * @private + */ + function checkForBreakAfter(node, msg) { + var nodeExpressionEnd = node; + var openParen = context.getTokenAfter(node); + + // Move along until the end of the wrapped expression + while (openParen.value === ")") { + nodeExpressionEnd = openParen; + openParen = context.getTokenAfter(nodeExpressionEnd); + } + + if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) { + context.report(node, openParen.loc.start, msg, { char: openParen.value }); + } } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - return { + return { - "MemberExpression": function(node) { - if (!node.computed) { - return; - } - checkForBreakAfter(node.object, PROPERTY_MESSAGE); - }, + MemberExpression: function(node) { + if (!node.computed) { + return; + } + checkForBreakAfter(node.object, PROPERTY_MESSAGE); + }, - "TaggedTemplateExpression": function(node) { - if (node.tag.loc.end.line === node.quasi.loc.start.line) { - return; - } - context.report(node, node.loc.start, TAGGED_TEMPLATE_MESSAGE); - }, + TaggedTemplateExpression: function(node) { + if (node.tag.loc.end.line === node.quasi.loc.start.line) { + return; + } + context.report(node, node.loc.start, TAGGED_TEMPLATE_MESSAGE); + }, - "CallExpression": function(node) { - if (node.arguments.length === 0) { - return; + CallExpression: function(node) { + if (node.arguments.length === 0) { + return; + } + checkForBreakAfter(node.callee, FUNCTION_MESSAGE); } - checkForBreakAfter(node.callee, FUNCTION_MESSAGE); - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-unmodified-loop-condition.js b/tools/eslint/lib/rules/no-unmodified-loop-condition.js index 47a0e3885a..ed49b5996e 100644 --- a/tools/eslint/lib/rules/no-unmodified-loop-condition.js +++ b/tools/eslint/lib/rules/no-unmodified-loop-condition.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to disallow use of unmodified expressions in loop conditions * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -245,115 +243,125 @@ function updateModifiedFlag(conditions, modifiers) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var groupMap = null; - - /** - * Reports a given condition info. - * - * @param {LoopConditionInfo} condition - A loop condition info to report. - * @returns {void} - */ - function report(condition) { - var node = condition.reference.identifier; - - context.report({ - node: node, - message: "'{{name}}' is not modified in this loop.", - data: node - }); - } +module.exports = { + meta: { + docs: { + description: "disallow unmodified loop conditions", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + var groupMap = null; + + /** + * Reports a given condition info. + * + * @param {LoopConditionInfo} condition - A loop condition info to report. + * @returns {void} + */ + function report(condition) { + var node = condition.reference.identifier; + + context.report({ + node: node, + message: "'{{name}}' is not modified in this loop.", + data: node + }); + } - /** - * Registers given conditions to the group the condition belongs to. - * - * @param {LoopConditionInfo[]} conditions - A loop condition info to - * register. - * @returns {void} - */ - function registerConditionsToGroup(conditions) { - for (var i = 0; i < conditions.length; ++i) { - var condition = conditions[i]; - - if (condition.group) { - var group = groupMap.get(condition.group); - - if (!group) { - group = []; - groupMap.set(condition.group, group); + /** + * Registers given conditions to the group the condition belongs to. + * + * @param {LoopConditionInfo[]} conditions - A loop condition info to + * register. + * @returns {void} + */ + function registerConditionsToGroup(conditions) { + for (var i = 0; i < conditions.length; ++i) { + var condition = conditions[i]; + + if (condition.group) { + var group = groupMap.get(condition.group); + + if (!group) { + group = []; + groupMap.set(condition.group, group); + } + group.push(condition); } - group.push(condition); } } - } - /** - * Reports references which are inside of unmodified groups. - * - * @param {LoopConditionInfo[]} conditions - A loop condition info to report. - * @returns {void} - */ - function checkConditionsInGroup(conditions) { - if (conditions.every(isUnmodified)) { - conditions.forEach(report); + /** + * Reports references which are inside of unmodified groups. + * + * @param {LoopConditionInfo[]} conditions - A loop condition info to report. + * @returns {void} + */ + function checkConditionsInGroup(conditions) { + if (conditions.every(isUnmodified)) { + conditions.forEach(report); + } } - } - /** - * Finds unmodified references which are inside of a loop condition. - * Then reports the references which are outside of groups. - * - * @param {escope.Variable} variable - A variable to report. - * @returns {void} - */ - function checkReferences(variable) { - - // Gets references that exist in loop conditions. - var conditions = variable - .references - .map(toLoopCondition) - .filter(Boolean); - - if (conditions.length === 0) { - return; - } + /** + * Finds unmodified references which are inside of a loop condition. + * Then reports the references which are outside of groups. + * + * @param {escope.Variable} variable - A variable to report. + * @returns {void} + */ + function checkReferences(variable) { + + // Gets references that exist in loop conditions. + var conditions = variable + .references + .map(toLoopCondition) + .filter(Boolean); + + if (conditions.length === 0) { + return; + } - // Registers the conditions to belonging groups. - registerConditionsToGroup(conditions); + // Registers the conditions to belonging groups. + registerConditionsToGroup(conditions); - // Check the conditions are modified. - var modifiers = variable.references.filter(isWriteReference); + // Check the conditions are modified. + var modifiers = variable.references.filter(isWriteReference); - if (modifiers.length > 0) { - updateModifiedFlag(conditions, modifiers); + if (modifiers.length > 0) { + updateModifiedFlag(conditions, modifiers); + } + + /* + * Reports the conditions which are not belonging to groups. + * Others will be reported after all variables are done. + */ + conditions + .filter(isUnmodifiedAndNotBelongToGroup) + .forEach(report); } - /* - * Reports the conditions which are not belonging to groups. - * Others will be reported after all variables are done. - */ - conditions - .filter(isUnmodifiedAndNotBelongToGroup) - .forEach(report); - } + return { + "Program:exit": function() { + var queue = [context.getScope()]; - return { - "Program:exit": function() { - var queue = [context.getScope()]; + groupMap = new Map(); - groupMap = new Map(); + var scope; - var scope; + while ((scope = queue.pop())) { + pushAll(queue, scope.childScopes); + scope.variables.forEach(checkReferences); + } - while ((scope = queue.pop())) { - pushAll(queue, scope.childScopes); - scope.variables.forEach(checkReferences); + groupMap.forEach(checkConditionsInGroup); + groupMap = null; } - - groupMap.forEach(checkConditionsInGroup); - groupMap = null; - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-unneeded-ternary.js b/tools/eslint/lib/rules/no-unneeded-ternary.js index fcbff265f9..1c344a42e6 100644 --- a/tools/eslint/lib/rules/no-unneeded-ternary.js +++ b/tools/eslint/lib/rules/no-unneeded-ternary.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag no-unneeded-ternary * @author Gyandeep Singh - * @copyright 2015 Gyandeep Singh. All rights reserved. - * @copyright 2015 Michael Ficarra. All rights reserved. */ "use strict"; @@ -11,52 +9,62 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0] || {}; - var defaultAssignment = options.defaultAssignment !== false; - - /** - * Test if the node is a boolean literal - * @param {ASTNode} node - The node to report. - * @returns {boolean} True if the its a boolean literal - * @private - */ - function isBooleanLiteral(node) { - return node.type === "Literal" && typeof node.value === "boolean"; - } +module.exports = { + meta: { + docs: { + description: "disallow ternary operators when simpler alternatives exist", + category: "Stylistic Issues", + recommended: false + }, - /** - * Test if the node matches the pattern id ? id : expression - * @param {ASTNode} node - The ConditionalExpression to check. - * @returns {boolean} True if the pattern is matched, and false otherwise - * @private - */ - function matchesDefaultAssignment(node) { - return node.test.type === "Identifier" && - node.consequent.type === "Identifier" && - node.test.name === node.consequent.name; - } + schema: [ + { + type: "object", + properties: { + defaultAssignment: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - return { + create: function(context) { + var options = context.options[0] || {}; + var defaultAssignment = options.defaultAssignment !== false; - "ConditionalExpression": function(node) { - if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) { - context.report(node, node.consequent.loc.start, "Unnecessary use of boolean literals in conditional expression"); - } else if (!defaultAssignment && matchesDefaultAssignment(node)) { - context.report(node, node.consequent.loc.start, "Unnecessary use of conditional expression for default assignment"); - } + /** + * Test if the node is a boolean literal + * @param {ASTNode} node - The node to report. + * @returns {boolean} True if the its a boolean literal + * @private + */ + function isBooleanLiteral(node) { + return node.type === "Literal" && typeof node.value === "boolean"; } - }; -}; -module.exports.schema = [ - { - "type": "object", - "properties": { - "defaultAssignment": { - "type": "boolean" + /** + * Test if the node matches the pattern id ? id : expression + * @param {ASTNode} node - The ConditionalExpression to check. + * @returns {boolean} True if the pattern is matched, and false otherwise + * @private + */ + function matchesDefaultAssignment(node) { + return node.test.type === "Identifier" && + node.consequent.type === "Identifier" && + node.test.name === node.consequent.name; + } + + return { + + ConditionalExpression: function(node) { + if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) { + context.report(node, node.consequent.loc.start, "Unnecessary use of boolean literals in conditional expression"); + } else if (!defaultAssignment && matchesDefaultAssignment(node)) { + context.report(node, node.consequent.loc.start, "Unnecessary use of conditional expression for default assignment"); + } } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/no-unreachable.js b/tools/eslint/lib/rules/no-unreachable.js index ce3f4ba01f..c28a6d2f2f 100644 --- a/tools/eslint/lib/rules/no-unreachable.js +++ b/tools/eslint/lib/rules/no-unreachable.js @@ -1,8 +1,6 @@ /** * @fileoverview Checks for unreachable code due to return, throws, break, and continue. * @author Joel Feenstra - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -32,63 +30,73 @@ function isUnreachable(segment) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var currentCodePath = null; +module.exports = { + meta: { + docs: { + description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + var currentCodePath = null; - /** - * Reports a given node if it's unreachable. - * @param {ASTNode} node - A statement node to report. - * @returns {void} - */ - function reportIfUnreachable(node) { - if (currentCodePath.currentSegments.every(isUnreachable)) { - context.report({message: "Unreachable code.", node: node}); + /** + * Reports a given node if it's unreachable. + * @param {ASTNode} node - A statement node to report. + * @returns {void} + */ + function reportIfUnreachable(node) { + if (currentCodePath.currentSegments.every(isUnreachable)) { + context.report({message: "Unreachable code.", node: node}); + } } - } - return { + return { - // Manages the current code path. - "onCodePathStart": function(codePath) { - currentCodePath = codePath; - }, + // Manages the current code path. + onCodePathStart: function(codePath) { + currentCodePath = codePath; + }, - "onCodePathEnd": function() { - currentCodePath = currentCodePath.upper; - }, + onCodePathEnd: function() { + currentCodePath = currentCodePath.upper; + }, - // Registers for all statement nodes (excludes FunctionDeclaration). - BlockStatement: reportIfUnreachable, - BreakStatement: reportIfUnreachable, - ClassDeclaration: reportIfUnreachable, - ContinueStatement: reportIfUnreachable, - DebuggerStatement: reportIfUnreachable, - DoWhileStatement: reportIfUnreachable, - EmptyStatement: reportIfUnreachable, - ExpressionStatement: reportIfUnreachable, - ForInStatement: reportIfUnreachable, - ForOfStatement: reportIfUnreachable, - ForStatement: reportIfUnreachable, - IfStatement: reportIfUnreachable, - ImportDeclaration: reportIfUnreachable, - LabeledStatement: reportIfUnreachable, - ReturnStatement: reportIfUnreachable, - SwitchStatement: reportIfUnreachable, - ThrowStatement: reportIfUnreachable, - TryStatement: reportIfUnreachable, + // Registers for all statement nodes (excludes FunctionDeclaration). + BlockStatement: reportIfUnreachable, + BreakStatement: reportIfUnreachable, + ClassDeclaration: reportIfUnreachable, + ContinueStatement: reportIfUnreachable, + DebuggerStatement: reportIfUnreachable, + DoWhileStatement: reportIfUnreachable, + EmptyStatement: reportIfUnreachable, + ExpressionStatement: reportIfUnreachable, + ForInStatement: reportIfUnreachable, + ForOfStatement: reportIfUnreachable, + ForStatement: reportIfUnreachable, + IfStatement: reportIfUnreachable, + ImportDeclaration: reportIfUnreachable, + LabeledStatement: reportIfUnreachable, + ReturnStatement: reportIfUnreachable, + SwitchStatement: reportIfUnreachable, + ThrowStatement: reportIfUnreachable, + TryStatement: reportIfUnreachable, - VariableDeclaration: function(node) { - if (node.kind !== "var" || node.declarations.some(isInitialized)) { - reportIfUnreachable(node); - } - }, + VariableDeclaration: function(node) { + if (node.kind !== "var" || node.declarations.some(isInitialized)) { + reportIfUnreachable(node); + } + }, - WhileStatement: reportIfUnreachable, - WithStatement: reportIfUnreachable, - ExportNamedDeclaration: reportIfUnreachable, - ExportDefaultDeclaration: reportIfUnreachable, - ExportAllDeclaration: reportIfUnreachable - }; + WhileStatement: reportIfUnreachable, + WithStatement: reportIfUnreachable, + ExportNamedDeclaration: reportIfUnreachable, + ExportDefaultDeclaration: reportIfUnreachable, + ExportAllDeclaration: reportIfUnreachable + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-unsafe-finally.js b/tools/eslint/lib/rules/no-unsafe-finally.js new file mode 100644 index 0000000000..55ea2971f8 --- /dev/null +++ b/tools/eslint/lib/rules/no-unsafe-finally.js @@ -0,0 +1,78 @@ +/** + * @fileoverview Rule to flag unsafe statements in finally block + * @author Onur Temizkan + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +var SENTINEL_NODE_TYPE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: "disallow control flow statements in finally blocks", + category: "Possible Errors", + recommended: false + } + }, + create: function(context) { + + /** + * Checks if the node is the finalizer of a TryStatement + * + * @param {ASTNode} node - node to check. + * @returns {Boolean} - true if the node is the finalizer of a TryStatement + */ + function isFinallyBlock(node) { + return node.parent.type === "TryStatement" && node.parent.finalizer === node; + } + + /** + * Climbs up the tree if the node is not a sentinel node + * + * @param {ASTNode} node - node to check. + * @returns {Boolean} - return whether the node is a finally block or a sentinel node + */ + function isInFinallyBlock(node) { + while (node && !SENTINEL_NODE_TYPE.test(node.type)) { + if (isFinallyBlock(node)) { + return true; + } + node = node.parent; + } + return false; + } + + /** + * Checks whether the possibly-unsafe statement is inside a finally block. + * + * @param {ASTNode} node - node to check. + * @returns {void} + */ + function check(node) { + if (isInFinallyBlock(node)) { + context.report({ + message: "Unsafe usage of " + node.type, + node: node, + line: node.loc.line, + column: node.loc.column + }); + } + } + + return { + ReturnStatement: check, + ThrowStatement: check, + BreakStatement: check, + ContinueStatement: check + }; + } +}; diff --git a/tools/eslint/lib/rules/no-unused-expressions.js b/tools/eslint/lib/rules/no-unused-expressions.js index 3da55602f0..9438268ab2 100644 --- a/tools/eslint/lib/rules/no-unused-expressions.js +++ b/tools/eslint/lib/rules/no-unused-expressions.js @@ -1,7 +1,6 @@ /** * @fileoverview Flag expressions in statement position that do not side effect * @author Michael Ficarra - * @copyright 2013 Michael Ficarra. All rights reserved. */ "use strict"; @@ -9,100 +8,110 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var config = context.options[0] || {}, - allowShortCircuit = config.allowShortCircuit || false, - allowTernary = config.allowTernary || false; +module.exports = { + meta: { + docs: { + description: "disallow unused expressions", + category: "Best Practices", + recommended: false + }, - /** - * @param {ASTNode} node - any node - * @returns {boolean} whether the given node structurally represents a directive - */ - function looksLikeDirective(node) { - return node.type === "ExpressionStatement" && - node.expression.type === "Literal" && typeof node.expression.value === "string"; - } + schema: [ + { + type: "object", + properties: { + allowShortCircuit: { + type: "boolean" + }, + allowTernary: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var config = context.options[0] || {}, + allowShortCircuit = config.allowShortCircuit || false, + allowTernary = config.allowTernary || false; + + /** + * @param {ASTNode} node - any node + * @returns {boolean} whether the given node structurally represents a directive + */ + function looksLikeDirective(node) { + return node.type === "ExpressionStatement" && + node.expression.type === "Literal" && typeof node.expression.value === "string"; + } - /** - * @param {Function} predicate - ([a] -> Boolean) the function used to make the determination - * @param {a[]} list - the input list - * @returns {a[]} the leading sequence of members in the given list that pass the given predicate - */ - function takeWhile(predicate, list) { - for (var i = 0, l = list.length; i < l; ++i) { - if (!predicate(list[i])) { - break; + /** + * @param {Function} predicate - ([a] -> Boolean) the function used to make the determination + * @param {a[]} list - the input list + * @returns {a[]} the leading sequence of members in the given list that pass the given predicate + */ + function takeWhile(predicate, list) { + for (var i = 0, l = list.length; i < l; ++i) { + if (!predicate(list[i])) { + break; + } } + return [].slice.call(list, 0, i); } - return [].slice.call(list, 0, i); - } - /** - * @param {ASTNode} node - a Program or BlockStatement node - * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body - */ - function directives(node) { - return takeWhile(looksLikeDirective, node.body); - } + /** + * @param {ASTNode} node - a Program or BlockStatement node + * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body + */ + function directives(node) { + return takeWhile(looksLikeDirective, node.body); + } - /** - * @param {ASTNode} node - any node - * @param {ASTNode[]} ancestors - the given node's ancestors - * @returns {boolean} whether the given node is considered a directive in its current position - */ - function isDirective(node, ancestors) { - var parent = ancestors[ancestors.length - 1], - grandparent = ancestors[ancestors.length - 2]; + /** + * @param {ASTNode} node - any node + * @param {ASTNode[]} ancestors - the given node's ancestors + * @returns {boolean} whether the given node is considered a directive in its current position + */ + function isDirective(node, ancestors) { + var parent = ancestors[ancestors.length - 1], + grandparent = ancestors[ancestors.length - 2]; - return (parent.type === "Program" || parent.type === "BlockStatement" && - (/Function/.test(grandparent.type))) && - directives(parent).indexOf(node) >= 0; - } + return (parent.type === "Program" || parent.type === "BlockStatement" && + (/Function/.test(grandparent.type))) && + directives(parent).indexOf(node) >= 0; + } - /** - * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags. - * @param {ASTNode} node - any node - * @returns {boolean} whether the given node is a valid expression - */ - function isValidExpression(node) { - if (allowTernary) { + /** + * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags. + * @param {ASTNode} node - any node + * @returns {boolean} whether the given node is a valid expression + */ + function isValidExpression(node) { + if (allowTernary) { - // Recursive check for ternary and logical expressions - if (node.type === "ConditionalExpression") { - return isValidExpression(node.consequent) && isValidExpression(node.alternate); + // Recursive check for ternary and logical expressions + if (node.type === "ConditionalExpression") { + return isValidExpression(node.consequent) && isValidExpression(node.alternate); + } } - } - if (allowShortCircuit) { - if (node.type === "LogicalExpression") { - return isValidExpression(node.right); + if (allowShortCircuit) { + if (node.type === "LogicalExpression") { + return isValidExpression(node.right); + } } - } - return /^(?:Assignment|Call|New|Update|Yield)Expression$/.test(node.type) || - (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0); - } - - return { - "ExpressionStatement": function(node) { - if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) { - context.report(node, "Expected an assignment or function call and instead saw an expression."); - } + return /^(?:Assignment|Call|New|Update|Yield)Expression$/.test(node.type) || + (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0); } - }; -}; - -module.exports.schema = [ - { - "type": "object", - "properties": { - "allowShortCircuit": { - "type": "boolean" - }, - "allowTernary": { - "type": "boolean" + return { + ExpressionStatement: function(node) { + if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) { + context.report(node, "Expected an assignment or function call and instead saw an expression."); + } } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/no-unused-labels.js b/tools/eslint/lib/rules/no-unused-labels.js index e53b333f2e..77713fc408 100644 --- a/tools/eslint/lib/rules/no-unused-labels.js +++ b/tools/eslint/lib/rules/no-unused-labels.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to disallow unused labels. * @author Toru Nagashima - * @copyright 2016 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -11,72 +9,82 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var scopeInfo = null; +module.exports = { + meta: { + docs: { + description: "disallow unused labels", + category: "Best Practices", + recommended: true + }, - /** - * Adds a scope info to the stack. - * - * @param {ASTNode} node - A node to add. This is a LabeledStatement. - * @returns {void} - */ - function enterLabeledScope(node) { - scopeInfo = { - label: node.label.name, - used: false, - upper: scopeInfo - }; - } + schema: [] + }, + + create: function(context) { + var scopeInfo = null; - /** - * Removes the top of the stack. - * At the same time, this reports the label if it's never used. - * - * @param {ASTNode} node - A node to report. This is a LabeledStatement. - * @returns {void} - */ - function exitLabeledScope(node) { - if (!scopeInfo.used) { - context.report({ - node: node.label, - message: "'{{name}}:' is defined but never used.", - data: node.label - }); + /** + * Adds a scope info to the stack. + * + * @param {ASTNode} node - A node to add. This is a LabeledStatement. + * @returns {void} + */ + function enterLabeledScope(node) { + scopeInfo = { + label: node.label.name, + used: false, + upper: scopeInfo + }; } - scopeInfo = scopeInfo.upper; - } + /** + * Removes the top of the stack. + * At the same time, this reports the label if it's never used. + * + * @param {ASTNode} node - A node to report. This is a LabeledStatement. + * @returns {void} + */ + function exitLabeledScope(node) { + if (!scopeInfo.used) { + context.report({ + node: node.label, + message: "'{{name}}:' is defined but never used.", + data: node.label + }); + } - /** - * Marks the label of a given node as used. - * - * @param {ASTNode} node - A node to mark. This is a BreakStatement or - * ContinueStatement. - * @returns {void} - */ - function markAsUsed(node) { - if (!node.label) { - return; + scopeInfo = scopeInfo.upper; } - var label = node.label.name; - var info = scopeInfo; + /** + * Marks the label of a given node as used. + * + * @param {ASTNode} node - A node to mark. This is a BreakStatement or + * ContinueStatement. + * @returns {void} + */ + function markAsUsed(node) { + if (!node.label) { + return; + } - while (info) { - if (info.label === label) { - info.used = true; - break; + var label = node.label.name; + var info = scopeInfo; + + while (info) { + if (info.label === label) { + info.used = true; + break; + } + info = info.upper; } - info = info.upper; } - } - return { - "LabeledStatement": enterLabeledScope, - "LabeledStatement:exit": exitLabeledScope, - "BreakStatement": markAsUsed, - "ContinueStatement": markAsUsed - }; + return { + LabeledStatement: enterLabeledScope, + "LabeledStatement:exit": exitLabeledScope, + BreakStatement: markAsUsed, + ContinueStatement: markAsUsed + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-unused-vars.js b/tools/eslint/lib/rules/no-unused-vars.js index fa889d1975..89d43c7bfd 100644 --- a/tools/eslint/lib/rules/no-unused-vars.js +++ b/tools/eslint/lib/rules/no-unused-vars.js @@ -15,318 +15,328 @@ var lodash = require("lodash"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow unused variables", + category: "Variables", + recommended: true + }, + + schema: [ + { + oneOf: [ + { + enum: ["all", "local"] + }, + { + type: "object", + properties: { + vars: { + enum: ["all", "local"] + }, + varsIgnorePattern: { + type: "string" + }, + args: { + enum: ["all", "after-used", "none"] + }, + argsIgnorePattern: { + type: "string" + }, + caughtErrors: { + enum: ["all", "none"] + }, + caughtErrorsIgnorePattern: { + type: "string" + } + } + } + ] + } + ] + }, - var MESSAGE = "'{{name}}' is defined but never used"; + create: function(context) { - var config = { - vars: "all", - args: "after-used", - caughtErrors: "none" - }; + var MESSAGE = "'{{name}}' is defined but never used"; - var firstOption = context.options[0]; + var config = { + vars: "all", + args: "after-used", + caughtErrors: "none" + }; - if (firstOption) { - if (typeof firstOption === "string") { - config.vars = firstOption; - } else { - config.vars = firstOption.vars || config.vars; - config.args = firstOption.args || config.args; - config.caughtErrors = firstOption.caughtErrors || config.caughtErrors; + var firstOption = context.options[0]; - if (firstOption.varsIgnorePattern) { - config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern); - } + if (firstOption) { + if (typeof firstOption === "string") { + config.vars = firstOption; + } else { + config.vars = firstOption.vars || config.vars; + config.args = firstOption.args || config.args; + config.caughtErrors = firstOption.caughtErrors || config.caughtErrors; - if (firstOption.argsIgnorePattern) { - config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern); - } + if (firstOption.varsIgnorePattern) { + config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern); + } + + if (firstOption.argsIgnorePattern) { + config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern); + } - if (firstOption.caughtErrorsIgnorePattern) { - config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern); + if (firstOption.caughtErrorsIgnorePattern) { + config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern); + } } } - } - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determines if a given variable is being exported from a module. + * @param {Variable} variable - EScope variable object. + * @returns {boolean} True if the variable is exported, false if not. + * @private + */ + function isExported(variable) { - /** - * Determines if a given variable is being exported from a module. - * @param {Variable} variable - EScope variable object. - * @returns {boolean} True if the variable is exported, false if not. - * @private - */ - function isExported(variable) { + var definition = variable.defs[0]; - var definition = variable.defs[0]; + if (definition) { - if (definition) { + var node = definition.node; - var node = definition.node; + if (node.type === "VariableDeclarator") { + node = node.parent; + } else if (definition.type === "Parameter") { + return false; + } - if (node.type === "VariableDeclarator") { - node = node.parent; - } else if (definition.type === "Parameter") { + return node.parent.type.indexOf("Export") === 0; + } else { return false; } - - return node.parent.type.indexOf("Export") === 0; - } else { - return false; } - } - /** - * Determines if a reference is a read operation. - * @param {Reference} ref - An escope Reference - * @returns {Boolean} whether the given reference represents a read operation - * @private - */ - function isReadRef(ref) { - return ref.isRead(); - } - - /** - * Determine if an identifier is referencing an enclosing function name. - * @param {Reference} ref - The reference to check. - * @param {ASTNode[]} nodes - The candidate function nodes. - * @returns {boolean} True if it's a self-reference, false if not. - * @private - */ - function isSelfReference(ref, nodes) { - var scope = ref.from; - - while (scope) { - if (nodes.indexOf(scope.block) >= 0) { - return true; - } - - scope = scope.upper; + /** + * Determines if a reference is a read operation. + * @param {Reference} ref - An escope Reference + * @returns {Boolean} whether the given reference represents a read operation + * @private + */ + function isReadRef(ref) { + return ref.isRead(); } - return false; - } - - /** - * Determines if the variable is used. - * @param {Variable} variable - The variable to check. - * @param {Reference[]} references - The variable references to check. - * @returns {boolean} True if the variable is used - */ - function isUsedVariable(variable) { - var functionNodes = variable.defs.filter(function(def) { - return def.type === "FunctionName"; - }).map(function(def) { - return def.node; - }), - isFunctionDefinition = functionNodes.length > 0; - - return variable.references.some(function(ref) { - return isReadRef(ref) && !(isFunctionDefinition && isSelfReference(ref, functionNodes)); - }); - } - - /** - * Gets an array of variables without read references. - * @param {Scope} scope - an escope Scope object. - * @param {Variable[]} unusedVars - an array that saving result. - * @returns {Variable[]} unused variables of the scope and descendant scopes. - * @private - */ - function collectUnusedVariables(scope, unusedVars) { - var variables = scope.variables; - var childScopes = scope.childScopes; - var i, l; - - if (scope.type !== "TDZ" && (scope.type !== "global" || config.vars === "all")) { - for (i = 0, l = variables.length; i < l; ++i) { - var variable = variables[i]; - - // skip a variable of class itself name in the class scope - if (scope.type === "class" && scope.block.id === variable.identifiers[0]) { - continue; + /** + * Determine if an identifier is referencing an enclosing function name. + * @param {Reference} ref - The reference to check. + * @param {ASTNode[]} nodes - The candidate function nodes. + * @returns {boolean} True if it's a self-reference, false if not. + * @private + */ + function isSelfReference(ref, nodes) { + var scope = ref.from; + + while (scope) { + if (nodes.indexOf(scope.block) >= 0) { + return true; } - // skip function expression names and variables marked with markVariableAsUsed() - if (scope.functionExpressionScope || variable.eslintUsed) { - continue; - } + scope = scope.upper; + } - // skip implicit "arguments" variable - if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) { - continue; - } + return false; + } - // explicit global variables don't have definitions. - var def = variable.defs[0]; + /** + * Determines if the variable is used. + * @param {Variable} variable - The variable to check. + * @param {Reference[]} references - The variable references to check. + * @returns {boolean} True if the variable is used + */ + function isUsedVariable(variable) { + var functionNodes = variable.defs.filter(function(def) { + return def.type === "FunctionName"; + }).map(function(def) { + return def.node; + }), + isFunctionDefinition = functionNodes.length > 0; + + return variable.references.some(function(ref) { + return isReadRef(ref) && !(isFunctionDefinition && isSelfReference(ref, functionNodes)); + }); + } - if (def) { - var type = def.type; + /** + * Gets an array of variables without read references. + * @param {Scope} scope - an escope Scope object. + * @param {Variable[]} unusedVars - an array that saving result. + * @returns {Variable[]} unused variables of the scope and descendant scopes. + * @private + */ + function collectUnusedVariables(scope, unusedVars) { + var variables = scope.variables; + var childScopes = scope.childScopes; + var i, l; + + if (scope.type !== "TDZ" && (scope.type !== "global" || config.vars === "all")) { + for (i = 0, l = variables.length; i < l; ++i) { + var variable = variables[i]; + + // skip a variable of class itself name in the class scope + if (scope.type === "class" && scope.block.id === variable.identifiers[0]) { + continue; + } - // skip catch variables - if (type === "CatchClause") { - if (config.caughtErrors === "none") { - continue; - } + // skip function expression names and variables marked with markVariableAsUsed() + if (scope.functionExpressionScope || variable.eslintUsed) { + continue; + } - // skip ignored parameters - if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) { - continue; - } + // skip implicit "arguments" variable + if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) { + continue; } - if (type === "Parameter") { + // explicit global variables don't have definitions. + var def = variable.defs[0]; - // skip any setter argument - if (def.node.parent.type === "Property" && def.node.parent.kind === "set") { - continue; - } + if (def) { + var type = def.type; - // if "args" option is "none", skip any parameter - if (config.args === "none") { - continue; - } + // skip catch variables + if (type === "CatchClause") { + if (config.caughtErrors === "none") { + continue; + } - // skip ignored parameters - if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) { - continue; + // skip ignored parameters + if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) { + continue; + } } - // if "args" option is "after-used", skip all but the last parameter - if (config.args === "after-used" && def.index < def.node.params.length - 1) { - continue; + if (type === "Parameter") { + + // skip any setter argument + if (def.node.parent.type === "Property" && def.node.parent.kind === "set") { + continue; + } + + // if "args" option is "none", skip any parameter + if (config.args === "none") { + continue; + } + + // skip ignored parameters + if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) { + continue; + } + + // if "args" option is "after-used", skip all but the last parameter + if (config.args === "after-used" && def.index < def.node.params.length - 1) { + continue; + } + } else { + + // skip ignored variables + if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) { + continue; + } } - } else { + } - // skip ignored variables - if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) { - continue; - } + if (!isUsedVariable(variable) && !isExported(variable)) { + unusedVars.push(variable); } } + } - if (!isUsedVariable(variable) && !isExported(variable)) { - unusedVars.push(variable); - } + for (i = 0, l = childScopes.length; i < l; ++i) { + collectUnusedVariables(childScopes[i], unusedVars); } - } - for (i = 0, l = childScopes.length; i < l; ++i) { - collectUnusedVariables(childScopes[i], unusedVars); + return unusedVars; } - return unusedVars; - } - - /** - * Gets the index of a given variable name in a given comment. - * @param {escope.Variable} variable - A variable to get. - * @param {ASTNode} comment - A comment node which includes the variable name. - * @returns {number} The index of the variable name's location. - */ - function getColumnInComment(variable, comment) { - var namePattern = new RegExp("[\\s,]" + lodash.escapeRegExp(variable.name) + "(?:$|[\\s,:])", "g"); - - // To ignore the first text "global". - namePattern.lastIndex = comment.value.indexOf("global") + 6; + /** + * Gets the index of a given variable name in a given comment. + * @param {escope.Variable} variable - A variable to get. + * @param {ASTNode} comment - A comment node which includes the variable name. + * @returns {number} The index of the variable name's location. + */ + function getColumnInComment(variable, comment) { + var namePattern = new RegExp("[\\s,]" + lodash.escapeRegExp(variable.name) + "(?:$|[\\s,:])", "g"); - // Search a given variable name. - var match = namePattern.exec(comment.value); + // To ignore the first text "global". + namePattern.lastIndex = comment.value.indexOf("global") + 6; - return match ? match.index + 1 : 0; - } + // Search a given variable name. + var match = namePattern.exec(comment.value); - /** - * Creates the correct location of a given variables. - * The location is at its name string in a `/*global` comment. - * - * @param {escope.Variable} variable - A variable to get its location. - * @returns {{line: number, column: number}} The location object for the variable. - */ - function getLocation(variable) { - var comment = variable.eslintExplicitGlobalComment; - var baseLoc = comment.loc.start; - var column = getColumnInComment(variable, comment); - var prefix = comment.value.slice(0, column); - var lineInComment = (prefix.match(/\n/g) || []).length; - - if (lineInComment > 0) { - column -= 1 + prefix.lastIndexOf("\n"); - } else { - - // 2 is for `/*` - column += baseLoc.column + 2; + return match ? match.index + 1 : 0; } - return { - line: baseLoc.line + lineInComment, - column: column - }; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "Program:exit": function(programNode) { - var unusedVars = collectUnusedVariables(context.getScope(), []); - - for (var i = 0, l = unusedVars.length; i < l; ++i) { - var unusedVar = unusedVars[i]; - - if (unusedVar.eslintExplicitGlobal) { - context.report({ - node: programNode, - loc: getLocation(unusedVar), - message: MESSAGE, - data: unusedVar - }); - } else if (unusedVar.defs.length > 0) { - context.report({ - node: unusedVar.identifiers[0], - message: MESSAGE, - data: unusedVar - }); - } + /** + * Creates the correct location of a given variables. + * The location is at its name string in a `/*global` comment. + * + * @param {escope.Variable} variable - A variable to get its location. + * @returns {{line: number, column: number}} The location object for the variable. + */ + function getLocation(variable) { + var comment = variable.eslintExplicitGlobalComment; + var baseLoc = comment.loc.start; + var column = getColumnInComment(variable, comment); + var prefix = comment.value.slice(0, column); + var lineInComment = (prefix.match(/\n/g) || []).length; + + if (lineInComment > 0) { + column -= 1 + prefix.lastIndexOf("\n"); + } else { + + // 2 is for `/*` + column += baseLoc.column + 2; } + + return { + line: baseLoc.line + lineInComment, + column: column + }; } - }; -}; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- -module.exports.schema = [ - { - "oneOf": [ - { - "enum": ["all", "local"] - }, - { - "type": "object", - "properties": { - "vars": { - "enum": ["all", "local"] - }, - "varsIgnorePattern": { - "type": "string" - }, - "args": { - "enum": ["all", "after-used", "none"] - }, - "argsIgnorePattern": { - "type": "string" - }, - "caughtErrors": { - "enum": ["all", "none"] - }, - "caughtErrorsIgnorePattern": { - "type": "string" + return { + "Program:exit": function(programNode) { + var unusedVars = collectUnusedVariables(context.getScope(), []); + + for (var i = 0, l = unusedVars.length; i < l; ++i) { + var unusedVar = unusedVars[i]; + + if (unusedVar.eslintExplicitGlobal) { + context.report({ + node: programNode, + loc: getLocation(unusedVar), + message: MESSAGE, + data: unusedVar + }); + } else if (unusedVar.defs.length > 0) { + context.report({ + node: unusedVar.identifiers[0], + message: MESSAGE, + data: unusedVar + }); } } } - ] + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/no-use-before-define.js b/tools/eslint/lib/rules/no-use-before-define.js index b8f67144c5..889e709948 100644 --- a/tools/eslint/lib/rules/no-use-before-define.js +++ b/tools/eslint/lib/rules/no-use-before-define.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag use of variables before they are defined * @author Ilya Volodin - * @copyright 2013 Ilya Volodin. All rights reserved. */ "use strict"; @@ -124,114 +123,124 @@ function isInInitializer(variable, reference) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = parseOptions(context.options[0]); +module.exports = { + meta: { + docs: { + description: "disallow the use of variables before they are defined", + category: "Variables", + recommended: false + }, - // Defines a function which checks whether or not a reference is allowed according to the option. - var isAllowed; - - if (options.functions && options.classes) { - isAllowed = alwaysFalse; - } else if (options.functions) { - isAllowed = isOuterClass; - } else if (options.classes) { - isAllowed = isFunction; - } else { - isAllowed = isFunctionOrOuterClass; - } - - /** - * Finds and validates all variables in a given scope. - * @param {Scope} scope The scope object. - * @returns {void} - * @private - */ - function findVariablesInScope(scope) { - scope.references.forEach(function(reference) { - var variable = reference.resolved; - - // Skips when the reference is: - // - initialization's. - // - referring to an undefined variable. - // - referring to a global environment variable (there're no identifiers). - // - located preceded by the variable (except in initializers). - // - allowed by options. - if (reference.init || - !variable || - variable.identifiers.length === 0 || - (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) || - isAllowed(variable, reference) - ) { - return; + schema: [ + { + oneOf: [ + { + enum: ["nofunc"] + }, + { + type: "object", + properties: { + functions: {type: "boolean"}, + classes: {type: "boolean"} + }, + additionalProperties: false + } + ] } + ] + }, + + create: function(context) { + var options = parseOptions(context.options[0]); + + // Defines a function which checks whether or not a reference is allowed according to the option. + var isAllowed; + + if (options.functions && options.classes) { + isAllowed = alwaysFalse; + } else if (options.functions) { + isAllowed = isOuterClass; + } else if (options.classes) { + isAllowed = isFunction; + } else { + isAllowed = isFunctionOrOuterClass; + } - // Reports. - context.report({ - node: reference.identifier, - message: "'{{name}}' was used before it was defined", - data: reference.identifier + /** + * Finds and validates all variables in a given scope. + * @param {Scope} scope The scope object. + * @returns {void} + * @private + */ + function findVariablesInScope(scope) { + scope.references.forEach(function(reference) { + var variable = reference.resolved; + + // Skips when the reference is: + // - initialization's. + // - referring to an undefined variable. + // - referring to a global environment variable (there're no identifiers). + // - located preceded by the variable (except in initializers). + // - allowed by options. + if (reference.init || + !variable || + variable.identifiers.length === 0 || + (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) || + isAllowed(variable, reference) + ) { + return; + } + + // Reports. + context.report({ + node: reference.identifier, + message: "'{{name}}' was used before it was defined", + data: reference.identifier + }); }); - }); - } - - /** - * Validates variables inside of a node's scope. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function findVariables() { - var scope = context.getScope(); - - findVariablesInScope(scope); - } + } - var ruleDefinition = { - "Program:exit": function(node) { - var scope = context.getScope(), - ecmaFeatures = context.parserOptions.ecmaFeatures || {}; + /** + * Validates variables inside of a node's scope. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function findVariables() { + var scope = context.getScope(); findVariablesInScope(scope); - - // both Node.js and Modules have an extra scope - if (ecmaFeatures.globalReturn || node.sourceType === "module") { - findVariablesInScope(scope.childScopes[0]); - } } - }; - if (context.parserOptions.ecmaVersion >= 6) { - ruleDefinition["BlockStatement:exit"] = - ruleDefinition["SwitchStatement:exit"] = findVariables; + var ruleDefinition = { + "Program:exit": function(node) { + var scope = context.getScope(), + ecmaFeatures = context.parserOptions.ecmaFeatures || {}; + + findVariablesInScope(scope); - ruleDefinition["ArrowFunctionExpression:exit"] = function(node) { - if (node.body.type !== "BlockStatement") { - findVariables(node); + // both Node.js and Modules have an extra scope + if (ecmaFeatures.globalReturn || node.sourceType === "module") { + findVariablesInScope(scope.childScopes[0]); + } } }; - } else { - ruleDefinition["FunctionExpression:exit"] = - ruleDefinition["FunctionDeclaration:exit"] = - ruleDefinition["ArrowFunctionExpression:exit"] = findVariables; - } - return ruleDefinition; -}; + if (context.parserOptions.ecmaVersion >= 6) { + ruleDefinition["BlockStatement:exit"] = + ruleDefinition["SwitchStatement:exit"] = findVariables; + + ruleDefinition["ArrowFunctionExpression:exit"] = function(node) { + if (node.body.type !== "BlockStatement") { + findVariables(node); + } + }; + } else { + ruleDefinition["FunctionExpression:exit"] = + ruleDefinition["FunctionDeclaration:exit"] = + ruleDefinition["ArrowFunctionExpression:exit"] = findVariables; + } -module.exports.schema = [ - { - "oneOf": [ - { - "enum": ["nofunc"] - }, - { - "type": "object", - "properties": { - "functions": {"type": "boolean"}, - "classes": {"type": "boolean"} - }, - "additionalProperties": false - } - ] + return ruleDefinition; } -]; +}; diff --git a/tools/eslint/lib/rules/no-useless-call.js b/tools/eslint/lib/rules/no-useless-call.js index fe6afd6d24..eb14f0baf9 100644 --- a/tools/eslint/lib/rules/no-useless-call.js +++ b/tools/eslint/lib/rules/no-useless-call.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to disallow unnecessary `.call()` and `.apply()`. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -72,25 +71,35 @@ function isValidThisArg(expectedThis, thisArg, context) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - return { - "CallExpression": function(node) { - if (!isCallOrNonVariadicApply(node)) { - return; - } +module.exports = { + meta: { + docs: { + description: "disallow unnecessary calls to `.call()` and `.apply()`", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + return { + CallExpression: function(node) { + if (!isCallOrNonVariadicApply(node)) { + return; + } - var applied = node.callee.object; - var expectedThis = (applied.type === "MemberExpression") ? applied.object : null; - var thisArg = node.arguments[0]; + var applied = node.callee.object; + var expectedThis = (applied.type === "MemberExpression") ? applied.object : null; + var thisArg = node.arguments[0]; - if (isValidThisArg(expectedThis, thisArg, context)) { - context.report( - node, - "unnecessary '.{{name}}()'.", - {name: node.callee.property.name}); + if (isValidThisArg(expectedThis, thisArg, context)) { + context.report( + node, + "unnecessary '.{{name}}()'.", + {name: node.callee.property.name}); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-useless-computed-key.js b/tools/eslint/lib/rules/no-useless-computed-key.js new file mode 100644 index 0000000000..0894fb7ca4 --- /dev/null +++ b/tools/eslint/lib/rules/no-useless-computed-key.js @@ -0,0 +1,37 @@ +/** + * @fileoverview Rule to disallow unnecessary computed property keys in object literals + * @author Burak Yigit Kaya + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +var MESSAGE_UNNECESSARY_COMPUTED = "Unnecessarily computed property [{{property}}] found."; + +module.exports = { + meta: { + docs: { + description: "disallow unnecessary computed property keys in object literals", + category: "ECMAScript 6", + recommended: false + } + }, + create: function(context) { + return { + Property: function(node) { + if (!node.computed) { + return; + } + + var key = node.key, + nodeType = typeof key.value; + + if (key.type === "Literal" && (nodeType === "string" || nodeType === "number")) { + context.report(node, MESSAGE_UNNECESSARY_COMPUTED, { property: context.getSource(key) }); + } + } + }; + } +}; diff --git a/tools/eslint/lib/rules/no-useless-concat.js b/tools/eslint/lib/rules/no-useless-concat.js index 96baf51a4e..ce9589d488 100644 --- a/tools/eslint/lib/rules/no-useless-concat.js +++ b/tools/eslint/lib/rules/no-useless-concat.js @@ -1,8 +1,6 @@ /** * @fileoverview disallow unncessary concatenation of template strings * @author Henry Zhu - * @copyright 2015 Henry Zhu. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -57,38 +55,48 @@ function getRight(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - return { - BinaryExpression: function(node) { +module.exports = { + meta: { + docs: { + description: "disallow unnecessary concatenation of literals or template literals", + category: "Best Practices", + recommended: false + }, - // check if not concatenation - if (node.operator !== "+") { - return; - } + schema: [] + }, + + create: function(context) { + return { + BinaryExpression: function(node) { - // account for the `foo + "a" + "b"` case - var left = getLeft(node); - var right = getRight(node); + // check if not concatenation + if (node.operator !== "+") { + return; + } - if (astUtils.isStringLiteral(left) && - astUtils.isStringLiteral(right) && - astUtils.isTokenOnSameLine(left, right) - ) { + // account for the `foo + "a" + "b"` case + var left = getLeft(node); + var right = getRight(node); - // move warning location to operator - var operatorToken = context.getTokenAfter(left); + if (astUtils.isStringLiteral(left) && + astUtils.isStringLiteral(right) && + astUtils.isTokenOnSameLine(left, right) + ) { - while (operatorToken.value !== "+") { - operatorToken = context.getTokenAfter(operatorToken); - } + // move warning location to operator + var operatorToken = context.getTokenAfter(left); - context.report( - node, - operatorToken.loc.start, - "Unexpected string concatenation of literals."); + while (operatorToken.value !== "+") { + operatorToken = context.getTokenAfter(operatorToken); + } + + context.report( + node, + operatorToken.loc.start, + "Unexpected string concatenation of literals."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-useless-constructor.js b/tools/eslint/lib/rules/no-useless-constructor.js index 244f2b458f..b91ab4bcae 100644 --- a/tools/eslint/lib/rules/no-useless-constructor.js +++ b/tools/eslint/lib/rules/no-useless-constructor.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag the use of redundant constructors in classes. * @author Alberto Rodríguez - * @copyright 2015 Alberto Rodríguez. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -142,33 +140,43 @@ function isRedundantSuperCall(body, ctorParams) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Checks whether a node is a redundant constructor - * @param {ASTNode} node - node to check - * @returns {void} - */ - function checkForConstructor(node) { - if (node.kind !== "constructor") { - return; +module.exports = { + meta: { + docs: { + description: "disallow unnecessary constructors", + category: "ECMAScript 6", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + /** + * Checks whether a node is a redundant constructor + * @param {ASTNode} node - node to check + * @returns {void} + */ + function checkForConstructor(node) { + if (node.kind !== "constructor") { + return; + } + + var body = node.value.body.body; + var ctorParams = node.value.params; + var superClass = node.parent.parent.superClass; + + if (superClass ? isRedundantSuperCall(body, ctorParams) : (body.length === 0)) { + context.report({ + node: node, + message: "Useless constructor." + }); + } } - var body = node.value.body.body; - var ctorParams = node.value.params; - var superClass = node.parent.parent.superClass; - - if (superClass ? isRedundantSuperCall(body, ctorParams) : (body.length === 0)) { - context.report({ - node: node, - message: "Useless constructor." - }); - } + return { + MethodDefinition: checkForConstructor + }; } - - return { - "MethodDefinition": checkForConstructor - }; }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-useless-escape.js b/tools/eslint/lib/rules/no-useless-escape.js index a6f5daa71c..036831fc39 100644 --- a/tools/eslint/lib/rules/no-useless-escape.js +++ b/tools/eslint/lib/rules/no-useless-escape.js @@ -1,7 +1,6 @@ /** * @fileoverview Look for useless escapes in strings and regexes * @author Onur Temizkan - * @copyright 2016 Onur Temizkan. All rights reserved. */ "use strict"; @@ -58,51 +57,74 @@ var VALID_REGEX_ESCAPES = [ "u" ]; -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow unnecessary escape characters", + category: "Best Practices", + recommended: false + }, - /** - * Checks if the escape character in given slice is unnecessary. - * - * @private - * @param {string} elm - string slice to validate. - * @param {ASTNode} node - node to validate. - * @returns {void} - * @this escapes_quote_node - */ - function validate(elm) { - var escapeNotFound = this.escapes.indexOf(elm[1]) === -1; - var isQuoteEscape = elm[1] === this.node.raw[0]; + schema: [] + }, - if (escapeNotFound && !isQuoteEscape) { - context.report(this.node, "Unnecessary escape character: " + elm); - } - } + create: function(context) { - /** - * Checks if a node has an escape. - * - * @param {ASTNode} node - node to check. - * @returns {void} - */ - function check(node) { - var nodeEscapes; + /** + * Checks if the escape character in given slice is unnecessary. + * + * @private + * @param {string[]} escapes - list of valid escapes + * @param {ASTNode} node - node to validate. + * @param {string} elm - string slice to validate. + * @returns {void} + */ + function validate(escapes, node, elm) { + var escapeNotFound = escapes.indexOf(elm[0][1]) === -1; + var isQuoteEscape = elm[0][1] === node.raw[0]; - if (typeof node.value === "string") { - nodeEscapes = VALID_STRING_ESCAPES; - } else if (node.regex) { - nodeEscapes = VALID_REGEX_ESCAPES; - } else { - return; + if (escapeNotFound && !isQuoteEscape) { + context.report({ + node: node, + loc: { + line: node.loc.start.line, + column: node.loc.start.column + elm.index + }, + message: "Unnecessary escape character: " + elm[0] + }); + } } - (node.raw.match(/\\[^\d]/g) || []).forEach(validate, { - "escapes": nodeEscapes, - "node": node - }); + /** + * Checks if a node has an escape. + * + * @param {ASTNode} node - node to check. + * @returns {void} + */ + function check(node) { + var nodeEscapes, match; + var pattern = /\\[^\d]/g; + + if (typeof node.value === "string") { + + // JSXAttribute doesn't have any escape sequence: https://facebook.github.io/jsx/ + if (node.parent.type === "JSXAttribute") { + return; + } + + nodeEscapes = VALID_STRING_ESCAPES; + } else if (node.regex) { + nodeEscapes = VALID_REGEX_ESCAPES; + } else { + return; + } + + while ((match = pattern.exec(node.raw))) { + validate(nodeEscapes, node, match); + } + } + return { + Literal: check + }; } - return { - "Literal": check - }; }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-var.js b/tools/eslint/lib/rules/no-var.js index 05cb6e99ec..2e9948a330 100644 --- a/tools/eslint/lib/rules/no-var.js +++ b/tools/eslint/lib/rules/no-var.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to check for the usage of var. * @author Jamund Ferguson - * @copyright 2014 Jamund Ferguson. All rights reserved. */ "use strict"; @@ -10,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require `let` or `const` instead of `var`", + category: "ECMAScript 6", + recommended: false + }, - return { - "VariableDeclaration": function(node) { - if (node.kind === "var") { - context.report(node, "Unexpected var, use let or const instead."); + schema: [] + }, + + create: function(context) { + + return { + VariableDeclaration: function(node) { + if (node.kind === "var") { + context.report(node, "Unexpected var, use let or const instead."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-void.js b/tools/eslint/lib/rules/no-void.js index 858304dd1d..4adae20b37 100644 --- a/tools/eslint/lib/rules/no-void.js +++ b/tools/eslint/lib/rules/no-void.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to disallow use of void operator. * @author Mike Sidorov - * @copyright 2014 Mike Sidorov. All rights reserved. */ "use strict"; @@ -9,20 +8,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `void` operators", + category: "Best Practices", + recommended: false + }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + schema: [] + }, - return { - "UnaryExpression": function(node) { - if (node.operator === "void") { - context.report(node, "Expected 'undefined' and instead saw 'void'."); + create: function(context) { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + UnaryExpression: function(node) { + if (node.operator === "void") { + context.report(node, "Expected 'undefined' and instead saw 'void'."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-warning-comments.js b/tools/eslint/lib/rules/no-warning-comments.js index 02450229d9..dadf231f5c 100644 --- a/tools/eslint/lib/rules/no-warning-comments.js +++ b/tools/eslint/lib/rules/no-warning-comments.js @@ -11,110 +11,120 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var configuration = context.options[0] || {}, - warningTerms = configuration.terms || ["todo", "fixme", "xxx"], - location = configuration.location || "start", - selfConfigRegEx = /\bno-warning-comments\b/, - warningRegExps; - - /** - * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified - * location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not - * require word boundaries on that side. - * - * @param {String} term A term to convert to a RegExp - * @returns {RegExp} The term converted to a RegExp - */ - function convertToRegExp(term) { - var escaped = term.replace(/[-\/\\$\^*+?.()|\[\]{}]/g, "\\$&"), - suffix, - prefix; - - /* - * If the term ends in a word character (a-z0-9_), ensure a word - * boundary at the end, so that substrings do not get falsely - * matched. eg "todo" in a string such as "mastodon". - * If the term ends in a non-word character, then \b won't match on - * the boundary to the next non-word character, which would likely - * be a space. For example `/\bFIX!\b/.test('FIX! blah') === false`. - * In these cases, use no bounding match. Same applies for the - * prefix, handled below. - */ - suffix = /\w$/.test(term) ? "\\b" : ""; +module.exports = { + meta: { + docs: { + description: "disallow specified warning terms in comments", + category: "Best Practices", + recommended: false + }, - if (location === "start") { + schema: [ + { + type: "object", + properties: { + terms: { + type: "array", + items: { + type: "string" + } + }, + location: { + enum: ["start", "anywhere"] + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + + var configuration = context.options[0] || {}, + warningTerms = configuration.terms || ["todo", "fixme", "xxx"], + location = configuration.location || "start", + selfConfigRegEx = /\bno-warning-comments\b/, + warningRegExps; + + /** + * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified + * location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not + * require word boundaries on that side. + * + * @param {String} term A term to convert to a RegExp + * @returns {RegExp} The term converted to a RegExp + */ + function convertToRegExp(term) { + var escaped = term.replace(/[-\/\\$\^*+?.()|\[\]{}]/g, "\\$&"), + suffix, + prefix; /* - * When matching at the start, ignore leading whitespace, and - * there's no need to worry about word boundaries. + * If the term ends in a word character (a-z0-9_), ensure a word + * boundary at the end, so that substrings do not get falsely + * matched. eg "todo" in a string such as "mastodon". + * If the term ends in a non-word character, then \b won't match on + * the boundary to the next non-word character, which would likely + * be a space. For example `/\bFIX!\b/.test('FIX! blah') === false`. + * In these cases, use no bounding match. Same applies for the + * prefix, handled below. */ - prefix = "^\\s*"; - } else if (/^\w/.test(term)) { - prefix = "\\b"; - } else { - prefix = ""; - } + suffix = /\w$/.test(term) ? "\\b" : ""; + + if (location === "start") { + + /* + * When matching at the start, ignore leading whitespace, and + * there's no need to worry about word boundaries. + */ + prefix = "^\\s*"; + } else if (/^\w/.test(term)) { + prefix = "\\b"; + } else { + prefix = ""; + } - return new RegExp(prefix + escaped + suffix, "i"); - } + return new RegExp(prefix + escaped + suffix, "i"); + } - /** - * Checks the specified comment for matches of the configured warning terms and returns the matches. - * @param {String} comment The comment which is checked. - * @returns {Array} All matched warning terms for this comment. - */ - function commentContainsWarningTerm(comment) { - var matches = []; - - warningRegExps.forEach(function(regex, index) { - if (regex.test(comment)) { - matches.push(warningTerms[index]); - } - }); + /** + * Checks the specified comment for matches of the configured warning terms and returns the matches. + * @param {String} comment The comment which is checked. + * @returns {Array} All matched warning terms for this comment. + */ + function commentContainsWarningTerm(comment) { + var matches = []; - return matches; - } + warningRegExps.forEach(function(regex, index) { + if (regex.test(comment)) { + matches.push(warningTerms[index]); + } + }); - /** - * Checks the specified node for matching warning comments and reports them. - * @param {ASTNode} node The AST node being checked. - * @returns {void} undefined. - */ - function checkComment(node) { - if (astUtils.isDirectiveComment(node) && selfConfigRegEx.test(node.value)) { - return; + return matches; } - var matches = commentContainsWarningTerm(node.value); + /** + * Checks the specified node for matching warning comments and reports them. + * @param {ASTNode} node The AST node being checked. + * @returns {void} undefined. + */ + function checkComment(node) { + if (astUtils.isDirectiveComment(node) && selfConfigRegEx.test(node.value)) { + return; + } - matches.forEach(function(matchedTerm) { - context.report(node, "Unexpected '" + matchedTerm + "' comment."); - }); - } + var matches = commentContainsWarningTerm(node.value); - warningRegExps = warningTerms.map(convertToRegExp); - return { - "BlockComment": checkComment, - "LineComment": checkComment - }; -}; + matches.forEach(function(matchedTerm) { + context.report(node, "Unexpected '" + matchedTerm + "' comment."); + }); + } -module.exports.schema = [ - { - "type": "object", - "properties": { - "terms": { - "type": "array", - "items": { - "type": "string" - } - }, - "location": { - "enum": ["start", "anywhere"] - } - }, - "additionalProperties": false + warningRegExps = warningTerms.map(convertToRegExp); + return { + BlockComment: checkComment, + LineComment: checkComment + }; } -]; +}; diff --git a/tools/eslint/lib/rules/no-whitespace-before-property.js b/tools/eslint/lib/rules/no-whitespace-before-property.js index 0910f2fce4..347c63d934 100644 --- a/tools/eslint/lib/rules/no-whitespace-before-property.js +++ b/tools/eslint/lib/rules/no-whitespace-before-property.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to disallow whitespace before properties * @author Kai Cataldo - * @copyright 2015 Kai Cataldo. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -12,70 +10,88 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var sourceCode = context.getSourceCode(); +module.exports = { + meta: { + docs: { + description: "disallow whitespace before properties", + category: "Stylistic Issues", + recommended: false + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + fixable: "whitespace", + schema: [] + }, - /** - * Finds opening bracket token of node's computed property - * @param {ASTNode} node - the node to check - * @returns {Token} opening bracket token of node's computed property - * @private - */ - function findOpeningBracket(node) { - var token = sourceCode.getTokenBefore(node.property); + create: function(context) { + var sourceCode = context.getSourceCode(); - while (token.value !== "[") { - token = sourceCode.getTokenBefore(token); - } - return token; - } + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * Reports whitespace before property token - * @param {ASTNode} node - the node to report in the event of an error - * @returns {void} - * @private - */ - function reportError(node) { - context.report({ - node: node, - message: "Unexpected whitespace before property {{propName}}.", - data: { - propName: sourceCode.getText(node.property) + /** + * Finds opening bracket token of node's computed property + * @param {ASTNode} node - the node to check + * @returns {Token} opening bracket token of node's computed property + * @private + */ + function findOpeningBracket(node) { + var token = sourceCode.getTokenBefore(node.property); + + while (token.value !== "[") { + token = sourceCode.getTokenBefore(token); } - }); - } + return token; + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + /** + * Reports whitespace before property token + * @param {ASTNode} node - the node to report in the event of an error + * @param {Token} leftToken - the left token + * @param {Token} rightToken - the right token + * @returns {void} + * @private + */ + function reportError(node, leftToken, rightToken) { + var replacementText = node.computed ? "" : "."; - return { - MemberExpression: function(node) { - var rightToken; - var leftToken; + context.report({ + node: node, + message: "Unexpected whitespace before property {{propName}}.", + data: { + propName: sourceCode.getText(node.property) + }, + fix: function(fixer) { + return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText); + } + }); + } - if (!astUtils.isTokenOnSameLine(node.object, node.property)) { - return; - } + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - if (node.computed) { - rightToken = findOpeningBracket(node); - leftToken = sourceCode.getTokenBefore(rightToken); - } else { - rightToken = sourceCode.getFirstToken(node.property); - leftToken = sourceCode.getTokenBefore(rightToken, 1); - } + return { + MemberExpression: function(node) { + var rightToken; + var leftToken; + + if (!astUtils.isTokenOnSameLine(node.object, node.property)) { + return; + } + + if (node.computed) { + rightToken = findOpeningBracket(node); + leftToken = sourceCode.getTokenBefore(rightToken); + } else { + rightToken = sourceCode.getFirstToken(node.property); + leftToken = sourceCode.getTokenBefore(rightToken, 1); + } - if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) { - reportError(node); + if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) { + reportError(node, leftToken, rightToken); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/no-with.js b/tools/eslint/lib/rules/no-with.js index beec2554db..df7528fdfa 100644 --- a/tools/eslint/lib/rules/no-with.js +++ b/tools/eslint/lib/rules/no-with.js @@ -9,14 +9,24 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `with` statements", + category: "Best Practices", + recommended: false + }, - return { - "WithStatement": function(node) { - context.report(node, "Unexpected use of 'with' statement."); - } - }; + schema: [] + }, -}; + create: function(context) { + + return { + WithStatement: function(node) { + context.report(node, "Unexpected use of 'with' statement."); + } + }; -module.exports.schema = []; + } +}; diff --git a/tools/eslint/lib/rules/object-curly-spacing.js b/tools/eslint/lib/rules/object-curly-spacing.js index 8a7b9aefee..11224bbdd1 100644 --- a/tools/eslint/lib/rules/object-curly-spacing.js +++ b/tools/eslint/lib/rules/object-curly-spacing.js @@ -1,12 +1,6 @@ /** * @fileoverview Disallows or enforces spaces inside of object literals. * @author Jamund Ferguson - * @copyright 2014 Brandyn Bennett. All rights reserved. - * @copyright 2014 Michael Ficarra. No rights reserved. - * @copyright 2014 Vignesh Anand. All rights reserved. - * @copyright 2015 Jamund Ferguson. All rights reserved. - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -16,265 +10,277 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var spaced = context.options[0] === "always", - sourceCode = context.getSourceCode(); - - /** - * Determines whether an option is set, relative to the spacing option. - * If spaced is "always", then check whether option is set to false. - * If spaced is "never", then check whether option is set to true. - * @param {Object} option - The option to exclude. - * @returns {boolean} Whether or not the property is excluded. - */ - function isOptionSet(option) { - return context.options[1] ? context.options[1][option] === !spaced : false; - } +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing inside braces", + category: "Stylistic Issues", + recommended: false + }, - var options = { - spaced: spaced, - arraysInObjectsException: isOptionSet("arraysInObjects"), - objectsInObjectsException: isOptionSet("objectsInObjects") - }; - - //-------------------------------------------------------------------------- - // 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. - * @returns {void} - */ - function reportNoBeginningSpace(node, token) { - context.report({ - node: node, - loc: token.loc.start, - message: "There should be no space after '" + token.value + "'", - fix: function(fixer) { - var nextToken = context.getSourceCode().getTokenAfter(token); - - return fixer.removeRange([token.range[1], nextToken.range[0]]); - } - }); - } + fixable: "whitespace", - /** - * 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. - * @returns {void} - */ - function reportNoEndingSpace(node, token) { - context.report({ - node: node, - loc: token.loc.start, - message: "There should be no space before '" + token.value + "'", - fix: function(fixer) { - var previousToken = context.getSourceCode().getTokenBefore(token); - - return fixer.removeRange([previousToken.range[1], token.range[0]]); - } - }); - } - - /** - * 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, " "); + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + arraysInObjects: { + type: "boolean" + }, + objectsInObjects: { + type: "boolean" + } + }, + additionalProperties: false } - }); - } + ] + }, - /** - * Reports that there should be a space before the last token - * @param {ASTNode} node - The node to report in the event of an error. - * @param {Token} token - The token to use for the report. - * @returns {void} - */ - function reportRequiredEndingSpace(node, token) { - context.report({ - node: node, - loc: token.loc.start, - message: "A space is required before '" + token.value + "'", - fix: function(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } + create: function(context) { + var spaced = context.options[0] === "always", + sourceCode = context.getSourceCode(); + + /** + * Determines whether an option is set, relative to the spacing option. + * If spaced is "always", then check whether option is set to false. + * If spaced is "never", then check whether option is set to true. + * @param {Object} option - The option to exclude. + * @returns {boolean} Whether or not the property is excluded. + */ + function isOptionSet(option) { + return context.options[1] ? context.options[1][option] === !spaced : false; + } - /** - * Determines if spacing in curly braces is valid. - * @param {ASTNode} node The AST node to check. - * @param {Token} first The first token to check (should be the opening brace) - * @param {Token} second The second token to check (should be first after the opening brace) - * @param {Token} penultimate The penultimate token to check (should be last before closing brace) - * @param {Token} last The last token to check (should be closing brace) - * @returns {void} - */ - function validateBraceSpacing(node, first, second, penultimate, last) { - var shouldCheckPenultimate, - penultimateType, - closingCurlyBraceMustBeSpaced, - firstSpaced, - lastSpaced; - - if (astUtils.isTokenOnSameLine(first, second)) { - firstSpaced = sourceCode.isSpaceBetweenTokens(first, second); - if (options.spaced && !firstSpaced) { - reportRequiredBeginningSpace(node, first); - } - if (!options.spaced && firstSpaced) { - reportNoBeginningSpace(node, first); - } + var options = { + spaced: spaced, + arraysInObjectsException: isOptionSet("arraysInObjects"), + objectsInObjectsException: isOptionSet("objectsInObjects") + }; + + //-------------------------------------------------------------------------- + // 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. + * @returns {void} + */ + function reportNoBeginningSpace(node, token) { + context.report({ + node: node, + loc: token.loc.start, + message: "There should be no space after '" + token.value + "'", + fix: function(fixer) { + var nextToken = context.getSourceCode().getTokenAfter(token); + + return fixer.removeRange([token.range[1], nextToken.range[0]]); + } + }); } - if (astUtils.isTokenOnSameLine(penultimate, last)) { - shouldCheckPenultimate = ( - options.arraysInObjectsException && penultimate.value === "]" || - options.objectsInObjectsException && penultimate.value === "}" - ); - penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.start).type; + /** + * 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. + * @returns {void} + */ + function reportNoEndingSpace(node, token) { + context.report({ + node: node, + loc: token.loc.start, + message: "There should be no space before '" + token.value + "'", + fix: function(fixer) { + var previousToken = context.getSourceCode().getTokenBefore(token); + + return fixer.removeRange([previousToken.range[1], token.range[0]]); + } + }); + } - closingCurlyBraceMustBeSpaced = ( - options.arraysInObjectsException && penultimateType === "ArrayExpression" || - options.objectsInObjectsException && penultimateType === "ObjectExpression" - ) ? !options.spaced : options.spaced; + /** + * 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, " "); + } + }); + } - lastSpaced = sourceCode.isSpaceBetweenTokens(penultimate, last); + /** + * 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, " "); + } + }); + } - if (closingCurlyBraceMustBeSpaced && !lastSpaced) { - reportRequiredEndingSpace(node, last); - } - if (!closingCurlyBraceMustBeSpaced && lastSpaced) { - reportNoEndingSpace(node, last); + /** + * Determines if spacing in curly braces is valid. + * @param {ASTNode} node The AST node to check. + * @param {Token} first The first token to check (should be the opening brace) + * @param {Token} second The second token to check (should be first after the opening brace) + * @param {Token} penultimate The penultimate token to check (should be last before closing brace) + * @param {Token} last The last token to check (should be closing brace) + * @returns {void} + */ + function validateBraceSpacing(node, first, second, penultimate, last) { + var shouldCheckPenultimate, + penultimateType, + closingCurlyBraceMustBeSpaced, + firstSpaced, + lastSpaced; + + if (astUtils.isTokenOnSameLine(first, second)) { + firstSpaced = sourceCode.isSpaceBetweenTokens(first, second); + if (options.spaced && !firstSpaced) { + reportRequiredBeginningSpace(node, first); + } + if (!options.spaced && firstSpaced) { + reportNoBeginningSpace(node, first); + } } - } - } - /** - * Reports a given object node if spacing in curly braces is invalid. - * @param {ASTNode} node - An ObjectExpression or ObjectPattern node to check. - * @returns {void} - */ - function checkForObject(node) { - if (node.properties.length === 0) { - return; + if (astUtils.isTokenOnSameLine(penultimate, last)) { + shouldCheckPenultimate = ( + options.arraysInObjectsException && penultimate.value === "]" || + options.objectsInObjectsException && penultimate.value === "}" + ); + penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.start).type; + + closingCurlyBraceMustBeSpaced = ( + options.arraysInObjectsException && penultimateType === "ArrayExpression" || + options.objectsInObjectsException && penultimateType === "ObjectExpression" + ) ? !options.spaced : options.spaced; + + lastSpaced = sourceCode.isSpaceBetweenTokens(penultimate, last); + + if (closingCurlyBraceMustBeSpaced && !lastSpaced) { + reportRequiredEndingSpace(node, last); + } + if (!closingCurlyBraceMustBeSpaced && lastSpaced) { + reportNoEndingSpace(node, last); + } + } } - var first = sourceCode.getFirstToken(node), - last = sourceCode.getLastToken(node), - second = sourceCode.getTokenAfter(first), - penultimate = sourceCode.getTokenBefore(last); + /** + * Reports a given object node if spacing in curly braces is invalid. + * @param {ASTNode} node - An ObjectExpression or ObjectPattern node to check. + * @returns {void} + */ + function checkForObject(node) { + if (node.properties.length === 0) { + return; + } - validateBraceSpacing(node, first, second, penultimate, last); - } + var first = sourceCode.getFirstToken(node), + last = sourceCode.getLastToken(node), + second = sourceCode.getTokenAfter(first), + penultimate = sourceCode.getTokenBefore(last); - /** - * Reports a given import node if spacing in curly braces is invalid. - * @param {ASTNode} node - An ImportDeclaration node to check. - * @returns {void} - */ - function checkForImport(node) { - if (node.specifiers.length === 0) { - return; + validateBraceSpacing(node, first, second, penultimate, last); } - var firstSpecifier = node.specifiers[0], - lastSpecifier = node.specifiers[node.specifiers.length - 1]; + /** + * Reports a given import node if spacing in curly braces is invalid. + * @param {ASTNode} node - An ImportDeclaration node to check. + * @returns {void} + */ + function checkForImport(node) { + if (node.specifiers.length === 0) { + return; + } - if (lastSpecifier.type !== "ImportSpecifier") { - return; - } - if (firstSpecifier.type !== "ImportSpecifier") { - firstSpecifier = node.specifiers[1]; - } + var firstSpecifier = node.specifiers[0], + lastSpecifier = node.specifiers[node.specifiers.length - 1]; - var first = sourceCode.getTokenBefore(firstSpecifier), - last = sourceCode.getTokenAfter(lastSpecifier); + if (lastSpecifier.type !== "ImportSpecifier") { + return; + } + if (firstSpecifier.type !== "ImportSpecifier") { + firstSpecifier = node.specifiers[1]; + } - // to support a trailing comma. - if (last.value === ",") { - last = sourceCode.getTokenAfter(last); - } + var first = sourceCode.getTokenBefore(firstSpecifier), + last = sourceCode.getTokenAfter(lastSpecifier); - var second = sourceCode.getTokenAfter(first), - penultimate = sourceCode.getTokenBefore(last); + // to support a trailing comma. + if (last.value === ",") { + last = sourceCode.getTokenAfter(last); + } - validateBraceSpacing(node, first, second, penultimate, last); - } + var second = sourceCode.getTokenAfter(first), + penultimate = sourceCode.getTokenBefore(last); - /** - * Reports a given export node if spacing in curly braces is invalid. - * @param {ASTNode} node - An ExportNamedDeclaration node to check. - * @returns {void} - */ - function checkForExport(node) { - if (node.specifiers.length === 0) { - return; + validateBraceSpacing(node, first, second, penultimate, last); } - var firstSpecifier = node.specifiers[0], - lastSpecifier = node.specifiers[node.specifiers.length - 1], - first = sourceCode.getTokenBefore(firstSpecifier), - last = sourceCode.getTokenAfter(lastSpecifier); + /** + * Reports a given export node if spacing in curly braces is invalid. + * @param {ASTNode} node - An ExportNamedDeclaration node to check. + * @returns {void} + */ + function checkForExport(node) { + if (node.specifiers.length === 0) { + return; + } - // to support a trailing comma. - if (last.value === ",") { - last = sourceCode.getTokenAfter(last); - } + var firstSpecifier = node.specifiers[0], + lastSpecifier = node.specifiers[node.specifiers.length - 1], + first = sourceCode.getTokenBefore(firstSpecifier), + last = sourceCode.getTokenAfter(lastSpecifier); - var second = sourceCode.getTokenAfter(first), - penultimate = sourceCode.getTokenBefore(last); + // to support a trailing comma. + if (last.value === ",") { + last = sourceCode.getTokenAfter(last); + } - validateBraceSpacing(node, first, second, penultimate, last); - } + var second = sourceCode.getTokenAfter(first), + penultimate = sourceCode.getTokenBefore(last); - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + validateBraceSpacing(node, first, second, penultimate, last); + } - return { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - // var {x} = y; - ObjectPattern: checkForObject, + return { - // var y = {x: 'y'} - ObjectExpression: checkForObject, + // var {x} = y; + ObjectPattern: checkForObject, - // import {y} from 'x'; - ImportDeclaration: checkForImport, + // var y = {x: 'y'} + ObjectExpression: checkForObject, - // export {name} from 'yo'; - ExportNamedDeclaration: checkForExport - }; + // import {y} from 'x'; + ImportDeclaration: checkForImport, -}; + // export {name} from 'yo'; + ExportNamedDeclaration: checkForExport + }; -module.exports.schema = [ - { - "enum": ["always", "never"] - }, - { - "type": "object", - "properties": { - "arraysInObjects": { - "type": "boolean" - }, - "objectsInObjects": { - "type": "boolean" - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/object-shorthand.js b/tools/eslint/lib/rules/object-shorthand.js index c6ffa0f8f3..4c7c066bcd 100644 --- a/tools/eslint/lib/rules/object-shorthand.js +++ b/tools/eslint/lib/rules/object-shorthand.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to enforce concise object methods and properties. * @author Jamund Ferguson - * @copyright 2015 Jamund Ferguson. All rights reserved. */ "use strict"; @@ -16,112 +15,127 @@ var OPTIONS = { //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var APPLY = context.options[0] || OPTIONS.always; - var APPLY_TO_METHODS = APPLY === OPTIONS.methods || APPLY === OPTIONS.always; - var APPLY_TO_PROPS = APPLY === OPTIONS.properties || APPLY === OPTIONS.always; - var APPLY_NEVER = APPLY === OPTIONS.never; - - var PARAMS = context.options[1] || {}; - var IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Determines if the first character of the name is a capital letter. - * @param {string} name The name of the node to evaluate. - * @returns {boolean} True if the first character of the property name is a capital letter, false if not. - * @private - */ - function isConstructor(name) { - var firstChar = name.charAt(0); - - return firstChar === firstChar.toUpperCase(); - } +module.exports = { + meta: { + docs: { + description: "require or disallow method and property shorthand syntax for object literals", + category: "ECMAScript 6", + recommended: false + }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always", "methods", "properties", "never"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["always", "methods"] + }, + { + type: "object", + properties: { + ignoreConstructors: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + } + }, + + create: function(context) { + var APPLY = context.options[0] || OPTIONS.always; + var APPLY_TO_METHODS = APPLY === OPTIONS.methods || APPLY === OPTIONS.always; + var APPLY_TO_PROPS = APPLY === OPTIONS.properties || APPLY === OPTIONS.always; + var APPLY_NEVER = APPLY === OPTIONS.never; + + var PARAMS = context.options[1] || {}; + var IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determines if the first character of the name is a capital letter. + * @param {string} name The name of the node to evaluate. + * @returns {boolean} True if the first character of the property name is a capital letter, false if not. + * @private + */ + function isConstructor(name) { + var firstChar = name.charAt(0); + + return firstChar === firstChar.toUpperCase(); + } - return { - "Property": function(node) { - var isConciseProperty = node.method || node.shorthand, - type; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - // Ignore destructuring assignment - if (node.parent.type === "ObjectPattern") { - return; - } + return { + Property: function(node) { + var isConciseProperty = node.method || node.shorthand, + type; - // if we're "never" and concise we should warn now - if (APPLY_NEVER && isConciseProperty) { - type = node.method ? "method" : "property"; - context.report(node, "Expected longform " + type + " syntax."); - } + // Ignore destructuring assignment + if (node.parent.type === "ObjectPattern") { + return; + } - // at this point if we're concise or if we're "never" we can leave - if (APPLY_NEVER || isConciseProperty) { - return; - } + // if we're "never" and concise we should warn now + if (APPLY_NEVER && isConciseProperty) { + type = node.method ? "method" : "property"; + context.report(node, "Expected longform " + type + " syntax."); + } - // getters, setters and computed properties are ignored - if (node.kind === "get" || node.kind === "set" || node.computed) { - return; - } + // at this point if we're concise or if we're "never" we can leave + if (APPLY_NEVER || isConciseProperty) { + return; + } - if (node.value.type === "FunctionExpression" && !node.value.id && APPLY_TO_METHODS) { - if (IGNORE_CONSTRUCTORS && isConstructor(node.key.name)) { + // only computed methods can fail the following checks + if (!APPLY_TO_METHODS && node.computed) { return; } - // {x: function(){}} should be written as {x() {}} - context.report(node, "Expected method shorthand."); - } else if (node.value.type === "Identifier" && node.key.name === node.value.name && APPLY_TO_PROPS) { + // getters and setters are ignored + if (node.kind === "get" || node.kind === "set") { + return; + } - // {x: x} should be written as {x} - context.report(node, "Expected property shorthand."); - } else if (node.value.type === "Identifier" && node.key.type === "Literal" && node.key.value === node.value.name && APPLY_TO_PROPS) { + if (node.value.type === "FunctionExpression" && !node.value.id && APPLY_TO_METHODS) { + if (IGNORE_CONSTRUCTORS && isConstructor(node.key.name)) { + return; + } - // {"x": x} should be written as {x} - context.report(node, "Expected property shorthand."); - } - } - }; + // {x: function(){}} should be written as {x() {}} + context.report(node, "Expected method shorthand."); + } else if (node.value.type === "Identifier" && node.key.name === node.value.name && APPLY_TO_PROPS) { -}; + // {x: x} should be written as {x} + context.report(node, "Expected property shorthand."); + } else if (node.value.type === "Identifier" && node.key.type === "Literal" && node.key.value === node.value.name && APPLY_TO_PROPS) { -module.exports.schema = { - "anyOf": [ - { - "type": "array", - "items": [ - { - "enum": ["always", "methods", "properties", "never"] + // {"x": x} should be written as {x} + context.report(node, "Expected property shorthand."); } - ], - "minItems": 0, - "maxItems": 1 - }, - { - "type": "array", - "items": [ - { - "enum": ["always", "methods"] - }, - { - "type": "object", - "properties": { - "ignoreConstructors": { - "type": "boolean" - } - }, - "additionalProperties": false - } - ], - "minItems": 0, - "maxItems": 2 - } - ] + } + }; + + } }; diff --git a/tools/eslint/lib/rules/one-var-declaration-per-line.js b/tools/eslint/lib/rules/one-var-declaration-per-line.js index 2f763485d7..f4cdb84b18 100644 --- a/tools/eslint/lib/rules/one-var-declaration-per-line.js +++ b/tools/eslint/lib/rules/one-var-declaration-per-line.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to check multiple var declarations per line * @author Alberto Rodríguez - * @copyright 2016 Alberto Rodríguez. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -10,66 +8,76 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require or disallow newlines around `var` declarations", + category: "Stylistic Issues", + recommended: false + }, - var ERROR_MESSAGE = "Expected variable declaration to be on a new line."; - var always = context.options[0] === "always"; + schema: [ + { + enum: ["always", "initializations"] + } + ] + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + create: function(context) { + var ERROR_MESSAGE = "Expected variable declaration to be on a new line."; + var always = context.options[0] === "always"; - /** - * Determine if provided keyword is a variant of for specifiers - * @private - * @param {string} keyword - keyword to test - * @returns {boolean} True if `keyword` is a variant of for specifier - */ - function isForTypeSpecifier(keyword) { - return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; - } + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * Checks newlines around variable declarations. - * @private - * @param {ASTNode} node - `VariableDeclaration` node to test - * @returns {void} - */ - function checkForNewLine(node) { - if (isForTypeSpecifier(node.parent.type)) { - return; - } - var declarations = node.declarations; - var prev; + /** + * Determine if provided keyword is a variant of for specifiers + * @private + * @param {string} keyword - keyword to test + * @returns {boolean} True if `keyword` is a variant of for specifier + */ + function isForTypeSpecifier(keyword) { + return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; + } - declarations.forEach(function(current) { - if (prev && prev.loc.end.line === current.loc.start.line) { - if (always || prev.init || current.init) { - context.report({ - node: node, - message: ERROR_MESSAGE, - loc: current.loc.start - }); - } + /** + * Checks newlines around variable declarations. + * @private + * @param {ASTNode} node - `VariableDeclaration` node to test + * @returns {void} + */ + function checkForNewLine(node) { + if (isForTypeSpecifier(node.parent.type)) { + return; } - prev = current; - }); - } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + var declarations = node.declarations; + var prev; - return { - "VariableDeclaration": checkForNewLine - }; + declarations.forEach(function(current) { + if (prev && prev.loc.end.line === current.loc.start.line) { + if (always || prev.init || current.init) { + context.report({ + node: node, + message: ERROR_MESSAGE, + loc: current.loc.start + }); + } + } + prev = current; + }); + } -}; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + VariableDeclaration: checkForNewLine + }; -module.exports.schema = [ - { - "enum": ["always", "initializations"] } -]; +}; diff --git a/tools/eslint/lib/rules/one-var.js b/tools/eslint/lib/rules/one-var.js index d7cccbfe9d..805cec3654 100644 --- a/tools/eslint/lib/rules/one-var.js +++ b/tools/eslint/lib/rules/one-var.js @@ -1,10 +1,6 @@ /** * @fileoverview A rule to control the use of single variable declarations. * @author Ian Christian Myers - * @copyright 2015 Ian VanSchooten. All rights reserved. - * @copyright 2015 Joey Baker. All rights reserved. - * @copyright 2015 Danny Fritz. All rights reserved. - * @copyright 2013 Ian Christian Myers. All rights reserved. */ "use strict"; @@ -13,311 +9,321 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce variables to be declared either together or separately in functions", + category: "Stylistic Issues", + recommended: false + }, - var MODE_ALWAYS = "always", - MODE_NEVER = "never"; + schema: [ + { + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + var: { + enum: ["always", "never"] + }, + let: { + enum: ["always", "never"] + }, + const: { + enum: ["always", "never"] + } + }, + additionalProperties: false + }, + { + type: "object", + properties: { + initialized: { + enum: ["always", "never"] + }, + uninitialized: { + enum: ["always", "never"] + } + }, + additionalProperties: false + } + ] + } + ] + }, - var mode = context.options[0] || MODE_ALWAYS; + create: function(context) { - var options = { - }; + var MODE_ALWAYS = "always", + MODE_NEVER = "never"; - if (typeof mode === "string") { // simple options configuration with just a string - options.var = { uninitialized: mode, initialized: mode}; - options.let = { uninitialized: mode, initialized: mode}; - options.const = { uninitialized: mode, initialized: mode}; - } else if (typeof mode === "object") { // options configuration is an object - if (mode.hasOwnProperty("var") && typeof mode.var === "string") { - options.var = { uninitialized: mode.var, initialized: mode.var}; - } - if (mode.hasOwnProperty("let") && typeof mode.let === "string") { - options.let = { uninitialized: mode.let, initialized: mode.let}; - } - if (mode.hasOwnProperty("const") && typeof mode.const === "string") { - options.const = { uninitialized: mode.const, initialized: mode.const}; - } - if (mode.hasOwnProperty("uninitialized")) { - if (!options.var) { - options.var = {}; - } - if (!options.let) { - options.let = {}; + var mode = context.options[0] || MODE_ALWAYS; + + var options = { + }; + + if (typeof mode === "string") { // simple options configuration with just a string + options.var = { uninitialized: mode, initialized: mode}; + options.let = { uninitialized: mode, initialized: mode}; + options.const = { uninitialized: mode, initialized: mode}; + } else if (typeof mode === "object") { // options configuration is an object + if (mode.hasOwnProperty("var") && typeof mode.var === "string") { + options.var = { uninitialized: mode.var, initialized: mode.var}; } - if (!options.const) { - options.const = {}; + if (mode.hasOwnProperty("let") && typeof mode.let === "string") { + options.let = { uninitialized: mode.let, initialized: mode.let}; } - options.var.uninitialized = mode.uninitialized; - options.let.uninitialized = mode.uninitialized; - options.const.uninitialized = mode.uninitialized; - } - if (mode.hasOwnProperty("initialized")) { - if (!options.var) { - options.var = {}; + if (mode.hasOwnProperty("const") && typeof mode.const === "string") { + options.const = { uninitialized: mode.const, initialized: mode.const}; } - if (!options.let) { - options.let = {}; + if (mode.hasOwnProperty("uninitialized")) { + if (!options.var) { + options.var = {}; + } + if (!options.let) { + options.let = {}; + } + if (!options.const) { + options.const = {}; + } + options.var.uninitialized = mode.uninitialized; + options.let.uninitialized = mode.uninitialized; + options.const.uninitialized = mode.uninitialized; } - if (!options.const) { - options.const = {}; + if (mode.hasOwnProperty("initialized")) { + if (!options.var) { + options.var = {}; + } + if (!options.let) { + options.let = {}; + } + if (!options.const) { + options.const = {}; + } + options.var.initialized = mode.initialized; + options.let.initialized = mode.initialized; + options.const.initialized = mode.initialized; } - options.var.initialized = mode.initialized; - options.let.initialized = mode.initialized; - options.const.initialized = mode.initialized; } - } - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - var functionStack = []; - var blockStack = []; - - /** - * Increments the blockStack counter. - * @returns {void} - * @private - */ - function startBlock() { - blockStack.push({ - let: {initialized: false, uninitialized: false}, - const: {initialized: false, uninitialized: false} - }); - } + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + var functionStack = []; + var blockStack = []; + + /** + * Increments the blockStack counter. + * @returns {void} + * @private + */ + function startBlock() { + blockStack.push({ + let: {initialized: false, uninitialized: false}, + const: {initialized: false, uninitialized: false} + }); + } - /** - * Increments the functionStack counter. - * @returns {void} - * @private - */ - function startFunction() { - functionStack.push({initialized: false, uninitialized: false}); - startBlock(); - } + /** + * Increments the functionStack counter. + * @returns {void} + * @private + */ + function startFunction() { + functionStack.push({initialized: false, uninitialized: false}); + startBlock(); + } - /** - * Decrements the blockStack counter. - * @returns {void} - * @private - */ - function endBlock() { - blockStack.pop(); - } + /** + * Decrements the blockStack counter. + * @returns {void} + * @private + */ + function endBlock() { + blockStack.pop(); + } - /** - * Decrements the functionStack counter. - * @returns {void} - * @private - */ - function endFunction() { - functionStack.pop(); - endBlock(); - } + /** + * Decrements the functionStack counter. + * @returns {void} + * @private + */ + function endFunction() { + functionStack.pop(); + endBlock(); + } - /** - * Records whether initialized or uninitialized variables are defined in current scope. - * @param {string} statementType node.kind, one of: "var", "let", or "const" - * @param {ASTNode[]} declarations List of declarations - * @param {Object} currentScope The scope being investigated - * @returns {void} - * @private - */ - function recordTypes(statementType, declarations, currentScope) { - for (var i = 0; i < declarations.length; i++) { - if (declarations[i].init === null) { - if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) { - currentScope.uninitialized = true; - } - } else { - if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) { - currentScope.initialized = true; + /** + * Records whether initialized or uninitialized variables are defined in current scope. + * @param {string} statementType node.kind, one of: "var", "let", or "const" + * @param {ASTNode[]} declarations List of declarations + * @param {Object} currentScope The scope being investigated + * @returns {void} + * @private + */ + function recordTypes(statementType, declarations, currentScope) { + for (var i = 0; i < declarations.length; i++) { + if (declarations[i].init === null) { + if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) { + currentScope.uninitialized = true; + } + } else { + if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) { + currentScope.initialized = true; + } } } } - } - /** - * Determines the current scope (function or block) - * @param {string} statementType node.kind, one of: "var", "let", or "const" - * @returns {Object} The scope associated with statementType - */ - function getCurrentScope(statementType) { - var currentScope; - - if (statementType === "var") { - currentScope = functionStack[functionStack.length - 1]; - } else if (statementType === "let") { - currentScope = blockStack[blockStack.length - 1].let; - } else if (statementType === "const") { - currentScope = blockStack[blockStack.length - 1].const; + /** + * Determines the current scope (function or block) + * @param {string} statementType node.kind, one of: "var", "let", or "const" + * @returns {Object} The scope associated with statementType + */ + function getCurrentScope(statementType) { + var currentScope; + + if (statementType === "var") { + currentScope = functionStack[functionStack.length - 1]; + } else if (statementType === "let") { + currentScope = blockStack[blockStack.length - 1].let; + } else if (statementType === "const") { + currentScope = blockStack[blockStack.length - 1].const; + } + return currentScope; } - return currentScope; - } - /** - * Counts the number of initialized and uninitialized declarations in a list of declarations - * @param {ASTNode[]} declarations List of declarations - * @returns {Object} Counts of 'uninitialized' and 'initialized' declarations - * @private - */ - function countDeclarations(declarations) { - var counts = { uninitialized: 0, initialized: 0 }; - - for (var i = 0; i < declarations.length; i++) { - if (declarations[i].init === null) { - counts.uninitialized++; - } else { - counts.initialized++; + /** + * Counts the number of initialized and uninitialized declarations in a list of declarations + * @param {ASTNode[]} declarations List of declarations + * @returns {Object} Counts of 'uninitialized' and 'initialized' declarations + * @private + */ + function countDeclarations(declarations) { + var counts = { uninitialized: 0, initialized: 0 }; + + for (var i = 0; i < declarations.length; i++) { + if (declarations[i].init === null) { + counts.uninitialized++; + } else { + counts.initialized++; + } } + return counts; } - return counts; - } - /** - * Determines if there is more than one var statement in the current scope. - * @param {string} statementType node.kind, one of: "var", "let", or "const" - * @param {ASTNode[]} declarations List of declarations - * @returns {boolean} Returns true if it is the first var declaration, false if not. - * @private - */ - function hasOnlyOneStatement(statementType, declarations) { - - var declarationCounts = countDeclarations(declarations); - var currentOptions = options[statementType] || {}; - var currentScope = getCurrentScope(statementType); - - if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) { - if (currentScope.uninitialized || currentScope.initialized) { - return false; + /** + * Determines if there is more than one var statement in the current scope. + * @param {string} statementType node.kind, one of: "var", "let", or "const" + * @param {ASTNode[]} declarations List of declarations + * @returns {boolean} Returns true if it is the first var declaration, false if not. + * @private + */ + function hasOnlyOneStatement(statementType, declarations) { + + var declarationCounts = countDeclarations(declarations); + var currentOptions = options[statementType] || {}; + var currentScope = getCurrentScope(statementType); + + if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) { + if (currentScope.uninitialized || currentScope.initialized) { + return false; + } } - } - if (declarationCounts.uninitialized > 0) { - if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) { - return false; + if (declarationCounts.uninitialized > 0) { + if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) { + return false; + } } - } - if (declarationCounts.initialized > 0) { - if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) { - return false; + if (declarationCounts.initialized > 0) { + if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) { + return false; + } } + recordTypes(statementType, declarations, currentScope); + return true; } - recordTypes(statementType, declarations, currentScope); - return true; - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - "Program": startFunction, - "FunctionDeclaration": startFunction, - "FunctionExpression": startFunction, - "ArrowFunctionExpression": startFunction, - "BlockStatement": startBlock, - "ForStatement": startBlock, - "ForInStatement": startBlock, - "ForOfStatement": startBlock, - "SwitchStatement": startBlock, - - "VariableDeclaration": function(node) { - var parent = node.parent, - type, declarations, declarationCounts; - - type = node.kind; - if (!options[type]) { - return; - } - declarations = node.declarations; - declarationCounts = countDeclarations(declarations); + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + Program: startFunction, + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, + BlockStatement: startBlock, + ForStatement: startBlock, + ForInStatement: startBlock, + ForOfStatement: startBlock, + SwitchStatement: startBlock, + + VariableDeclaration: function(node) { + var parent = node.parent, + type, declarations, declarationCounts; + + type = node.kind; + if (!options[type]) { + return; + } - // always - if (!hasOnlyOneStatement(type, declarations)) { - if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) { - context.report(node, "Combine this with the previous '" + type + "' statement."); - } else { - if (options[type].initialized === MODE_ALWAYS) { - context.report(node, "Combine this with the previous '" + type + "' statement with initialized variables."); - } - if (options[type].uninitialized === MODE_ALWAYS) { - context.report(node, "Combine this with the previous '" + type + "' statement with uninitialized variables."); + declarations = node.declarations; + declarationCounts = countDeclarations(declarations); + + // always + if (!hasOnlyOneStatement(type, declarations)) { + if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) { + context.report(node, "Combine this with the previous '" + type + "' statement."); + } else { + if (options[type].initialized === MODE_ALWAYS) { + context.report(node, "Combine this with the previous '" + type + "' statement with initialized variables."); + } + if (options[type].uninitialized === MODE_ALWAYS) { + context.report(node, "Combine this with the previous '" + type + "' statement with uninitialized variables."); + } } } - } - // never - if (parent.type !== "ForStatement" || parent.init !== node) { - var totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized; + // never + if (parent.type !== "ForStatement" || parent.init !== node) { + var totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized; - if (totalDeclarations > 1) { + if (totalDeclarations > 1) { - if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) { + if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) { - // both initialized and uninitialized - context.report(node, "Split '" + type + "' declarations into multiple statements."); - } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) { + // both initialized and uninitialized + context.report(node, "Split '" + type + "' declarations into multiple statements."); + } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) { - // initialized - context.report(node, "Split initialized '" + type + "' declarations into multiple statements."); - } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) { + // initialized + context.report(node, "Split initialized '" + type + "' declarations into multiple statements."); + } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) { - // uninitialized - context.report(node, "Split uninitialized '" + type + "' declarations into multiple statements."); + // uninitialized + context.report(node, "Split uninitialized '" + type + "' declarations into multiple statements."); + } } } - } - }, + }, - "ForStatement:exit": endBlock, - "ForOfStatement:exit": endBlock, - "ForInStatement:exit": endBlock, - "SwitchStatement:exit": endBlock, - "BlockStatement:exit": endBlock, - "Program:exit": endFunction, - "FunctionDeclaration:exit": endFunction, - "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction - }; + "ForStatement:exit": endBlock, + "ForOfStatement:exit": endBlock, + "ForInStatement:exit": endBlock, + "SwitchStatement:exit": endBlock, + "BlockStatement:exit": endBlock, + "Program:exit": endFunction, + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction + }; -}; - -module.exports.schema = [ - { - "oneOf": [ - { - "enum": ["always", "never"] - }, - { - "type": "object", - "properties": { - "var": { - "enum": ["always", "never"] - }, - "let": { - "enum": ["always", "never"] - }, - "const": { - "enum": ["always", "never"] - } - }, - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "initialized": { - "enum": ["always", "never"] - }, - "uninitialized": { - "enum": ["always", "never"] - } - }, - "additionalProperties": false - } - ] } -]; +}; diff --git a/tools/eslint/lib/rules/operator-assignment.js b/tools/eslint/lib/rules/operator-assignment.js index 9656846019..7e60c9efd4 100644 --- a/tools/eslint/lib/rules/operator-assignment.js +++ b/tools/eslint/lib/rules/operator-assignment.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to replace assignment expressions with operator assignment * @author Brandon Mills - * @copyright 2014 Brandon Mills. All rights reserved. */ "use strict"; @@ -71,54 +70,64 @@ function same(a, b) { } } -module.exports = function(context) { - - /** - * Ensures that an assignment uses the shorthand form where possible. - * @param {ASTNode} node An AssignmentExpression node. - * @returns {void} - */ - function verify(node) { - var expr, left, operator; +module.exports = { + meta: { + docs: { + description: "require or disallow assignment operator shorthand where possible", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + enum: ["always", "never"] + } + ] + }, - if (node.operator !== "=" || node.right.type !== "BinaryExpression") { - return; - } + create: function(context) { - left = node.left; - expr = node.right; - operator = expr.operator; + /** + * Ensures that an assignment uses the shorthand form where possible. + * @param {ASTNode} node An AssignmentExpression node. + * @returns {void} + */ + function verify(node) { + var expr, left, operator; - if (isCommutativeOperatorWithShorthand(operator)) { - if (same(left, expr.left) || same(left, expr.right)) { - context.report(node, "Assignment can be replaced with operator assignment."); + if (node.operator !== "=" || node.right.type !== "BinaryExpression") { + return; } - } else if (isNonCommutativeOperatorWithShorthand(operator)) { - if (same(left, expr.left)) { - context.report(node, "Assignment can be replaced with operator assignment."); + + left = node.left; + expr = node.right; + operator = expr.operator; + + if (isCommutativeOperatorWithShorthand(operator)) { + if (same(left, expr.left) || same(left, expr.right)) { + context.report(node, "Assignment can be replaced with operator assignment."); + } + } else if (isNonCommutativeOperatorWithShorthand(operator)) { + if (same(left, expr.left)) { + context.report(node, "Assignment can be replaced with operator assignment."); + } } } - } - /** - * Warns if an assignment expression uses operator assignment shorthand. - * @param {ASTNode} node An AssignmentExpression node. - * @returns {void} - */ - function prohibit(node) { - if (node.operator !== "=") { - context.report(node, "Unexpected operator assignment shorthand."); + /** + * Warns if an assignment expression uses operator assignment shorthand. + * @param {ASTNode} node An AssignmentExpression node. + * @returns {void} + */ + function prohibit(node) { + if (node.operator !== "=") { + context.report(node, "Unexpected operator assignment shorthand."); + } } - } - return { - "AssignmentExpression": context.options[0] !== "never" ? verify : prohibit - }; + return { + AssignmentExpression: context.options[0] !== "never" ? verify : prohibit + }; -}; - -module.exports.schema = [ - { - "enum": ["always", "never"] } -]; +}; diff --git a/tools/eslint/lib/rules/operator-linebreak.js b/tools/eslint/lib/rules/operator-linebreak.js index aa3c036928..85f90b908f 100644 --- a/tools/eslint/lib/rules/operator-linebreak.js +++ b/tools/eslint/lib/rules/operator-linebreak.js @@ -1,7 +1,6 @@ /** * @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before * @author Benoît Zugmeyer - * @copyright 2015 Benoît Zugmeyer. All rights reserved. */ "use strict"; @@ -13,136 +12,146 @@ var lodash = require("lodash"), // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce consistent linebreak style for operators", + category: "Stylistic Issues", + recommended: false + }, - var usedDefaultGlobal = !context.options[0]; - var globalStyle = context.options[0] || "after"; - var options = context.options[1] || {}; - var styleOverrides = options.overrides ? lodash.assign({}, options.overrides) : {}; + schema: [ + { + enum: ["after", "before", "none", null] + }, + { + type: "object", + properties: { + overrides: { + type: "object", + properties: { + anyOf: { + type: "string", + enum: ["after", "before", "none", "ignore"] + } + } + } + }, + additionalProperties: false + } + ] + }, - if (usedDefaultGlobal && !styleOverrides["?"]) { - styleOverrides["?"] = "before"; - } + create: function(context) { - if (usedDefaultGlobal && !styleOverrides[":"]) { - styleOverrides[":"] = "before"; - } + var usedDefaultGlobal = !context.options[0]; + var globalStyle = context.options[0] || "after"; + var options = context.options[1] || {}; + var styleOverrides = options.overrides ? lodash.assign({}, options.overrides) : {}; - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Checks the operator placement - * @param {ASTNode} node The node to check - * @param {ASTNode} leftSide The node that comes before the operator in `node` - * @private - * @returns {void} - */ - function validateNode(node, leftSide) { - var leftToken = context.getLastToken(leftSide); - var operatorToken = context.getTokenAfter(leftToken); - - // When the left part of a binary expression is a single expression wrapped in - // parentheses (ex: `(a) + b`), leftToken will be the last token of the expression - // and operatorToken will be the closing parenthesis. - // The leftToken should be the last closing parenthesis, and the operatorToken - // should be the token right after that. - while (operatorToken.value === ")") { - leftToken = operatorToken; - operatorToken = context.getTokenAfter(operatorToken); + if (usedDefaultGlobal && !styleOverrides["?"]) { + styleOverrides["?"] = "before"; } - var rightToken = context.getTokenAfter(operatorToken); - var operator = operatorToken.value; - var operatorStyleOverride = styleOverrides[operator]; - var style = operatorStyleOverride || globalStyle; + if (usedDefaultGlobal && !styleOverrides[":"]) { + styleOverrides[":"] = "before"; + } - // if single line - if (astUtils.isTokenOnSameLine(leftToken, operatorToken) && - astUtils.isTokenOnSameLine(operatorToken, rightToken)) { + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks the operator placement + * @param {ASTNode} node The node to check + * @param {ASTNode} leftSide The node that comes before the operator in `node` + * @private + * @returns {void} + */ + function validateNode(node, leftSide) { + var leftToken = context.getLastToken(leftSide); + var operatorToken = context.getTokenAfter(leftToken); + + // When the left part of a binary expression is a single expression wrapped in + // parentheses (ex: `(a) + b`), leftToken will be the last token of the expression + // and operatorToken will be the closing parenthesis. + // The leftToken should be the last closing parenthesis, and the operatorToken + // should be the token right after that. + while (operatorToken.value === ")") { + leftToken = operatorToken; + operatorToken = context.getTokenAfter(operatorToken); + } - return; + var rightToken = context.getTokenAfter(operatorToken); + var operator = operatorToken.value; + var operatorStyleOverride = styleOverrides[operator]; + var style = operatorStyleOverride || globalStyle; - } else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) && - !astUtils.isTokenOnSameLine(operatorToken, rightToken)) { + // if single line + if (astUtils.isTokenOnSameLine(leftToken, operatorToken) && + astUtils.isTokenOnSameLine(operatorToken, rightToken)) { - // lone operator - context.report(node, { - line: operatorToken.loc.end.line, - column: operatorToken.loc.end.column - }, "Bad line breaking before and after '" + operator + "'."); + return; - } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) { + } else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) && + !astUtils.isTokenOnSameLine(operatorToken, rightToken)) { - context.report(node, { - line: operatorToken.loc.end.line, - column: operatorToken.loc.end.column - }, "'" + operator + "' should be placed at the beginning of the line."); + // lone operator + context.report(node, { + line: operatorToken.loc.end.line, + column: operatorToken.loc.end.column + }, "Bad line breaking before and after '" + operator + "'."); - } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) { + } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) { - context.report(node, { - line: operatorToken.loc.end.line, - column: operatorToken.loc.end.column - }, "'" + operator + "' should be placed at the end of the line."); + context.report(node, { + line: operatorToken.loc.end.line, + column: operatorToken.loc.end.column + }, "'" + operator + "' should be placed at the beginning of the line."); - } else if (style === "none") { + } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) { - context.report(node, { - line: operatorToken.loc.end.line, - column: operatorToken.loc.end.column - }, "There should be no line break before or after '" + operator + "'"); + context.report(node, { + line: operatorToken.loc.end.line, + column: operatorToken.loc.end.column + }, "'" + operator + "' should be placed at the end of the line."); - } - } + } else if (style === "none") { - /** - * Validates a binary expression using `validateNode` - * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated - * @returns {void} - */ - function validateBinaryExpression(node) { - validateNode(node, node.left); - } + context.report(node, { + line: operatorToken.loc.end.line, + column: operatorToken.loc.end.column + }, "There should be no line break before or after '" + operator + "'"); - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "BinaryExpression": validateBinaryExpression, - "LogicalExpression": validateBinaryExpression, - "AssignmentExpression": validateBinaryExpression, - "VariableDeclarator": function(node) { - if (node.init) { - validateNode(node, node.id); } - }, - "ConditionalExpression": function(node) { - validateNode(node, node.test); - validateNode(node, node.consequent); } - }; -}; -module.exports.schema = [ - { - "enum": ["after", "before", "none", null] - }, - { - "type": "object", - "properties": { - "overrides": { - "type": "object", - "properties": { - "anyOf": { - "type": "string", - "enum": ["after", "before", "none", "ignore"] - } + /** + * Validates a binary expression using `validateNode` + * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated + * @returns {void} + */ + function validateBinaryExpression(node) { + validateNode(node, node.left); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + BinaryExpression: validateBinaryExpression, + LogicalExpression: validateBinaryExpression, + AssignmentExpression: validateBinaryExpression, + VariableDeclarator: function(node) { + if (node.init) { + validateNode(node, node.id); } + }, + ConditionalExpression: function(node) { + validateNode(node, node.test); + validateNode(node, node.consequent); } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/padded-blocks.js b/tools/eslint/lib/rules/padded-blocks.js index f245a858f0..39a2b07c8d 100644 --- a/tools/eslint/lib/rules/padded-blocks.js +++ b/tools/eslint/lib/rules/padded-blocks.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to ensure blank lines within blocks. * @author Mathias Schreck - * @copyright 2014 Mathias Schreck. All rights reserved. */ "use strict"; @@ -10,209 +9,219 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = {}; - var config = context.options[0] || "always"; +module.exports = { + meta: { + docs: { + description: "require or disallow padding within blocks", + category: "Stylistic Issues", + recommended: false + }, - if (typeof config === "string") { - options.blocks = config === "always"; - } else { - if (config.hasOwnProperty("blocks")) { - options.blocks = config.blocks === "always"; - } - if (config.hasOwnProperty("switches")) { - options.switches = config.switches === "always"; - } - if (config.hasOwnProperty("classes")) { - options.classes = config.classes === "always"; + schema: [ + { + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + blocks: { + enum: ["always", "never"] + }, + switches: { + enum: ["always", "never"] + }, + classes: { + enum: ["always", "never"] + } + }, + additionalProperties: false, + minProperties: 1 + } + ] + } + ] + }, + + create: function(context) { + var options = {}; + var config = context.options[0] || "always"; + + if (typeof config === "string") { + options.blocks = config === "always"; + } else { + if (config.hasOwnProperty("blocks")) { + options.blocks = config.blocks === "always"; + } + if (config.hasOwnProperty("switches")) { + options.switches = config.switches === "always"; + } + if (config.hasOwnProperty("classes")) { + options.classes = config.classes === "always"; + } } - } - var ALWAYS_MESSAGE = "Block must be padded by blank lines.", - NEVER_MESSAGE = "Block must not be padded by blank lines."; + var ALWAYS_MESSAGE = "Block must be padded by blank lines.", + NEVER_MESSAGE = "Block must not be padded by blank lines."; - var sourceCode = context.getSourceCode(); + var sourceCode = context.getSourceCode(); - /** - * Gets the open brace token from a given node. - * @param {ASTNode} node - A BlockStatement or SwitchStatement node from which to get the open brace. - * @returns {Token} The token of the open brace. - */ - function getOpenBrace(node) { - if (node.type === "SwitchStatement") { - return sourceCode.getTokenBefore(node.cases[0]); + /** + * Gets the open brace token from a given node. + * @param {ASTNode} node - A BlockStatement or SwitchStatement node from which to get the open brace. + * @returns {Token} The token of the open brace. + */ + function getOpenBrace(node) { + if (node.type === "SwitchStatement") { + return sourceCode.getTokenBefore(node.cases[0]); + } + return sourceCode.getFirstToken(node); } - return sourceCode.getFirstToken(node); - } - - /** - * Checks if the given parameter is a comment node - * @param {ASTNode|Token} node An AST node or token - * @returns {boolean} True if node is a comment - */ - function isComment(node) { - return node.type === "Line" || node.type === "Block"; - } - /** - * Checks if the given token has a blank line after it. - * @param {Token} token The token to check. - * @returns {boolean} Whether or not the token is followed by a blank line. - */ - function isTokenTopPadded(token) { - var tokenStartLine = token.loc.start.line, - expectedFirstLine = tokenStartLine + 2, - first, - firstLine; - - first = token; - do { - first = sourceCode.getTokenOrCommentAfter(first); - } while (isComment(first) && first.loc.start.line === tokenStartLine); - - firstLine = first.loc.start.line; - return expectedFirstLine <= firstLine; - } + /** + * Checks if the given parameter is a comment node + * @param {ASTNode|Token} node An AST node or token + * @returns {boolean} True if node is a comment + */ + function isComment(node) { + return node.type === "Line" || node.type === "Block"; + } - /** - * Checks if the given token is preceeded by a blank line. - * @param {Token} token The token to check - * @returns {boolean} Whether or not the token is preceeded by a blank line - */ - function isTokenBottomPadded(token) { - var blockEnd = token.loc.end.line, - expectedLastLine = blockEnd - 2, - last, - lastLine; - - last = token; - do { - last = sourceCode.getTokenOrCommentBefore(last); - } while (isComment(last) && last.loc.end.line === blockEnd); - - lastLine = last.loc.end.line; - return lastLine <= expectedLastLine; - } + /** + * Checks if the given token has a blank line after it. + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the token is followed by a blank line. + */ + function isTokenTopPadded(token) { + var tokenStartLine = token.loc.start.line, + expectedFirstLine = tokenStartLine + 2, + first, + firstLine; + + first = token; + do { + first = sourceCode.getTokenOrCommentAfter(first); + } while (isComment(first) && first.loc.start.line === tokenStartLine); + + firstLine = first.loc.start.line; + return expectedFirstLine <= firstLine; + } - /** - * Checks if a node should be padded, according to the rule config. - * @param {ASTNode} node The AST node to check. - * @returns {boolean} True if the node should be padded, false otherwise. - */ - function requirePaddingFor(node) { - switch (node.type) { - case "BlockStatement": - return options.blocks; - case "SwitchStatement": - return options.switches; - case "ClassBody": - return options.classes; - - /* istanbul ignore next */ - default: - throw new Error("unreachable"); + /** + * Checks if the given token is preceeded by a blank line. + * @param {Token} token The token to check + * @returns {boolean} Whether or not the token is preceeded by a blank line + */ + function isTokenBottomPadded(token) { + var blockEnd = token.loc.end.line, + expectedLastLine = blockEnd - 2, + last, + lastLine; + + last = token; + do { + last = sourceCode.getTokenOrCommentBefore(last); + } while (isComment(last) && last.loc.end.line === blockEnd); + + lastLine = last.loc.end.line; + return lastLine <= expectedLastLine; } - } - /** - * Checks the given BlockStatement node to be padded if the block is not empty. - * @param {ASTNode} node The AST node of a BlockStatement. - * @returns {void} undefined. - */ - function checkPadding(node) { - var openBrace = getOpenBrace(node), - closeBrace = sourceCode.getLastToken(node), - blockHasTopPadding = isTokenTopPadded(openBrace), - blockHasBottomPadding = isTokenBottomPadded(closeBrace); - - if (requirePaddingFor(node)) { - if (!blockHasTopPadding) { - context.report({ - node: node, - loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column }, - message: ALWAYS_MESSAGE - }); - } - if (!blockHasBottomPadding) { - context.report({ - node: node, - loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 }, - message: ALWAYS_MESSAGE - }); - } - } else { - if (blockHasTopPadding) { - context.report({ - node: node, - loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column }, - message: NEVER_MESSAGE - }); + /** + * Checks if a node should be padded, according to the rule config. + * @param {ASTNode} node The AST node to check. + * @returns {boolean} True if the node should be padded, false otherwise. + */ + function requirePaddingFor(node) { + switch (node.type) { + case "BlockStatement": + return options.blocks; + case "SwitchStatement": + return options.switches; + case "ClassBody": + return options.classes; + + /* istanbul ignore next */ + default: + throw new Error("unreachable"); } + } - if (blockHasBottomPadding) { - context.report({ - node: node, - loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 }, - message: NEVER_MESSAGE - }); + /** + * Checks the given BlockStatement node to be padded if the block is not empty. + * @param {ASTNode} node The AST node of a BlockStatement. + * @returns {void} undefined. + */ + function checkPadding(node) { + var openBrace = getOpenBrace(node), + closeBrace = sourceCode.getLastToken(node), + blockHasTopPadding = isTokenTopPadded(openBrace), + blockHasBottomPadding = isTokenBottomPadded(closeBrace); + + if (requirePaddingFor(node)) { + if (!blockHasTopPadding) { + context.report({ + node: node, + loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column }, + message: ALWAYS_MESSAGE + }); + } + if (!blockHasBottomPadding) { + context.report({ + node: node, + loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 }, + message: ALWAYS_MESSAGE + }); + } + } else { + if (blockHasTopPadding) { + context.report({ + node: node, + loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column }, + message: NEVER_MESSAGE + }); + } + + if (blockHasBottomPadding) { + context.report({ + node: node, + loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 }, + message: NEVER_MESSAGE + }); + } } } - } - - var rule = {}; - if (options.hasOwnProperty("switches")) { - rule.SwitchStatement = function(node) { - if (node.cases.length === 0) { - return; - } - checkPadding(node); - }; - } + var rule = {}; - if (options.hasOwnProperty("blocks")) { - rule.BlockStatement = function(node) { - if (node.body.length === 0) { - return; - } - checkPadding(node); - }; - } + if (options.hasOwnProperty("switches")) { + rule.SwitchStatement = function(node) { + if (node.cases.length === 0) { + return; + } + checkPadding(node); + }; + } - if (options.hasOwnProperty("classes")) { - rule.ClassBody = function(node) { - if (node.body.length === 0) { - return; - } - checkPadding(node); - }; - } + if (options.hasOwnProperty("blocks")) { + rule.BlockStatement = function(node) { + if (node.body.length === 0) { + return; + } + checkPadding(node); + }; + } - return rule; -}; + if (options.hasOwnProperty("classes")) { + rule.ClassBody = function(node) { + if (node.body.length === 0) { + return; + } + checkPadding(node); + }; + } -module.exports.schema = [ - { - "oneOf": [ - { - "enum": ["always", "never"] - }, - { - "type": "object", - "properties": { - "blocks": { - "enum": ["always", "never"] - }, - "switches": { - "enum": ["always", "never"] - }, - "classes": { - "enum": ["always", "never"] - } - }, - "additionalProperties": false, - "minProperties": 1 - } - ] + return rule; } -]; +}; diff --git a/tools/eslint/lib/rules/prefer-arrow-callback.js b/tools/eslint/lib/rules/prefer-arrow-callback.js index a51fa6026e..4c4c2e6f2b 100644 --- a/tools/eslint/lib/rules/prefer-arrow-callback.js +++ b/tools/eslint/lib/rules/prefer-arrow-callback.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to suggest using arrow functions as callbacks. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -27,11 +26,6 @@ function isFunctionName(variable) { * @returns {boolean} `true` if the node is the specific value. */ function checkMetaProperty(node, metaName, propertyName) { - - // TODO: Remove this if block after https://github.com/eslint/espree/issues/206 was fixed. - if (typeof node.meta === "string") { - return node.meta === metaName && node.property === propertyName; - } return node.meta.name === metaName && node.property.name === propertyName; } @@ -125,104 +119,136 @@ function getCallbackInfo(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /* - * {Array<{this: boolean, super: boolean, meta: boolean}>} - * - this - A flag which shows there are one or more ThisExpression. - * - super - A flag which shows there are one or more Super. - * - meta - A flag which shows there are one or more MethProperty. - */ - var stack = []; - - /** - * Pushes new function scope with all `false` flags. - * @returns {void} - */ - function enterScope() { - stack.push({this: false, super: false, meta: false}); - } +module.exports = { + meta: { + docs: { + description: "require arrow functions as callbacks", + category: "ECMAScript 6", + recommended: false + }, - /** - * Pops a function scope from the stack. - * @returns {{this: boolean, super: boolean, meta: boolean}} The information of the last scope. - */ - function exitScope() { - return stack.pop(); - } + schema: [ + { + type: "object", + properties: { + allowNamedFunctions: { + type: "boolean" + }, + allowUnboundThis: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var options = context.options[0] || {}; + + var allowUnboundThis = options.allowUnboundThis !== false; // default to true + var allowNamedFunctions = options.allowNamedFunctions; + + /* + * {Array<{this: boolean, super: boolean, meta: boolean}>} + * - this - A flag which shows there are one or more ThisExpression. + * - super - A flag which shows there are one or more Super. + * - meta - A flag which shows there are one or more MethProperty. + */ + var stack = []; + + /** + * Pushes new function scope with all `false` flags. + * @returns {void} + */ + function enterScope() { + stack.push({this: false, super: false, meta: false}); + } - return { + /** + * Pops a function scope from the stack. + * @returns {{this: boolean, super: boolean, meta: boolean}} The information of the last scope. + */ + function exitScope() { + return stack.pop(); + } - // Reset internal state. - Program: function() { - stack = []; - }, + return { - // If there are below, it cannot replace with arrow functions merely. - ThisExpression: function() { - var info = stack[stack.length - 1]; + // Reset internal state. + Program: function() { + stack = []; + }, - if (info) { - info.this = true; - } - }, + // If there are below, it cannot replace with arrow functions merely. + ThisExpression: function() { + var info = stack[stack.length - 1]; - Super: function() { - var info = stack[stack.length - 1]; + if (info) { + info.this = true; + } + }, - if (info) { - info.super = true; - } - }, + Super: function() { + var info = stack[stack.length - 1]; - MetaProperty: function(node) { - var info = stack[stack.length - 1]; + if (info) { + info.super = true; + } + }, - if (info && checkMetaProperty(node, "new", "target")) { - info.meta = true; - } - }, + MetaProperty: function(node) { + var info = stack[stack.length - 1]; - // To skip nested scopes. - FunctionDeclaration: enterScope, - "FunctionDeclaration:exit": exitScope, + if (info && checkMetaProperty(node, "new", "target")) { + info.meta = true; + } + }, - // Main. - FunctionExpression: enterScope, - "FunctionExpression:exit": function(node) { - var scopeInfo = exitScope(); + // To skip nested scopes. + FunctionDeclaration: enterScope, + "FunctionDeclaration:exit": exitScope, - // Skip generators. - if (node.generator) { - return; - } + // Main. + FunctionExpression: enterScope, + "FunctionExpression:exit": function(node) { + var scopeInfo = exitScope(); - // Skip recursive functions. - var nameVar = context.getDeclaredVariables(node)[0]; + // Skip named function expressions + if (allowNamedFunctions && node.id && node.id.name) { + return; + } - if (isFunctionName(nameVar) && nameVar.references.length > 0) { - return; - } + // Skip generators. + if (node.generator) { + return; + } - // Skip if it's using arguments. - var variable = getVariableOfArguments(context.getScope()); + // Skip recursive functions. + var nameVar = context.getDeclaredVariables(node)[0]; - if (variable && variable.references.length > 0) { - return; - } + if (isFunctionName(nameVar) && nameVar.references.length > 0) { + return; + } - // Reports if it's a callback which can replace with arrows. - var callbackInfo = getCallbackInfo(node); + // Skip if it's using arguments. + var variable = getVariableOfArguments(context.getScope()); - if (callbackInfo.isCallback && - (!scopeInfo.this || callbackInfo.isLexicalThis) && - !scopeInfo.super && - !scopeInfo.meta - ) { - context.report(node, "Unexpected function expression."); + if (variable && variable.references.length > 0) { + return; + } + + // Reports if it's a callback which can replace with arrows. + var callbackInfo = getCallbackInfo(node); + + if (callbackInfo.isCallback && + (!allowUnboundThis || !scopeInfo.this || callbackInfo.isLexicalThis) && + !scopeInfo.super && + !scopeInfo.meta + ) { + context.report(node, "Unexpected function expression."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/prefer-const.js b/tools/eslint/lib/rules/prefer-const.js index ae55b51645..668453520f 100644 --- a/tools/eslint/lib/rules/prefer-const.js +++ b/tools/eslint/lib/rules/prefer-const.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to suggest using of const declaration for variables that are never reassigned after declared. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -179,90 +178,100 @@ function groupByDestructuring(variables) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0] || {}; - var checkingMixedDestructuring = options.destructuring !== "all"; - var variables = null; - - /** - * Reports a given reference. - * - * @param {escope.Reference} reference - A reference to report. - * @returns {void} - */ - function report(reference) { - var id = reference.identifier; - - context.report({ - node: id, - message: "'{{name}}' is never reassigned, use 'const' instead.", - data: id - }); - } +module.exports = { + meta: { + docs: { + description: "require `const` declarations for variables that are never reassigned after declared", + category: "ECMAScript 6", + recommended: false + }, - /** - * Reports a given variable if the variable should be declared as const. - * - * @param {escope.Variable} variable - A variable to report. - * @returns {void} - */ - function checkVariable(variable) { - var writer = getWriteReferenceIfShouldBeConst(variable); + schema: [ + { + type: "object", + properties: { + destructuring: {enum: ["any", "all"]} + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var options = context.options[0] || {}; + var checkingMixedDestructuring = options.destructuring !== "all"; + var variables = null; + + /** + * Reports a given reference. + * + * @param {escope.Reference} reference - A reference to report. + * @returns {void} + */ + function report(reference) { + var id = reference.identifier; - if (writer) { - report(writer); + context.report({ + node: id, + message: "'{{name}}' is never reassigned, use 'const' instead.", + data: id + }); } - } - /** - * Reports given references if all of the reference should be declared as - * const. - * - * The argument 'writers' is an array of references. - * This reference is the result of - * 'getWriteReferenceIfShouldBeConst(variable)', so it's nullable. - * In simple declaration or assignment cases, the length of the array is 1. - * In destructuring cases, the length of the array can be 2 or more. - * - * @param {(escope.Reference|null)[]} writers - References which are grouped - * by destructuring to report. - * @returns {void} - */ - function checkGroup(writers) { - if (writers.every(Boolean)) { - writers.forEach(report); + /** + * Reports a given variable if the variable should be declared as const. + * + * @param {escope.Variable} variable - A variable to report. + * @returns {void} + */ + function checkVariable(variable) { + var writer = getWriteReferenceIfShouldBeConst(variable); + + if (writer) { + report(writer); + } } - } - return { - "Program": function() { - variables = []; - }, - - "Program:exit": function() { - if (checkingMixedDestructuring) { - variables.forEach(checkVariable); - } else { - groupByDestructuring(variables).forEach(checkGroup); + /** + * Reports given references if all of the reference should be declared as + * const. + * + * The argument 'writers' is an array of references. + * This reference is the result of + * 'getWriteReferenceIfShouldBeConst(variable)', so it's nullable. + * In simple declaration or assignment cases, the length of the array is 1. + * In destructuring cases, the length of the array can be 2 or more. + * + * @param {(escope.Reference|null)[]} writers - References which are grouped + * by destructuring to report. + * @returns {void} + */ + function checkGroup(writers) { + if (writers.every(Boolean)) { + writers.forEach(report); } + } - variables = null; - }, + return { + Program: function() { + variables = []; + }, - "VariableDeclaration": function(node) { - if (node.kind === "let" && !isInitOfForStatement(node)) { - pushAll(variables, context.getDeclaredVariables(node)); - } - } - }; -}; + "Program:exit": function() { + if (checkingMixedDestructuring) { + variables.forEach(checkVariable); + } else { + groupByDestructuring(variables).forEach(checkGroup); + } -module.exports.schema = [ - { - type: "object", - properties: { - destructuring: {enum: ["any", "all"]} - }, - additionalProperties: false + variables = null; + }, + + VariableDeclaration: function(node) { + if (node.kind === "let" && !isInitOfForStatement(node)) { + pushAll(variables, context.getDeclaredVariables(node)); + } + } + }; } -]; +}; diff --git a/tools/eslint/lib/rules/prefer-reflect.js b/tools/eslint/lib/rules/prefer-reflect.js index 83b805a30d..38bb093ba6 100644 --- a/tools/eslint/lib/rules/prefer-reflect.js +++ b/tools/eslint/lib/rules/prefer-reflect.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to suggest using "Reflect" api over Function/Object methods * @author Keith Cirkel - * @copyright 2015 Keith Cirkel. All rights reserved. */ "use strict"; @@ -9,94 +8,104 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var existingNames = { - "apply": "Function.prototype.apply", - "call": "Function.prototype.call", - "defineProperty": "Object.defineProperty", - "getOwnPropertyDescriptor": "Object.getOwnPropertyDescriptor", - "getPrototypeOf": "Object.getPrototypeOf", - "setPrototypeOf": "Object.setPrototypeOf", - "isExtensible": "Object.isExtensible", - "getOwnPropertyNames": "Object.getOwnPropertyNames", - "preventExtensions": "Object.preventExtensions" - }; +module.exports = { + meta: { + docs: { + description: "require `Reflect` methods where applicable", + category: "ECMAScript 6", + recommended: false + }, - var reflectSubsitutes = { - "apply": "Reflect.apply", - "call": "Reflect.apply", - "defineProperty": "Reflect.defineProperty", - "getOwnPropertyDescriptor": "Reflect.getOwnPropertyDescriptor", - "getPrototypeOf": "Reflect.getPrototypeOf", - "setPrototypeOf": "Reflect.setPrototypeOf", - "isExtensible": "Reflect.isExtensible", - "getOwnPropertyNames": "Reflect.getOwnPropertyNames", - "preventExtensions": "Reflect.preventExtensions" - }; + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + enum: [ + "apply", + "call", + "delete", + "defineProperty", + "getOwnPropertyDescriptor", + "getPrototypeOf", + "setPrototypeOf", + "isExtensible", + "getOwnPropertyNames", + "preventExtensions" + ] + }, + uniqueItems: true + } + }, + additionalProperties: false + } + ] + }, - var exceptions = (context.options[0] || {}).exceptions || []; + create: function(context) { + var existingNames = { + apply: "Function.prototype.apply", + call: "Function.prototype.call", + defineProperty: "Object.defineProperty", + getOwnPropertyDescriptor: "Object.getOwnPropertyDescriptor", + getPrototypeOf: "Object.getPrototypeOf", + setPrototypeOf: "Object.setPrototypeOf", + isExtensible: "Object.isExtensible", + getOwnPropertyNames: "Object.getOwnPropertyNames", + preventExtensions: "Object.preventExtensions" + }; - /** - * Reports the Reflect violation based on the `existing` and `substitute` - * @param {Object} node The node that violates the rule. - * @param {string} existing The existing method name that has been used. - * @param {string} substitute The Reflect substitute that should be used. - * @returns {void} - */ - function report(node, existing, substitute) { - context.report(node, "Avoid using {{existing}}, instead use {{substitute}}", { - existing: existing, - substitute: substitute - }); - } + var reflectSubsitutes = { + apply: "Reflect.apply", + call: "Reflect.apply", + defineProperty: "Reflect.defineProperty", + getOwnPropertyDescriptor: "Reflect.getOwnPropertyDescriptor", + getPrototypeOf: "Reflect.getPrototypeOf", + setPrototypeOf: "Reflect.setPrototypeOf", + isExtensible: "Reflect.isExtensible", + getOwnPropertyNames: "Reflect.getOwnPropertyNames", + preventExtensions: "Reflect.preventExtensions" + }; - return { - "CallExpression": function(node) { - var methodName = (node.callee.property || {}).name; - var isReflectCall = (node.callee.object || {}).name === "Reflect"; - var hasReflectSubsitute = reflectSubsitutes.hasOwnProperty(methodName); - var userConfiguredException = exceptions.indexOf(methodName) !== -1; - - if (hasReflectSubsitute && !isReflectCall && !userConfiguredException) { - report(node, existingNames[methodName], reflectSubsitutes[methodName]); - } - }, - "UnaryExpression": function(node) { - var isDeleteOperator = node.operator === "delete"; - var targetsIdentifier = node.argument.type === "Identifier"; - var userConfiguredException = exceptions.indexOf("delete") !== -1; + var exceptions = (context.options[0] || {}).exceptions || []; - if (isDeleteOperator && !targetsIdentifier && !userConfiguredException) { - report(node, "the delete keyword", "Reflect.deleteProperty"); - } + /** + * Reports the Reflect violation based on the `existing` and `substitute` + * @param {Object} node The node that violates the rule. + * @param {string} existing The existing method name that has been used. + * @param {string} substitute The Reflect substitute that should be used. + * @returns {void} + */ + function report(node, existing, substitute) { + context.report(node, "Avoid using {{existing}}, instead use {{substitute}}", { + existing: existing, + substitute: substitute + }); } - }; -}; + return { + CallExpression: function(node) { + var methodName = (node.callee.property || {}).name; + var isReflectCall = (node.callee.object || {}).name === "Reflect"; + var hasReflectSubsitute = reflectSubsitutes.hasOwnProperty(methodName); + var userConfiguredException = exceptions.indexOf(methodName) !== -1; -module.exports.schema = [ - { - "type": "object", - "properties": { - "exceptions": { - "type": "array", - "items": { - "enum": [ - "apply", - "call", - "delete", - "defineProperty", - "getOwnPropertyDescriptor", - "getPrototypeOf", - "setPrototypeOf", - "isExtensible", - "getOwnPropertyNames", - "preventExtensions" - ] - }, - "uniqueItems": true + if (hasReflectSubsitute && !isReflectCall && !userConfiguredException) { + report(node, existingNames[methodName], reflectSubsitutes[methodName]); + } + }, + UnaryExpression: function(node) { + var isDeleteOperator = node.operator === "delete"; + var targetsIdentifier = node.argument.type === "Identifier"; + var userConfiguredException = exceptions.indexOf("delete") !== -1; + + if (isDeleteOperator && !targetsIdentifier && !userConfiguredException) { + report(node, "the delete keyword", "Reflect.deleteProperty"); + } } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/prefer-rest-params.js b/tools/eslint/lib/rules/prefer-rest-params.js index d52c1164cb..0ce1d8a122 100644 --- a/tools/eslint/lib/rules/prefer-rest-params.js +++ b/tools/eslint/lib/rules/prefer-rest-params.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -38,38 +36,48 @@ function getVariableOfArguments(scope) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require rest parameters instead of `arguments`", + category: "ECMAScript 6", + recommended: false + }, - /** - * Reports a given reference. - * - * @param {escope.Reference} reference - A reference to report. - * @returns {void} - */ - function report(reference) { - context.report({ - node: reference.identifier, - message: "Use the rest parameters instead of 'arguments'." - }); - } + schema: [] + }, - /** - * Reports references of the implicit `arguments` variable if exist. - * - * @returns {void} - */ - function checkForArguments() { - var argumentsVar = getVariableOfArguments(context.getScope()); + create: function(context) { - if (argumentsVar) { - argumentsVar.references.forEach(report); + /** + * Reports a given reference. + * + * @param {escope.Reference} reference - A reference to report. + * @returns {void} + */ + function report(reference) { + context.report({ + node: reference.identifier, + message: "Use the rest parameters instead of 'arguments'." + }); } - } - return { - FunctionDeclaration: checkForArguments, - FunctionExpression: checkForArguments - }; -}; + /** + * Reports references of the implicit `arguments` variable if exist. + * + * @returns {void} + */ + function checkForArguments() { + var argumentsVar = getVariableOfArguments(context.getScope()); -module.exports.schema = []; + if (argumentsVar) { + argumentsVar.references.forEach(report); + } + } + + return { + FunctionDeclaration: checkForArguments, + FunctionExpression: checkForArguments + }; + } +}; diff --git a/tools/eslint/lib/rules/prefer-spread.js b/tools/eslint/lib/rules/prefer-spread.js index de0cbbe532..79c7eb0243 100644 --- a/tools/eslint/lib/rules/prefer-spread.js +++ b/tools/eslint/lib/rules/prefer-spread.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to suggest using of the spread operator instead of `.apply()`. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -71,22 +70,32 @@ function isValidThisArg(expectedThis, thisArg, context) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - return { - "CallExpression": function(node) { - if (!isVariadicApplyCalling(node)) { - return; - } +module.exports = { + meta: { + docs: { + description: "require spread operators instead of `.apply()`", + category: "ECMAScript 6", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + return { + CallExpression: function(node) { + if (!isVariadicApplyCalling(node)) { + return; + } - var applied = node.callee.object; - var expectedThis = (applied.type === "MemberExpression") ? applied.object : null; - var thisArg = node.arguments[0]; + var applied = node.callee.object; + var expectedThis = (applied.type === "MemberExpression") ? applied.object : null; + var thisArg = node.arguments[0]; - if (isValidThisArg(expectedThis, thisArg, context)) { - context.report(node, "use the spread operator instead of the '.apply()'."); + if (isValidThisArg(expectedThis, thisArg, context)) { + context.report(node, "use the spread operator instead of the '.apply()'."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/prefer-template.js b/tools/eslint/lib/rules/prefer-template.js index 19b3d10c0a..0165aaecd6 100644 --- a/tools/eslint/lib/rules/prefer-template.js +++ b/tools/eslint/lib/rules/prefer-template.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to suggest using template literals instead of string concatenation. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -55,43 +54,53 @@ function hasNonStringLiteral(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var done = Object.create(null); - - /** - * Reports if a given node is string concatenation with non string literals. - * - * @param {ASTNode} node - A node to check. - * @returns {void} - */ - function checkForStringConcat(node) { - if (!astUtils.isStringLiteral(node) || !isConcatenation(node.parent)) { - return; - } - - var topBinaryExpr = getTopConcatBinaryExpression(node.parent); +module.exports = { + meta: { + docs: { + description: "require template literals instead of string concatenation", + category: "ECMAScript 6", + recommended: false + }, - // Checks whether or not this node had been checked already. - if (done[topBinaryExpr.range[0]]) { - return; + schema: [] + }, + + create: function(context) { + var done = Object.create(null); + + /** + * Reports if a given node is string concatenation with non string literals. + * + * @param {ASTNode} node - A node to check. + * @returns {void} + */ + function checkForStringConcat(node) { + if (!astUtils.isStringLiteral(node) || !isConcatenation(node.parent)) { + return; + } + + var topBinaryExpr = getTopConcatBinaryExpression(node.parent); + + // Checks whether or not this node had been checked already. + if (done[topBinaryExpr.range[0]]) { + return; + } + done[topBinaryExpr.range[0]] = true; + + if (hasNonStringLiteral(topBinaryExpr)) { + context.report( + topBinaryExpr, + "Unexpected string concatenation."); + } } - done[topBinaryExpr.range[0]] = true; - if (hasNonStringLiteral(topBinaryExpr)) { - context.report( - topBinaryExpr, - "Unexpected string concatenation."); - } - } + return { + Program: function() { + done = Object.create(null); + }, - return { - Program: function() { - done = Object.create(null); - }, - - Literal: checkForStringConcat, - TemplateLiteral: checkForStringConcat - }; + Literal: checkForStringConcat, + TemplateLiteral: checkForStringConcat + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/quote-props.js b/tools/eslint/lib/rules/quote-props.js index f3453adb06..1fd2e74a5a 100644 --- a/tools/eslint/lib/rules/quote-props.js +++ b/tools/eslint/lib/rules/quote-props.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag non-quoted property names in object literals. * @author Mathias Bynens - * @copyright 2014 Brandon Mills. All rights reserved. - * @copyright 2015 Tomasz Olędzki. All rights reserved. */ "use strict"; @@ -17,207 +15,217 @@ var espree = require("espree"), // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var MODE = context.options[0], - KEYWORDS = context.options[1] && context.options[1].keywords, - CHECK_UNNECESSARY = !context.options[1] || context.options[1].unnecessary !== false, - NUMBERS = context.options[1] && context.options[1].numbers, - - MESSAGE_UNNECESSARY = "Unnecessarily quoted property '{{property}}' found.", - MESSAGE_UNQUOTED = "Unquoted property '{{property}}' found.", - MESSAGE_NUMERIC = "Unquoted number literal '{{property}}' used as key.", - MESSAGE_RESERVED = "Unquoted reserved word '{{property}}' used as key."; - - - /** - * Checks whether a certain string constitutes an ES3 token - * @param {string} tokenStr - The string to be checked. - * @returns {boolean} `true` if it is an ES3 token. - */ - function isKeyword(tokenStr) { - return keywords.indexOf(tokenStr) >= 0; - } - - /** - * Checks if an espree-tokenized key has redundant quotes (i.e. whether quotes are unnecessary) - * @param {string} rawKey The raw key value from the source - * @param {espreeTokens} tokens The espree-tokenized node key - * @param {boolean} [skipNumberLiterals=false] Indicates whether number literals should be checked - * @returns {boolean} Whether or not a key has redundant quotes. - * @private - */ - function areQuotesRedundant(rawKey, tokens, skipNumberLiterals) { - return tokens.length === 1 && tokens[0].start === 0 && tokens[0].end === rawKey.length && - (["Identifier", "Keyword", "Null", "Boolean"].indexOf(tokens[0].type) >= 0 || - (tokens[0].type === "Numeric" && !skipNumberLiterals && "" + +tokens[0].value === tokens[0].value)); - } +module.exports = { + meta: { + docs: { + description: "require quotes around object literal property names", + category: "Stylistic Issues", + recommended: false + }, - /** - * Ensures that a property's key is quoted only when necessary - * @param {ASTNode} node Property AST node - * @returns {void} - */ - function checkUnnecessaryQuotes(node) { - var key = node.key, - isKeywordToken, - tokens; - - if (node.method || node.computed || node.shorthand) { - return; + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always", "as-needed", "consistent", "consistent-as-needed"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["always", "as-needed", "consistent", "consistent-as-needed"] + }, + { + type: "object", + properties: { + keywords: { + type: "boolean" + }, + unnecessary: { + type: "boolean" + }, + numbers: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] } + }, - if (key.type === "Literal" && typeof key.value === "string") { - try { - tokens = espree.tokenize(key.value); - } catch (e) { - return; - } + create: function(context) { - if (tokens.length !== 1) { - return; - } + var MODE = context.options[0], + KEYWORDS = context.options[1] && context.options[1].keywords, + CHECK_UNNECESSARY = !context.options[1] || context.options[1].unnecessary !== false, + NUMBERS = context.options[1] && context.options[1].numbers, - isKeywordToken = isKeyword(tokens[0].value); + MESSAGE_UNNECESSARY = "Unnecessarily quoted property '{{property}}' found.", + MESSAGE_UNQUOTED = "Unquoted property '{{property}}' found.", + MESSAGE_NUMERIC = "Unquoted number literal '{{property}}' used as key.", + MESSAGE_RESERVED = "Unquoted reserved word '{{property}}' used as key."; - if (isKeywordToken && KEYWORDS) { - return; - } - if (CHECK_UNNECESSARY && areQuotesRedundant(key.value, tokens, NUMBERS)) { - context.report(node, MESSAGE_UNNECESSARY, {property: key.value}); - } - } else if (KEYWORDS && key.type === "Identifier" && isKeyword(key.name)) { - context.report(node, MESSAGE_RESERVED, {property: key.name}); - } else if (NUMBERS && key.type === "Literal" && typeof key.value === "number") { - context.report(node, MESSAGE_NUMERIC, {property: key.value}); + /** + * Checks whether a certain string constitutes an ES3 token + * @param {string} tokenStr - The string to be checked. + * @returns {boolean} `true` if it is an ES3 token. + */ + function isKeyword(tokenStr) { + return keywords.indexOf(tokenStr) >= 0; } - } - /** - * Ensures that a property's key is quoted - * @param {ASTNode} node Property AST node - * @returns {void} - */ - function checkOmittedQuotes(node) { - var key = node.key; - - if (!node.method && !node.computed && !node.shorthand && !(key.type === "Literal" && typeof key.value === "string")) { - context.report(node, MESSAGE_UNQUOTED, { - property: key.name || key.value - }); + /** + * Checks if an espree-tokenized key has redundant quotes (i.e. whether quotes are unnecessary) + * @param {string} rawKey The raw key value from the source + * @param {espreeTokens} tokens The espree-tokenized node key + * @param {boolean} [skipNumberLiterals=false] Indicates whether number literals should be checked + * @returns {boolean} Whether or not a key has redundant quotes. + * @private + */ + function areQuotesRedundant(rawKey, tokens, skipNumberLiterals) { + return tokens.length === 1 && tokens[0].start === 0 && tokens[0].end === rawKey.length && + (["Identifier", "Keyword", "Null", "Boolean"].indexOf(tokens[0].type) >= 0 || + (tokens[0].type === "Numeric" && !skipNumberLiterals && "" + +tokens[0].value === tokens[0].value)); } - } - /** - * Ensures that an object's keys are consistently quoted, optionally checks for redundancy of quotes - * @param {ASTNode} node Property AST node - * @param {boolean} checkQuotesRedundancy Whether to check quotes' redundancy - * @returns {void} - */ - function checkConsistency(node, checkQuotesRedundancy) { - var quotes = false, - lackOfQuotes = false, - necessaryQuotes = false; - - node.properties.forEach(function(property) { - var key = property.key, + /** + * Ensures that a property's key is quoted only when necessary + * @param {ASTNode} node Property AST node + * @returns {void} + */ + function checkUnnecessaryQuotes(node) { + var key = node.key, + isKeywordToken, tokens; - if (!key || property.method || property.computed || property.shorthand) { + if (node.method || node.computed || node.shorthand) { return; } if (key.type === "Literal" && typeof key.value === "string") { + try { + tokens = espree.tokenize(key.value); + } catch (e) { + return; + } - quotes = true; + if (tokens.length !== 1) { + return; + } - if (checkQuotesRedundancy) { - try { - tokens = espree.tokenize(key.value); - } catch (e) { - necessaryQuotes = true; - return; - } + isKeywordToken = isKeyword(tokens[0].value); - necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value); + if (isKeywordToken && KEYWORDS) { + return; } - } else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) { - necessaryQuotes = true; - context.report(node, "Properties should be quoted as '{{property}}' is a reserved word.", {property: key.name}); - } else { - lackOfQuotes = true; - } - if (quotes && lackOfQuotes) { - context.report(node, "Inconsistently quoted property '{{key}}' found.", { - key: key.name || key.value - }); + if (CHECK_UNNECESSARY && areQuotesRedundant(key.value, tokens, NUMBERS)) { + context.report(node, MESSAGE_UNNECESSARY, {property: key.value}); + } + } else if (KEYWORDS && key.type === "Identifier" && isKeyword(key.name)) { + context.report(node, MESSAGE_RESERVED, {property: key.name}); + } else if (NUMBERS && key.type === "Literal" && typeof key.value === "number") { + context.report(node, MESSAGE_NUMERIC, {property: key.value}); } - }); - - if (checkQuotesRedundancy && quotes && !necessaryQuotes) { - context.report(node, "Properties shouldn't be quoted as all quotes are redundant."); } - } - return { - "Property": function(node) { - if (MODE === "always" || !MODE) { - checkOmittedQuotes(node); - } - if (MODE === "as-needed") { - checkUnnecessaryQuotes(node); - } - }, - "ObjectExpression": function(node) { - if (MODE === "consistent") { - checkConsistency(node, false); - } - if (MODE === "consistent-as-needed") { - checkConsistency(node, true); + /** + * Ensures that a property's key is quoted + * @param {ASTNode} node Property AST node + * @returns {void} + */ + function checkOmittedQuotes(node) { + var key = node.key; + + if (!node.method && !node.computed && !node.shorthand && !(key.type === "Literal" && typeof key.value === "string")) { + context.report(node, MESSAGE_UNQUOTED, { + property: key.name || key.value + }); } } - }; - -}; -module.exports.schema = { - "anyOf": [ - { - "type": "array", - "items": [ - { - "enum": ["always", "as-needed", "consistent", "consistent-as-needed"] + /** + * Ensures that an object's keys are consistently quoted, optionally checks for redundancy of quotes + * @param {ASTNode} node Property AST node + * @param {boolean} checkQuotesRedundancy Whether to check quotes' redundancy + * @returns {void} + */ + function checkConsistency(node, checkQuotesRedundancy) { + var quotes = false, + lackOfQuotes = false, + necessaryQuotes = false; + + node.properties.forEach(function(property) { + var key = property.key, + tokens; + + if (!key || property.method || property.computed || property.shorthand) { + return; } - ], - "minItems": 0, - "maxItems": 1 - }, - { - "type": "array", - "items": [ - { - "enum": ["always", "as-needed", "consistent", "consistent-as-needed"] - }, - { - "type": "object", - "properties": { - "keywords": { - "type": "boolean" - }, - "unnecessary": { - "type": "boolean" - }, - "numbers": { - "type": "boolean" + + if (key.type === "Literal" && typeof key.value === "string") { + + quotes = true; + + if (checkQuotesRedundancy) { + try { + tokens = espree.tokenize(key.value); + } catch (e) { + necessaryQuotes = true; + return; } - }, - "additionalProperties": false + + necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value); + } + } else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) { + necessaryQuotes = true; + context.report(node, "Properties should be quoted as '{{property}}' is a reserved word.", {property: key.name}); + } else { + lackOfQuotes = true; + } + + if (quotes && lackOfQuotes) { + context.report(node, "Inconsistently quoted property '{{key}}' found.", { + key: key.name || key.value + }); } - ], - "minItems": 0, - "maxItems": 2 + }); + + if (checkQuotesRedundancy && quotes && !necessaryQuotes) { + context.report(node, "Properties shouldn't be quoted as all quotes are redundant."); + } } - ] + + return { + Property: function(node) { + if (MODE === "always" || !MODE) { + checkOmittedQuotes(node); + } + if (MODE === "as-needed") { + checkUnnecessaryQuotes(node); + } + }, + ObjectExpression: function(node) { + if (MODE === "consistent") { + checkConsistency(node, false); + } + if (MODE === "consistent-as-needed") { + checkConsistency(node, true); + } + } + }; + + } }; diff --git a/tools/eslint/lib/rules/quotes.js b/tools/eslint/lib/rules/quotes.js index b5cce4bbfe..d4e9203811 100644 --- a/tools/eslint/lib/rules/quotes.js +++ b/tools/eslint/lib/rules/quotes.js @@ -1,8 +1,6 @@ /** * @fileoverview A rule to choose between single and double quote marks * @author Matt DuVall , Brandon Payton - * @copyright 2013 Matt DuVall. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -18,17 +16,17 @@ var astUtils = require("../ast-utils"); //------------------------------------------------------------------------------ var QUOTE_SETTINGS = { - "double": { + double: { quote: "\"", alternateQuote: "'", description: "doublequote" }, - "single": { + single: { quote: "'", alternateQuote: "\"", description: "singlequote" }, - "backtick": { + backtick: { quote: "`", alternateQuote: "\"", description: "backtick" @@ -74,154 +72,189 @@ var AVOID_ESCAPE = "avoid-escape", // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var quoteOption = context.options[0], - settings = QUOTE_SETTINGS[quoteOption || "double"], - avoidEscape = context.options[1] === AVOID_ESCAPE, - sourceCode = context.getSourceCode(); - - /** - * Determines if a given node is part of JSX syntax. - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node is a JSX node, false if not. - * @private - */ - function isJSXElement(node) { - return node.type.indexOf("JSX") === 0; - } +module.exports = { + meta: { + docs: { + description: "enforce the consistent use of either backticks, double, or single quotes", + category: "Stylistic Issues", + recommended: false + }, - /** - * Checks whether or not a given node is a directive. - * The directive is a `ExpressionStatement` which has only a string literal. - * @param {ASTNode} node - A node to check. - * @returns {boolean} Whether or not the node is a directive. - * @private - */ - function isDirective(node) { - return ( - node.type === "ExpressionStatement" && - node.expression.type === "Literal" && - typeof node.expression.value === "string" - ); - } + fixable: "code", + + schema: [ + { + enum: ["single", "double", "backtick"] + }, + { + anyOf: [ + { + enum: ["avoid-escape"] + }, + { + type: "object", + properties: { + avoidEscape: { + type: "boolean" + }, + allowTemplateLiterals: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + } + ] + }, - /** - * Checks whether or not a given node is a part of directive prologues. - * See also: http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive - * @param {ASTNode} node - A node to check. - * @returns {boolean} Whether or not the node is a part of directive prologues. - * @private - */ - function isPartOfDirectivePrologue(node) { - var block = node.parent.parent; - - if (block.type !== "Program" && (block.type !== "BlockStatement" || !FUNCTION_TYPE.test(block.parent.type))) { - return false; + create: function(context) { + + var quoteOption = context.options[0], + settings = QUOTE_SETTINGS[quoteOption || "double"], + options = context.options[1], + avoidEscape = options && options.avoidEscape === true, + allowTemplateLiterals = options && options.allowTemplateLiterals === true, + sourceCode = context.getSourceCode(); + + // deprecated + if (options === AVOID_ESCAPE) { + avoidEscape = true; + } + + /** + * Determines if a given node is part of JSX syntax. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a JSX node, false if not. + * @private + */ + function isJSXElement(node) { + return node.type.indexOf("JSX") === 0; } - // Check the node is at a prologue. - for (var i = 0; i < block.body.length; ++i) { - var statement = block.body[i]; + /** + * Checks whether or not a given node is a directive. + * The directive is a `ExpressionStatement` which has only a string literal. + * @param {ASTNode} node - A node to check. + * @returns {boolean} Whether or not the node is a directive. + * @private + */ + function isDirective(node) { + return ( + node.type === "ExpressionStatement" && + node.expression.type === "Literal" && + typeof node.expression.value === "string" + ); + } - if (statement === node.parent) { - return true; + /** + * Checks whether or not a given node is a part of directive prologues. + * See also: http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive + * @param {ASTNode} node - A node to check. + * @returns {boolean} Whether or not the node is a part of directive prologues. + * @private + */ + function isPartOfDirectivePrologue(node) { + var block = node.parent.parent; + + if (block.type !== "Program" && (block.type !== "BlockStatement" || !FUNCTION_TYPE.test(block.parent.type))) { + return false; } - if (!isDirective(statement)) { - break; + + // Check the node is at a prologue. + for (var i = 0; i < block.body.length; ++i) { + var statement = block.body[i]; + + if (statement === node.parent) { + return true; + } + if (!isDirective(statement)) { + break; + } } - } - return false; - } + return false; + } - /** - * Checks whether or not a given node is allowed as non backtick. - * @param {ASTNode} node - A node to check. - * @returns {boolean} Whether or not the node is allowed as non backtick. - * @private - */ - function isAllowedAsNonBacktick(node) { - var parent = node.parent; - - switch (parent.type) { - - // Directive Prologues. - case "ExpressionStatement": - return isPartOfDirectivePrologue(node); - - // LiteralPropertyName. - case "Property": - return parent.key === node && !parent.computed; - - // ModuleSpecifier. - case "ImportDeclaration": - case "ExportNamedDeclaration": - case "ExportAllDeclaration": - return parent.source === node; - - // Others don't allow. - default: - return false; + /** + * Checks whether or not a given node is allowed as non backtick. + * @param {ASTNode} node - A node to check. + * @returns {boolean} Whether or not the node is allowed as non backtick. + * @private + */ + function isAllowedAsNonBacktick(node) { + var parent = node.parent; + + switch (parent.type) { + + // Directive Prologues. + case "ExpressionStatement": + return isPartOfDirectivePrologue(node); + + // LiteralPropertyName. + case "Property": + return parent.key === node && !parent.computed; + + // ModuleSpecifier. + case "ImportDeclaration": + case "ExportNamedDeclaration": + case "ExportAllDeclaration": + return parent.source === node; + + // Others don't allow. + default: + return false; + } } - } - return { + return { + + Literal: function(node) { + var val = node.value, + rawVal = node.raw, + isValid; - "Literal": function(node) { - var val = node.value, - rawVal = node.raw, - isValid; + if (settings && typeof val === "string") { + isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) || + isJSXElement(node.parent) || + astUtils.isSurroundedBy(rawVal, settings.quote); - if (settings && typeof val === "string") { - isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) || - isJSXElement(node.parent) || - astUtils.isSurroundedBy(rawVal, settings.quote); + if (!isValid && avoidEscape) { + isValid = astUtils.isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.indexOf(settings.quote) >= 0; + } - if (!isValid && avoidEscape) { - isValid = astUtils.isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.indexOf(settings.quote) >= 0; + if (!isValid) { + context.report({ + node: node, + message: "Strings must use " + settings.description + ".", + fix: function(fixer) { + return fixer.replaceText(node, settings.convert(node.raw)); + } + }); + } } + }, + + TemplateLiteral: function(node) { - if (!isValid) { + // If backticks are expected or it's a tagged template, then this shouldn't throw an errors + if (allowTemplateLiterals || quoteOption === "backtick" || node.parent.type === "TaggedTemplateExpression") { + return; + } + + var shouldWarn = node.quasis.length === 1 && (node.quasis[0].value.cooked.indexOf("\n") === -1); + + if (shouldWarn) { context.report({ node: node, message: "Strings must use " + settings.description + ".", fix: function(fixer) { - return fixer.replaceText(node, settings.convert(node.raw)); + return fixer.replaceText(node, settings.convert(sourceCode.getText(node))); } }); } } - }, - - "TemplateLiteral": function(node) { - - // If backticks are expected or it's a tagged template, then this shouldn't throw an errors - if (quoteOption === "backtick" || node.parent.type === "TaggedTemplateExpression") { - return; - } - - var shouldWarn = node.quasis.length === 1 && (node.quasis[0].value.cooked.indexOf("\n") === -1); + }; - if (shouldWarn) { - context.report({ - node: node, - message: "Strings must use " + settings.description + ".", - fix: function(fixer) { - return fixer.replaceText(node, settings.convert(sourceCode.getText(node))); - } - }); - } - } - }; - -}; - -module.exports.schema = [ - { - "enum": ["single", "double", "backtick"] - }, - { - "enum": ["avoid-escape"] } -]; +}; diff --git a/tools/eslint/lib/rules/radix.js b/tools/eslint/lib/rules/radix.js index 23a2f71d44..05a1c130a9 100644 --- a/tools/eslint/lib/rules/radix.js +++ b/tools/eslint/lib/rules/radix.js @@ -76,86 +76,96 @@ function isDefaultRadix(radix) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var mode = context.options[0] || MODE_ALWAYS; - - /** - * Checks the arguments of a given CallExpression node and reports it if it - * offends this rule. - * - * @param {ASTNode} node - A CallExpression node to check. - * @returns {void} - */ - function checkArguments(node) { - var args = node.arguments; - - switch (args.length) { - case 0: - context.report({ - node: node, - message: "Missing parameters." - }); - break; - - case 1: - if (mode === MODE_ALWAYS) { - context.report({ - node: node, - message: "Missing radix parameter." - }); - } - break; - - default: - if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) { - context.report({ - node: node, - message: "Redundant radix parameter." - }); - } else if (!isValidRadix(args[1])) { +module.exports = { + meta: { + docs: { + description: "enforce the consistent use of the radix argument when using `parseInt()`", + category: "Best Practices", + recommended: false + }, + + schema: [ + { + enum: ["always", "as-needed"] + } + ] + }, + + create: function(context) { + var mode = context.options[0] || MODE_ALWAYS; + + /** + * Checks the arguments of a given CallExpression node and reports it if it + * offends this rule. + * + * @param {ASTNode} node - A CallExpression node to check. + * @returns {void} + */ + function checkArguments(node) { + var args = node.arguments; + + switch (args.length) { + case 0: context.report({ node: node, - message: "Invalid radix parameter." + message: "Missing parameters." }); - } - break; + break; + + case 1: + if (mode === MODE_ALWAYS) { + context.report({ + node: node, + message: "Missing radix parameter." + }); + } + break; + + default: + if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) { + context.report({ + node: node, + message: "Redundant radix parameter." + }); + } else if (!isValidRadix(args[1])) { + context.report({ + node: node, + message: "Invalid radix parameter." + }); + } + break; + } } - } - return { - "Program:exit": function() { - var scope = context.getScope(); - var variable; + return { + "Program:exit": function() { + var scope = context.getScope(); + var variable; - // Check `parseInt()` - variable = astUtils.getVariableByName(scope, "parseInt"); - if (!isShadowed(variable)) { - variable.references.forEach(function(reference) { - var node = reference.identifier; + // Check `parseInt()` + variable = astUtils.getVariableByName(scope, "parseInt"); + if (!isShadowed(variable)) { + variable.references.forEach(function(reference) { + var node = reference.identifier; - if (astUtils.isCallee(node)) { - checkArguments(node.parent); - } - }); - } + if (astUtils.isCallee(node)) { + checkArguments(node.parent); + } + }); + } - // Check `Number.parseInt()` - variable = astUtils.getVariableByName(scope, "Number"); - if (!isShadowed(variable)) { - variable.references.forEach(function(reference) { - var node = reference.identifier.parent; + // Check `Number.parseInt()` + variable = astUtils.getVariableByName(scope, "Number"); + if (!isShadowed(variable)) { + variable.references.forEach(function(reference) { + var node = reference.identifier.parent; - if (isParseIntMethod(node) && astUtils.isCallee(node)) { - checkArguments(node.parent); - } - }); + if (isParseIntMethod(node) && astUtils.isCallee(node)) { + checkArguments(node.parent); + } + }); + } } - } - }; -}; - -module.exports.schema = [ - { - "enum": ["always", "as-needed"] + }; } -]; +}; diff --git a/tools/eslint/lib/rules/require-jsdoc.js b/tools/eslint/lib/rules/require-jsdoc.js index bac4475e9e..083e79b1f5 100644 --- a/tools/eslint/lib/rules/require-jsdoc.js +++ b/tools/eslint/lib/rules/require-jsdoc.js @@ -1,97 +1,106 @@ /** * @fileoverview Rule to check for jsdoc presence. * @author Gyandeep Singh - * @copyright 2015 Gyandeep Singh. All rights reserved. */ "use strict"; var lodash = require("lodash"); -module.exports = function(context) { - var source = context.getSourceCode(); - var DEFAULT_OPTIONS = { - "FunctionDeclaration": true, - "MethodDefinition": false, - "ClassDeclaration": false - }; - var options = lodash.assign(DEFAULT_OPTIONS, context.options[0] && context.options[0].require || {}); +module.exports = { + meta: { + docs: { + description: "require JSDoc comments", + category: "Stylistic Issues", + recommended: false + }, - /** - * Report the error message - * @param {ASTNode} node node to report - * @returns {void} - */ - function report(node) { - context.report(node, "Missing JSDoc comment."); - } + schema: [ + { + type: "object", + properties: { + require: { + type: "object", + properties: { + ClassDeclaration: { + type: "boolean" + }, + MethodDefinition: { + type: "boolean" + }, + FunctionDeclaration: { + type: "boolean" + } + }, + additionalProperties: false + } + }, + additionalProperties: false + } + ] + }, - /** - * Check if the jsdoc comment is present for class methods - * @param {ASTNode} node node to examine - * @returns {void} - */ - function checkClassMethodJsDoc(node) { - if (node.parent.type === "MethodDefinition") { - var jsdocComment = source.getJSDocComment(node); + create: function(context) { + var source = context.getSourceCode(); + var DEFAULT_OPTIONS = { + FunctionDeclaration: true, + MethodDefinition: false, + ClassDeclaration: false + }; + var options = lodash.assign(DEFAULT_OPTIONS, context.options[0] && context.options[0].require || {}); - if (!jsdocComment) { - report(node); - } + /** + * Report the error message + * @param {ASTNode} node node to report + * @returns {void} + */ + function report(node) { + context.report(node, "Missing JSDoc comment."); } - } - /** - * Check if the jsdoc comment is present or not. - * @param {ASTNode} node node to examine - * @returns {void} - */ - function checkJsDoc(node) { - var jsdocComment = source.getJSDocComment(node); + /** + * Check if the jsdoc comment is present for class methods + * @param {ASTNode} node node to examine + * @returns {void} + */ + function checkClassMethodJsDoc(node) { + if (node.parent.type === "MethodDefinition") { + var jsdocComment = source.getJSDocComment(node); - if (!jsdocComment) { - report(node); + if (!jsdocComment) { + report(node); + } + } } - } - return { - "FunctionDeclaration": function(node) { - if (options.FunctionDeclaration) { - checkJsDoc(node); - } - }, - "FunctionExpression": function(node) { - if (options.MethodDefinition) { - checkClassMethodJsDoc(node); - } - }, - "ClassDeclaration": function(node) { - if (options.ClassDeclaration) { - checkJsDoc(node); + /** + * Check if the jsdoc comment is present or not. + * @param {ASTNode} node node to examine + * @returns {void} + */ + function checkJsDoc(node) { + var jsdocComment = source.getJSDocComment(node); + + if (!jsdocComment) { + report(node); } } - }; -}; -module.exports.schema = [ - { - "type": "object", - "properties": { - "require": { - "type": "object", - "properties": { - "ClassDeclaration": { - "type": "boolean" - }, - "MethodDefinition": { - "type": "boolean" - }, - "FunctionDeclaration": { - "type": "boolean" - } - }, - "additionalProperties": false + return { + FunctionDeclaration: function(node) { + if (options.FunctionDeclaration) { + checkJsDoc(node); + } + }, + FunctionExpression: function(node) { + if (options.MethodDefinition) { + checkClassMethodJsDoc(node); + } + }, + ClassDeclaration: function(node) { + if (options.ClassDeclaration) { + checkJsDoc(node); + } } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/require-yield.js b/tools/eslint/lib/rules/require-yield.js index b293ee4c9e..441d354ed8 100644 --- a/tools/eslint/lib/rules/require-yield.js +++ b/tools/eslint/lib/rules/require-yield.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag the generator functions that does not have yield. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. */ "use strict"; @@ -10,55 +9,65 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var stack = []; +module.exports = { + meta: { + docs: { + description: "require generator functions to contain `yield`", + category: "ECMAScript 6", + recommended: false + }, - /** - * If the node is a generator function, start counting `yield` keywords. - * @param {Node} node - A function node to check. - * @returns {void} - */ - function beginChecking(node) { - if (node.generator) { - stack.push(0); - } - } + schema: [] + }, + + create: function(context) { + var stack = []; - /** - * If the node is a generator function, end counting `yield` keywords, then - * reports result. - * @param {Node} node - A function node to check. - * @returns {void} - */ - function endChecking(node) { - if (!node.generator) { - return; + /** + * If the node is a generator function, start counting `yield` keywords. + * @param {Node} node - A function node to check. + * @returns {void} + */ + function beginChecking(node) { + if (node.generator) { + stack.push(0); + } } - var countYield = stack.pop(); + /** + * If the node is a generator function, end counting `yield` keywords, then + * reports result. + * @param {Node} node - A function node to check. + * @returns {void} + */ + function endChecking(node) { + if (!node.generator) { + return; + } + + var countYield = stack.pop(); - if (countYield === 0 && node.body.body.length > 0) { - context.report( - node, - "This generator function does not have 'yield'."); + if (countYield === 0 && node.body.body.length > 0) { + context.report( + node, + "This generator function does not have 'yield'."); + } } - } - return { - "FunctionDeclaration": beginChecking, - "FunctionDeclaration:exit": endChecking, - "FunctionExpression": beginChecking, - "FunctionExpression:exit": endChecking, + return { + FunctionDeclaration: beginChecking, + "FunctionDeclaration:exit": endChecking, + FunctionExpression: beginChecking, + "FunctionExpression:exit": endChecking, - // Increases the count of `yield` keyword. - "YieldExpression": function() { + // Increases the count of `yield` keyword. + YieldExpression: function() { - /* istanbul ignore else */ - if (stack.length > 0) { - stack[stack.length - 1] += 1; + /* istanbul ignore else */ + if (stack.length > 0) { + stack[stack.length - 1] += 1; + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/semi-spacing.js b/tools/eslint/lib/rules/semi-spacing.js index 37fbc6f3c5..ea43e9243d 100644 --- a/tools/eslint/lib/rules/semi-spacing.js +++ b/tools/eslint/lib/rules/semi-spacing.js @@ -1,7 +1,6 @@ /** * @fileoverview Validates spacing before and after semicolon * @author Mathias Schreck - * @copyright 2015 Mathias Schreck */ "use strict"; @@ -12,200 +11,212 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing before and after semicolons", + category: "Stylistic Issues", + recommended: false + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { + type: "boolean" + }, + after: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - var config = context.options[0], - requireSpaceBefore = false, - requireSpaceAfter = true, - sourceCode = context.getSourceCode(); + create: function(context) { - if (typeof config === "object") { - if (config.hasOwnProperty("before")) { - requireSpaceBefore = config.before; - } - if (config.hasOwnProperty("after")) { - requireSpaceAfter = config.after; + var config = context.options[0], + requireSpaceBefore = false, + requireSpaceAfter = true, + sourceCode = context.getSourceCode(); + + if (typeof config === "object") { + if (config.hasOwnProperty("before")) { + requireSpaceBefore = config.before; + } + if (config.hasOwnProperty("after")) { + requireSpaceAfter = config.after; + } } - } - /** - * Checks if a given token has leading whitespace. - * @param {Object} token The token to check. - * @returns {boolean} True if the given token has leading space, false if not. - */ - function hasLeadingSpace(token) { - var tokenBefore = context.getTokenBefore(token); + /** + * Checks if a given token has leading whitespace. + * @param {Object} token The token to check. + * @returns {boolean} True if the given token has leading space, false if not. + */ + function hasLeadingSpace(token) { + var tokenBefore = context.getTokenBefore(token); - return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token); - } + return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token); + } - /** - * Checks if a given token has trailing whitespace. - * @param {Object} token The token to check. - * @returns {boolean} True if the given token has trailing space, false if not. - */ - function hasTrailingSpace(token) { - var tokenAfter = context.getTokenAfter(token); + /** + * Checks if a given token has trailing whitespace. + * @param {Object} token The token to check. + * @returns {boolean} True if the given token has trailing space, false if not. + */ + function hasTrailingSpace(token) { + var tokenAfter = context.getTokenAfter(token); - return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter); - } + return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter); + } - /** - * Checks if the given token is the last token in its line. - * @param {Token} token The token to check. - * @returns {boolean} Whether or not the token is the last in its line. - */ - function isLastTokenInCurrentLine(token) { - var tokenAfter = context.getTokenAfter(token); + /** + * Checks if the given token is the last token in its line. + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the token is the last in its line. + */ + function isLastTokenInCurrentLine(token) { + var tokenAfter = context.getTokenAfter(token); - return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter)); - } + return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter)); + } - /** - * Checks if the given token is the first token in its line - * @param {Token} token The token to check. - * @returns {boolean} Whether or not the token is the first in its line. - */ - function isFirstTokenInCurrentLine(token) { - var tokenBefore = context.getTokenBefore(token); + /** + * Checks if the given token is the first token in its line + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the token is the first in its line. + */ + function isFirstTokenInCurrentLine(token) { + var tokenBefore = context.getTokenBefore(token); - return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore)); - } + return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore)); + } - /** - * Checks if the next token of a given token is a closing parenthesis. - * @param {Token} token The token to check. - * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis. - */ - function isBeforeClosingParen(token) { - var nextToken = context.getTokenAfter(token); - - return ( - nextToken && - nextToken.type === "Punctuator" && - (nextToken.value === "}" || nextToken.value === ")") - ); - } + /** + * Checks if the next token of a given token is a closing parenthesis. + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis. + */ + function isBeforeClosingParen(token) { + var nextToken = context.getTokenAfter(token); + + return ( + nextToken && + nextToken.type === "Punctuator" && + (nextToken.value === "}" || nextToken.value === ")") + ); + } - /** - * Checks if the given token is a semicolon. - * @param {Token} token The token to check. - * @returns {boolean} Whether or not the given token is a semicolon. - */ - function isSemicolon(token) { - return token.type === "Punctuator" && token.value === ";"; - } + /** + * Checks if the given token is a semicolon. + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the given token is a semicolon. + */ + function isSemicolon(token) { + return token.type === "Punctuator" && token.value === ";"; + } - /** - * Reports if the given token has invalid spacing. - * @param {Token} token The semicolon token to check. - * @param {ASTNode} node The corresponding node of the token. - * @returns {void} - */ - function checkSemicolonSpacing(token, node) { - var location; - - if (isSemicolon(token)) { - location = token.loc.start; - - if (hasLeadingSpace(token)) { - if (!requireSpaceBefore) { - context.report({ - node: node, - loc: location, - message: "Unexpected whitespace before semicolon.", - fix: function(fixer) { - var tokenBefore = context.getTokenBefore(token); - - return fixer.removeRange([tokenBefore.range[1], token.range[0]]); - } - }); - } - } else { - if (requireSpaceBefore) { - context.report({ - node: node, - loc: location, - message: "Missing whitespace before semicolon.", - fix: function(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } - } + /** + * Reports if the given token has invalid spacing. + * @param {Token} token The semicolon token to check. + * @param {ASTNode} node The corresponding node of the token. + * @returns {void} + */ + function checkSemicolonSpacing(token, node) { + var location; + + if (isSemicolon(token)) { + location = token.loc.start; - if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) { - if (hasTrailingSpace(token)) { - if (!requireSpaceAfter) { + if (hasLeadingSpace(token)) { + if (!requireSpaceBefore) { context.report({ node: node, loc: location, - message: "Unexpected whitespace after semicolon.", + message: "Unexpected whitespace before semicolon.", fix: function(fixer) { - var tokenAfter = context.getTokenAfter(token); + var tokenBefore = context.getTokenBefore(token); - return fixer.removeRange([token.range[1], tokenAfter.range[0]]); + return fixer.removeRange([tokenBefore.range[1], token.range[0]]); } }); } } else { - if (requireSpaceAfter) { + if (requireSpaceBefore) { context.report({ node: node, loc: location, - message: "Missing whitespace after semicolon.", + message: "Missing whitespace before semicolon.", fix: function(fixer) { - return fixer.insertTextAfter(token, " "); + return fixer.insertTextBefore(token, " "); } }); } } + + if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) { + if (hasTrailingSpace(token)) { + if (!requireSpaceAfter) { + context.report({ + node: node, + loc: location, + message: "Unexpected whitespace after semicolon.", + fix: function(fixer) { + var tokenAfter = context.getTokenAfter(token); + + return fixer.removeRange([token.range[1], tokenAfter.range[0]]); + } + }); + } + } else { + if (requireSpaceAfter) { + context.report({ + node: node, + loc: location, + message: "Missing whitespace after semicolon.", + fix: function(fixer) { + return fixer.insertTextAfter(token, " "); + } + }); + } + } + } } } - } - /** - * Checks the spacing of the semicolon with the assumption that the last token is the semicolon. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkNode(node) { - var token = context.getLastToken(node); + /** + * Checks the spacing of the semicolon with the assumption that the last token is the semicolon. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkNode(node) { + var token = context.getLastToken(node); - checkSemicolonSpacing(token, node); - } - - return { - "VariableDeclaration": checkNode, - "ExpressionStatement": checkNode, - "BreakStatement": checkNode, - "ContinueStatement": checkNode, - "DebuggerStatement": checkNode, - "ReturnStatement": checkNode, - "ThrowStatement": checkNode, - "ForStatement": function(node) { - if (node.init) { - checkSemicolonSpacing(context.getTokenAfter(node.init), node); - } - - if (node.test) { - checkSemicolonSpacing(context.getTokenAfter(node.test), node); - } + checkSemicolonSpacing(token, node); } - }; -}; -module.exports.schema = [ - { - "type": "object", - "properties": { - "before": { - "type": "boolean" - }, - "after": { - "type": "boolean" + return { + VariableDeclaration: checkNode, + ExpressionStatement: checkNode, + BreakStatement: checkNode, + ContinueStatement: checkNode, + DebuggerStatement: checkNode, + ReturnStatement: checkNode, + ThrowStatement: checkNode, + ForStatement: function(node) { + if (node.init) { + checkSemicolonSpacing(context.getTokenAfter(node.init), node); + } + + if (node.test) { + checkSemicolonSpacing(context.getTokenAfter(node.test), node); + } } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/semi.js b/tools/eslint/lib/rules/semi.js index 2986f406e6..e386084faf 100644 --- a/tools/eslint/lib/rules/semi.js +++ b/tools/eslint/lib/rules/semi.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag missing semicolons. * @author Nicholas C. Zakas - * @copyright 2013 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -10,207 +8,219 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var OPT_OUT_PATTERN = /[\[\(\/\+\-]/; // One of [(/+- - var options = context.options[1]; - var never = context.options[0] === "never", - exceptOneLine = options && options.omitLastInOneLineBlock === true, - sourceCode = context.getSourceCode(); - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Reports a semicolon error with appropriate location and message. - * @param {ASTNode} node The node with an extra or missing semicolon. - * @param {boolean} missing True if the semicolon is missing. - * @returns {void} - */ - function report(node, missing) { - var message, - fix, - lastToken = sourceCode.getLastToken(node), - loc = lastToken.loc; - - if (!missing) { - message = "Missing semicolon."; - loc = loc.end; - fix = function(fixer) { - return fixer.insertTextAfter(lastToken, ";"); - }; - } else { - message = "Extra semicolon."; - loc = loc.start; - fix = function(fixer) { - return fixer.remove(lastToken); - }; - } +module.exports = { + meta: { + docs: { + description: "require or disallow semicolons instead of ASI", + category: "Stylistic Issues", + recommended: false + }, - context.report({ - node: node, - loc: loc, - message: message, - fix: fix - }); + fixable: "code", - } + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["never"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["always"] + }, + { + type: "object", + properties: { + omitLastInOneLineBlock: {type: "boolean"} + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + } + }, + + create: function(context) { + + var OPT_OUT_PATTERN = /[\[\(\/\+\-]/; // One of [(/+- + var options = context.options[1]; + var never = context.options[0] === "never", + exceptOneLine = options && options.omitLastInOneLineBlock === true, + sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports a semicolon error with appropriate location and message. + * @param {ASTNode} node The node with an extra or missing semicolon. + * @param {boolean} missing True if the semicolon is missing. + * @returns {void} + */ + function report(node, missing) { + var message, + fix, + lastToken = sourceCode.getLastToken(node), + loc = lastToken.loc; + + if (!missing) { + message = "Missing semicolon."; + loc = loc.end; + fix = function(fixer) { + return fixer.insertTextAfter(lastToken, ";"); + }; + } else { + message = "Extra semicolon."; + loc = loc.start; + fix = function(fixer) { + return fixer.remove(lastToken); + }; + } - /** - * Checks whether a token is a semicolon punctuator. - * @param {Token} token The token. - * @returns {boolean} True if token is a semicolon punctuator. - */ - function isSemicolon(token) { - return (token.type === "Punctuator" && token.value === ";"); - } + context.report({ + node: node, + loc: loc, + message: message, + fix: fix + }); - /** - * Check if a semicolon is unnecessary, only true if: - * - next token is on a new line and is not one of the opt-out tokens - * - next token is a valid statement divider - * @param {Token} lastToken last token of current node. - * @returns {boolean} whether the semicolon is unnecessary. - */ - function isUnnecessarySemicolon(lastToken) { - var isDivider, isOptOutToken, lastTokenLine, nextToken, nextTokenLine; - - if (!isSemicolon(lastToken)) { - return false; } - nextToken = context.getTokenAfter(lastToken); - - if (!nextToken) { - return true; + /** + * Checks whether a token is a semicolon punctuator. + * @param {Token} token The token. + * @returns {boolean} True if token is a semicolon punctuator. + */ + function isSemicolon(token) { + return (token.type === "Punctuator" && token.value === ";"); } - lastTokenLine = lastToken.loc.end.line; - nextTokenLine = nextToken.loc.start.line; - isOptOutToken = OPT_OUT_PATTERN.test(nextToken.value); - isDivider = (nextToken.value === "}" || nextToken.value === ";"); + /** + * Check if a semicolon is unnecessary, only true if: + * - next token is on a new line and is not one of the opt-out tokens + * - next token is a valid statement divider + * @param {Token} lastToken last token of current node. + * @returns {boolean} whether the semicolon is unnecessary. + */ + function isUnnecessarySemicolon(lastToken) { + var isDivider, isOptOutToken, lastTokenLine, nextToken, nextTokenLine; - return (lastTokenLine !== nextTokenLine && !isOptOutToken) || isDivider; - } + if (!isSemicolon(lastToken)) { + return false; + } - /** - * Checks a node to see if it's in a one-liner block statement. - * @param {ASTNode} node The node to check. - * @returns {boolean} whether the node is in a one-liner block statement. - */ - function isOneLinerBlock(node) { - var nextToken = context.getTokenAfter(node); + nextToken = context.getTokenAfter(lastToken); - if (!nextToken || nextToken.value !== "}") { - return false; - } + if (!nextToken) { + return true; + } - var parent = node.parent; + lastTokenLine = lastToken.loc.end.line; + nextTokenLine = nextToken.loc.start.line; + isOptOutToken = OPT_OUT_PATTERN.test(nextToken.value); + isDivider = (nextToken.value === "}" || nextToken.value === ";"); - return parent && parent.type === "BlockStatement" && - parent.loc.start.line === parent.loc.end.line; - } + return (lastTokenLine !== nextTokenLine && !isOptOutToken) || isDivider; + } + + /** + * Checks a node to see if it's in a one-liner block statement. + * @param {ASTNode} node The node to check. + * @returns {boolean} whether the node is in a one-liner block statement. + */ + function isOneLinerBlock(node) { + var nextToken = context.getTokenAfter(node); - /** - * Checks a node to see if it's followed by a semicolon. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkForSemicolon(node) { - var lastToken = context.getLastToken(node); - - if (never) { - if (isUnnecessarySemicolon(lastToken)) { - report(node, true); + if (!nextToken || nextToken.value !== "}") { + return false; } - } else { - if (!isSemicolon(lastToken)) { - if (!exceptOneLine || !isOneLinerBlock(node)) { - report(node); + + var parent = node.parent; + + return parent && parent.type === "BlockStatement" && + parent.loc.start.line === parent.loc.end.line; + } + + /** + * Checks a node to see if it's followed by a semicolon. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkForSemicolon(node) { + var lastToken = context.getLastToken(node); + + if (never) { + if (isUnnecessarySemicolon(lastToken)) { + report(node, true); } } else { - if (exceptOneLine && isOneLinerBlock(node)) { - report(node, true); + if (!isSemicolon(lastToken)) { + if (!exceptOneLine || !isOneLinerBlock(node)) { + report(node); + } + } else { + if (exceptOneLine && isOneLinerBlock(node)) { + report(node, true); + } } } } - } - - /** - * Checks to see if there's a semicolon after a variable declaration. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkForSemicolonForVariableDeclaration(node) { - var ancestors = context.getAncestors(), - parentIndex = ancestors.length - 1, - parent = ancestors[parentIndex]; - - if ((parent.type !== "ForStatement" || parent.init !== node) && - (!/^For(?:In|Of)Statement/.test(parent.type) || parent.left !== node) - ) { - checkForSemicolon(node); - } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - "VariableDeclaration": checkForSemicolonForVariableDeclaration, - "ExpressionStatement": checkForSemicolon, - "ReturnStatement": checkForSemicolon, - "ThrowStatement": checkForSemicolon, - "DoWhileStatement": checkForSemicolon, - "DebuggerStatement": checkForSemicolon, - "BreakStatement": checkForSemicolon, - "ContinueStatement": checkForSemicolon, - "ImportDeclaration": checkForSemicolon, - "ExportAllDeclaration": checkForSemicolon, - "ExportNamedDeclaration": function(node) { - if (!node.declaration) { - checkForSemicolon(node); - } - }, - "ExportDefaultDeclaration": function(node) { - if (!/(?:Class|Function)Declaration/.test(node.declaration.type)) { + /** + * Checks to see if there's a semicolon after a variable declaration. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkForSemicolonForVariableDeclaration(node) { + var ancestors = context.getAncestors(), + parentIndex = ancestors.length - 1, + parent = ancestors[parentIndex]; + + if ((parent.type !== "ForStatement" || parent.init !== node) && + (!/^For(?:In|Of)Statement/.test(parent.type) || parent.left !== node) + ) { checkForSemicolon(node); } } - }; -}; - -module.exports.schema = { - "anyOf": [ - { - "type": "array", - "items": [ - { - "enum": ["never"] + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + VariableDeclaration: checkForSemicolonForVariableDeclaration, + ExpressionStatement: checkForSemicolon, + ReturnStatement: checkForSemicolon, + ThrowStatement: checkForSemicolon, + DoWhileStatement: checkForSemicolon, + DebuggerStatement: checkForSemicolon, + BreakStatement: checkForSemicolon, + ContinueStatement: checkForSemicolon, + ImportDeclaration: checkForSemicolon, + ExportAllDeclaration: checkForSemicolon, + ExportNamedDeclaration: function(node) { + if (!node.declaration) { + checkForSemicolon(node); } - ], - "minItems": 0, - "maxItems": 1 - }, - { - "type": "array", - "items": [ - { - "enum": ["always"] - }, - { - "type": "object", - "properties": { - "omitLastInOneLineBlock": {"type": "boolean"} - }, - "additionalProperties": false + }, + ExportDefaultDeclaration: function(node) { + if (!/(?:Class|Function)Declaration/.test(node.declaration.type)) { + checkForSemicolon(node); } - ], - "minItems": 0, - "maxItems": 2 - } - ] + } + }; + + } }; diff --git a/tools/eslint/lib/rules/sort-imports.js b/tools/eslint/lib/rules/sort-imports.js index 4e8807cedb..18b34f86a0 100644 --- a/tools/eslint/lib/rules/sort-imports.js +++ b/tools/eslint/lib/rules/sort-imports.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to require sorting of import declarations * @author Christian Schuller - * @copyright 2015 Christian Schuller. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -11,157 +9,167 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var configuration = context.options[0] || {}, - ignoreCase = configuration.ignoreCase || false, - ignoreMemberSort = configuration.ignoreMemberSort || false, - memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"], - previousDeclaration = null; - - /** - * Gets the used member syntax style. - * - * import "my-module.js" --> none - * import * as myModule from "my-module.js" --> all - * import {myMember} from "my-module.js" --> single - * import {foo, bar} from "my-module.js" --> multiple - * - * @param {ASTNode} node - the ImportDeclaration node. - * @returns {string} used member parameter style, ["all", "multiple", "single"] - */ - function usedMemberSyntax(node) { - if (node.specifiers.length === 0) { - return "none"; - } else if (node.specifiers[0].type === "ImportNamespaceSpecifier") { - return "all"; - } else if (node.specifiers.length === 1) { - return "single"; - } else { - return "multiple"; +module.exports = { + meta: { + docs: { + description: "enforce sorted import declarations within modules", + category: "ECMAScript 6", + recommended: false + }, + + schema: [ + { + type: "object", + properties: { + ignoreCase: { + type: "boolean" + }, + memberSyntaxSortOrder: { + type: "array", + items: { + enum: ["none", "all", "multiple", "single"] + }, + uniqueItems: true, + minItems: 4, + maxItems: 4 + }, + ignoreMemberSort: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + + var configuration = context.options[0] || {}, + ignoreCase = configuration.ignoreCase || false, + ignoreMemberSort = configuration.ignoreMemberSort || false, + memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"], + previousDeclaration = null; + + /** + * Gets the used member syntax style. + * + * import "my-module.js" --> none + * import * as myModule from "my-module.js" --> all + * import {myMember} from "my-module.js" --> single + * import {foo, bar} from "my-module.js" --> multiple + * + * @param {ASTNode} node - the ImportDeclaration node. + * @returns {string} used member parameter style, ["all", "multiple", "single"] + */ + function usedMemberSyntax(node) { + if (node.specifiers.length === 0) { + return "none"; + } else if (node.specifiers[0].type === "ImportNamespaceSpecifier") { + return "all"; + } else if (node.specifiers.length === 1) { + return "single"; + } else { + return "multiple"; + } } - } - /** - * Gets the group by member parameter index for given declaration. - * @param {ASTNode} node - the ImportDeclaration node. - * @returns {number} the declaration group by member index. - */ - function getMemberParameterGroupIndex(node) { - return memberSyntaxSortOrder.indexOf(usedMemberSyntax(node)); - } + /** + * Gets the group by member parameter index for given declaration. + * @param {ASTNode} node - the ImportDeclaration node. + * @returns {number} the declaration group by member index. + */ + function getMemberParameterGroupIndex(node) { + return memberSyntaxSortOrder.indexOf(usedMemberSyntax(node)); + } - /** - * Gets the local name of the first imported module. - * @param {ASTNode} node - the ImportDeclaration node. - * @returns {?string} the local name of the first imported module. - */ - function getFirstLocalMemberName(node) { - if (node.specifiers[0]) { - return node.specifiers[0].local.name; - } else { - return null; + /** + * Gets the local name of the first imported module. + * @param {ASTNode} node - the ImportDeclaration node. + * @returns {?string} the local name of the first imported module. + */ + function getFirstLocalMemberName(node) { + if (node.specifiers[0]) { + return node.specifiers[0].local.name; + } else { + return null; + } } - } - return { - "ImportDeclaration": function(node) { - if (previousDeclaration) { - var currentLocalMemberName = getFirstLocalMemberName(node), - currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node), - previousLocalMemberName = getFirstLocalMemberName(previousDeclaration), - previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration); - - if (ignoreCase) { - previousLocalMemberName = previousLocalMemberName && previousLocalMemberName.toLowerCase(); - currentLocalMemberName = currentLocalMemberName && currentLocalMemberName.toLowerCase(); - } + return { + ImportDeclaration: function(node) { + if (previousDeclaration) { + var currentLocalMemberName = getFirstLocalMemberName(node), + currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node), + previousLocalMemberName = getFirstLocalMemberName(previousDeclaration), + previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration); - // When the current declaration uses a different member syntax, - // then check if the ordering is correct. - // Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name. - if (currentMemberSyntaxGroupIndex !== previousMemberSyntaxGroupIndex) { - if (currentMemberSyntaxGroupIndex < previousMemberSyntaxGroupIndex) { - context.report({ - node: node, - message: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax.", - data: { - syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex], - syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex] - } - }); + if (ignoreCase) { + previousLocalMemberName = previousLocalMemberName && previousLocalMemberName.toLowerCase(); + currentLocalMemberName = currentLocalMemberName && currentLocalMemberName.toLowerCase(); } - } else { - if (previousLocalMemberName && - currentLocalMemberName && - currentLocalMemberName < previousLocalMemberName - ) { - context.report({ - node: node, - message: "Imports should be sorted alphabetically." - }); + + // When the current declaration uses a different member syntax, + // then check if the ordering is correct. + // Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name. + if (currentMemberSyntaxGroupIndex !== previousMemberSyntaxGroupIndex) { + if (currentMemberSyntaxGroupIndex < previousMemberSyntaxGroupIndex) { + context.report({ + node: node, + message: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax.", + data: { + syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex], + syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex] + } + }); + } + } else { + if (previousLocalMemberName && + currentLocalMemberName && + currentLocalMemberName < previousLocalMemberName + ) { + context.report({ + node: node, + message: "Imports should be sorted alphabetically." + }); + } } } - } - // Multiple members of an import declaration should also be sorted alphabetically. - if (!ignoreMemberSort && node.specifiers.length > 1) { - var previousSpecifier = null; - var previousSpecifierName = null; + // Multiple members of an import declaration should also be sorted alphabetically. + if (!ignoreMemberSort && node.specifiers.length > 1) { + var previousSpecifier = null; + var previousSpecifierName = null; - for (var i = 0; i < node.specifiers.length; ++i) { - var currentSpecifier = node.specifiers[i]; + for (var i = 0; i < node.specifiers.length; ++i) { + var currentSpecifier = node.specifiers[i]; - if (currentSpecifier.type !== "ImportSpecifier") { - continue; - } + if (currentSpecifier.type !== "ImportSpecifier") { + continue; + } - var currentSpecifierName = currentSpecifier.local.name; + var currentSpecifierName = currentSpecifier.local.name; - if (ignoreCase) { - currentSpecifierName = currentSpecifierName.toLowerCase(); - } + if (ignoreCase) { + currentSpecifierName = currentSpecifierName.toLowerCase(); + } - if (previousSpecifier && currentSpecifierName < previousSpecifierName) { - context.report({ - node: currentSpecifier, - message: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.", - data: { - memberName: currentSpecifier.local.name - } - }); - } + if (previousSpecifier && currentSpecifierName < previousSpecifierName) { + context.report({ + node: currentSpecifier, + message: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.", + data: { + memberName: currentSpecifier.local.name + } + }); + } - previousSpecifier = currentSpecifier; - previousSpecifierName = currentSpecifierName; + previousSpecifier = currentSpecifier; + previousSpecifierName = currentSpecifierName; + } } - } - previousDeclaration = node; - } - }; -}; - -module.exports.schema = [ - { - "type": "object", - "properties": { - "ignoreCase": { - "type": "boolean" - }, - "memberSyntaxSortOrder": { - "type": "array", - "items": { - "enum": ["none", "all", "multiple", "single"] - }, - "uniqueItems": true, - "minItems": 4, - "maxItems": 4 - }, - "ignoreMemberSort": { - "type": "boolean" + previousDeclaration = node; } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/sort-vars.js b/tools/eslint/lib/rules/sort-vars.js index 9aa85c8cbd..8a7b0e710a 100644 --- a/tools/eslint/lib/rules/sort-vars.js +++ b/tools/eslint/lib/rules/sort-vars.js @@ -9,45 +9,55 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var configuration = context.options[0] || {}, - ignoreCase = configuration.ignoreCase || false; - - return { - "VariableDeclaration": function(node) { - node.declarations.reduce(function(memo, decl) { - if (decl.id.type === "ObjectPattern" || decl.id.type === "ArrayPattern") { - return memo; - } - - var lastVariableName = memo.id.name, - currenVariableName = decl.id.name; - - if (ignoreCase) { - lastVariableName = lastVariableName.toLowerCase(); - currenVariableName = currenVariableName.toLowerCase(); - } - - if (currenVariableName < lastVariableName) { - context.report(decl, "Variables within the same declaration block should be sorted alphabetically"); - return memo; - } else { - return decl; - } - }, node.declarations[0]); - } - }; -}; +module.exports = { + meta: { + docs: { + description: "require variables within the same declaration block to be sorted", + category: "Stylistic Issues", + recommended: false + }, -module.exports.schema = [ - { - "type": "object", - "properties": { - "ignoreCase": { - "type": "boolean" + schema: [ + { + type: "object", + properties: { + ignoreCase: { + type: "boolean" + } + }, + additionalProperties: false } - }, - "additionalProperties": false + ] + }, + + create: function(context) { + + var configuration = context.options[0] || {}, + ignoreCase = configuration.ignoreCase || false; + + return { + VariableDeclaration: function(node) { + node.declarations.reduce(function(memo, decl) { + if (decl.id.type === "ObjectPattern" || decl.id.type === "ArrayPattern") { + return memo; + } + + var lastVariableName = memo.id.name, + currenVariableName = decl.id.name; + + if (ignoreCase) { + lastVariableName = lastVariableName.toLowerCase(); + currenVariableName = currenVariableName.toLowerCase(); + } + + if (currenVariableName < lastVariableName) { + context.report(decl, "Variables within the same declaration block should be sorted alphabetically"); + return memo; + } else { + return decl; + } + }, node.declarations[0]); + } + }; } -]; +}; diff --git a/tools/eslint/lib/rules/space-before-blocks.js b/tools/eslint/lib/rules/space-before-blocks.js index c7fbec8331..7fb9d5cddc 100644 --- a/tools/eslint/lib/rules/space-before-blocks.js +++ b/tools/eslint/lib/rules/space-before-blocks.js @@ -1,7 +1,6 @@ /** * @fileoverview A rule to ensure whitespace before blocks. * @author Mathias Schreck - * @copyright 2014 Mathias Schreck. All rights reserved. */ "use strict"; @@ -12,129 +11,141 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var config = context.options[0], - sourceCode = context.getSourceCode(), - checkFunctions = true, - checkKeywords = true, - checkClasses = true; - - if (typeof config === "object") { - checkFunctions = config.functions !== "never"; - checkKeywords = config.keywords !== "never"; - checkClasses = config.classes !== "never"; - } else if (config === "never") { - checkFunctions = false; - checkKeywords = false; - checkClasses = false; - } +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing before blocks", + category: "Stylistic Issues", + recommended: false + }, - /** - * Checks whether or not a given token is an arrow operator (=>) or a keyword - * in order to avoid to conflict with `arrow-spacing` and `keyword-spacing`. - * - * @param {Token} token - A token to check. - * @returns {boolean} `true` if the token is an arrow operator. - */ - function isConflicted(token) { - return (token.type === "Punctuator" && token.value === "=>") || token.type === "Keyword"; - } + fixable: "whitespace", - /** - * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line. - * @param {ASTNode|Token} node The AST node of a BlockStatement. - * @returns {void} undefined. - */ - function checkPrecedingSpace(node) { - var precedingToken = context.getTokenBefore(node), - hasSpace, - parent, - requireSpace; - - if (precedingToken && !isConflicted(precedingToken) && astUtils.isTokenOnSameLine(precedingToken, node)) { - hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node); - parent = context.getAncestors().pop(); - if (parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") { - requireSpace = checkFunctions; - } else if (node.type === "ClassBody") { - requireSpace = checkClasses; - } else { - requireSpace = checkKeywords; + schema: [ + { + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + keywords: { + enum: ["always", "never"] + }, + functions: { + enum: ["always", "never"] + }, + classes: { + enum: ["always", "never"] + } + }, + additionalProperties: false + } + ] } + ] + }, + + create: function(context) { + var config = context.options[0], + sourceCode = context.getSourceCode(), + checkFunctions = true, + checkKeywords = true, + checkClasses = true; - if (requireSpace) { - if (!hasSpace) { - context.report({ - node: node, - message: "Missing space before opening brace.", - fix: function(fixer) { - return fixer.insertTextBefore(node, " "); - } - }); + if (typeof config === "object") { + checkFunctions = config.functions !== "never"; + checkKeywords = config.keywords !== "never"; + checkClasses = config.classes !== "never"; + } else if (config === "never") { + checkFunctions = false; + checkKeywords = false; + checkClasses = false; + } + + /** + * Checks whether or not a given token is an arrow operator (=>) or a keyword + * in order to avoid to conflict with `arrow-spacing` and `keyword-spacing`. + * + * @param {Token} token - A token to check. + * @returns {boolean} `true` if the token is an arrow operator. + */ + function isConflicted(token) { + return (token.type === "Punctuator" && token.value === "=>") || token.type === "Keyword"; + } + + /** + * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line. + * @param {ASTNode|Token} node The AST node of a BlockStatement. + * @returns {void} undefined. + */ + function checkPrecedingSpace(node) { + var precedingToken = context.getTokenBefore(node), + hasSpace, + parent, + requireSpace; + + if (precedingToken && !isConflicted(precedingToken) && astUtils.isTokenOnSameLine(precedingToken, node)) { + hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node); + parent = context.getAncestors().pop(); + if (parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") { + requireSpace = checkFunctions; + } else if (node.type === "ClassBody") { + requireSpace = checkClasses; + } else { + requireSpace = checkKeywords; } - } else { - if (hasSpace) { - context.report({ - node: node, - message: "Unexpected space before opening brace.", - fix: function(fixer) { - return fixer.removeRange([precedingToken.range[1], node.range[0]]); - } - }); + + if (requireSpace) { + if (!hasSpace) { + context.report({ + node: node, + message: "Missing space before opening brace.", + fix: function(fixer) { + return fixer.insertTextBefore(node, " "); + } + }); + } + } else { + if (hasSpace) { + context.report({ + node: node, + message: "Unexpected space before opening brace.", + fix: function(fixer) { + return fixer.removeRange([precedingToken.range[1], node.range[0]]); + } + }); + } } } } - } - /** - * Checks if the CaseBlock of an given SwitchStatement node has a preceding space. - * @param {ASTNode} node The node of a SwitchStatement. - * @returns {void} undefined. - */ - function checkSpaceBeforeCaseBlock(node) { - var cases = node.cases, - firstCase, - openingBrace; - - if (cases.length > 0) { - firstCase = cases[0]; - openingBrace = context.getTokenBefore(firstCase); - } else { - openingBrace = context.getLastToken(node, 1); - } + /** + * Checks if the CaseBlock of an given SwitchStatement node has a preceding space. + * @param {ASTNode} node The node of a SwitchStatement. + * @returns {void} undefined. + */ + function checkSpaceBeforeCaseBlock(node) { + var cases = node.cases, + firstCase, + openingBrace; - checkPrecedingSpace(openingBrace); - } + if (cases.length > 0) { + firstCase = cases[0]; + openingBrace = context.getTokenBefore(firstCase); + } else { + openingBrace = context.getLastToken(node, 1); + } - return { - "BlockStatement": checkPrecedingSpace, - "ClassBody": checkPrecedingSpace, - "SwitchStatement": checkSpaceBeforeCaseBlock - }; + checkPrecedingSpace(openingBrace); + } -}; + return { + BlockStatement: checkPrecedingSpace, + ClassBody: checkPrecedingSpace, + SwitchStatement: checkSpaceBeforeCaseBlock + }; -module.exports.schema = [ - { - "oneOf": [ - { - "enum": ["always", "never"] - }, - { - "type": "object", - "properties": { - "keywords": { - "enum": ["always", "never"] - }, - "functions": { - "enum": ["always", "never"] - }, - "classes": { - "enum": ["always", "never"] - } - }, - "additionalProperties": false - } - ] } -]; +}; diff --git a/tools/eslint/lib/rules/space-before-function-paren.js b/tools/eslint/lib/rules/space-before-function-paren.js index b96acb6678..d96cb4a608 100644 --- a/tools/eslint/lib/rules/space-before-function-paren.js +++ b/tools/eslint/lib/rules/space-before-function-paren.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to validate spacing before function paren. * @author Mathias Schreck - * @copyright 2015 Mathias Schreck - * See LICENSE in root directory for full license. */ "use strict"; @@ -10,115 +8,135 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing before `function` definition opening parenthesis", + category: "Stylistic Issues", + recommended: false + }, - var configuration = context.options[0], - sourceCode = context.getSourceCode(), - requireAnonymousFunctionSpacing = true, - requireNamedFunctionSpacing = true; + fixable: "whitespace", - if (typeof configuration === "object") { - requireAnonymousFunctionSpacing = configuration.anonymous !== "never"; - requireNamedFunctionSpacing = configuration.named !== "never"; - } else if (configuration === "never") { - requireAnonymousFunctionSpacing = false; - requireNamedFunctionSpacing = false; - } + schema: [ + { + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + anonymous: { + enum: ["always", "never", "ignore"] + }, + named: { + enum: ["always", "never", "ignore"] + } + }, + additionalProperties: false + } + ] + } + ] + }, + + create: function(context) { - /** - * Determines whether a function has a name. - * @param {ASTNode} node The function node. - * @returns {boolean} Whether the function has a name. - */ - function isNamedFunction(node) { - var parent; + var configuration = context.options[0], + sourceCode = context.getSourceCode(), + requireAnonymousFunctionSpacing = true, + forbidAnonymousFunctionSpacing = false, + requireNamedFunctionSpacing = true, + forbidNamedFunctionSpacing = false; - if (node.id) { - return true; + if (typeof configuration === "object") { + requireAnonymousFunctionSpacing = ( + !configuration.anonymous || configuration.anonymous === "always"); + forbidAnonymousFunctionSpacing = configuration.anonymous === "never"; + requireNamedFunctionSpacing = ( + !configuration.named || configuration.named === "always"); + forbidNamedFunctionSpacing = configuration.named === "never"; + } else if (configuration === "never") { + requireAnonymousFunctionSpacing = false; + forbidAnonymousFunctionSpacing = true; + requireNamedFunctionSpacing = false; + forbidNamedFunctionSpacing = true; } - parent = node.parent; - return parent.type === "MethodDefinition" || - (parent.type === "Property" && - ( - parent.kind === "get" || - parent.kind === "set" || - parent.method - ) - ); - } + /** + * Determines whether a function has a name. + * @param {ASTNode} node The function node. + * @returns {boolean} Whether the function has a name. + */ + function isNamedFunction(node) { + var parent; - /** - * Validates the spacing before function parentheses. - * @param {ASTNode} node The node to be validated. - * @returns {void} - */ - function validateSpacingBeforeParentheses(node) { - var isNamed = isNamedFunction(node), - leftToken, - rightToken, - location; + if (node.id) { + return true; + } - if (node.generator && !isNamed) { - return; + parent = node.parent; + return parent.type === "MethodDefinition" || + (parent.type === "Property" && + ( + parent.kind === "get" || + parent.kind === "set" || + parent.method + ) + ); } - rightToken = sourceCode.getFirstToken(node); - while (rightToken.value !== "(") { - rightToken = sourceCode.getTokenAfter(rightToken); - } - leftToken = context.getTokenBefore(rightToken); - location = leftToken.loc.end; + /** + * Validates the spacing before function parentheses. + * @param {ASTNode} node The node to be validated. + * @returns {void} + */ + function validateSpacingBeforeParentheses(node) { + var isNamed = isNamedFunction(node), + leftToken, + rightToken, + location; - if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) { - if ((isNamed && !requireNamedFunctionSpacing) || (!isNamed && !requireAnonymousFunctionSpacing)) { - context.report({ - node: node, - loc: location, - message: "Unexpected space before function parentheses.", - fix: function(fixer) { - return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); - } - }); - } - } else { - if ((isNamed && requireNamedFunctionSpacing) || (!isNamed && requireAnonymousFunctionSpacing)) { - context.report({ - node: node, - loc: location, - message: "Missing space before function parentheses.", - fix: function(fixer) { - return fixer.insertTextAfter(leftToken, " "); - } - }); + if (node.generator && !isNamed) { + return; } - } - } - return { - "FunctionDeclaration": validateSpacingBeforeParentheses, - "FunctionExpression": validateSpacingBeforeParentheses - }; -}; + rightToken = sourceCode.getFirstToken(node); + while (rightToken.value !== "(") { + rightToken = sourceCode.getTokenAfter(rightToken); + } + leftToken = context.getTokenBefore(rightToken); + location = leftToken.loc.end; -module.exports.schema = [ - { - "oneOf": [ - { - "enum": ["always", "never"] - }, - { - "type": "object", - "properties": { - "anonymous": { - "enum": ["always", "never"] - }, - "named": { - "enum": ["always", "never"] - } - }, - "additionalProperties": false + if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) { + if ((isNamed && forbidNamedFunctionSpacing) || (!isNamed && forbidAnonymousFunctionSpacing)) { + context.report({ + node: node, + loc: location, + message: "Unexpected space before function parentheses.", + fix: function(fixer) { + return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); + } + }); + } + } else { + if ((isNamed && requireNamedFunctionSpacing) || (!isNamed && requireAnonymousFunctionSpacing)) { + context.report({ + node: node, + loc: location, + message: "Missing space before function parentheses.", + fix: function(fixer) { + return fixer.insertTextAfter(leftToken, " "); + } + }); + } } - ] + } + + return { + FunctionDeclaration: validateSpacingBeforeParentheses, + FunctionExpression: validateSpacingBeforeParentheses + }; } -]; +}; diff --git a/tools/eslint/lib/rules/space-in-parens.js b/tools/eslint/lib/rules/space-in-parens.js index bd15a41f3c..11361aec12 100644 --- a/tools/eslint/lib/rules/space-in-parens.js +++ b/tools/eslint/lib/rules/space-in-parens.js @@ -1,8 +1,6 @@ /** * @fileoverview Disallows or enforces spaces inside of parentheses. * @author Jonathan Rajavuori - * @copyright 2014 David Clark. All rights reserved. - * @copyright 2014 Jonathan Rajavuori. All rights reserved. */ "use strict"; @@ -12,260 +10,272 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var MISSING_SPACE_MESSAGE = "There must be a space inside this paren.", - REJECTED_SPACE_MESSAGE = "There should be no spaces inside this paren.", - ALWAYS = context.options[0] === "always", - - exceptionsArrayOptions = (context.options.length === 2) ? context.options[1].exceptions : [], - options = {}, - exceptions; +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing inside parentheses", + category: "Stylistic Issues", + recommended: false + }, - if (exceptionsArrayOptions.length) { - options.braceException = exceptionsArrayOptions.indexOf("{}") !== -1; - options.bracketException = exceptionsArrayOptions.indexOf("[]") !== -1; - options.parenException = exceptionsArrayOptions.indexOf("()") !== -1; - options.empty = exceptionsArrayOptions.indexOf("empty") !== -1; - } + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + enum: ["{}", "[]", "()", "empty"] + }, + uniqueItems: true + } + }, + additionalProperties: false + } + ] + }, - /** - * Produces an object with the opener and closer exception values - * @param {Object} opts The exception options - * @returns {Object} `openers` and `closers` exception values - * @private - */ - function getExceptions() { - var openers = [], - closers = []; - - if (options.braceException) { - openers.push("{"); - closers.push("}"); - } + create: function(context) { - if (options.bracketException) { - openers.push("["); - closers.push("]"); - } + var MISSING_SPACE_MESSAGE = "There must be a space inside this paren.", + REJECTED_SPACE_MESSAGE = "There should be no spaces inside this paren.", + ALWAYS = context.options[0] === "always", - if (options.parenException) { - openers.push("("); - closers.push(")"); - } + exceptionsArrayOptions = (context.options.length === 2) ? context.options[1].exceptions : [], + options = {}, + exceptions; - if (options.empty) { - openers.push(")"); - closers.push("("); + if (exceptionsArrayOptions.length) { + options.braceException = exceptionsArrayOptions.indexOf("{}") !== -1; + options.bracketException = exceptionsArrayOptions.indexOf("[]") !== -1; + options.parenException = exceptionsArrayOptions.indexOf("()") !== -1; + options.empty = exceptionsArrayOptions.indexOf("empty") !== -1; } - return { - openers: openers, - closers: closers - }; - } - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - var sourceCode = context.getSourceCode(); - - /** - * Determines if a token is one of the exceptions for the opener paren - * @param {Object} token The token to check - * @returns {boolean} True if the token is one of the exceptions for the opener paren - */ - function isOpenerException(token) { - return token.type === "Punctuator" && exceptions.openers.indexOf(token.value) >= 0; - } - - /** - * Determines if a token is one of the exceptions for the closer paren - * @param {Object} token The token to check - * @returns {boolean} True if the token is one of the exceptions for the closer paren - */ - function isCloserException(token) { - return token.type === "Punctuator" && exceptions.closers.indexOf(token.value) >= 0; - } - - /** - * Determines if an opener paren should have a missing space after it - * @param {Object} left The paren token - * @param {Object} right The token after it - * @returns {boolean} True if the paren should have a space - */ - function shouldOpenerHaveSpace(left, right) { - if (sourceCode.isSpaceBetweenTokens(left, right)) { - return false; - } + /** + * Produces an object with the opener and closer exception values + * @param {Object} opts The exception options + * @returns {Object} `openers` and `closers` exception values + * @private + */ + function getExceptions() { + var openers = [], + closers = []; + + if (options.braceException) { + openers.push("{"); + closers.push("}"); + } - if (ALWAYS) { - if (right.type === "Punctuator" && right.value === ")") { - return false; + if (options.bracketException) { + openers.push("["); + closers.push("]"); } - return !isOpenerException(right); - } else { - return isOpenerException(right); - } - } - /** - * Determines if an closer paren should have a missing space after it - * @param {Object} left The token before the paren - * @param {Object} right The paren token - * @returns {boolean} True if the paren should have a space - */ - function shouldCloserHaveSpace(left, right) { - if (left.type === "Punctuator" && left.value === "(") { - return false; - } + if (options.parenException) { + openers.push("("); + closers.push(")"); + } - if (sourceCode.isSpaceBetweenTokens(left, right)) { - return false; - } + if (options.empty) { + openers.push(")"); + closers.push("("); + } - if (ALWAYS) { - return !isCloserException(left); - } else { - return isCloserException(left); + return { + openers: openers, + closers: closers + }; } - } - /** - * Determines if an opener paren should not have an existing space after it - * @param {Object} left The paren token - * @param {Object} right The token after it - * @returns {boolean} True if the paren should reject the space - */ - function shouldOpenerRejectSpace(left, right) { - if (right.type === "Line") { - return false; + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + var sourceCode = context.getSourceCode(); + + /** + * Determines if a token is one of the exceptions for the opener paren + * @param {Object} token The token to check + * @returns {boolean} True if the token is one of the exceptions for the opener paren + */ + function isOpenerException(token) { + return token.type === "Punctuator" && exceptions.openers.indexOf(token.value) >= 0; } - if (!astUtils.isTokenOnSameLine(left, right)) { - return false; + /** + * Determines if a token is one of the exceptions for the closer paren + * @param {Object} token The token to check + * @returns {boolean} True if the token is one of the exceptions for the closer paren + */ + function isCloserException(token) { + return token.type === "Punctuator" && exceptions.closers.indexOf(token.value) >= 0; } - if (!sourceCode.isSpaceBetweenTokens(left, right)) { - return false; - } + /** + * Determines if an opener paren should have a missing space after it + * @param {Object} left The paren token + * @param {Object} right The token after it + * @returns {boolean} True if the paren should have a space + */ + function shouldOpenerHaveSpace(left, right) { + if (sourceCode.isSpaceBetweenTokens(left, right)) { + return false; + } - if (ALWAYS) { - return isOpenerException(right); - } else { - return !isOpenerException(right); + if (ALWAYS) { + if (right.type === "Punctuator" && right.value === ")") { + return false; + } + return !isOpenerException(right); + } else { + return isOpenerException(right); + } } - } - /** - * Determines if an closer paren should not have an existing space after it - * @param {Object} left The token before the paren - * @param {Object} right The paren token - * @returns {boolean} True if the paren should reject the space - */ - function shouldCloserRejectSpace(left, right) { - if (left.type === "Punctuator" && left.value === "(") { - return false; - } + /** + * Determines if an closer paren should have a missing space after it + * @param {Object} left The token before the paren + * @param {Object} right The paren token + * @returns {boolean} True if the paren should have a space + */ + function shouldCloserHaveSpace(left, right) { + if (left.type === "Punctuator" && left.value === "(") { + return false; + } - if (!astUtils.isTokenOnSameLine(left, right)) { - return false; - } + if (sourceCode.isSpaceBetweenTokens(left, right)) { + return false; + } - if (!sourceCode.isSpaceBetweenTokens(left, right)) { - return false; + if (ALWAYS) { + return !isCloserException(left); + } else { + return isCloserException(left); + } } - if (ALWAYS) { - return isCloserException(left); - } else { - return !isCloserException(left); - } - } + /** + * Determines if an opener paren should not have an existing space after it + * @param {Object} left The paren token + * @param {Object} right The token after it + * @returns {boolean} True if the paren should reject the space + */ + function shouldOpenerRejectSpace(left, right) { + if (right.type === "Line") { + return false; + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + if (!astUtils.isTokenOnSameLine(left, right)) { + return false; + } - return { - "Program": function checkParenSpaces(node) { - var tokens, prevToken, nextToken; + if (!sourceCode.isSpaceBetweenTokens(left, right)) { + return false; + } - exceptions = getExceptions(); - tokens = sourceCode.tokensAndComments; + if (ALWAYS) { + return isOpenerException(right); + } else { + return !isOpenerException(right); + } + } - tokens.forEach(function(token, i) { - prevToken = tokens[i - 1]; - nextToken = tokens[i + 1]; + /** + * Determines if an closer paren should not have an existing space after it + * @param {Object} left The token before the paren + * @param {Object} right The paren token + * @returns {boolean} True if the paren should reject the space + */ + function shouldCloserRejectSpace(left, right) { + if (left.type === "Punctuator" && left.value === "(") { + return false; + } - if (token.type !== "Punctuator") { - return; - } + if (!astUtils.isTokenOnSameLine(left, right)) { + return false; + } - if (token.value !== "(" && token.value !== ")") { - return; - } + if (!sourceCode.isSpaceBetweenTokens(left, right)) { + return false; + } - if (token.value === "(" && shouldOpenerHaveSpace(token, nextToken)) { - context.report({ - node: node, - loc: token.loc.start, - message: MISSING_SPACE_MESSAGE, - fix: function(fixer) { - return fixer.insertTextAfter(token, " "); - } - }); - } else if (token.value === "(" && shouldOpenerRejectSpace(token, nextToken)) { - context.report({ - node: node, - loc: token.loc.start, - message: REJECTED_SPACE_MESSAGE, - fix: function(fixer) { - return fixer.removeRange([token.range[1], nextToken.range[0]]); - } - }); - } else if (token.value === ")" && shouldCloserHaveSpace(prevToken, token)) { - - // context.report(node, token.loc.start, MISSING_SPACE_MESSAGE); - context.report({ - node: node, - loc: token.loc.start, - message: MISSING_SPACE_MESSAGE, - fix: function(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } else if (token.value === ")" && shouldCloserRejectSpace(prevToken, token)) { - context.report({ - node: node, - loc: token.loc.start, - message: REJECTED_SPACE_MESSAGE, - fix: function(fixer) { - return fixer.removeRange([prevToken.range[1], token.range[0]]); - } - }); - } - }); + if (ALWAYS) { + return isCloserException(left); + } else { + return !isCloserException(left); + } } - }; -}; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- -module.exports.schema = [ - { - "enum": ["always", "never"] - }, - { - "type": "object", - "properties": { - "exceptions": { - "type": "array", - "items": { - "enum": ["{}", "[]", "()", "empty"] - }, - "uniqueItems": true + return { + Program: function checkParenSpaces(node) { + var tokens, prevToken, nextToken; + + exceptions = getExceptions(); + tokens = sourceCode.tokensAndComments; + + tokens.forEach(function(token, i) { + prevToken = tokens[i - 1]; + nextToken = tokens[i + 1]; + + if (token.type !== "Punctuator") { + return; + } + + if (token.value !== "(" && token.value !== ")") { + return; + } + + if (token.value === "(" && shouldOpenerHaveSpace(token, nextToken)) { + context.report({ + node: node, + loc: token.loc.start, + message: MISSING_SPACE_MESSAGE, + fix: function(fixer) { + return fixer.insertTextAfter(token, " "); + } + }); + } else if (token.value === "(" && shouldOpenerRejectSpace(token, nextToken)) { + context.report({ + node: node, + loc: token.loc.start, + message: REJECTED_SPACE_MESSAGE, + fix: function(fixer) { + return fixer.removeRange([token.range[1], nextToken.range[0]]); + } + }); + } else if (token.value === ")" && shouldCloserHaveSpace(prevToken, token)) { + + // context.report(node, token.loc.start, MISSING_SPACE_MESSAGE); + context.report({ + node: node, + loc: token.loc.start, + message: MISSING_SPACE_MESSAGE, + fix: function(fixer) { + return fixer.insertTextBefore(token, " "); + } + }); + } else if (token.value === ")" && shouldCloserRejectSpace(prevToken, token)) { + context.report({ + node: node, + loc: token.loc.start, + message: REJECTED_SPACE_MESSAGE, + fix: function(fixer) { + return fixer.removeRange([prevToken.range[1], token.range[0]]); + } + }); + } + }); } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/tools/eslint/lib/rules/space-infix-ops.js b/tools/eslint/lib/rules/space-infix-ops.js index dee07ee31a..862ff66fb5 100644 --- a/tools/eslint/lib/rules/space-infix-ops.js +++ b/tools/eslint/lib/rules/space-infix-ops.js @@ -8,141 +8,153 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var int32Hint = context.options[0] ? context.options[0].int32Hint === true : false; - - var OPERATORS = [ - "*", "/", "%", "+", "-", "<<", ">>", ">>>", "<", "<=", ">", ">=", "in", - "instanceof", "==", "!=", "===", "!==", "&", "^", "|", "&&", "||", "=", - "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|=", - "?", ":", ",", "**" - ]; - - /** - * Returns the first token which violates the rule - * @param {ASTNode} left - The left node of the main node - * @param {ASTNode} right - The right node of the main node - * @returns {object} The violator token or null - * @private - */ - function getFirstNonSpacedToken(left, right) { - var op, - tokens = context.getTokensBetween(left, right, 1); - - for (var i = 1, l = tokens.length - 1; i < l; ++i) { - op = tokens[i]; - if ( - op.type === "Punctuator" && - OPERATORS.indexOf(op.value) >= 0 && - (tokens[i - 1].range[1] >= op.range[0] || op.range[1] >= tokens[i + 1].range[0]) - ) { - return op; +module.exports = { + meta: { + docs: { + description: "require spacing around operators", + category: "Stylistic Issues", + recommended: false + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + int32Hint: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var int32Hint = context.options[0] ? context.options[0].int32Hint === true : false; + + var OPERATORS = [ + "*", "/", "%", "+", "-", "<<", ">>", ">>>", "<", "<=", ">", ">=", "in", + "instanceof", "==", "!=", "===", "!==", "&", "^", "|", "&&", "||", "=", + "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|=", + "?", ":", ",", "**" + ]; + + /** + * Returns the first token which violates the rule + * @param {ASTNode} left - The left node of the main node + * @param {ASTNode} right - The right node of the main node + * @returns {object} The violator token or null + * @private + */ + function getFirstNonSpacedToken(left, right) { + var op, + tokens = context.getTokensBetween(left, right, 1); + + for (var i = 1, l = tokens.length - 1; i < l; ++i) { + op = tokens[i]; + if ( + op.type === "Punctuator" && + OPERATORS.indexOf(op.value) >= 0 && + (tokens[i - 1].range[1] >= op.range[0] || op.range[1] >= tokens[i + 1].range[0]) + ) { + return op; + } } + return null; } - return null; - } - /** - * Reports an AST node as a rule violation - * @param {ASTNode} mainNode - The node to report - * @param {object} culpritToken - The token which has a problem - * @returns {void} - * @private - */ - function report(mainNode, culpritToken) { - context.report({ - node: mainNode, - loc: culpritToken.loc.start, - message: "Infix operators must be spaced.", - fix: function(fixer) { - var previousToken = context.getTokenBefore(culpritToken); - var afterToken = context.getTokenAfter(culpritToken); - var fixString = ""; - - if (culpritToken.range[0] - previousToken.range[1] === 0) { - fixString = " "; + /** + * Reports an AST node as a rule violation + * @param {ASTNode} mainNode - The node to report + * @param {object} culpritToken - The token which has a problem + * @returns {void} + * @private + */ + function report(mainNode, culpritToken) { + context.report({ + node: mainNode, + loc: culpritToken.loc.start, + message: "Infix operators must be spaced.", + fix: function(fixer) { + var previousToken = context.getTokenBefore(culpritToken); + var afterToken = context.getTokenAfter(culpritToken); + var fixString = ""; + + if (culpritToken.range[0] - previousToken.range[1] === 0) { + fixString = " "; + } + + fixString += culpritToken.value; + + if (afterToken.range[0] - culpritToken.range[1] === 0) { + fixString += " "; + } + + return fixer.replaceText(culpritToken, fixString); } + }); + } - fixString += culpritToken.value; + /** + * Check if the node is binary then report + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkBinary(node) { + var nonSpacedNode = getFirstNonSpacedToken(node.left, node.right); - if (afterToken.range[0] - culpritToken.range[1] === 0) { - fixString += " "; + if (nonSpacedNode) { + if (!(int32Hint && context.getSource(node).substr(-2) === "|0")) { + report(node, nonSpacedNode); } - - return fixer.replaceText(culpritToken, fixString); - } - }); - } - - /** - * Check if the node is binary then report - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkBinary(node) { - var nonSpacedNode = getFirstNonSpacedToken(node.left, node.right); - - if (nonSpacedNode) { - if (!(int32Hint && context.getSource(node).substr(-2) === "|0")) { - report(node, nonSpacedNode); } } - } - /** - * Check if the node is conditional - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkConditional(node) { - var nonSpacedConsequesntNode = getFirstNonSpacedToken(node.test, node.consequent); - var nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate); - - if (nonSpacedConsequesntNode) { - report(node, nonSpacedConsequesntNode); - } else if (nonSpacedAlternateNode) { - report(node, nonSpacedAlternateNode); + /** + * Check if the node is conditional + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkConditional(node) { + var nonSpacedConsequesntNode = getFirstNonSpacedToken(node.test, node.consequent); + var nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate); + + if (nonSpacedConsequesntNode) { + report(node, nonSpacedConsequesntNode); + } else if (nonSpacedAlternateNode) { + report(node, nonSpacedAlternateNode); + } } - } - /** - * Check if the node is a variable - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkVar(node) { - var nonSpacedNode; - - if (node.init) { - nonSpacedNode = getFirstNonSpacedToken(node.id, node.init); - if (nonSpacedNode) { - report(node, nonSpacedNode); + /** + * Check if the node is a variable + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkVar(node) { + var nonSpacedNode; + + if (node.init) { + nonSpacedNode = getFirstNonSpacedToken(node.id, node.init); + if (nonSpacedNode) { + report(node, nonSpacedNode); + } } } - } - return { - "AssignmentExpression": checkBinary, - "AssignmentPattern": checkBinary, - "BinaryExpression": checkBinary, - "LogicalExpression": checkBinary, - "ConditionalExpression": checkConditional, - "VariableDeclarator": checkVar - }; + return { + AssignmentExpression: checkBinary, + AssignmentPattern: checkBinary, + BinaryExpression: checkBinary, + LogicalExpression: checkBinary, + ConditionalExpression: checkConditional, + VariableDeclarator: checkVar + }; -}; - -module.exports.schema = [ - { - "type": "object", - "properties": { - "int32Hint": { - "type": "boolean" - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/space-unary-ops.js b/tools/eslint/lib/rules/space-unary-ops.js index d5412e36f3..0bb92af1e6 100644 --- a/tools/eslint/lib/rules/space-unary-ops.js +++ b/tools/eslint/lib/rules/space-unary-ops.js @@ -1,7 +1,6 @@ /** * @fileoverview This rule shoud require or disallow spaces before or after unary operations. * @author Marcin Kumorek - * @copyright 2014 Marcin Kumorek. All rights reserved. */ "use strict"; @@ -9,259 +8,271 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options && Array.isArray(context.options) && context.options[0] || { words: true, nonwords: false }; +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing before or after unary operators", + category: "Stylistic Issues", + recommended: false + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + fixable: "whitespace", - /** - * Check if the node is the first "!" in a "!!" convert to Boolean expression - * @param {ASTnode} node AST node - * @returns {boolean} Whether or not the node is first "!" in "!!" - */ - function isFirstBangInBangBangExpression(node) { - return node && node.type === "UnaryExpression" && node.argument.operator === "!" && - node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!"; - } + schema: [ + { + type: "object", + properties: { + words: { + type: "boolean" + }, + nonwords: { + type: "boolean" + }, + overrides: { + type: "object", + additionalProperties: { + type: "boolean" + } + } + }, + additionalProperties: false + } + ] + }, - /** - * Check if the node's child argument is an "ObjectExpression" - * @param {ASTnode} node AST node - * @returns {boolean} Whether or not the argument's type is "ObjectExpression" - */ - function isArgumentObjectExpression(node) { - return node.argument && node.argument.type && node.argument.type === "ObjectExpression"; - } + create: function(context) { + var options = context.options && Array.isArray(context.options) && context.options[0] || { words: true, nonwords: false }; - /** - * Checks if an override exists for a given operator. - * @param {ASTnode} node AST node - * @param {string} operator Operator - * @returns {boolean} Whether or not an override has been provided for the operator - */ - function overrideExistsForOperator(node, operator) { - return options.overrides && options.overrides.hasOwnProperty(operator); - } + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * Gets the value that the override was set to for this operator - * @param {ASTnode} node AST node - * @param {string} operator Operator - * @returns {boolean} Whether or not an override enforces a space with this operator - */ - function overrideEnforcesSpaces(node, operator) { - return options.overrides[operator]; - } + /** + * Check if the node is the first "!" in a "!!" convert to Boolean expression + * @param {ASTnode} node AST node + * @returns {boolean} Whether or not the node is first "!" in "!!" + */ + function isFirstBangInBangBangExpression(node) { + return node && node.type === "UnaryExpression" && node.argument.operator === "!" && + node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!"; + } - /** - * Verify Unary Word Operator has spaces after the word operator - * @param {ASTnode} node AST node - * @param {object} firstToken first token from the AST node - * @param {object} secondToken second token from the AST node - * @param {string} word The word to be used for reporting - * @returns {void} - */ - function verifyWordHasSpaces(node, firstToken, secondToken, word) { - if (secondToken.range[0] === firstToken.range[1]) { - context.report({ - node: node, - message: "Unary word operator '" + word + "' must be followed by whitespace.", - fix: function(fixer) { - return fixer.insertTextAfter(firstToken, " "); - } - }); + /** + * Check if the node's child argument is an "ObjectExpression" + * @param {ASTnode} node AST node + * @returns {boolean} Whether or not the argument's type is "ObjectExpression" + */ + function isArgumentObjectExpression(node) { + return node.argument && node.argument.type && node.argument.type === "ObjectExpression"; + } + + /** + * Checks if an override exists for a given operator. + * @param {ASTnode} node AST node + * @param {string} operator Operator + * @returns {boolean} Whether or not an override has been provided for the operator + */ + function overrideExistsForOperator(node, operator) { + return options.overrides && options.overrides.hasOwnProperty(operator); + } + + /** + * Gets the value that the override was set to for this operator + * @param {ASTnode} node AST node + * @param {string} operator Operator + * @returns {boolean} Whether or not an override enforces a space with this operator + */ + function overrideEnforcesSpaces(node, operator) { + return options.overrides[operator]; } - } - /** - * Verify Unary Word Operator doesn't have spaces after the word operator - * @param {ASTnode} node AST node - * @param {object} firstToken first token from the AST node - * @param {object} secondToken second token from the AST node - * @param {string} word The word to be used for reporting - * @returns {void} - */ - function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) { - if (isArgumentObjectExpression(node)) { - if (secondToken.range[0] > firstToken.range[1]) { + /** + * Verify Unary Word Operator has spaces after the word operator + * @param {ASTnode} node AST node + * @param {object} firstToken first token from the AST node + * @param {object} secondToken second token from the AST node + * @param {string} word The word to be used for reporting + * @returns {void} + */ + function verifyWordHasSpaces(node, firstToken, secondToken, word) { + if (secondToken.range[0] === firstToken.range[1]) { context.report({ node: node, - message: "Unexpected space after unary word operator '" + word + "'.", + message: "Unary word operator '" + word + "' must be followed by whitespace.", fix: function(fixer) { - return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); + return fixer.insertTextAfter(firstToken, " "); } }); } } - } - /** - * Check Unary Word Operators for spaces after the word operator - * @param {ASTnode} node AST node - * @param {object} firstToken first token from the AST node - * @param {object} secondToken second token from the AST node - * @param {string} word The word to be used for reporting - * @returns {void} - */ - function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) { - word = word || firstToken.value; + /** + * Verify Unary Word Operator doesn't have spaces after the word operator + * @param {ASTnode} node AST node + * @param {object} firstToken first token from the AST node + * @param {object} secondToken second token from the AST node + * @param {string} word The word to be used for reporting + * @returns {void} + */ + function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) { + if (isArgumentObjectExpression(node)) { + if (secondToken.range[0] > firstToken.range[1]) { + context.report({ + node: node, + message: "Unexpected space after unary word operator '" + word + "'.", + fix: function(fixer) { + return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); + } + }); + } + } + } + + /** + * Check Unary Word Operators for spaces after the word operator + * @param {ASTnode} node AST node + * @param {object} firstToken first token from the AST node + * @param {object} secondToken second token from the AST node + * @param {string} word The word to be used for reporting + * @returns {void} + */ + function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) { + word = word || firstToken.value; - if (overrideExistsForOperator(node, word)) { - if (overrideEnforcesSpaces(node, word)) { + if (overrideExistsForOperator(node, word)) { + if (overrideEnforcesSpaces(node, word)) { + verifyWordHasSpaces(node, firstToken, secondToken, word); + } else { + verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word); + } + } else if (options.words) { verifyWordHasSpaces(node, firstToken, secondToken, word); } else { verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word); } - } else if (options.words) { - verifyWordHasSpaces(node, firstToken, secondToken, word); - } else { - verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word); } - } - /** - * Verifies YieldExpressions satisfy spacing requirements - * @param {ASTnode} node AST node - * @returns {void} - */ - function checkForSpacesAfterYield(node) { - var tokens = context.getFirstTokens(node, 3), - word = "yield"; + /** + * Verifies YieldExpressions satisfy spacing requirements + * @param {ASTnode} node AST node + * @returns {void} + */ + function checkForSpacesAfterYield(node) { + var tokens = context.getFirstTokens(node, 3), + word = "yield"; - if (!node.argument || node.delegate) { - return; - } - - checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word); - } - - /** - * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator - * @param {ASTnode} node AST node - * @param {object} firstToken First token in the expression - * @param {object} secondToken Second token in the expression - * @returns {void} - */ - function verifyNonWordsHaveSpaces(node, firstToken, secondToken) { - if (node.prefix) { - if (isFirstBangInBangBangExpression(node)) { + if (!node.argument || node.delegate) { return; } - if (firstToken.range[1] === secondToken.range[0]) { - context.report({ - node: node, - message: "Unary operator '" + firstToken.value + "' must be followed by whitespace.", - fix: function(fixer) { - return fixer.insertTextAfter(firstToken, " "); - } - }); - } - } else { - if (firstToken.range[1] === secondToken.range[0]) { - context.report({ - node: node, - message: "Space is required before unary expressions '" + secondToken.value + "'.", - fix: function(fixer) { - return fixer.insertTextBefore(secondToken, " "); - } - }); - } + + checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word); } - } - /** - * Verifies UnaryExpression, UpdateExpression and NewExpression don't have spaces before or after the operator - * @param {ASTnode} node AST node - * @param {object} firstToken First token in the expression - * @param {object} secondToken Second token in the expression - * @returns {void} - */ - function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) { - if (node.prefix) { - if (secondToken.range[0] > firstToken.range[1]) { - context.report({ - node: node, - message: "Unexpected space after unary operator '" + firstToken.value + "'.", - fix: function(fixer) { - return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); - } - }); + /** + * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator + * @param {ASTnode} node AST node + * @param {object} firstToken First token in the expression + * @param {object} secondToken Second token in the expression + * @returns {void} + */ + function verifyNonWordsHaveSpaces(node, firstToken, secondToken) { + if (node.prefix) { + if (isFirstBangInBangBangExpression(node)) { + return; + } + if (firstToken.range[1] === secondToken.range[0]) { + context.report({ + node: node, + message: "Unary operator '" + firstToken.value + "' must be followed by whitespace.", + fix: function(fixer) { + return fixer.insertTextAfter(firstToken, " "); + } + }); + } + } else { + if (firstToken.range[1] === secondToken.range[0]) { + context.report({ + node: node, + message: "Space is required before unary expressions '" + secondToken.value + "'.", + fix: function(fixer) { + return fixer.insertTextBefore(secondToken, " "); + } + }); + } } - } else { - if (secondToken.range[0] > firstToken.range[1]) { - context.report({ - node: node, - message: "Unexpected space before unary operator '" + secondToken.value + "'.", - fix: function(fixer) { - return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); - } - }); + } + + /** + * Verifies UnaryExpression, UpdateExpression and NewExpression don't have spaces before or after the operator + * @param {ASTnode} node AST node + * @param {object} firstToken First token in the expression + * @param {object} secondToken Second token in the expression + * @returns {void} + */ + function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) { + if (node.prefix) { + if (secondToken.range[0] > firstToken.range[1]) { + context.report({ + node: node, + message: "Unexpected space after unary operator '" + firstToken.value + "'.", + fix: function(fixer) { + return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); + } + }); + } + } else { + if (secondToken.range[0] > firstToken.range[1]) { + context.report({ + node: node, + message: "Unexpected space before unary operator '" + secondToken.value + "'.", + fix: function(fixer) { + return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); + } + }); + } } } - } - /** - * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements - * @param {ASTnode} node AST node - * @returns {void} - */ - function checkForSpaces(node) { - var tokens = context.getFirstTokens(node, 2), - firstToken = tokens[0], - secondToken = tokens[1]; + /** + * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements + * @param {ASTnode} node AST node + * @returns {void} + */ + function checkForSpaces(node) { + var tokens = context.getFirstTokens(node, 2), + firstToken = tokens[0], + secondToken = tokens[1]; - if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") { - checkUnaryWordOperatorForSpaces(node, firstToken, secondToken); - return; - } + if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") { + checkUnaryWordOperatorForSpaces(node, firstToken, secondToken); + return; + } - var operator = node.prefix ? tokens[0].value : tokens[1].value; + var operator = node.prefix ? tokens[0].value : tokens[1].value; - if (overrideExistsForOperator(node, operator)) { - if (overrideEnforcesSpaces(node, operator)) { + if (overrideExistsForOperator(node, operator)) { + if (overrideEnforcesSpaces(node, operator)) { + verifyNonWordsHaveSpaces(node, firstToken, secondToken); + } else { + verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); + } + } else if (options.nonwords) { verifyNonWordsHaveSpaces(node, firstToken, secondToken); } else { verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); } - } else if (options.nonwords) { - verifyNonWordsHaveSpaces(node, firstToken, secondToken); - } else { - verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); } - } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { - "UnaryExpression": checkForSpaces, - "UpdateExpression": checkForSpaces, - "NewExpression": checkForSpaces, - "YieldExpression": checkForSpacesAfterYield - }; - -}; + return { + UnaryExpression: checkForSpaces, + UpdateExpression: checkForSpaces, + NewExpression: checkForSpaces, + YieldExpression: checkForSpacesAfterYield + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "words": { - "type": "boolean" - }, - "nonwords": { - "type": "boolean" - }, - "overrides": { - "type": "object", - "additionalProperties": { - "type": "boolean" - } - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/spaced-comment.js b/tools/eslint/lib/rules/spaced-comment.js index 73f0861c8f..149862024c 100644 --- a/tools/eslint/lib/rules/spaced-comment.js +++ b/tools/eslint/lib/rules/spaced-comment.js @@ -1,9 +1,6 @@ /** * @fileoverview Source code for spaced-comments rule * @author Gyandeep Singh - * @copyright 2015 Toru Nagashima. All rights reserved. - * @copyright 2015 Gyandeep Singh. All rights reserved. - * @copyright 2014 Greg Cochard. All rights reserved. */ "use strict"; @@ -140,166 +137,178 @@ function createNeverStylePattern(markers) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce consistent spacing after the `//` or `/*` in a comment", + category: "Stylistic Issues", + recommended: false + }, - // Unless the first option is never, require a space - var requireSpace = context.options[0] !== "never"; + fixable: "whitespace", - /* - * Parse the second options. - * If markers don't include `"*"`, it's added automatically for JSDoc - * comments. - */ - var config = context.options[1] || {}; - var styleRules = ["block", "line"].reduce(function(rule, type) { - var markers = parseMarkersOption(config[type] && config[type].markers || config.markers); - var exceptions = config[type] && config[type].exceptions || config.exceptions || []; - - // Create RegExp object for valid patterns. - rule[type] = { - regex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers), - hasExceptions: exceptions.length > 0, - markers: new RegExp("^(" + markers.map(escape).join("|") + ")") - }; - - return rule; - }, {}); + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string" + } + }, + markers: { + type: "array", + items: { + type: "string" + } + }, + line: { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string" + } + }, + markers: { + type: "array", + items: { + type: "string" + } + } + }, + additionalProperties: false + }, + block: { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string" + } + }, + markers: { + type: "array", + items: { + type: "string" + } + } + }, + additionalProperties: false + } + }, + additionalProperties: false + } + ] + }, - /** - * Reports a spacing error with an appropriate message. - * @param {ASTNode} node - A comment node to check. - * @param {string} message - An error message to report - * @param {Array} match - An array of match results for markers. - * @returns {void} - */ - function report(node, message, match) { - var type = node.type.toLowerCase(), - commentIdentifier = type === "block" ? "/*" : "//"; - - context.report({ - node: node, - fix: function(fixer) { - var start = node.range[0], - end = start + 2; - - if (requireSpace) { - if (match) { + create: function(context) { + + // Unless the first option is never, require a space + var requireSpace = context.options[0] !== "never"; + + /* + * Parse the second options. + * If markers don't include `"*"`, it's added automatically for JSDoc + * comments. + */ + var config = context.options[1] || {}; + var styleRules = ["block", "line"].reduce(function(rule, type) { + var markers = parseMarkersOption(config[type] && config[type].markers || config.markers); + var exceptions = config[type] && config[type].exceptions || config.exceptions || []; + + // Create RegExp object for valid patterns. + rule[type] = { + regex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers), + hasExceptions: exceptions.length > 0, + markers: new RegExp("^(" + markers.map(escape).join("|") + ")") + }; + + return rule; + }, {}); + + /** + * Reports a spacing error with an appropriate message. + * @param {ASTNode} node - A comment node to check. + * @param {string} message - An error message to report + * @param {Array} match - An array of match results for markers. + * @returns {void} + */ + function report(node, message, match) { + var type = node.type.toLowerCase(), + commentIdentifier = type === "block" ? "/*" : "//"; + + context.report({ + node: node, + fix: function(fixer) { + var start = node.range[0], + end = start + 2; + + if (requireSpace) { + if (match) { + end += match[0].length; + } + return fixer.insertTextAfterRange([start, end], " "); + } else { end += match[0].length; + return fixer.replaceTextRange([start, end], commentIdentifier + (match[1] ? match[1] : "")); } - return fixer.insertTextAfterRange([start, end], " "); - } else { - end += match[0].length; - return fixer.replaceTextRange([start, end], commentIdentifier + (match[1] ? match[1] : "")); - } - }, - message: message - }); - } - - /** - * Reports a given comment if it's invalid. - * @param {ASTNode} node - a comment node to check. - * @returns {void} - */ - function checkCommentForSpace(node) { - var type = node.type.toLowerCase(), - rule = styleRules[type], - commentIdentifier = type === "block" ? "/*" : "//"; - - // Ignores empty comments. - if (node.value.length === 0) { - return; + }, + message: message + }); } - // Checks. - if (requireSpace) { - if (!rule.regex.test(node.value)) { - var hasMarker = rule.markers.exec(node.value); - var marker = hasMarker ? commentIdentifier + hasMarker[0] : commentIdentifier; - - if (rule.hasExceptions) { - report(node, "Expected exception block, space or tab after '" + marker + "' in comment.", hasMarker); - } else { - report(node, "Expected space or tab after '" + marker + "' in comment.", hasMarker); - } + /** + * Reports a given comment if it's invalid. + * @param {ASTNode} node - a comment node to check. + * @returns {void} + */ + function checkCommentForSpace(node) { + var type = node.type.toLowerCase(), + rule = styleRules[type], + commentIdentifier = type === "block" ? "/*" : "//"; + + // Ignores empty comments. + if (node.value.length === 0) { + return; } - } else { - var matched = rule.regex.exec(node.value); - if (matched) { - if (!matched[1]) { - report(node, "Unexpected space or tab after '" + commentIdentifier + "' in comment.", matched); - } else { - report(node, "Unexpected space or tab after marker (" + matched[1] + ") in comment.", matched); + // Checks. + if (requireSpace) { + if (!rule.regex.test(node.value)) { + var hasMarker = rule.markers.exec(node.value); + var marker = hasMarker ? commentIdentifier + hasMarker[0] : commentIdentifier; + + if (rule.hasExceptions) { + report(node, "Expected exception block, space or tab after '" + marker + "' in comment.", hasMarker); + } else { + report(node, "Expected space or tab after '" + marker + "' in comment.", hasMarker); + } + } + } else { + var matched = rule.regex.exec(node.value); + + if (matched) { + if (!matched[1]) { + report(node, "Unexpected space or tab after '" + commentIdentifier + "' in comment.", matched); + } else { + report(node, "Unexpected space or tab after marker (" + matched[1] + ") in comment.", matched); + } } } } - } - return { + return { - "LineComment": checkCommentForSpace, - "BlockComment": checkCommentForSpace + LineComment: checkCommentForSpace, + BlockComment: checkCommentForSpace - }; -}; - -module.exports.schema = [ - { - "enum": ["always", "never"] - }, - { - "type": "object", - "properties": { - "exceptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "markers": { - "type": "array", - "items": { - "type": "string" - } - }, - "line": { - "type": "object", - "properties": { - "exceptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "markers": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "block": { - "type": "object", - "properties": { - "exceptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "markers": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/tools/eslint/lib/rules/strict.js b/tools/eslint/lib/rules/strict.js index 81f1cfbf8c..4097a32793 100644 --- a/tools/eslint/lib/rules/strict.js +++ b/tools/eslint/lib/rules/strict.js @@ -1,9 +1,6 @@ /** * @fileoverview Rule to control usage of strict mode directives. * @author Brandon Mills - * @copyright 2015 Brandon Mills. All rights reserved. - * @copyright 2013-2014 Nicholas C. Zakas. All rights reserved. - * @copyright 2013 Ian Christian Myers. All rights reserved. */ "use strict"; @@ -60,155 +57,165 @@ function getUseStrictDirectives(statements) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require or disallow strict mode directives", + category: "Strict Mode", + recommended: false + }, - var mode = context.options[0] || "safe", - ecmaFeatures = context.parserOptions.ecmaFeatures || {}, - scopes = [], - classScopes = [], - rule; + schema: [ + { + enum: ["never", "global", "function", "safe"] + } + ] + }, - if (ecmaFeatures.impliedStrict) { - mode = "implied"; - } else if (mode === "safe") { - mode = ecmaFeatures.globalReturn ? "global" : "function"; - } + create: function(context) { - /** - * Report a slice of an array of nodes with a given message. - * @param {ASTNode[]} nodes Nodes. - * @param {string} start Index to start from. - * @param {string} end Index to end before. - * @param {string} message Message to display. - * @returns {void} - */ - function reportSlice(nodes, start, end, message) { - var i; - - for (i = start; i < end; i++) { - context.report(nodes[i], message); - } - } + var mode = context.options[0] || "safe", + ecmaFeatures = context.parserOptions.ecmaFeatures || {}, + scopes = [], + classScopes = [], + rule; - /** - * Report all nodes in an array with a given message. - * @param {ASTNode[]} nodes Nodes. - * @param {string} message Message to display. - * @returns {void} - */ - function reportAll(nodes, message) { - reportSlice(nodes, 0, nodes.length, message); - } - - /** - * Report all nodes in an array, except the first, with a given message. - * @param {ASTNode[]} nodes Nodes. - * @param {string} message Message to display. - * @returns {void} - */ - function reportAllExceptFirst(nodes, message) { - reportSlice(nodes, 1, nodes.length, message); - } + if (ecmaFeatures.impliedStrict) { + mode = "implied"; + } else if (mode === "safe") { + mode = ecmaFeatures.globalReturn ? "global" : "function"; + } - /** - * Entering a function in 'function' mode pushes a new nested scope onto the - * stack. The new scope is true if the nested function is strict mode code. - * @param {ASTNode} node The function declaration or expression. - * @param {ASTNode[]} useStrictDirectives The Use Strict Directives of the node. - * @returns {void} - */ - function enterFunctionInFunctionMode(node, useStrictDirectives) { - var isInClass = classScopes.length > 0, - isParentGlobal = scopes.length === 0 && classScopes.length === 0, - isParentStrict = scopes.length > 0 && scopes[scopes.length - 1], - isStrict = useStrictDirectives.length > 0; - - if (isStrict) { - if (isParentStrict) { - context.report(useStrictDirectives[0], messages.unnecessary); - } else if (isInClass) { - context.report(useStrictDirectives[0], messages.unnecessaryInClasses); + /** + * Report a slice of an array of nodes with a given message. + * @param {ASTNode[]} nodes Nodes. + * @param {string} start Index to start from. + * @param {string} end Index to end before. + * @param {string} message Message to display. + * @returns {void} + */ + function reportSlice(nodes, start, end, message) { + var i; + + for (i = start; i < end; i++) { + context.report(nodes[i], message); } + } - reportAllExceptFirst(useStrictDirectives, messages.multiple); - } else if (isParentGlobal) { - context.report(node, messages.function); + /** + * Report all nodes in an array with a given message. + * @param {ASTNode[]} nodes Nodes. + * @param {string} message Message to display. + * @returns {void} + */ + function reportAll(nodes, message) { + reportSlice(nodes, 0, nodes.length, message); } - scopes.push(isParentStrict || isStrict); - } + /** + * Report all nodes in an array, except the first, with a given message. + * @param {ASTNode[]} nodes Nodes. + * @param {string} message Message to display. + * @returns {void} + */ + function reportAllExceptFirst(nodes, message) { + reportSlice(nodes, 1, nodes.length, message); + } - /** - * Exiting a function in 'function' mode pops its scope off the stack. - * @returns {void} - */ - function exitFunctionInFunctionMode() { - scopes.pop(); - } + /** + * Entering a function in 'function' mode pushes a new nested scope onto the + * stack. The new scope is true if the nested function is strict mode code. + * @param {ASTNode} node The function declaration or expression. + * @param {ASTNode[]} useStrictDirectives The Use Strict Directives of the node. + * @returns {void} + */ + function enterFunctionInFunctionMode(node, useStrictDirectives) { + var isInClass = classScopes.length > 0, + isParentGlobal = scopes.length === 0 && classScopes.length === 0, + isParentStrict = scopes.length > 0 && scopes[scopes.length - 1], + isStrict = useStrictDirectives.length > 0; + + if (isStrict) { + if (isParentStrict) { + context.report(useStrictDirectives[0], messages.unnecessary); + } else if (isInClass) { + context.report(useStrictDirectives[0], messages.unnecessaryInClasses); + } - /** - * Enter a function and either: - * - Push a new nested scope onto the stack (in 'function' mode). - * - Report all the Use Strict Directives (in the other modes). - * @param {ASTNode} node The function declaration or expression. - * @returns {void} - */ - function enterFunction(node) { - var isBlock = node.body.type === "BlockStatement", - useStrictDirectives = isBlock ? - getUseStrictDirectives(node.body.body) : []; + reportAllExceptFirst(useStrictDirectives, messages.multiple); + } else if (isParentGlobal) { + context.report(node, messages.function); + } - if (mode === "function") { - enterFunctionInFunctionMode(node, useStrictDirectives); - } else { - reportAll(useStrictDirectives, messages[mode]); + scopes.push(isParentStrict || isStrict); } - } - - rule = { - "Program": function(node) { - var useStrictDirectives = getUseStrictDirectives(node.body); - if (node.sourceType === "module") { - mode = "module"; - } + /** + * Exiting a function in 'function' mode pops its scope off the stack. + * @returns {void} + */ + function exitFunctionInFunctionMode() { + scopes.pop(); + } - if (mode === "global") { - if (node.body.length > 0 && useStrictDirectives.length === 0) { - context.report(node, messages.global); - } - reportAllExceptFirst(useStrictDirectives, messages.multiple); + /** + * Enter a function and either: + * - Push a new nested scope onto the stack (in 'function' mode). + * - Report all the Use Strict Directives (in the other modes). + * @param {ASTNode} node The function declaration or expression. + * @returns {void} + */ + function enterFunction(node) { + var isBlock = node.body.type === "BlockStatement", + useStrictDirectives = isBlock ? + getUseStrictDirectives(node.body.body) : []; + + if (mode === "function") { + enterFunctionInFunctionMode(node, useStrictDirectives); } else { reportAll(useStrictDirectives, messages[mode]); } - }, - "FunctionDeclaration": enterFunction, - "FunctionExpression": enterFunction, - "ArrowFunctionExpression": enterFunction - }; + } - if (mode === "function") { - lodash.assign(rule, { + rule = { + Program: function(node) { + var useStrictDirectives = getUseStrictDirectives(node.body); - // Inside of class bodies are always strict mode. - "ClassBody": function() { - classScopes.push(true); - }, - "ClassBody:exit": function() { - classScopes.pop(); - }, + if (node.sourceType === "module") { + mode = "module"; + } - "FunctionDeclaration:exit": exitFunctionInFunctionMode, - "FunctionExpression:exit": exitFunctionInFunctionMode, - "ArrowFunctionExpression:exit": exitFunctionInFunctionMode - }); - } + if (mode === "global") { + if (node.body.length > 0 && useStrictDirectives.length === 0) { + context.report(node, messages.global); + } + reportAllExceptFirst(useStrictDirectives, messages.multiple); + } else { + reportAll(useStrictDirectives, messages[mode]); + } + }, + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + ArrowFunctionExpression: enterFunction + }; - return rule; -}; + if (mode === "function") { + lodash.assign(rule, { + + // Inside of class bodies are always strict mode. + ClassBody: function() { + classScopes.push(true); + }, + "ClassBody:exit": function() { + classScopes.pop(); + }, + + "FunctionDeclaration:exit": exitFunctionInFunctionMode, + "FunctionExpression:exit": exitFunctionInFunctionMode, + "ArrowFunctionExpression:exit": exitFunctionInFunctionMode + }); + } -module.exports.schema = [ - { - "enum": ["never", "global", "function", "safe"] + return rule; } -]; +}; diff --git a/tools/eslint/lib/rules/template-curly-spacing.js b/tools/eslint/lib/rules/template-curly-spacing.js index 4f5f9e1a41..144a035369 100644 --- a/tools/eslint/lib/rules/template-curly-spacing.js +++ b/tools/eslint/lib/rules/template-curly-spacing.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to enforce spacing around embedded expressions of template strings * @author Toru Nagashima - * @copyright 2016 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -24,82 +22,94 @@ var CLOSE_PAREN = /^\}/; // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var sourceCode = context.getSourceCode(); - var always = context.options[0] === "always"; - var prefix = always ? "Expected" : "Unexpected"; - - /** - * Checks spacing before `}` of a given token. - * @param {Token} token - A token to check. This is a Template token. - * @returns {void} - */ - function checkSpacingBefore(token) { - var prevToken = sourceCode.getTokenBefore(token); - - if (prevToken && - CLOSE_PAREN.test(token.value) && - astUtils.isTokenOnSameLine(prevToken, token) && - sourceCode.isSpaceBetweenTokens(prevToken, token) !== always - ) { - context.report({ - loc: token.loc.start, - message: prefix + " space(s) before '}'.", - fix: function(fixer) { - if (always) { - return fixer.insertTextBefore(token, " "); +module.exports = { + meta: { + docs: { + description: "require or disallow spacing around embedded expressions of template strings", + category: "ECMAScript 6", + recommended: false + }, + + fixable: "whitespace", + + schema: [ + {enum: ["always", "never"]} + ] + }, + + create: function(context) { + var sourceCode = context.getSourceCode(); + var always = context.options[0] === "always"; + var prefix = always ? "Expected" : "Unexpected"; + + /** + * Checks spacing before `}` of a given token. + * @param {Token} token - A token to check. This is a Template token. + * @returns {void} + */ + function checkSpacingBefore(token) { + var prevToken = sourceCode.getTokenBefore(token); + + if (prevToken && + CLOSE_PAREN.test(token.value) && + astUtils.isTokenOnSameLine(prevToken, token) && + sourceCode.isSpaceBetweenTokens(prevToken, token) !== always + ) { + context.report({ + loc: token.loc.start, + message: prefix + " space(s) before '}'.", + fix: function(fixer) { + if (always) { + return fixer.insertTextBefore(token, " "); + } + return fixer.removeRange([ + prevToken.range[1], + token.range[0] + ]); } - return fixer.removeRange([ - prevToken.range[1], - token.range[0] - ]); - } - }); + }); + } } - } - /** - * Checks spacing after `${` of a given token. - * @param {Token} token - A token to check. This is a Template token. - * @returns {void} - */ - function checkSpacingAfter(token) { - var nextToken = sourceCode.getTokenAfter(token); - - if (nextToken && - OPEN_PAREN.test(token.value) && - astUtils.isTokenOnSameLine(token, nextToken) && - sourceCode.isSpaceBetweenTokens(token, nextToken) !== always - ) { - context.report({ - loc: { - line: token.loc.end.line, - column: token.loc.end.column - 2 - }, - message: prefix + " space(s) after '${'.", - fix: function(fixer) { - if (always) { - return fixer.insertTextAfter(token, " "); + /** + * Checks spacing after `${` of a given token. + * @param {Token} token - A token to check. This is a Template token. + * @returns {void} + */ + function checkSpacingAfter(token) { + var nextToken = sourceCode.getTokenAfter(token); + + if (nextToken && + OPEN_PAREN.test(token.value) && + astUtils.isTokenOnSameLine(token, nextToken) && + sourceCode.isSpaceBetweenTokens(token, nextToken) !== always + ) { + context.report({ + loc: { + line: token.loc.end.line, + column: token.loc.end.column - 2 + }, + message: prefix + " space(s) after '${'.", + fix: function(fixer) { + if (always) { + return fixer.insertTextAfter(token, " "); + } + return fixer.removeRange([ + token.range[1], + nextToken.range[0] + ]); } - return fixer.removeRange([ - token.range[1], - nextToken.range[0] - ]); - } - }); + }); + } } - } - return { - TemplateElement: function(node) { - var token = sourceCode.getFirstToken(node); + return { + TemplateElement: function(node) { + var token = sourceCode.getFirstToken(node); - checkSpacingBefore(token); - checkSpacingAfter(token); - } - }; + checkSpacingBefore(token); + checkSpacingAfter(token); + } + }; + } }; - -module.exports.schema = [ - {enum: ["always", "never"]} -]; diff --git a/tools/eslint/lib/rules/use-isnan.js b/tools/eslint/lib/rules/use-isnan.js index 7d65f9c0d8..10f7b71c8b 100644 --- a/tools/eslint/lib/rules/use-isnan.js +++ b/tools/eslint/lib/rules/use-isnan.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to flag comparisons to the value NaN * @author James Allardice - * @copyright 2014 Jordan Harband. All rights reserved. - * @copyright 2013 James Allardice. All rights reserved. */ "use strict"; @@ -11,16 +9,26 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require calls to `isNaN()` when checking for `NaN`", + category: "Possible Errors", + recommended: true + }, - return { - "BinaryExpression": function(node) { - if (/^(?:[<>]|[!=]=)=?$/.test(node.operator) && (node.left.name === "NaN" || node.right.name === "NaN")) { - context.report(node, "Use the isNaN function to compare with NaN."); + schema: [] + }, + + create: function(context) { + + return { + BinaryExpression: function(node) { + if (/^(?:[<>]|[!=]=)=?$/.test(node.operator) && (node.left.name === "NaN" || node.right.name === "NaN")) { + context.report(node, "Use the isNaN function to compare with NaN."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/valid-jsdoc.js b/tools/eslint/lib/rules/valid-jsdoc.js index 299095064d..e7d6fdeadf 100644 --- a/tools/eslint/lib/rules/valid-jsdoc.js +++ b/tools/eslint/lib/rules/valid-jsdoc.js @@ -1,7 +1,6 @@ /** * @fileoverview Validates JSDoc comments are syntactically correct * @author Nicholas C. Zakas - * @copyright 2014 Nicholas C. Zakas. All rights reserved. */ "use strict"; @@ -15,367 +14,383 @@ var doctrine = require("doctrine"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var options = context.options[0] || {}, - prefer = options.prefer || {}, - sourceCode = context.getSourceCode(), - - // these both default to true, so you have to explicitly make them false - requireReturn = options.requireReturn !== false, - requireParamDescription = options.requireParamDescription !== false, - requireReturnDescription = options.requireReturnDescription !== false, - requireReturnType = options.requireReturnType !== false, - preferType = options.preferType || {}, - checkPreferType = Object.keys(preferType).length !== 0; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - // Using a stack to store if a function returns or not (handling nested functions) - var fns = []; - - /** - * Check if node type is a Class - * @param {ASTNode} node node to check. - * @returns {boolean} True is its a class - * @private - */ - function isTypeClass(node) { - return node.type === "ClassExpression" || node.type === "ClassDeclaration"; - } - - /** - * When parsing a new function, store it in our function stack. - * @param {ASTNode} node A function node to check. - * @returns {void} - * @private - */ - function startFunction(node) { - fns.push({ - returnPresent: (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") || - isTypeClass(node) - }); - } +module.exports = { + meta: { + docs: { + description: "enforce valid JSDoc comments", + category: "Possible Errors", + recommended: false + }, - /** - * Indicate that return has been found in the current function. - * @param {ASTNode} node The return node. - * @returns {void} - * @private - */ - function addReturn(node) { - var functionState = fns[fns.length - 1]; - - if (functionState && node.argument !== null) { - functionState.returnPresent = true; + schema: [ + { + type: "object", + properties: { + prefer: { + type: "object", + additionalProperties: { + type: "string" + } + }, + preferType: { + type: "object", + additionalProperties: { + type: "string" + } + }, + requireReturn: { + type: "boolean" + }, + requireParamDescription: { + type: "boolean" + }, + requireReturnDescription: { + type: "boolean" + }, + matchDescription: { + type: "string" + }, + requireReturnType: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + + var options = context.options[0] || {}, + prefer = options.prefer || {}, + sourceCode = context.getSourceCode(), + + // these both default to true, so you have to explicitly make them false + requireReturn = options.requireReturn !== false, + requireParamDescription = options.requireParamDescription !== false, + requireReturnDescription = options.requireReturnDescription !== false, + requireReturnType = options.requireReturnType !== false, + preferType = options.preferType || {}, + checkPreferType = Object.keys(preferType).length !== 0; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // Using a stack to store if a function returns or not (handling nested functions) + var fns = []; + + /** + * Check if node type is a Class + * @param {ASTNode} node node to check. + * @returns {boolean} True is its a class + * @private + */ + function isTypeClass(node) { + return node.type === "ClassExpression" || node.type === "ClassDeclaration"; } - } - /** - * Check if return tag type is void or undefined - * @param {Object} tag JSDoc tag - * @returns {boolean} True if its of type void or undefined - * @private - */ - function isValidReturnType(tag) { - return tag.type === null || tag.type.name === "void" || tag.type.type === "UndefinedLiteral"; - } - - /** - * Check if type should be validated based on some exceptions - * @param {Object} type JSDoc tag - * @returns {boolean} True if it can be validated - * @private - */ - function canTypeBeValidated(type) { - return type !== "UndefinedLiteral" && // {undefined} as there is no name property available. - type !== "NullLiteral" && // {null} - type !== "NullableLiteral" && // {?} - type !== "FunctionType" && // {function(a)} - type !== "AllLiteral"; // {*} - } - - /** - * Extract the current and expected type based on the input type object - * @param {Object} type JSDoc tag - * @returns {Object} current and expected type object - * @private - */ - function getCurrentExpectedTypes(type) { - var currentType; - var expectedType; - - if (type.name) { - currentType = type.name; - } else if (type.expression) { - currentType = type.expression.name; + /** + * When parsing a new function, store it in our function stack. + * @param {ASTNode} node A function node to check. + * @returns {void} + * @private + */ + function startFunction(node) { + fns.push({ + returnPresent: (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") || + isTypeClass(node) + }); } - expectedType = currentType && preferType[currentType]; - - return { - currentType: currentType, - expectedType: expectedType - }; - } + /** + * Indicate that return has been found in the current function. + * @param {ASTNode} node The return node. + * @returns {void} + * @private + */ + function addReturn(node) { + var functionState = fns[fns.length - 1]; + + if (functionState && node.argument !== null) { + functionState.returnPresent = true; + } + } - /** - * Check if return tag type is void or undefined - * @param {Object} jsdocNode JSDoc node - * @param {Object} type JSDoc tag - * @returns {void} - * @private - */ - function validateType(jsdocNode, type) { - if (!type || !canTypeBeValidated(type.type)) { - return; + /** + * Check if return tag type is void or undefined + * @param {Object} tag JSDoc tag + * @returns {boolean} True if its of type void or undefined + * @private + */ + function isValidReturnType(tag) { + return tag.type === null || tag.type.name === "void" || tag.type.type === "UndefinedLiteral"; } - var typesToCheck = []; - var elements = []; - - switch (type.type) { - case "TypeApplication": // {Array.} - elements = type.applications[0].type === "UnionType" ? type.applications[0].elements : type.applications; - typesToCheck.push(getCurrentExpectedTypes(type)); - break; - case "RecordType": // {{20:String}} - elements = type.fields; - break; - case "UnionType": // {String|number|Test} - case "ArrayType": // {[String, number, Test]} - elements = type.elements; - break; - case "FieldType": // Array.<{count: number, votes: number}> - typesToCheck.push(getCurrentExpectedTypes(type.value)); - break; - default: - typesToCheck.push(getCurrentExpectedTypes(type)); + /** + * Check if type should be validated based on some exceptions + * @param {Object} type JSDoc tag + * @returns {boolean} True if it can be validated + * @private + */ + function canTypeBeValidated(type) { + return type !== "UndefinedLiteral" && // {undefined} as there is no name property available. + type !== "NullLiteral" && // {null} + type !== "NullableLiteral" && // {?} + type !== "FunctionType" && // {function(a)} + type !== "AllLiteral"; // {*} } - elements.forEach(validateType.bind(null, jsdocNode)); - - typesToCheck.forEach(function(typeToCheck) { - if (typeToCheck.expectedType && - typeToCheck.expectedType !== typeToCheck.currentType) { - context.report({ - node: jsdocNode, - message: "Use '{{expectedType}}' instead of '{{currentType}}'.", - data: { - currentType: typeToCheck.currentType, - expectedType: typeToCheck.expectedType - } - }); + /** + * Extract the current and expected type based on the input type object + * @param {Object} type JSDoc tag + * @returns {Object} current and expected type object + * @private + */ + function getCurrentExpectedTypes(type) { + var currentType; + var expectedType; + + if (type.name) { + currentType = type.name; + } else if (type.expression) { + currentType = type.expression.name; } - }); - } - /** - * Validate the JSDoc node and output warnings if anything is wrong. - * @param {ASTNode} node The AST node to check. - * @returns {void} - * @private - */ - function checkJSDoc(node) { - var jsdocNode = sourceCode.getJSDocComment(node), - functionData = fns.pop(), - hasReturns = false, - hasConstructor = false, - isInterface = false, - isOverride = false, - params = Object.create(null), - jsdoc; - - // make sure only to validate JSDoc comments - if (jsdocNode) { - - try { - jsdoc = doctrine.parse(jsdocNode.value, { - strict: true, - unwrap: true, - sloppy: true - }); - } catch (ex) { + expectedType = currentType && preferType[currentType]; - if (/braces/i.test(ex.message)) { - context.report(jsdocNode, "JSDoc type missing brace."); - } else { - context.report(jsdocNode, "JSDoc syntax error."); - } + return { + currentType: currentType, + expectedType: expectedType + }; + } + /** + * Check if return tag type is void or undefined + * @param {Object} jsdocNode JSDoc node + * @param {Object} type JSDoc tag + * @returns {void} + * @private + */ + function validateType(jsdocNode, type) { + if (!type || !canTypeBeValidated(type.type)) { return; } - jsdoc.tags.forEach(function(tag) { - - switch (tag.title.toLowerCase()) { + var typesToCheck = []; + var elements = []; + + switch (type.type) { + case "TypeApplication": // {Array.} + elements = type.applications[0].type === "UnionType" ? type.applications[0].elements : type.applications; + typesToCheck.push(getCurrentExpectedTypes(type)); + break; + case "RecordType": // {{20:String}} + elements = type.fields; + break; + case "UnionType": // {String|number|Test} + case "ArrayType": // {[String, number, Test]} + elements = type.elements; + break; + case "FieldType": // Array.<{count: number, votes: number}> + typesToCheck.push(getCurrentExpectedTypes(type.value)); + break; + default: + typesToCheck.push(getCurrentExpectedTypes(type)); + } - case "param": - case "arg": - case "argument": - if (!tag.type) { - context.report(jsdocNode, "Missing JSDoc parameter type for '{{name}}'.", { name: tag.name }); + elements.forEach(validateType.bind(null, jsdocNode)); + + typesToCheck.forEach(function(typeToCheck) { + if (typeToCheck.expectedType && + typeToCheck.expectedType !== typeToCheck.currentType) { + context.report({ + node: jsdocNode, + message: "Use '{{expectedType}}' instead of '{{currentType}}'.", + data: { + currentType: typeToCheck.currentType, + expectedType: typeToCheck.expectedType } + }); + } + }); + } - if (!tag.description && requireParamDescription) { - context.report(jsdocNode, "Missing JSDoc parameter description for '{{name}}'.", { name: tag.name }); - } + /** + * Validate the JSDoc node and output warnings if anything is wrong. + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function checkJSDoc(node) { + var jsdocNode = sourceCode.getJSDocComment(node), + functionData = fns.pop(), + hasReturns = false, + hasConstructor = false, + isInterface = false, + isOverride = false, + isAbstract = false, + params = Object.create(null), + jsdoc; + + // make sure only to validate JSDoc comments + if (jsdocNode) { + + try { + jsdoc = doctrine.parse(jsdocNode.value, { + strict: true, + unwrap: true, + sloppy: true + }); + } catch (ex) { + + if (/braces/i.test(ex.message)) { + context.report(jsdocNode, "JSDoc type missing brace."); + } else { + context.report(jsdocNode, "JSDoc syntax error."); + } - if (params[tag.name]) { - context.report(jsdocNode, "Duplicate JSDoc parameter '{{name}}'.", { name: tag.name }); - } else if (tag.name.indexOf(".") === -1) { - params[tag.name] = 1; - } - break; + return; + } - case "return": - case "returns": - hasReturns = true; + jsdoc.tags.forEach(function(tag) { - if (!requireReturn && !functionData.returnPresent && (tag.type === null || !isValidReturnType(tag))) { - context.report(jsdocNode, "Unexpected @" + tag.title + " tag; function has no return statement."); - } else { - if (requireReturnType && !tag.type) { - context.report(jsdocNode, "Missing JSDoc return type."); + switch (tag.title.toLowerCase()) { + + case "param": + case "arg": + case "argument": + if (!tag.type) { + context.report(jsdocNode, "Missing JSDoc parameter type for '{{name}}'.", { name: tag.name }); } - if (!isValidReturnType(tag) && !tag.description && requireReturnDescription) { - context.report(jsdocNode, "Missing JSDoc return description."); + if (!tag.description && requireParamDescription) { + context.report(jsdocNode, "Missing JSDoc parameter description for '{{name}}'.", { name: tag.name }); } - } - break; + if (params[tag.name]) { + context.report(jsdocNode, "Duplicate JSDoc parameter '{{name}}'.", { name: tag.name }); + } else if (tag.name.indexOf(".") === -1) { + params[tag.name] = 1; + } + break; + + case "return": + case "returns": + hasReturns = true; + + if (!requireReturn && !functionData.returnPresent && (tag.type === null || !isValidReturnType(tag)) && !isAbstract) { + context.report(jsdocNode, "Unexpected @" + tag.title + " tag; function has no return statement."); + } else { + if (requireReturnType && !tag.type) { + context.report(jsdocNode, "Missing JSDoc return type."); + } + + if (!isValidReturnType(tag) && !tag.description && requireReturnDescription) { + context.report(jsdocNode, "Missing JSDoc return description."); + } + } - case "constructor": - case "class": - hasConstructor = true; - break; + break; - case "override": - case "inheritdoc": - isOverride = true; - break; + case "constructor": + case "class": + hasConstructor = true; + break; - case "interface": - isInterface = true; - break; + case "override": + case "inheritdoc": + isOverride = true; + break; - // no default - } + case "abstract": + case "virtual": + isAbstract = true; + break; - // check tag preferences - if (prefer.hasOwnProperty(tag.title) && tag.title !== prefer[tag.title]) { - context.report(jsdocNode, "Use @{{name}} instead.", { name: prefer[tag.title] }); - } + case "interface": + isInterface = true; + break; - // validate the types - if (checkPreferType && tag.type) { - validateType(jsdocNode, tag.type); - } - }); + // no default + } - // check for functions missing @returns - if (!isOverride && !hasReturns && !hasConstructor && !isInterface && - node.parent.kind !== "get" && node.parent.kind !== "constructor" && - node.parent.kind !== "set" && !isTypeClass(node)) { - if (requireReturn || functionData.returnPresent) { - context.report(jsdocNode, "Missing JSDoc @" + (prefer.returns || "returns") + " for function."); + // check tag preferences + if (prefer.hasOwnProperty(tag.title) && tag.title !== prefer[tag.title]) { + context.report(jsdocNode, "Use @{{name}} instead.", { name: prefer[tag.title] }); + } + + // validate the types + if (checkPreferType && tag.type) { + validateType(jsdocNode, tag.type); + } + }); + + // check for functions missing @returns + if (!isOverride && !hasReturns && !hasConstructor && !isInterface && + node.parent.kind !== "get" && node.parent.kind !== "constructor" && + node.parent.kind !== "set" && !isTypeClass(node)) { + if (requireReturn || functionData.returnPresent) { + context.report(jsdocNode, "Missing JSDoc @" + (prefer.returns || "returns") + " for function."); + } } - } - // check the parameters - var jsdocParams = Object.keys(params); + // check the parameters + var jsdocParams = Object.keys(params); - if (node.params) { - node.params.forEach(function(param, i) { - var name = param.name; + if (node.params) { + node.params.forEach(function(param, i) { + var name = param.name; - if (param.type === "AssignmentPattern") { - name = param.left.name; - } + if (param.type === "AssignmentPattern") { + name = param.left.name; + } - // TODO(nzakas): Figure out logical things to do with destructured, default, rest params - if (param.type === "Identifier" || param.type === "AssignmentPattern") { - if (jsdocParams[i] && (name !== jsdocParams[i])) { - context.report(jsdocNode, "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", { - name: name, - jsdocName: jsdocParams[i] - }); - } else if (!params[name] && !isOverride) { - context.report(jsdocNode, "Missing JSDoc for parameter '{{name}}'.", { - name: name - }); + // TODO(nzakas): Figure out logical things to do with destructured, default, rest params + if (param.type === "Identifier" || param.type === "AssignmentPattern") { + if (jsdocParams[i] && (name !== jsdocParams[i])) { + context.report(jsdocNode, "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", { + name: name, + jsdocName: jsdocParams[i] + }); + } else if (!params[name] && !isOverride) { + context.report(jsdocNode, "Missing JSDoc for parameter '{{name}}'.", { + name: name + }); + } } - } - }); - } + }); + } - if (options.matchDescription) { - var regex = new RegExp(options.matchDescription); + if (options.matchDescription) { + var regex = new RegExp(options.matchDescription); - if (!regex.test(jsdoc.description)) { - context.report(jsdocNode, "JSDoc description does not satisfy the regex pattern."); + if (!regex.test(jsdoc.description)) { + context.report(jsdocNode, "JSDoc description does not satisfy the regex pattern."); + } } + } } - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "ArrowFunctionExpression": startFunction, - "FunctionExpression": startFunction, - "FunctionDeclaration": startFunction, - "ClassExpression": startFunction, - "ClassDeclaration": startFunction, - "ArrowFunctionExpression:exit": checkJSDoc, - "FunctionExpression:exit": checkJSDoc, - "FunctionDeclaration:exit": checkJSDoc, - "ClassExpression:exit": checkJSDoc, - "ClassDeclaration:exit": checkJSDoc, - "ReturnStatement": addReturn - }; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- -}; + return { + ArrowFunctionExpression: startFunction, + FunctionExpression: startFunction, + FunctionDeclaration: startFunction, + ClassExpression: startFunction, + ClassDeclaration: startFunction, + "ArrowFunctionExpression:exit": checkJSDoc, + "FunctionExpression:exit": checkJSDoc, + "FunctionDeclaration:exit": checkJSDoc, + "ClassExpression:exit": checkJSDoc, + "ClassDeclaration:exit": checkJSDoc, + ReturnStatement: addReturn + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "prefer": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "preferType": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "requireReturn": { - "type": "boolean" - }, - "requireParamDescription": { - "type": "boolean" - }, - "requireReturnDescription": { - "type": "boolean" - }, - "matchDescription": { - "type": "string" - }, - "requireReturnType": { - "type": "boolean" - } - }, - "additionalProperties": false } -]; +}; diff --git a/tools/eslint/lib/rules/valid-typeof.js b/tools/eslint/lib/rules/valid-typeof.js index d67a46bf3d..289375a88b 100644 --- a/tools/eslint/lib/rules/valid-typeof.js +++ b/tools/eslint/lib/rules/valid-typeof.js @@ -8,35 +8,45 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce comparing `typeof` expressions against valid strings", + category: "Possible Errors", + recommended: true + }, - var VALID_TYPES = ["symbol", "undefined", "object", "boolean", "number", "string", "function"], - OPERATORS = ["==", "===", "!=", "!=="]; + schema: [] + }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + create: function(context) { - return { + var VALID_TYPES = ["symbol", "undefined", "object", "boolean", "number", "string", "function"], + OPERATORS = ["==", "===", "!=", "!=="]; - "UnaryExpression": function(node) { - var parent, sibling; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - if (node.operator === "typeof") { - parent = context.getAncestors().pop(); + return { - if (parent.type === "BinaryExpression" && OPERATORS.indexOf(parent.operator) !== -1) { - sibling = parent.left === node ? parent.right : parent.left; + UnaryExpression: function(node) { + var parent, sibling; - if (sibling.type === "Literal" && VALID_TYPES.indexOf(sibling.value) === -1) { - context.report(sibling, "Invalid typeof comparison value"); + if (node.operator === "typeof") { + parent = context.getAncestors().pop(); + + if (parent.type === "BinaryExpression" && OPERATORS.indexOf(parent.operator) !== -1) { + sibling = parent.left === node ? parent.right : parent.left; + + if (sibling.type === "Literal" && VALID_TYPES.indexOf(sibling.value) === -1) { + context.report(sibling, "Invalid typeof comparison value"); + } } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/vars-on-top.js b/tools/eslint/lib/rules/vars-on-top.js index ae009f85ad..b44f77eb3f 100644 --- a/tools/eslint/lib/rules/vars-on-top.js +++ b/tools/eslint/lib/rules/vars-on-top.js @@ -2,8 +2,6 @@ * @fileoverview Rule to enforce var declarations are only at the top of a function. * @author Danny Fritz * @author Gyandeep Singh - * @copyright 2014 Danny Fritz. All rights reserved. - * @copyright 2014 Gyandeep Singh. All rights reserved. */ "use strict"; @@ -11,116 +9,126 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var errorMessage = "All 'var' declarations must be at the top of the function scope."; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * @param {ASTNode} node - any node - * @returns {Boolean} whether the given node structurally represents a directive - */ - function looksLikeDirective(node) { - return node.type === "ExpressionStatement" && - node.expression.type === "Literal" && typeof node.expression.value === "string"; - } - - /** - * Check to see if its a ES6 import declaration - * @param {ASTNode} node - any node - * @returns {Boolean} whether the given node represents a import declaration - */ - function looksLikeImport(node) { - return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" || - node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier"; - } +module.exports = { + meta: { + docs: { + description: "require `var` declarations be placed at the top of their containing scope", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + var errorMessage = "All 'var' declarations must be at the top of the function scope."; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * @param {ASTNode} node - any node + * @returns {Boolean} whether the given node structurally represents a directive + */ + function looksLikeDirective(node) { + return node.type === "ExpressionStatement" && + node.expression.type === "Literal" && typeof node.expression.value === "string"; + } - /** - * Checks whether this variable is on top of the block body - * @param {ASTNode} node - The node to check - * @param {ASTNode[]} statements - collection of ASTNodes for the parent node block - * @returns {Boolean} True if var is on top otherwise false - */ - function isVarOnTop(node, statements) { - var i = 0, - l = statements.length; - - // skip over directives - for (; i < l; ++i) { - if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) { - break; - } + /** + * Check to see if its a ES6 import declaration + * @param {ASTNode} node - any node + * @returns {Boolean} whether the given node represents a import declaration + */ + function looksLikeImport(node) { + return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" || + node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier"; } - for (; i < l; ++i) { - if (statements[i].type !== "VariableDeclaration" && - (statements[i].type !== "ExportNamedDeclaration" || - statements[i].declaration.type !== "VariableDeclaration")) { - return false; - } - if (statements[i] === node) { - return true; + /** + * Checks whether this variable is on top of the block body + * @param {ASTNode} node - The node to check + * @param {ASTNode[]} statements - collection of ASTNodes for the parent node block + * @returns {Boolean} True if var is on top otherwise false + */ + function isVarOnTop(node, statements) { + var i = 0, + l = statements.length; + + // skip over directives + for (; i < l; ++i) { + if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) { + break; + } } - } - return false; - } + for (; i < l; ++i) { + if (statements[i].type !== "VariableDeclaration" && + (statements[i].type !== "ExportNamedDeclaration" || + statements[i].declaration.type !== "VariableDeclaration")) { + return false; + } + if (statements[i] === node) { + return true; + } + } - /** - * Checks whether variable is on top at the global level - * @param {ASTNode} node - The node to check - * @param {ASTNode} parent - Parent of the node - * @returns {void} - */ - function globalVarCheck(node, parent) { - if (!isVarOnTop(node, parent.body)) { - context.report(node, errorMessage); + return false; } - } - /** - * Checks whether variable is on top at functional block scope level - * @param {ASTNode} node - The node to check - * @param {ASTNode} parent - Parent of the node - * @param {ASTNode} grandParent - Parent of the node's parent - * @returns {void} - */ - function blockScopeVarCheck(node, parent, grandParent) { - if (!(/Function/.test(grandParent.type) && - parent.type === "BlockStatement" && - isVarOnTop(node, parent.body))) { - context.report(node, errorMessage); + /** + * Checks whether variable is on top at the global level + * @param {ASTNode} node - The node to check + * @param {ASTNode} parent - Parent of the node + * @returns {void} + */ + function globalVarCheck(node, parent) { + if (!isVarOnTop(node, parent.body)) { + context.report(node, errorMessage); + } } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - "VariableDeclaration": function(node) { - var ancestors = context.getAncestors(); - var parent = ancestors.pop(); - var grandParent = ancestors.pop(); - - if (node.kind === "var") { // check variable is `var` type and not `let` or `const` - if (parent.type === "ExportNamedDeclaration") { - node = parent; - parent = grandParent; - grandParent = ancestors.pop(); - } + /** + * Checks whether variable is on top at functional block scope level + * @param {ASTNode} node - The node to check + * @param {ASTNode} parent - Parent of the node + * @param {ASTNode} grandParent - Parent of the node's parent + * @returns {void} + */ + function blockScopeVarCheck(node, parent, grandParent) { + if (!(/Function/.test(grandParent.type) && + parent.type === "BlockStatement" && + isVarOnTop(node, parent.body))) { + context.report(node, errorMessage); + } + } - if (parent.type === "Program") { // That means its a global variable - globalVarCheck(node, parent); - } else { - blockScopeVarCheck(node, parent, grandParent); + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + VariableDeclaration: function(node) { + var ancestors = context.getAncestors(); + var parent = ancestors.pop(); + var grandParent = ancestors.pop(); + + if (node.kind === "var") { // check variable is `var` type and not `let` or `const` + if (parent.type === "ExportNamedDeclaration") { + node = parent; + parent = grandParent; + grandParent = ancestors.pop(); + } + + if (parent.type === "Program") { // That means its a global variable + globalVarCheck(node, parent); + } else { + blockScopeVarCheck(node, parent, grandParent); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/wrap-iife.js b/tools/eslint/lib/rules/wrap-iife.js index ac5c8a45f0..1dd1a0c5af 100644 --- a/tools/eslint/lib/rules/wrap-iife.js +++ b/tools/eslint/lib/rules/wrap-iife.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to flag when IIFE is not wrapped in parens * @author Ilya Volodin - * @copyright 2013 Ilya Volodin. All rights reserved. */ "use strict"; @@ -10,46 +9,56 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require parentheses around immediate `function` invocations", + category: "Best Practices", + recommended: false + }, - var style = context.options[0] || "outside"; + schema: [ + { + enum: ["outside", "inside", "any"] + } + ] + }, - /** - * Check if the node is wrapped in () - * @param {ASTNode} node node to evaluate - * @returns {boolean} True if it is wrapped - * @private - */ - function wrapped(node) { - var previousToken = context.getTokenBefore(node), - nextToken = context.getTokenAfter(node); + create: function(context) { - return previousToken && previousToken.value === "(" && - nextToken && nextToken.value === ")"; - } + var style = context.options[0] || "outside"; + + /** + * Check if the node is wrapped in () + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if it is wrapped + * @private + */ + function wrapped(node) { + var previousToken = context.getTokenBefore(node), + nextToken = context.getTokenAfter(node); + + return previousToken && previousToken.value === "(" && + nextToken && nextToken.value === ")"; + } - return { + return { - "CallExpression": function(node) { - if (node.callee.type === "FunctionExpression") { - var callExpressionWrapped = wrapped(node), - functionExpressionWrapped = wrapped(node.callee); + CallExpression: function(node) { + if (node.callee.type === "FunctionExpression") { + var callExpressionWrapped = wrapped(node), + functionExpressionWrapped = wrapped(node.callee); - if (!callExpressionWrapped && !functionExpressionWrapped) { - context.report(node, "Wrap an immediate function invocation in parentheses."); - } else if (style === "inside" && !functionExpressionWrapped) { - context.report(node, "Wrap only the function expression in parens."); - } else if (style === "outside" && !callExpressionWrapped) { - context.report(node, "Move the invocation into the parens that contain the function."); + if (!callExpressionWrapped && !functionExpressionWrapped) { + context.report(node, "Wrap an immediate function invocation in parentheses."); + } else if (style === "inside" && !functionExpressionWrapped) { + context.report(node, "Wrap only the function expression in parens."); + } else if (style === "outside" && !callExpressionWrapped) { + context.report(node, "Move the invocation into the parens that contain the function."); + } } } - } - }; - -}; + }; -module.exports.schema = [ - { - "enum": ["outside", "inside", "any"] } -]; +}; diff --git a/tools/eslint/lib/rules/wrap-regex.js b/tools/eslint/lib/rules/wrap-regex.js index f2625cc2b9..96df3304c8 100644 --- a/tools/eslint/lib/rules/wrap-regex.js +++ b/tools/eslint/lib/rules/wrap-regex.js @@ -9,30 +9,40 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - return { - - "Literal": function(node) { - var token = context.getFirstToken(node), - nodeType = token.type, - source, - grandparent, - ancestors; - - if (nodeType === "RegularExpression") { - source = context.getTokenBefore(node); - ancestors = context.getAncestors(); - grandparent = ancestors[ancestors.length - 1]; - - if (grandparent.type === "MemberExpression" && grandparent.object === node && - (!source || source.value !== "(")) { - context.report(node, "Wrap the regexp literal in parens to disambiguate the slash."); +module.exports = { + meta: { + docs: { + description: "require parenthesis around regex literals", + category: "Stylistic Issues", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + return { + + Literal: function(node) { + var token = context.getFirstToken(node), + nodeType = token.type, + source, + grandparent, + ancestors; + + if (nodeType === "RegularExpression") { + source = context.getTokenBefore(node); + ancestors = context.getAncestors(); + grandparent = ancestors[ancestors.length - 1]; + + if (grandparent.type === "MemberExpression" && grandparent.object === node && + (!source || source.value !== "(")) { + context.report(node, "Wrap the regexp literal in parens to disambiguate the slash."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/tools/eslint/lib/rules/yield-star-spacing.js b/tools/eslint/lib/rules/yield-star-spacing.js index c69594ae15..e2911b7200 100644 --- a/tools/eslint/lib/rules/yield-star-spacing.js +++ b/tools/eslint/lib/rules/yield-star-spacing.js @@ -1,7 +1,6 @@ /** * @fileoverview Rule to check the spacing around the * in yield* expressions. * @author Bryan Smith - * @copyright 2015 Bryan Smith. All rights reserved. */ "use strict"; @@ -10,93 +9,105 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var sourceCode = context.getSourceCode(); +module.exports = { + meta: { + docs: { + description: "require or disallow spacing around the `*` in `yield*` expressions", + category: "ECMAScript 6", + recommended: false + }, - var mode = (function(option) { - if (!option || typeof option === "string") { - return { - before: { before: true, after: false }, - after: { before: false, after: true }, - both: { before: true, after: true }, - neither: { before: false, after: false } - }[option || "after"]; - } - return option; - }(context.options[0])); + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["before", "after", "both", "neither"] + }, + { + type: "object", + properties: { + before: {type: "boolean"}, + after: {type: "boolean"} + }, + additionalProperties: false + } + ] + } + ] + }, - /** - * Checks the spacing between two tokens before or after the star token. - * @param {string} side Either "before" or "after". - * @param {Token} leftToken `function` keyword token if side is "before", or - * star token if side is "after". - * @param {Token} rightToken Star token if side is "before", or identifier - * token if side is "after". - * @returns {void} - */ - function checkSpacing(side, leftToken, rightToken) { - if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken) !== 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 + " *."; + create: function(context) { + var sourceCode = context.getSourceCode(); - context.report({ - node: node, - message: message, - fix: function(fixer) { - if (spaceRequired) { - if (after) { - return fixer.insertTextAfter(node, " "); + var mode = (function(option) { + if (!option || typeof option === "string") { + return { + before: { before: true, after: false }, + after: { before: false, after: true }, + both: { before: true, after: true }, + neither: { before: false, after: false } + }[option || "after"]; + } + return option; + }(context.options[0])); + + /** + * Checks the spacing between two tokens before or after the star token. + * @param {string} side Either "before" or "after". + * @param {Token} leftToken `function` keyword token if side is "before", or + * star token if side is "after". + * @param {Token} rightToken Star token if side is "before", or identifier + * token if side is "after". + * @returns {void} + */ + function checkSpacing(side, leftToken, rightToken) { + if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken) !== 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.insertTextBefore(node, " "); + return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); } - return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); - } - }); - } - } - - /** - * Enforces the spacing around the star if node is a yield* expression. - * @param {ASTNode} node A yield expression node. - * @returns {void} - */ - function checkExpression(node) { - if (!node.delegate) { - return; + }); + } } - var tokens = sourceCode.getFirstTokens(node, 3); - var yieldToken = tokens[0]; - var starToken = tokens[1]; - var nextToken = tokens[2]; + /** + * Enforces the spacing around the star if node is a yield* expression. + * @param {ASTNode} node A yield expression node. + * @returns {void} + */ + function checkExpression(node) { + if (!node.delegate) { + return; + } - checkSpacing("before", yieldToken, starToken); - checkSpacing("after", starToken, nextToken); - } + var tokens = sourceCode.getFirstTokens(node, 3); + var yieldToken = tokens[0]; + var starToken = tokens[1]; + var nextToken = tokens[2]; - return { - "YieldExpression": checkExpression - }; + checkSpacing("before", yieldToken, starToken); + checkSpacing("after", starToken, nextToken); + } -}; + return { + YieldExpression: checkExpression + }; -module.exports.schema = [ - { - "oneOf": [ - { - "enum": ["before", "after", "both", "neither"] - }, - { - "type": "object", - "properties": { - "before": {"type": "boolean"}, - "after": {"type": "boolean"} - }, - "additionalProperties": false - } - ] } -]; +}; diff --git a/tools/eslint/lib/rules/yoda.js b/tools/eslint/lib/rules/yoda.js index a5ff4a9626..ce2709ec9a 100644 --- a/tools/eslint/lib/rules/yoda.js +++ b/tools/eslint/lib/rules/yoda.js @@ -1,8 +1,6 @@ /** * @fileoverview Rule to require or disallow yoda comparisons * @author Nicholas C. Zakas - * @copyright 2014 Nicholas C. Zakas. All rights reserved. - * @copyright 2014 Brandon Mills. All rights reserved. */ "use strict"; @@ -119,129 +117,139 @@ function same(a, b) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - // Default to "never" (!always) if no option - var always = (context.options[0] === "always"); - var exceptRange = (context.options[1] && context.options[1].exceptRange); - var onlyEquality = (context.options[1] && context.options[1].onlyEquality); - - /** - * Determines whether node represents a range test. - * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside" - * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and - * both operators must be `<` or `<=`. Finally, the literal on the left side - * must be less than or equal to the literal on the right side so that the - * test makes any sense. - * @param {ASTNode} node LogicalExpression node to test. - * @returns {Boolean} Whether node is a range test. - */ - function isRangeTest(node) { - var left = node.left, - right = node.right; +module.exports = { + meta: { + docs: { + description: "require or disallow \"Yoda\" conditions", + category: "Best Practices", + recommended: false + }, - /** - * Determines whether node is of the form `0 <= x && x < 1`. - * @returns {Boolean} Whether node is a "between" range test. - */ - function isBetweenTest() { - var leftLiteral, rightLiteral; - - return (node.operator === "&&" && - (leftLiteral = getNormalizedLiteral(left.left)) && - (rightLiteral = getNormalizedLiteral(right.right)) && - leftLiteral.value <= rightLiteral.value && - same(left.right, right.left)); - } + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + exceptRange: { + type: "boolean" + }, + onlyEquality: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - /** - * Determines whether node is of the form `x < 0 || 1 <= x`. - * @returns {Boolean} Whether node is an "outside" range test. - */ - function isOutsideTest() { - var leftLiteral, rightLiteral; - - return (node.operator === "||" && - (leftLiteral = getNormalizedLiteral(left.right)) && - (rightLiteral = getNormalizedLiteral(right.left)) && - leftLiteral.value <= rightLiteral.value && - same(left.left, right.right)); - } + create: function(context) { + + // Default to "never" (!always) if no option + var always = (context.options[0] === "always"); + var exceptRange = (context.options[1] && context.options[1].exceptRange); + var onlyEquality = (context.options[1] && context.options[1].onlyEquality); /** - * Determines whether node is wrapped in parentheses. - * @returns {Boolean} Whether node is preceded immediately by an open - * paren token and followed immediately by a close - * paren token. + * Determines whether node represents a range test. + * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside" + * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and + * both operators must be `<` or `<=`. Finally, the literal on the left side + * must be less than or equal to the literal on the right side so that the + * test makes any sense. + * @param {ASTNode} node LogicalExpression node to test. + * @returns {Boolean} Whether node is a range test. */ - function isParenWrapped() { - var tokenBefore, tokenAfter; - - return ((tokenBefore = context.getTokenBefore(node)) && - tokenBefore.value === "(" && - (tokenAfter = context.getTokenAfter(node)) && - tokenAfter.value === ")"); - } - - return (node.type === "LogicalExpression" && - left.type === "BinaryExpression" && - right.type === "BinaryExpression" && - isRangeTestOperator(left.operator) && - isRangeTestOperator(right.operator) && - (isBetweenTest() || isOutsideTest()) && - isParenWrapped()); - } + function isRangeTest(node) { + var left = node.left, + right = node.right; + + /** + * Determines whether node is of the form `0 <= x && x < 1`. + * @returns {Boolean} Whether node is a "between" range test. + */ + function isBetweenTest() { + var leftLiteral, rightLiteral; + + return (node.operator === "&&" && + (leftLiteral = getNormalizedLiteral(left.left)) && + (rightLiteral = getNormalizedLiteral(right.right)) && + leftLiteral.value <= rightLiteral.value && + same(left.right, right.left)); + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "BinaryExpression": always ? function(node) { - - // Comparisons must always be yoda-style: if ("blue" === color) - if ( - (node.right.type === "Literal" || looksLikeLiteral(node.right)) && - !(node.left.type === "Literal" || looksLikeLiteral(node.left)) && - !(!isEqualityOperator(node.operator) && onlyEquality) && - isComparisonOperator(node.operator) && - !(exceptRange && isRangeTest(context.getAncestors().pop())) - ) { - context.report(node, "Expected literal to be on the left side of " + node.operator + "."); + /** + * Determines whether node is of the form `x < 0 || 1 <= x`. + * @returns {Boolean} Whether node is an "outside" range test. + */ + function isOutsideTest() { + var leftLiteral, rightLiteral; + + return (node.operator === "||" && + (leftLiteral = getNormalizedLiteral(left.right)) && + (rightLiteral = getNormalizedLiteral(right.left)) && + leftLiteral.value <= rightLiteral.value && + same(left.left, right.right)); } - } : function(node) { - - // Comparisons must never be yoda-style (default) - if ( - (node.left.type === "Literal" || looksLikeLiteral(node.left)) && - !(node.right.type === "Literal" || looksLikeLiteral(node.right)) && - !(!isEqualityOperator(node.operator) && onlyEquality) && - isComparisonOperator(node.operator) && - !(exceptRange && isRangeTest(context.getAncestors().pop())) - ) { - context.report(node, "Expected literal to be on the right side of " + node.operator + "."); + /** + * Determines whether node is wrapped in parentheses. + * @returns {Boolean} Whether node is preceded immediately by an open + * paren token and followed immediately by a close + * paren token. + */ + function isParenWrapped() { + var tokenBefore, tokenAfter; + + return ((tokenBefore = context.getTokenBefore(node)) && + tokenBefore.value === "(" && + (tokenAfter = context.getTokenAfter(node)) && + tokenAfter.value === ")"); } + return (node.type === "LogicalExpression" && + left.type === "BinaryExpression" && + right.type === "BinaryExpression" && + isRangeTestOperator(left.operator) && + isRangeTestOperator(right.operator) && + (isBetweenTest() || isOutsideTest()) && + isParenWrapped()); } - }; -}; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + BinaryExpression: always ? function(node) { + + // Comparisons must always be yoda-style: if ("blue" === color) + if ( + (node.right.type === "Literal" || looksLikeLiteral(node.right)) && + !(node.left.type === "Literal" || looksLikeLiteral(node.left)) && + !(!isEqualityOperator(node.operator) && onlyEquality) && + isComparisonOperator(node.operator) && + !(exceptRange && isRangeTest(context.getAncestors().pop())) + ) { + context.report(node, "Expected literal to be on the left side of " + node.operator + "."); + } + + } : function(node) { + + // Comparisons must never be yoda-style (default) + if ( + (node.left.type === "Literal" || looksLikeLiteral(node.left)) && + !(node.right.type === "Literal" || looksLikeLiteral(node.right)) && + !(!isEqualityOperator(node.operator) && onlyEquality) && + isComparisonOperator(node.operator) && + !(exceptRange && isRangeTest(context.getAncestors().pop())) + ) { + context.report(node, "Expected literal to be on the right side of " + node.operator + "."); + } -module.exports.schema = [ - { - "enum": ["always", "never"] - }, - { - "type": "object", - "properties": { - "exceptRange": { - "type": "boolean" - }, - "onlyEquality": { - "type": "boolean" } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/tools/eslint/lib/testers/event-generator-tester.js b/tools/eslint/lib/testers/event-generator-tester.js index c275a74b76..00b2d03307 100644 --- a/tools/eslint/lib/testers/event-generator-tester.js +++ b/tools/eslint/lib/testers/event-generator-tester.js @@ -1,8 +1,6 @@ /** * @fileoverview Helpers to test EventGenerator interface. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/testers/rule-tester.js b/tools/eslint/lib/testers/rule-tester.js index ce4ca1c4ba..4485e0d5fc 100644 --- a/tools/eslint/lib/testers/rule-tester.js +++ b/tools/eslint/lib/testers/rule-tester.js @@ -1,10 +1,6 @@ /** * @fileoverview Mocha test wrapper * @author Ilya Volodin - * @copyright 2015 Kevin Partington. All rights reserved. - * @copyright 2015 Nicholas C. Zakas. All rights reserved. - * @copyright 2014 Ilya Volodin. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/timing.js b/tools/eslint/lib/timing.js index c1346480fd..6dfffc19d9 100644 --- a/tools/eslint/lib/timing.js +++ b/tools/eslint/lib/timing.js @@ -1,7 +1,6 @@ /** * @fileoverview Tracks performance of individual rules. * @author Brandon Mills - * @copyright 2014 Brandon Mills. All rights reserved. */ "use strict"; @@ -102,7 +101,7 @@ function display(data) { return ALIGN[index](":", w + 1, "-"); }).join("|")); - console.log(table.join("\n")); + console.log(table.join("\n")); // eslint-disable-line no-console } /* istanbul ignore next */ diff --git a/tools/eslint/lib/token-store.js b/tools/eslint/lib/token-store.js index 91409b7eed..0262b69a52 100644 --- a/tools/eslint/lib/token-store.js +++ b/tools/eslint/lib/token-store.js @@ -1,8 +1,6 @@ /** * @fileoverview Object to handle access and retrieval of tokens. * @author Brandon Mills - * @copyright 2014 Nicholas C. Zakas. All rights reserved. - * @copyright 2014 Brandon Mills. All rights reserved. */ "use strict"; diff --git a/tools/eslint/lib/util/comment-event-generator.js b/tools/eslint/lib/util/comment-event-generator.js index 90bbfe9f2f..2989f4ee26 100644 --- a/tools/eslint/lib/util/comment-event-generator.js +++ b/tools/eslint/lib/util/comment-event-generator.js @@ -1,8 +1,6 @@ /** * @fileoverview The event generator for comments. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/util/glob-util.js b/tools/eslint/lib/util/glob-util.js index 9a704d6308..dadefbd966 100644 --- a/tools/eslint/lib/util/glob-util.js +++ b/tools/eslint/lib/util/glob-util.js @@ -1,8 +1,6 @@ /** * @fileoverview Utilities for working with globs and the filesystem. * @author Ian VanSchooten - * @copyright 2015 Ian VanSchooten. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/util/hash.js b/tools/eslint/lib/util/hash.js index b0271bbf03..9f3b734c27 100644 --- a/tools/eslint/lib/util/hash.js +++ b/tools/eslint/lib/util/hash.js @@ -1,8 +1,6 @@ /** * @fileoverview Defining the hashing function in one place. * @author Michael Ficarra - * @copyright 2016 Michael Ficarra. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/util/keywords.js b/tools/eslint/lib/util/keywords.js index dde29c6b76..3fbb77771d 100644 --- a/tools/eslint/lib/util/keywords.js +++ b/tools/eslint/lib/util/keywords.js @@ -1,7 +1,6 @@ /** * @fileoverview A shared list of ES3 keywords. * @author Josh Perez - * @copyright 2015 Jose Roberto Vidal. All rights reserved. */ "use strict"; diff --git a/tools/eslint/lib/util/module-resolver.js b/tools/eslint/lib/util/module-resolver.js index 9df544cf2b..251292280d 100644 --- a/tools/eslint/lib/util/module-resolver.js +++ b/tools/eslint/lib/util/module-resolver.js @@ -1,8 +1,6 @@ /** * @fileoverview Implements the Node.js require.resolve algorithm * @author Nicholas C. Zakas - * @copyright 2016 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; @@ -24,8 +22,11 @@ var DEFAULT_OPTIONS = { * module.paths is an array of paths to search for resolving things relative * to this file. Module.globalPaths contains all of the special Node.js * directories that can also be searched for modules. + * + * Need to check for existence of module.paths because Jest seems not to + * include it. See https://github.com/eslint/eslint/issues/5791. */ - lookupPaths: module.paths.concat(Module.globalPaths) + lookupPaths: module.paths ? module.paths.concat(Module.globalPaths) : Module.globalPaths.concat() }; /** diff --git a/tools/eslint/lib/util/node-event-generator.js b/tools/eslint/lib/util/node-event-generator.js index 002bd29dfe..92253f6ca2 100644 --- a/tools/eslint/lib/util/node-event-generator.js +++ b/tools/eslint/lib/util/node-event-generator.js @@ -1,8 +1,6 @@ /** * @fileoverview The event generator for AST nodes. * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/util/npm-util.js b/tools/eslint/lib/util/npm-util.js index c45f079011..fd081307fd 100644 --- a/tools/eslint/lib/util/npm-util.js +++ b/tools/eslint/lib/util/npm-util.js @@ -1,8 +1,6 @@ /** * @fileoverview Utility for executing npm commands. * @author Ian VanSchooten - * @copyright 2016 Ilya Volodin. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/util/path-util.js b/tools/eslint/lib/util/path-util.js index d1c8197adf..ddc0b60625 100644 --- a/tools/eslint/lib/util/path-util.js +++ b/tools/eslint/lib/util/path-util.js @@ -1,8 +1,6 @@ /** * @fileoverview Common helpers for operations on filenames and paths * @author Ian VanSchooten - * @copyright 2016 Ian VanSchooten. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/util/rule-fixer.js b/tools/eslint/lib/util/rule-fixer.js index 0f9ef9adf3..91f1033e42 100644 --- a/tools/eslint/lib/util/rule-fixer.js +++ b/tools/eslint/lib/util/rule-fixer.js @@ -1,8 +1,6 @@ /** * @fileoverview An object that creates fix commands for rules. * @author Nicholas C. Zakas - * @copyright 2015 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/util/source-code-fixer.js b/tools/eslint/lib/util/source-code-fixer.js index 0ea68d382b..e8c440d7c2 100644 --- a/tools/eslint/lib/util/source-code-fixer.js +++ b/tools/eslint/lib/util/source-code-fixer.js @@ -1,8 +1,6 @@ /** * @fileoverview An object that caches and applies source code fixes. * @author Nicholas C. Zakas - * @copyright 2015 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/util/source-code-util.js b/tools/eslint/lib/util/source-code-util.js index b12b095f17..e51c1c124c 100644 --- a/tools/eslint/lib/util/source-code-util.js +++ b/tools/eslint/lib/util/source-code-util.js @@ -1,8 +1,6 @@ /** * @fileoverview Tools for obtaining SourceCode objects. * @author Ian VanSchooten - * @copyright 2016 Ian VanSchooten. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/util/source-code.js b/tools/eslint/lib/util/source-code.js index a79bd5b531..adf4df93a6 100644 --- a/tools/eslint/lib/util/source-code.js +++ b/tools/eslint/lib/util/source-code.js @@ -1,8 +1,6 @@ /** * @fileoverview Abstraction of JavaScript source code. * @author Nicholas C. Zakas - * @copyright 2015 Nicholas C. Zakas. All rights reserved. - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/tools/eslint/lib/util/traverser.js b/tools/eslint/lib/util/traverser.js index 74cfbec2b2..03a1c376e1 100644 --- a/tools/eslint/lib/util/traverser.js +++ b/tools/eslint/lib/util/traverser.js @@ -1,8 +1,6 @@ /** * @fileoverview Wrapper around estraverse * @author Nicholas C. Zakas - * @copyright 2016 Nicholas C. Zakas. All rights reserved. - * See LICENSE in root directory for full license. */ "use strict"; @@ -54,3 +52,5 @@ Traverser.getKeys = function(node) { }; module.exports = Traverser; + + diff --git a/tools/eslint/node_modules/.bin/eslint b/tools/eslint/node_modules/.bin/eslint new file mode 120000 index 0000000000..810e4bcb32 --- /dev/null +++ b/tools/eslint/node_modules/.bin/eslint @@ -0,0 +1 @@ +../eslint/bin/eslint.js \ No newline at end of file diff --git a/tools/eslint/node_modules/acorn-jsx/README.md b/tools/eslint/node_modules/acorn-jsx/README.md index 6b3826d3fc..cd9674c0b3 100644 --- a/tools/eslint/node_modules/acorn-jsx/README.md +++ b/tools/eslint/node_modules/acorn-jsx/README.md @@ -37,6 +37,28 @@ var ast = acorn.parse(code, { }); ``` +Note that official spec doesn't support mix of XML namespaces and object-style access in tag names (#27) like in ``, so it was deprecated in `acorn-jsx@3.0`. If you still want to opt-in to support of such constructions, you can pass the following option: + +```javascript +var ast = acorn.parse(code, { + plugins: { + jsx: { allowNamespacedObjects: true } + } +}); +``` + +Also, since most apps use pure React transformer, a new option was introduced that allows to prohibit namespaces completely: + +```javascript +var ast = acorn.parse(code, { + plugins: { + jsx: { allowNamespaces: false } + } +}); +``` + +Note that by default `allowNamespaces` is enabled for spec compliancy. + ## License This plugin is issued under the [MIT license](./LICENSE). diff --git a/tools/eslint/node_modules/acorn-jsx/inject.js b/tools/eslint/node_modules/acorn-jsx/inject.js index bfe0358097..2bc4e9fd39 100644 --- a/tools/eslint/node_modules/acorn-jsx/inject.js +++ b/tools/eslint/node_modules/acorn-jsx/inject.js @@ -198,7 +198,7 @@ module.exports = function(acorn) { pp.jsx_parseNamespacedName = function() { var startPos = this.start, startLoc = this.startLoc; var name = this.jsx_parseIdentifier(); - if (!this.eat(tt.colon)) return name; + if (!this.options.plugins.jsx.allowNamespaces || !this.eat(tt.colon)) return name; var node = this.startNodeAt(startPos, startLoc); node.namespace = name; node.name = this.jsx_parseIdentifier(); @@ -211,6 +211,9 @@ module.exports = function(acorn) { pp.jsx_parseElementName = function() { var startPos = this.start, startLoc = this.startLoc; var node = this.jsx_parseNamespacedName(); + if (this.type === tt.dot && node.type === 'JSXNamespacedName' && !this.options.plugins.jsx.allowNamespacedObjects) { + this.unexpected(); + } while (this.eat(tt.dot)) { var newNode = this.startNodeAt(startPos, startLoc); newNode.object = node; @@ -356,7 +359,20 @@ module.exports = function(acorn) { return this.jsx_parseElementAt(startPos, startLoc); }; - acorn.plugins.jsx = function(instance) { + acorn.plugins.jsx = function(instance, opts) { + if (!opts) { + return; + } + + if (typeof opts !== 'object') { + opts = {}; + } + + instance.options.plugins.jsx = { + allowNamespaces: opts.allowNamespaces !== false, + allowNamespacedObjects: !!opts.allowNamespacedObjects + }; + instance.extend('parseExprAtom', function(inner) { return function(refShortHandDefaultPos) { if (this.type === tt.jsxText) diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/.bin/acorn b/tools/eslint/node_modules/acorn-jsx/node_modules/.bin/acorn deleted file mode 120000 index cf76760386..0000000000 --- a/tools/eslint/node_modules/acorn-jsx/node_modules/.bin/acorn +++ /dev/null @@ -1 +0,0 @@ -../acorn/bin/acorn \ No newline at end of file diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/.tern-project b/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/.tern-project deleted file mode 100644 index 6718ce07e1..0000000000 --- a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/.tern-project +++ /dev/null @@ -1,6 +0,0 @@ -{ - "plugins": { - "node": true, - "es_modules": true - } -} \ No newline at end of file diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/AUTHORS b/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/AUTHORS deleted file mode 100644 index 0e8f48b695..0000000000 --- a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/AUTHORS +++ /dev/null @@ -1,43 +0,0 @@ -List of Acorn contributors. Updated before every release. - -Adrian Rakovsky -Alistair Braidwood -Andres Suarez -Aparajita Fishman -Arian Stolwijk -Artem Govorov -Brandon Mills -Charles Hughes -Conrad Irwin -David Bonnet -ForbesLindesay -Forbes Lindesay -Gilad Peleg -impinball -Ingvar Stepanyan -Jesse McCarthy -Jiaxing Wang -Joel Kemp -Johannes Herr -Jürg Lehni -keeyipchan -Kevin Kwok -krator -Marijn Haverbeke -Martin Carlberg -Mathias Bynens -Mathieu 'p01' Henri -Max Schaefer -Max Zerzouri -Mihai Bazon -Mike Rennie -Nick Fitzgerald -Oskar Schöldström -Paul Harper -Peter Rust -PlNG -r-e-d -Rich Harris -Sebastian McKenzie -Timothy Gu -zsjforcn diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/LICENSE b/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/LICENSE deleted file mode 100644 index d4c7fc5838..0000000000 --- a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2012-2014 by various contributors (see AUTHORS) - -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. diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/README.md b/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/README.md deleted file mode 100644 index 12680abf6b..0000000000 --- a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/README.md +++ /dev/null @@ -1,396 +0,0 @@ -# Acorn - -[![Build Status](https://travis-ci.org/ternjs/acorn.svg?branch=master)](https://travis-ci.org/ternjs/acorn) -[![NPM version](https://img.shields.io/npm/v/acorn.svg)](https://www.npmjs.com/package/acorn) -[Author funding status: ![maintainer happiness](https://marijnhaverbeke.nl/fund/status_s.png?force)](https://marijnhaverbeke.nl/fund/) - -A tiny, fast JavaScript parser, written completely in JavaScript. - -## Community - -Acorn is open source software released under an -[MIT license](https://github.com/ternjs/acorn/blob/master/LICENSE). - -You are welcome to -[report bugs](https://github.com/ternjs/acorn/issues) or create pull -requests on [github](https://github.com/ternjs/acorn). For questions -and discussion, please use the -[Tern discussion forum](https://discuss.ternjs.net). - -## Installation - -The easiest way to install acorn is with [`npm`][npm]. - -[npm]: https://www.npmjs.com/ - -```sh -npm install acorn -``` - -Alternately, download the source. - -```sh -git clone https://github.com/ternjs/acorn.git -``` - -## Components - -When run in a CommonJS (node.js) or AMD environment, exported values -appear in the interfaces exposed by the individual files, as usual. -When loaded in the browser (Acorn works in any JS-enabled browser more -recent than IE5) without any kind of module management, a single -global object `acorn` will be defined, and all the exported properties -will be added to that. - -### Main parser - -This is implemented in `dist/acorn.js`, and is what you get when you -`require("acorn")` in node.js. - -**parse**`(input, options)` is used to parse a JavaScript program. -The `input` parameter is a string, `options` can be undefined or an -object setting some of the options listed below. The return value will -be an abstract syntax tree object as specified by the -[ESTree spec][estree]. - -When encountering a syntax error, the parser will raise a -`SyntaxError` object with a meaningful message. The error object will -have a `pos` property that indicates the character offset at which the -error occurred, and a `loc` object that contains a `{line, column}` -object referring to that same position. - -[estree]: https://github.com/estree/estree - -- **ecmaVersion**: Indicates the ECMAScript version to parse. Must be - either 3, 5, or 6. This influences support for strict mode, the set - of reserved words, and support for new syntax features. Default is 5. - -- **sourceType**: Indicate the mode the code should be parsed in. Can be - either `"script"` or `"module"`. - -- **onInsertedSemicolon**: If given a callback, that callback will be - called whenever a missing semicolon is inserted by the parser. The - callback will be given the character offset of the point where the - semicolon is inserted as argument, and if `locations` is on, also a - `{line, column}` object representing this position. - -- **onTrailingComma**: Like `onInsertedSemicolon`, but for trailing - commas. - -- **allowReserved**: If `false`, using a reserved word will generate - an error. Defaults to `true` for `ecmaVersion` 3, `false` for higher - versions. When given the value `"never"`, reserved words and - keywords can also not be used as property names (as in Internet - Explorer's old parser). - -- **allowReturnOutsideFunction**: By default, a return statement at - the top level raises an error. Set this to `true` to accept such - code. - -- **allowImportExportEverywhere**: By default, `import` and `export` - declarations can only appear at a program's top level. Setting this - option to `true` allows them anywhere where a statement is allowed. - -- **allowHashBang**: When this is enabled (off by default), if the - code starts with the characters `#!` (as in a shellscript), the - first line will be treated as a comment. - -- **locations**: When `true`, each node has a `loc` object attached - with `start` and `end` subobjects, each of which contains the - one-based line and zero-based column numbers in `{line, column}` - form. Default is `false`. - -- **onToken**: If a function is passed for this option, each found - token will be passed in same format as tokens returned from - `tokenizer().getToken()`. - - If array is passed, each found token is pushed to it. - - Note that you are not allowed to call the parser from the - callback—that will corrupt its internal state. - -- **onComment**: If a function is passed for this option, whenever a - comment is encountered the function will be called with the - following parameters: - - - `block`: `true` if the comment is a block comment, false if it - is a line comment. - - `text`: The content of the comment. - - `start`: Character offset of the start of the comment. - - `end`: Character offset of the end of the comment. - - When the `locations` options is on, the `{line, column}` locations - of the comment’s start and end are passed as two additional - parameters. - - If array is passed for this option, each found comment is pushed - to it as object in Esprima format: - - ```javascript - { - "type": "Line" | "Block", - "value": "comment text", - "start": Number, - "end": Number, - // If `locations` option is on: - "loc": { - "start": {line: Number, column: Number} - "end": {line: Number, column: Number} - }, - // If `ranges` option is on: - "range": [Number, Number] - } - ``` - - Note that you are not allowed to call the parser from the - callback—that will corrupt its internal state. - -- **ranges**: Nodes have their start and end characters offsets - recorded in `start` and `end` properties (directly on the node, - rather than the `loc` object, which holds line/column data. To also - add a [semi-standardized][range] `range` property holding a - `[start, end]` array with the same numbers, set the `ranges` option - to `true`. - -- **program**: It is possible to parse multiple files into a single - AST by passing the tree produced by parsing the first file as the - `program` option in subsequent parses. This will add the toplevel - forms of the parsed file to the "Program" (top) node of an existing - parse tree. - -- **sourceFile**: When the `locations` option is `true`, you can pass - this option to add a `source` attribute in every node’s `loc` - object. Note that the contents of this option are not examined or - processed in any way; you are free to use whatever format you - choose. - -- **directSourceFile**: Like `sourceFile`, but a `sourceFile` property - will be added directly to the nodes, rather than the `loc` object. - -- **preserveParens**: If this option is `true`, parenthesized expressions - are represented by (non-standard) `ParenthesizedExpression` nodes - that have a single `expression` property containing the expression - inside parentheses. - -[range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 - -**parseExpressionAt**`(input, offset, options)` will parse a single -expression in a string, and return its AST. It will not complain if -there is more of the string left after the expression. - -**getLineInfo**`(input, offset)` can be used to get a `{line, -column}` object for a given program string and character offset. - -**tokenizer**`(input, options)` returns an object with a `getToken` -method that can be called repeatedly to get the next token, a `{start, -end, type, value}` object (with added `loc` property when the -`locations` option is enabled and `range` property when the `ranges` -option is enabled). When the token's type is `tokTypes.eof`, you -should stop calling the method, since it will keep returning that same -token forever. - -In ES6 environment, returned result can be used as any other -protocol-compliant iterable: - -```javascript -for (let token of acorn.tokenizer(str)) { - // iterate over the tokens -} - -// transform code to array of tokens: -var tokens = [...acorn.tokenizer(str)]; -``` - -**tokTypes** holds an object mapping names to the token type objects -that end up in the `type` properties of tokens. - -#### Note on using with [Escodegen][escodegen] - -Escodegen supports generating comments from AST, attached in -Esprima-specific format. In order to simulate same format in -Acorn, consider following example: - -```javascript -var comments = [], tokens = []; - -var ast = acorn.parse('var x = 42; // answer', { - // collect ranges for each node - ranges: true, - // collect comments in Esprima's format - onComment: comments, - // collect token ranges - onToken: tokens -}); - -// attach comments using collected information -escodegen.attachComments(ast, comments, tokens); - -// generate code -console.log(escodegen.generate(ast, {comment: true})); -// > 'var x = 42; // answer' -``` - -[escodegen]: https://github.com/estools/escodegen - -### dist/acorn_loose.js ### - -This file implements an error-tolerant parser. It exposes a single -function. The loose parser is accessible in node.js via `require("acorn/dist/acorn_loose")`. - -**parse_dammit**`(input, options)` takes the same arguments and -returns the same syntax tree as the `parse` function in `acorn.js`, -but never raises an error, and will do its best to parse syntactically -invalid code in as meaningful a way as it can. It'll insert identifier -nodes with name `"✖"` as placeholders in places where it can't make -sense of the input. Depends on `acorn.js`, because it uses the same -tokenizer. - -### dist/walk.js ### - -Implements an abstract syntax tree walker. Will store its interface in -`acorn.walk` when loaded without a module system. - -**simple**`(node, visitors, base, state)` does a 'simple' walk over -a tree. `node` should be the AST node to walk, and `visitors` an -object with properties whose names correspond to node types in the -[ESTree spec][estree]. The properties should contain functions -that will be called with the node object and, if applicable the state -at that point. The last two arguments are optional. `base` is a walker -algorithm, and `state` is a start state. The default walker will -simply visit all statements and expressions and not produce a -meaningful state. (An example of a use of state is to track scope at -each point in the tree.) - -**ancestor**`(node, visitors, base, state)` does a 'simple' walk over -a tree, building up an array of ancestor nodes (including the current node) -and passing the array to callbacks in the `state` parameter. - -**recursive**`(node, state, functions, base)` does a 'recursive' -walk, where the walker functions are responsible for continuing the -walk on the child nodes of their target node. `state` is the start -state, and `functions` should contain an object that maps node types -to walker functions. Such functions are called with `(node, state, c)` -arguments, and can cause the walk to continue on a sub-node by calling -the `c` argument on it with `(node, state)` arguments. The optional -`base` argument provides the fallback walker functions for node types -that aren't handled in the `functions` object. If not given, the -default walkers will be used. - -**make**`(functions, base)` builds a new walker object by using the -walker functions in `functions` and filling in the missing ones by -taking defaults from `base`. - -**findNodeAt**`(node, start, end, test, base, state)` tries to -locate a node in a tree at the given start and/or end offsets, which -satisfies the predicate `test`. `start` and `end` can be either `null` -(as wildcard) or a number. `test` may be a string (indicating a node -type) or a function that takes `(nodeType, node)` arguments and -returns a boolean indicating whether this node is interesting. `base` -and `state` are optional, and can be used to specify a custom walker. -Nodes are tested from inner to outer, so if two nodes match the -boundaries, the inner one will be preferred. - -**findNodeAround**`(node, pos, test, base, state)` is a lot like -`findNodeAt`, but will match any node that exists 'around' (spanning) -the given position. - -**findNodeAfter**`(node, pos, test, base, state)` is similar to -`findNodeAround`, but will match all nodes *after* the given position -(testing outer nodes before inner nodes). - -## Command line interface - -The `bin/acorn` utility can be used to parse a file from the command -line. It accepts as arguments its input file and the following -options: - -- `--ecma3|--ecma5|--ecma6`: Sets the ECMAScript version to parse. Default is - version 5. - -- `--module`: Sets the parsing mode to `"module"`. Is set to `"script"` otherwise. - -- `--locations`: Attaches a "loc" object to each node with "start" and - "end" subobjects, each of which contains the one-based line and - zero-based column numbers in `{line, column}` form. - -- `--allow-hash-bang`: If the code starts with the characters #! (as in a shellscript), the first line will be treated as a comment. - -- `--compact`: No whitespace is used in the AST output. - -- `--silent`: Do not output the AST, just return the exit status. - -- `--help`: Print the usage information and quit. - -The utility spits out the syntax tree as JSON data. - -## Build system - -Acorn is written in ECMAScript 6, as a set of small modules, in the -project's `src` directory, and compiled down to bigger ECMAScript 3 -files in `dist` using [Browserify](http://browserify.org) and -[Babel](http://babeljs.io/). If you are already using Babel, you can -consider including the modules directly. - -The command-line test runner (`npm test`) uses the ES6 modules. The -browser-based test page (`test/index.html`) uses the compiled modules. -The `bin/build-acorn.js` script builds the latter from the former. - -If you are working on Acorn, you'll probably want to try the code out -directly, without an intermediate build step. In your scripts, you can -register the Babel require shim like this: - - require("babel-core/register") - -That will allow you to directly `require` the ES6 modules. - -## Plugins - -Acorn is designed support allow plugins which, within reasonable -bounds, redefine the way the parser works. Plugins can add new token -types and new tokenizer contexts (if necessary), and extend methods in -the parser object. This is not a clean, elegant API—using it requires -an understanding of Acorn's internals, and plugins are likely to break -whenever those internals are significantly changed. But still, it is -_possible_, in this way, to create parsers for JavaScript dialects -without forking all of Acorn. And in principle it is even possible to -combine such plugins, so that if you have, for example, a plugin for -parsing types and a plugin for parsing JSX-style XML literals, you -could load them both and parse code with both JSX tags and types. - -A plugin should register itself by adding a property to -`acorn.plugins`, which holds a function. Calling `acorn.parse`, a -`plugins` option can be passed, holding an object mapping plugin names -to configuration values (or just `true` for plugins that don't take -options). After the parser object has been created, the initialization -functions for the chosen plugins are called with `(parser, -configValue)` arguments. They are expected to use the `parser.extend` -method to extend parser methods. For example, the `readToken` method -could be extended like this: - -```javascript -parser.extend("readToken", function(nextMethod) { - return function(code) { - console.log("Reading a token!") - return nextMethod.call(this, code) - } -}) -``` - -The `nextMethod` argument passed to `extend`'s second argument is the -previous value of this method, and should usually be called through to -whenever the extended method does not handle the call itself. - -Similarly, the loose parser allows plugins to register themselves via -`acorn.pluginsLoose`. The extension mechanism is the same as for the -normal parser: - -```javascript -looseParser.extend("readToken", function(nextMethod) { - return function() { - console.log("Reading a token in the loose parser!") - return nextMethod.call(this) - } -}) -``` - -There is a proof-of-concept JSX plugin in the [`acorn-jsx`](https://github.com/RReverser/acorn-jsx) project. diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/acorn b/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/acorn deleted file mode 100755 index db079096d3..0000000000 --- a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/acorn +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env node -"use strict"; - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } - -var _path = require("path"); - -var _fs = require("fs"); - -var _distAcornJs = require("../dist/acorn.js"); - -var acorn = _interopRequireWildcard(_distAcornJs); - -var infile = undefined, - forceFile = undefined, - silent = false, - compact = false, - tokenize = false; -var options = {}; - -function help(status) { - var print = status == 0 ? console.log : console.error; - print("usage: " + (0, _path.basename)(process.argv[1]) + " [--ecma3|--ecma5|--ecma6]"); - print(" [--tokenize] [--locations] [---allow-hash-bang] [--compact] [--silent] [--module] [--help] [--] [infile]"); - process.exit(status); -} - -for (var i = 2; i < process.argv.length; ++i) { - var arg = process.argv[i]; - if ((arg == "-" || arg[0] != "-") && !infile) infile = arg;else if (arg == "--" && !infile && i + 2 == process.argv.length) forceFile = infile = process.argv[++i];else if (arg == "--ecma3") options.ecmaVersion = 3;else if (arg == "--ecma5") options.ecmaVersion = 5;else if (arg == "--ecma6") options.ecmaVersion = 6;else if (arg == "--locations") options.locations = true;else if (arg == "--allow-hash-bang") options.allowHashBang = true;else if (arg == "--silent") silent = true;else if (arg == "--compact") compact = true;else if (arg == "--help") help(0);else if (arg == "--tokenize") tokenize = true;else if (arg == "--module") options.sourceType = 'module';else help(1); -} - -function run(code) { - var result = undefined; - if (!tokenize) { - try { - result = acorn.parse(code, options); - } catch (e) { - console.error(e.message);process.exit(1); - } - } else { - result = []; - var tokenizer = acorn.tokenizer(code, options), - token = undefined; - while (true) { - try { - token = tokenizer.getToken(); - } catch (e) { - console.error(e.message);process.exit(1); - } - result.push(token); - if (token.type == acorn.tokTypes.eof) break; - } - } - if (!silent) console.log(JSON.stringify(result, null, compact ? null : 2)); -} - -if (forceFile || infile && infile != "-") { - run((0, _fs.readFileSync)(infile, "utf8")); -} else { - (function () { - var code = ""; - process.stdin.resume(); - process.stdin.on("data", function (chunk) { - return code += chunk; - }); - process.stdin.on("end", function () { - return run(code); - }); - })(); -} \ No newline at end of file diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/build-acorn.js b/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/build-acorn.js deleted file mode 100644 index 71f2cf941f..0000000000 --- a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/build-acorn.js +++ /dev/null @@ -1,82 +0,0 @@ -var fs = require("fs"), path = require("path") -var stream = require("stream") - -var browserify = require("browserify") -var babel = require('babel-core') -var babelify = require("babelify").configure({loose: "all"}) - -process.chdir(path.resolve(__dirname, "..")) - -browserify({standalone: "acorn"}) - .plugin(require('browserify-derequire')) - .transform(babelify) - .require("./src/index.js", {entry: true}) - .bundle() - .on("error", function (err) { console.log("Error: " + err.message) }) - .pipe(fs.createWriteStream("dist/acorn.js")) - -var ACORN_PLACEHOLDER = "this_function_call_should_be_replaced_with_a_call_to_load_acorn()"; -function acornShimPrepare(file) { - var tr = new stream.Transform - if (file == path.resolve(__dirname, "../src/index.js")) { - var sent = false - tr._transform = function(chunk, _, callback) { - if (!sent) { - sent = true - callback(null, ACORN_PLACEHOLDER); - } else { - callback() - } - } - } else { - tr._transform = function(chunk, _, callback) { callback(null, chunk) } - } - return tr -} -function acornShimComplete() { - var tr = new stream.Transform - var buffer = ""; - tr._transform = function(chunk, _, callback) { - buffer += chunk.toString("utf8"); - callback(); - }; - tr._flush = function (callback) { - tr.push(buffer.replace(ACORN_PLACEHOLDER, "module.exports = typeof acorn != 'undefined' ? acorn : require(\"./acorn\")")); - callback(null); - }; - return tr; -} - -browserify({standalone: "acorn.loose"}) - .plugin(require('browserify-derequire')) - .transform(acornShimPrepare) - .transform(babelify) - .require("./src/loose/index.js", {entry: true}) - .bundle() - .on("error", function (err) { console.log("Error: " + err.message) }) - .pipe(acornShimComplete()) - .pipe(fs.createWriteStream("dist/acorn_loose.js")) - -browserify({standalone: "acorn.walk"}) - .plugin(require('browserify-derequire')) - .transform(acornShimPrepare) - .transform(babelify) - .require("./src/walk/index.js", {entry: true}) - .bundle() - .on("error", function (err) { console.log("Error: " + err.message) }) - .pipe(acornShimComplete()) - .pipe(fs.createWriteStream("dist/walk.js")) - -babel.transformFile("./src/bin/acorn.js", function (err, result) { - if (err) return console.log("Error: " + err.message) - fs.writeFile("bin/acorn", result.code, function (err) { - if (err) return console.log("Error: " + err.message) - - // Make bin/acorn executable - if (process.platform === 'win32') - return - var stat = fs.statSync("bin/acorn") - var newPerm = stat.mode | parseInt('111', 8) - fs.chmodSync("bin/acorn", newPerm) - }) -}) diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/generate-identifier-regex.js b/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/generate-identifier-regex.js deleted file mode 100644 index 0d7c50fc38..0000000000 --- a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/generate-identifier-regex.js +++ /dev/null @@ -1,47 +0,0 @@ -// Note: run `npm install unicode-7.0.0` first. - -// Which Unicode version should be used? -var version = '7.0.0'; - -var start = require('unicode-' + version + '/properties/ID_Start/code-points') - .filter(function(ch) { return ch > 127; }); -var cont = [0x200c, 0x200d].concat(require('unicode-' + version + '/properties/ID_Continue/code-points') - .filter(function(ch) { return ch > 127 && start.indexOf(ch) == -1; })); - -function pad(str, width) { - while (str.length < width) str = "0" + str; - return str; -} - -function esc(code) { - var hex = code.toString(16); - if (hex.length <= 2) return "\\x" + pad(hex, 2); - else return "\\u" + pad(hex, 4); -} - -function generate(chars) { - var astral = [], re = ""; - for (var i = 0, at = 0x10000; i < chars.length; i++) { - var from = chars[i], to = from; - while (i < chars.length - 1 && chars[i + 1] == to + 1) { - i++; - to++; - } - if (to <= 0xffff) { - if (from == to) re += esc(from); - else if (from + 1 == to) re += esc(from) + esc(to); - else re += esc(from) + "-" + esc(to); - } else { - astral.push(from - at, to - from); - at = to; - } - } - return {nonASCII: re, astral: astral}; -} - -var startData = generate(start), contData = generate(cont); - -console.log(" var nonASCIIidentifierStartChars = \"" + startData.nonASCII + "\";"); -console.log(" var nonASCIIidentifierChars = \"" + contData.nonASCII + "\";"); -console.log(" var astralIdentifierStartCodes = " + JSON.stringify(startData.astral) + ";"); -console.log(" var astralIdentifierCodes = " + JSON.stringify(contData.astral) + ";"); diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/update_authors.sh b/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/update_authors.sh deleted file mode 100755 index 466c8db586..0000000000 --- a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/bin/update_authors.sh +++ /dev/null @@ -1,6 +0,0 @@ -# Combine existing list of authors with everyone known in git, sort, add header. -tail --lines=+3 AUTHORS > AUTHORS.tmp -git log --format='%aN' | grep -v abraidwood >> AUTHORS.tmp -echo -e "List of Acorn contributors. Updated before every release.\n" > AUTHORS -sort -u AUTHORS.tmp >> AUTHORS -rm -f AUTHORS.tmp diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/dist/.keep b/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/dist/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/dist/acorn.js b/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/dist/acorn.js deleted file mode 100644 index 9419f862ae..0000000000 --- a/tools/eslint/node_modules/acorn-jsx/node_modules/acorn/dist/acorn.js +++ /dev/null @@ -1,3340 +0,0 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.acorn = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 6 && (prop.computed || prop.method || prop.shorthand)) return; - var key = prop.key;var name = undefined; - switch (key.type) { - case "Identifier": - name = key.name;break; - case "Literal": - name = String(key.value);break; - default: - return; - } - var kind = prop.kind; - - if (this.options.ecmaVersion >= 6) { - if (name === "__proto__" && kind === "init") { - if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property"); - propHash.proto = true; - } - return; - } - name = "$" + name; - var other = propHash[name]; - if (other) { - var isGetSet = kind !== "init"; - if ((this.strict || isGetSet) && other[kind] || !(isGetSet ^ other.init)) this.raise(key.start, "Redefinition of property"); - } else { - other = propHash[name] = { - init: false, - get: false, - set: false - }; - } - other[kind] = true; -}; - -// ### Expression parsing - -// These nest, from the most general expression type at the top to -// 'atomic', nondivisible expression types at the bottom. Most of -// the functions will simply let the function(s) below them parse, -// and, *if* the syntactic construct they handle is present, wrap -// the AST node that the inner parser gave them in another node. - -// Parse a full expression. The optional arguments are used to -// forbid the `in` operator (in for loops initalization expressions) -// and provide reference for storing '=' operator inside shorthand -// property assignment in contexts where both object expression -// and object pattern might appear (so it's possible to raise -// delayed syntax error at correct position). - -pp.parseExpression = function (noIn, refDestructuringErrors) { - var startPos = this.start, - startLoc = this.startLoc; - var expr = this.parseMaybeAssign(noIn, refDestructuringErrors); - if (this.type === _tokentype.types.comma) { - var node = this.startNodeAt(startPos, startLoc); - node.expressions = [expr]; - while (this.eat(_tokentype.types.comma)) node.expressions.push(this.parseMaybeAssign(noIn, refDestructuringErrors)); - return this.finishNode(node, "SequenceExpression"); - } - return expr; -}; - -// Parse an assignment expression. This includes applications of -// operators like `+=`. - -pp.parseMaybeAssign = function (noIn, refDestructuringErrors, afterLeftParse) { - if (this.type == _tokentype.types._yield && this.inGenerator) return this.parseYield(); - - var validateDestructuring = false; - if (!refDestructuringErrors) { - refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 }; - validateDestructuring = true; - } - var startPos = this.start, - startLoc = this.startLoc; - if (this.type == _tokentype.types.parenL || this.type == _tokentype.types.name) this.potentialArrowAt = this.start; - var left = this.parseMaybeConditional(noIn, refDestructuringErrors); - if (afterLeftParse) left = afterLeftParse.call(this, left, startPos, startLoc); - if (this.type.isAssign) { - if (validateDestructuring) this.checkPatternErrors(refDestructuringErrors, true); - var node = this.startNodeAt(startPos, startLoc); - node.operator = this.value; - node.left = this.type === _tokentype.types.eq ? this.toAssignable(left) : left; - refDestructuringErrors.shorthandAssign = 0; // reset because shorthand default was used correctly - this.checkLVal(left); - this.next(); - node.right = this.parseMaybeAssign(noIn); - return this.finishNode(node, "AssignmentExpression"); - } else { - if (validateDestructuring) this.checkExpressionErrors(refDestructuringErrors, true); - } - return left; -}; - -// Parse a ternary conditional (`?:`) operator. - -pp.parseMaybeConditional = function (noIn, refDestructuringErrors) { - var startPos = this.start, - startLoc = this.startLoc; - var expr = this.parseExprOps(noIn, refDestructuringErrors); - if (this.checkExpressionErrors(refDestructuringErrors)) return expr; - if (this.eat(_tokentype.types.question)) { - var node = this.startNodeAt(startPos, startLoc); - node.test = expr; - node.consequent = this.parseMaybeAssign(); - this.expect(_tokentype.types.colon); - node.alternate = this.parseMaybeAssign(noIn); - return this.finishNode(node, "ConditionalExpression"); - } - return expr; -}; - -// Start the precedence parser. - -pp.parseExprOps = function (noIn, refDestructuringErrors) { - var startPos = this.start, - startLoc = this.startLoc; - var expr = this.parseMaybeUnary(refDestructuringErrors); - if (this.checkExpressionErrors(refDestructuringErrors)) return expr; - return this.parseExprOp(expr, startPos, startLoc, -1, noIn); -}; - -// Parse binary operators with the operator precedence parsing -// algorithm. `left` is the left-hand side of the operator. -// `minPrec` provides context that allows the function to stop and -// defer further parser to one of its callers when it encounters an -// operator that has a lower precedence than the set it is parsing. - -pp.parseExprOp = function (left, leftStartPos, leftStartLoc, minPrec, noIn) { - var prec = this.type.binop; - if (prec != null && (!noIn || this.type !== _tokentype.types._in)) { - if (prec > minPrec) { - var node = this.startNodeAt(leftStartPos, leftStartLoc); - node.left = left; - node.operator = this.value; - var op = this.type; - this.next(); - var startPos = this.start, - startLoc = this.startLoc; - node.right = this.parseExprOp(this.parseMaybeUnary(), startPos, startLoc, prec, noIn); - this.finishNode(node, op === _tokentype.types.logicalOR || op === _tokentype.types.logicalAND ? "LogicalExpression" : "BinaryExpression"); - return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn); - } - } - return left; -}; - -// Parse unary operators, both prefix and postfix. - -pp.parseMaybeUnary = function (refDestructuringErrors) { - if (this.type.prefix) { - var node = this.startNode(), - update = this.type === _tokentype.types.incDec; - node.operator = this.value; - node.prefix = true; - this.next(); - node.argument = this.parseMaybeUnary(); - this.checkExpressionErrors(refDestructuringErrors, true); - if (update) this.checkLVal(node.argument);else if (this.strict && node.operator === "delete" && node.argument.type === "Identifier") this.raise(node.start, "Deleting local variable in strict mode"); - return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); - } - var startPos = this.start, - startLoc = this.startLoc; - var expr = this.parseExprSubscripts(refDestructuringErrors); - if (this.checkExpressionErrors(refDestructuringErrors)) return expr; - while (this.type.postfix && !this.canInsertSemicolon()) { - var node = this.startNodeAt(startPos, startLoc); - node.operator = this.value; - node.prefix = false; - node.argument = expr; - this.checkLVal(expr); - this.next(); - expr = this.finishNode(node, "UpdateExpression"); - } - return expr; -}; - -// Parse call, dot, and `[]`-subscript expressions. - -pp.parseExprSubscripts = function (refDestructuringErrors) { - var startPos = this.start, - startLoc = this.startLoc; - var expr = this.parseExprAtom(refDestructuringErrors); - var skipArrowSubscripts = expr.type === "ArrowFunctionExpression" && this.input.slice(this.lastTokStart, this.lastTokEnd) !== ")"; - if (this.checkExpressionErrors(refDestructuringErrors) || skipArrowSubscripts) return expr; - return this.parseSubscripts(expr, startPos, startLoc); -}; - -pp.parseSubscripts = function (base, startPos, startLoc, noCalls) { - for (;;) { - if (this.eat(_tokentype.types.dot)) { - var node = this.startNodeAt(startPos, startLoc); - node.object = base; - node.property = this.parseIdent(true); - node.computed = false; - base = this.finishNode(node, "MemberExpression"); - } else if (this.eat(_tokentype.types.bracketL)) { - var node = this.startNodeAt(startPos, startLoc); - node.object = base; - node.property = this.parseExpression(); - node.computed = true; - this.expect(_tokentype.types.bracketR); - base = this.finishNode(node, "MemberExpression"); - } else if (!noCalls && this.eat(_tokentype.types.parenL)) { - var node = this.startNodeAt(startPos, startLoc); - node.callee = base; - node.arguments = this.parseExprList(_tokentype.types.parenR, false); - base = this.finishNode(node, "CallExpression"); - } else if (this.type === _tokentype.types.backQuote) { - var node = this.startNodeAt(startPos, startLoc); - node.tag = base; - node.quasi = this.parseTemplate(); - base = this.finishNode(node, "TaggedTemplateExpression"); - } else { - return base; - } - } -}; - -// Parse an atomic expression — either a single token that is an -// expression, an expression started by a keyword like `function` or -// `new`, or an expression wrapped in punctuation like `()`, `[]`, -// or `{}`. - -pp.parseExprAtom = function (refDestructuringErrors) { - var node = undefined, - canBeArrow = this.potentialArrowAt == this.start; - switch (this.type) { - case _tokentype.types._super: - if (!this.inFunction) this.raise(this.start, "'super' outside of function or class"); - case _tokentype.types._this: - var type = this.type === _tokentype.types._this ? "ThisExpression" : "Super"; - node = this.startNode(); - this.next(); - return this.finishNode(node, type); - - case _tokentype.types._yield: - if (this.inGenerator) this.unexpected(); - - case _tokentype.types.name: - var startPos = this.start, - startLoc = this.startLoc; - var id = this.parseIdent(this.type !== _tokentype.types.name); - if (canBeArrow && !this.canInsertSemicolon() && this.eat(_tokentype.types.arrow)) return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id]); - return id; - - case _tokentype.types.regexp: - var value = this.value; - node = this.parseLiteral(value.value); - node.regex = { pattern: value.pattern, flags: value.flags }; - return node; - - case _tokentype.types.num:case _tokentype.types.string: - return this.parseLiteral(this.value); - - case _tokentype.types._null:case _tokentype.types._true:case _tokentype.types._false: - node = this.startNode(); - node.value = this.type === _tokentype.types._null ? null : this.type === _tokentype.types._true; - node.raw = this.type.keyword; - this.next(); - return this.finishNode(node, "Literal"); - - case _tokentype.types.parenL: - return this.parseParenAndDistinguishExpression(canBeArrow); - - case _tokentype.types.bracketL: - node = this.startNode(); - this.next(); - // check whether this is array comprehension or regular array - if (this.options.ecmaVersion >= 7 && this.type === _tokentype.types._for) { - return this.parseComprehension(node, false); - } - node.elements = this.parseExprList(_tokentype.types.bracketR, true, true, refDestructuringErrors); - return this.finishNode(node, "ArrayExpression"); - - case _tokentype.types.braceL: - return this.parseObj(false, refDestructuringErrors); - - case _tokentype.types._function: - node = this.startNode(); - this.next(); - return this.parseFunction(node, false); - - case _tokentype.types._class: - return this.parseClass(this.startNode(), false); - - case _tokentype.types._new: - return this.parseNew(); - - case _tokentype.types.backQuote: - return this.parseTemplate(); - - default: - this.unexpected(); - } -}; - -pp.parseLiteral = function (value) { - var node = this.startNode(); - node.value = value; - node.raw = this.input.slice(this.start, this.end); - this.next(); - return this.finishNode(node, "Literal"); -}; - -pp.parseParenExpression = function () { - this.expect(_tokentype.types.parenL); - var val = this.parseExpression(); - this.expect(_tokentype.types.parenR); - return val; -}; - -pp.parseParenAndDistinguishExpression = function (canBeArrow) { - var startPos = this.start, - startLoc = this.startLoc, - val = undefined; - if (this.options.ecmaVersion >= 6) { - this.next(); - - if (this.options.ecmaVersion >= 7 && this.type === _tokentype.types._for) { - return this.parseComprehension(this.startNodeAt(startPos, startLoc), true); - } - - var innerStartPos = this.start, - innerStartLoc = this.startLoc; - var exprList = [], - first = true; - var refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 }, - spreadStart = undefined, - innerParenStart = undefined; - while (this.type !== _tokentype.types.parenR) { - first ? first = false : this.expect(_tokentype.types.comma); - if (this.type === _tokentype.types.ellipsis) { - spreadStart = this.start; - exprList.push(this.parseParenItem(this.parseRest())); - break; - } else { - if (this.type === _tokentype.types.parenL && !innerParenStart) { - innerParenStart = this.start; - } - exprList.push(this.parseMaybeAssign(false, refDestructuringErrors, this.parseParenItem)); - } - } - var innerEndPos = this.start, - innerEndLoc = this.startLoc; - this.expect(_tokentype.types.parenR); - - if (canBeArrow && !this.canInsertSemicolon() && this.eat(_tokentype.types.arrow)) { - this.checkPatternErrors(refDestructuringErrors, true); - if (innerParenStart) this.unexpected(innerParenStart); - return this.parseParenArrowList(startPos, startLoc, exprList); - } - - if (!exprList.length) this.unexpected(this.lastTokStart); - if (spreadStart) this.unexpected(spreadStart); - this.checkExpressionErrors(refDestructuringErrors, true); - - if (exprList.length > 1) { - val = this.startNodeAt(innerStartPos, innerStartLoc); - val.expressions = exprList; - this.finishNodeAt(val, "SequenceExpression", innerEndPos, innerEndLoc); - } else { - val = exprList[0]; - } - } else { - val = this.parseParenExpression(); - } - - if (this.options.preserveParens) { - var par = this.startNodeAt(startPos, startLoc); - par.expression = val; - return this.finishNode(par, "ParenthesizedExpression"); - } else { - return val; - } -}; - -pp.parseParenItem = function (item) { - return item; -}; - -pp.parseParenArrowList = function (startPos, startLoc, exprList) { - return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList); -}; - -// New's precedence is slightly tricky. It must allow its argument to -// be a `[]` or dot subscript expression, but not a call — at least, -// not without wrapping it in parentheses. Thus, it uses the noCalls -// argument to parseSubscripts to prevent it from consuming the -// argument list. - -var empty = []; - -pp.parseNew = function () { - var node = this.startNode(); - var meta = this.parseIdent(true); - if (this.options.ecmaVersion >= 6 && this.eat(_tokentype.types.dot)) { - node.meta = meta; - node.property = this.parseIdent(true); - if (node.property.name !== "target") this.raise(node.property.start, "The only valid meta property for new is new.target"); - if (!this.inFunction) this.raise(node.start, "new.target can only be used in functions"); - return this.finishNode(node, "MetaProperty"); - } - var startPos = this.start, - startLoc = this.startLoc; - node.callee = this.parseSubscripts(this.parseExprAtom(), startPos, startLoc, true); - if (this.eat(_tokentype.types.parenL)) node.arguments = this.parseExprList(_tokentype.types.parenR, false);else node.arguments = empty; - return this.finishNode(node, "NewExpression"); -}; - -// Parse template expression. - -pp.parseTemplateElement = function () { - var elem = this.startNode(); - elem.value = { - raw: this.input.slice(this.start, this.end).replace(/\r\n?/g, '\n'), - cooked: this.value - }; - this.next(); - elem.tail = this.type === _tokentype.types.backQuote; - return this.finishNode(elem, "TemplateElement"); -}; - -pp.parseTemplate = function () { - var node = this.startNode(); - this.next(); - node.expressions = []; - var curElt = this.parseTemplateElement(); - node.quasis = [curElt]; - while (!curElt.tail) { - this.expect(_tokentype.types.dollarBraceL); - node.expressions.push(this.parseExpression()); - this.expect(_tokentype.types.braceR); - node.quasis.push(curElt = this.parseTemplateElement()); - } - this.next(); - return this.finishNode(node, "TemplateLiteral"); -}; - -// Parse an object literal or binding pattern. - -pp.parseObj = function (isPattern, refDestructuringErrors) { - var node = this.startNode(), - first = true, - propHash = {}; - node.properties = []; - this.next(); - while (!this.eat(_tokentype.types.braceR)) { - if (!first) { - this.expect(_tokentype.types.comma); - if (this.afterTrailingComma(_tokentype.types.braceR)) break; - } else first = false; - - var prop = this.startNode(), - isGenerator = undefined, - startPos = undefined, - startLoc = undefined; - if (this.options.ecmaVersion >= 6) { - prop.method = false; - prop.shorthand = false; - if (isPattern || refDestructuringErrors) { - startPos = this.start; - startLoc = this.startLoc; - } - if (!isPattern) isGenerator = this.eat(_tokentype.types.star); - } - this.parsePropertyName(prop); - this.parsePropertyValue(prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors); - this.checkPropClash(prop, propHash); - node.properties.push(this.finishNode(prop, "Property")); - } - return this.finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression"); -}; - -pp.parsePropertyValue = function (prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors) { - if (this.eat(_tokentype.types.colon)) { - prop.value = isPattern ? this.parseMaybeDefault(this.start, this.startLoc) : this.parseMaybeAssign(false, refDestructuringErrors); - prop.kind = "init"; - } else if (this.options.ecmaVersion >= 6 && this.type === _tokentype.types.parenL) { - if (isPattern) this.unexpected(); - prop.kind = "init"; - prop.method = true; - prop.value = this.parseMethod(isGenerator); - } else if (this.options.ecmaVersion >= 5 && !prop.computed && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set") && (this.type != _tokentype.types.comma && this.type != _tokentype.types.braceR)) { - if (isGenerator || isPattern) this.unexpected(); - prop.kind = prop.key.name; - this.parsePropertyName(prop); - prop.value = this.parseMethod(false); - var paramCount = prop.kind === "get" ? 0 : 1; - if (prop.value.params.length !== paramCount) { - var start = prop.value.start; - if (prop.kind === "get") this.raise(start, "getter should have no params");else this.raise(start, "setter should have exactly one param"); - } - if (prop.kind === "set" && prop.value.params[0].type === "RestElement") this.raise(prop.value.params[0].start, "Setter cannot use rest params"); - } else if (this.options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") { - prop.kind = "init"; - if (isPattern) { - if (this.keywords.test(prop.key.name) || (this.strict ? this.reservedWordsStrictBind : this.reservedWords).test(prop.key.name)) this.raise(prop.key.start, "Binding " + prop.key.name); - prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key); - } else if (this.type === _tokentype.types.eq && refDestructuringErrors) { - if (!refDestructuringErrors.shorthandAssign) refDestructuringErrors.shorthandAssign = this.start; - prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key); - } else { - prop.value = prop.key; - } - prop.shorthand = true; - } else this.unexpected(); -}; - -pp.parsePropertyName = function (prop) { - if (this.options.ecmaVersion >= 6) { - if (this.eat(_tokentype.types.bracketL)) { - prop.computed = true; - prop.key = this.parseMaybeAssign(); - this.expect(_tokentype.types.bracketR); - return prop.key; - } else { - prop.computed = false; - } - } - return prop.key = this.type === _tokentype.types.num || this.type === _tokentype.types.string ? this.parseExprAtom() : this.parseIdent(true); -}; - -// Initialize empty function node. - -pp.initFunction = function (node) { - node.id = null; - if (this.options.ecmaVersion >= 6) { - node.generator = false; - node.expression = false; - } -}; - -// Parse object or class method. - -pp.parseMethod = function (isGenerator) { - var node = this.startNode(); - this.initFunction(node); - this.expect(_tokentype.types.parenL); - node.params = this.parseBindingList(_tokentype.types.parenR, false, false); - if (this.options.ecmaVersion >= 6) node.generator = isGenerator; - this.parseFunctionBody(node, false); - return this.finishNode(node, "FunctionExpression"); -}; - -// Parse arrow function expression with given parameters. - -pp.parseArrowExpression = function (node, params) { - this.initFunction(node); - node.params = this.toAssignableList(params, true); - this.parseFunctionBody(node, true); - return this.finishNode(node, "ArrowFunctionExpression"); -}; - -// Parse function body and check parameters. - -pp.parseFunctionBody = function (node, isArrowFunction) { - var isExpression = isArrowFunction && this.type !== _tokentype.types.braceL; - - if (isExpression) { - node.body = this.parseMaybeAssign(); - node.expression = true; - } else { - // Start a new scope with regard to labels and the `inFunction` - // flag (restore them to their old value afterwards). - var oldInFunc = this.inFunction, - oldInGen = this.inGenerator, - oldLabels = this.labels; - this.inFunction = true;this.inGenerator = node.generator;this.labels = []; - node.body = this.parseBlock(true); - node.expression = false; - this.inFunction = oldInFunc;this.inGenerator = oldInGen;this.labels = oldLabels; - } - - // If this is a strict mode function, verify that argument names - // are not repeated, and it does not try to bind the words `eval` - // or `arguments`. - if (this.strict || !isExpression && node.body.body.length && this.isUseStrict(node.body.body[0])) { - var oldStrict = this.strict; - this.strict = true; - if (node.id) this.checkLVal(node.id, true); - this.checkParams(node); - this.strict = oldStrict; - } else if (isArrowFunction) { - this.checkParams(node); - } -}; - -// Checks function params for various disallowed patterns such as using "eval" -// or "arguments" and duplicate parameters. - -pp.checkParams = function (node) { - var nameHash = {}; - for (var i = 0; i < node.params.length; i++) { - this.checkLVal(node.params[i], true, nameHash); - } -}; - -// Parses a comma-separated list of expressions, and returns them as -// an array. `close` is the token type that ends the list, and -// `allowEmpty` can be turned on to allow subsequent commas with -// nothing in between them to be parsed as `null` (which is needed -// for array literals). - -pp.parseExprList = function (close, allowTrailingComma, allowEmpty, refDestructuringErrors) { - var elts = [], - first = true; - while (!this.eat(close)) { - if (!first) { - this.expect(_tokentype.types.comma); - if (this.type === close && refDestructuringErrors && !refDestructuringErrors.trailingComma) { - refDestructuringErrors.trailingComma = this.lastTokStart; - } - if (allowTrailingComma && this.afterTrailingComma(close)) break; - } else first = false; - - var elt = undefined; - if (allowEmpty && this.type === _tokentype.types.comma) elt = null;else if (this.type === _tokentype.types.ellipsis) elt = this.parseSpread(refDestructuringErrors);else elt = this.parseMaybeAssign(false, refDestructuringErrors); - elts.push(elt); - } - return elts; -}; - -// Parse the next token as an identifier. If `liberal` is true (used -// when parsing properties), it will also convert keywords into -// identifiers. - -pp.parseIdent = function (liberal) { - var node = this.startNode(); - if (liberal && this.options.allowReserved == "never") liberal = false; - if (this.type === _tokentype.types.name) { - if (!liberal && (this.strict ? this.reservedWordsStrict : this.reservedWords).test(this.value) && (this.options.ecmaVersion >= 6 || this.input.slice(this.start, this.end).indexOf("\\") == -1)) this.raise(this.start, "The keyword '" + this.value + "' is reserved"); - node.name = this.value; - } else if (liberal && this.type.keyword) { - node.name = this.type.keyword; - } else { - this.unexpected(); - } - this.next(); - return this.finishNode(node, "Identifier"); -}; - -// Parses yield expression inside generator. - -pp.parseYield = function () { - var node = this.startNode(); - this.next(); - if (this.type == _tokentype.types.semi || this.canInsertSemicolon() || this.type != _tokentype.types.star && !this.type.startsExpr) { - node.delegate = false; - node.argument = null; - } else { - node.delegate = this.eat(_tokentype.types.star); - node.argument = this.parseMaybeAssign(); - } - return this.finishNode(node, "YieldExpression"); -}; - -// Parses array and generator comprehensions. - -pp.parseComprehension = function (node, isGenerator) { - node.blocks = []; - while (this.type === _tokentype.types._for) { - var block = this.startNode(); - this.next(); - this.expect(_tokentype.types.parenL); - block.left = this.parseBindingAtom(); - this.checkLVal(block.left, true); - this.expectContextual("of"); - block.right = this.parseExpression(); - this.expect(_tokentype.types.parenR); - node.blocks.push(this.finishNode(block, "ComprehensionBlock")); - } - node.filter = this.eat(_tokentype.types._if) ? this.parseParenExpression() : null; - node.body = this.parseExpression(); - this.expect(isGenerator ? _tokentype.types.parenR : _tokentype.types.bracketR); - node.generator = isGenerator; - return this.finishNode(node, "ComprehensionExpression"); -}; - -},{"./state":10,"./tokentype":14}],2:[function(_dereq_,module,exports){ -// This is a trick taken from Esprima. It turns out that, on -// non-Chrome browsers, to check whether a string is in a set, a -// predicate containing a big ugly `switch` statement is faster than -// a regular expression, and on Chrome the two are about on par. -// This function uses `eval` (non-lexical) to produce such a -// predicate from a space-separated string of words. -// -// It starts by sorting the words by length. - -// Reserved word lists for various dialects of the language - -"use strict"; - -exports.__esModule = true; -exports.isIdentifierStart = isIdentifierStart; -exports.isIdentifierChar = isIdentifierChar; -var reservedWords = { - 3: "abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile", - 5: "class enum extends super const export import", - 6: "enum", - strict: "implements interface let package private protected public static yield", - strictBind: "eval arguments" -}; - -exports.reservedWords = reservedWords; -// And the keywords - -var ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"; - -var keywords = { - 5: ecma5AndLessKeywords, - 6: ecma5AndLessKeywords + " let const class extends export import yield super" -}; - -exports.keywords = keywords; -// ## Character categories - -// Big ugly regular expressions that match characters in the -// whitespace, identifier, and identifier-start categories. These -// are only applied when a character is found to actually have a -// code point above 128. -// Generated by `bin/generate-identifier-regex.js`. - -var nonASCIIidentifierStartChars = "ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢲऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕ℘-ℝℤΩℨK-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞ々-〇〡-〩〱-〵〸-〼ぁ-ゖ゛-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞭꞰꞱꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭟꭤꭥꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ"; -var nonASCIIidentifierChars = "‌‍·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-٩ٰۖ-ۜ۟-۪ۤۧۨ-ۭ۰-۹ܑܰ-݊ަ-ް߀-߉߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣤ-ःऺ-़ा-ॏ॑-ॗॢॣ०-९ঁ-ঃ়া-ৄেৈো-্ৗৢৣ০-৯ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑ੦-ੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣ૦-૯ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣ୦-୯ஂா-ூெ-ைொ-்ௗ௦-௯ఀ-ఃా-ౄె-ైొ-్ౕౖౢౣ౦-౯ಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣ೦-೯ഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣ൦-൯ංඃ්ා-ුූෘ-ෟ෦-෯ෲෳัิ-ฺ็-๎๐-๙ັິ-ູົຼ່-ໍ໐-໙༘༙༠-༩༹༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှ၀-၉ၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏ-ႝ፝-፟፩-፱ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝០-៩᠋-᠍᠐-᠙ᢩᤠ-ᤫᤰ-᤻᥆-᥏ᦰ-ᧀᧈᧉ᧐-᧚ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼-᪉᪐-᪙᪰-᪽ᬀ-ᬄ᬴-᭄᭐-᭙᭫-᭳ᮀ-ᮂᮡ-ᮭ᮰-᮹᯦-᯳ᰤ-᰷᱀-᱉᱐-᱙᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷼-᷿‿⁀⁔⃐-⃥⃜⃡-⃰⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꘠-꘩꙯ꙴ-꙽ꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-꣄꣐-꣙꣠-꣱꤀-꤉ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀꧐-꧙ꧥ꧰-꧹ꨩ-ꨶꩃꩌꩍ꩐-꩙ꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭꯰-꯹ﬞ︀-️︠-︭︳︴﹍-﹏0-9_"; - -var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); -var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); - -nonASCIIidentifierStartChars = nonASCIIidentifierChars = null; - -// These are a run-length and offset encoded representation of the -// >0xffff code points that are a valid part of identifiers. The -// offset starts at 0x10000, and each pair of numbers represents an -// offset to the next range, and then a size of the range. They were -// generated by tools/generate-identifier-regex.js -var astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 17, 26, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 99, 39, 9, 51, 157, 310, 10, 21, 11, 7, 153, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 98, 21, 11, 25, 71, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 26, 45, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 955, 52, 76, 44, 33, 24, 27, 35, 42, 34, 4, 0, 13, 47, 15, 3, 22, 0, 38, 17, 2, 24, 133, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 32, 4, 287, 47, 21, 1, 2, 0, 185, 46, 82, 47, 21, 0, 60, 42, 502, 63, 32, 0, 449, 56, 1288, 920, 104, 110, 2962, 1070, 13266, 568, 8, 30, 114, 29, 19, 47, 17, 3, 32, 20, 6, 18, 881, 68, 12, 0, 67, 12, 16481, 1, 3071, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 4149, 196, 1340, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42710, 42, 4148, 12, 221, 16355, 541]; -var astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 1306, 2, 54, 14, 32, 9, 16, 3, 46, 10, 54, 9, 7, 2, 37, 13, 2, 9, 52, 0, 13, 2, 49, 13, 16, 9, 83, 11, 168, 11, 6, 9, 8, 2, 57, 0, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 316, 19, 13, 9, 214, 6, 3, 8, 112, 16, 16, 9, 82, 12, 9, 9, 535, 9, 20855, 9, 135, 4, 60, 6, 26, 9, 1016, 45, 17, 3, 19723, 1, 5319, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 4305, 6, 792618, 239]; - -// This has a complexity linear to the value of the code. The -// assumption is that looking up astral identifier characters is -// rare. -function isInAstralSet(code, set) { - var pos = 0x10000; - for (var i = 0; i < set.length; i += 2) { - pos += set[i]; - if (pos > code) return false; - pos += set[i + 1]; - if (pos >= code) return true; - } -} - -// Test whether a given character code starts an identifier. - -function isIdentifierStart(code, astral) { - if (code < 65) return code === 36; - if (code < 91) return true; - if (code < 97) return code === 95; - if (code < 123) return true; - if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); - if (astral === false) return false; - return isInAstralSet(code, astralIdentifierStartCodes); -} - -// Test whether a given character is part of an identifier. - -function isIdentifierChar(code, astral) { - if (code < 48) return code === 36; - if (code < 58) return true; - if (code < 65) return false; - if (code < 91) return true; - if (code < 97) return code === 95; - if (code < 123) return true; - if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); - if (astral === false) return false; - return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes); -} - -},{}],3:[function(_dereq_,module,exports){ -// Acorn is a tiny, fast JavaScript parser written in JavaScript. -// -// Acorn was written by Marijn Haverbeke, Ingvar Stepanyan, and -// various contributors and released under an MIT license. -// -// Git repositories for Acorn are available at -// -// http://marijnhaverbeke.nl/git/acorn -// https://github.com/ternjs/acorn.git -// -// Please use the [github bug tracker][ghbt] to report issues. -// -// [ghbt]: https://github.com/ternjs/acorn/issues -// -// This file defines the main parser interface. The library also comes -// with a [error-tolerant parser][dammit] and an -// [abstract syntax tree walker][walk], defined in other files. -// -// [dammit]: acorn_loose.js -// [walk]: util/walk.js - -"use strict"; - -exports.__esModule = true; -exports.parse = parse; -exports.parseExpressionAt = parseExpressionAt; -exports.tokenizer = tokenizer; - -var _state = _dereq_("./state"); - -_dereq_("./parseutil"); - -_dereq_("./statement"); - -_dereq_("./lval"); - -_dereq_("./expression"); - -_dereq_("./location"); - -exports.Parser = _state.Parser; -exports.plugins = _state.plugins; - -var _options = _dereq_("./options"); - -exports.defaultOptions = _options.defaultOptions; - -var _locutil = _dereq_("./locutil"); - -exports.Position = _locutil.Position; -exports.SourceLocation = _locutil.SourceLocation; -exports.getLineInfo = _locutil.getLineInfo; - -var _node = _dereq_("./node"); - -exports.Node = _node.Node; - -var _tokentype = _dereq_("./tokentype"); - -exports.TokenType = _tokentype.TokenType; -exports.tokTypes = _tokentype.types; - -var _tokencontext = _dereq_("./tokencontext"); - -exports.TokContext = _tokencontext.TokContext; -exports.tokContexts = _tokencontext.types; - -var _identifier = _dereq_("./identifier"); - -exports.isIdentifierChar = _identifier.isIdentifierChar; -exports.isIdentifierStart = _identifier.isIdentifierStart; - -var _tokenize = _dereq_("./tokenize"); - -exports.Token = _tokenize.Token; - -var _whitespace = _dereq_("./whitespace"); - -exports.isNewLine = _whitespace.isNewLine; -exports.lineBreak = _whitespace.lineBreak; -exports.lineBreakG = _whitespace.lineBreakG; -var version = "2.7.0"; - -exports.version = version; -// The main exported interface (under `self.acorn` when in the -// browser) is a `parse` function that takes a code string and -// returns an abstract syntax tree as specified by [Mozilla parser -// API][api]. -// -// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API - -function parse(input, options) { - return new _state.Parser(options, input).parse(); -} - -// This function tries to parse a single expression at a given -// offset in a string. Useful for parsing mixed-language formats -// that embed JavaScript expressions. - -function parseExpressionAt(input, pos, options) { - var p = new _state.Parser(options, input, pos); - p.nextToken(); - return p.parseExpression(); -} - -// Acorn is organized as a tokenizer and a recursive-descent parser. -// The `tokenizer` export provides an interface to the tokenizer. - -function tokenizer(input, options) { - return new _state.Parser(options, input); -} - -},{"./expression":1,"./identifier":2,"./location":4,"./locutil":5,"./lval":6,"./node":7,"./options":8,"./parseutil":9,"./state":10,"./statement":11,"./tokencontext":12,"./tokenize":13,"./tokentype":14,"./whitespace":16}],4:[function(_dereq_,module,exports){ -"use strict"; - -var _state = _dereq_("./state"); - -var _locutil = _dereq_("./locutil"); - -var pp = _state.Parser.prototype; - -// This function is used to raise exceptions on parse errors. It -// takes an offset integer (into the current `input`) to indicate -// the location of the error, attaches the position to the end -// of the error message, and then raises a `SyntaxError` with that -// message. - -pp.raise = function (pos, message) { - var loc = _locutil.getLineInfo(this.input, pos); - message += " (" + loc.line + ":" + loc.column + ")"; - var err = new SyntaxError(message); - err.pos = pos;err.loc = loc;err.raisedAt = this.pos; - throw err; -}; - -pp.curPosition = function () { - if (this.options.locations) { - return new _locutil.Position(this.curLine, this.pos - this.lineStart); - } -}; - -},{"./locutil":5,"./state":10}],5:[function(_dereq_,module,exports){ -"use strict"; - -exports.__esModule = true; -exports.getLineInfo = getLineInfo; - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var _whitespace = _dereq_("./whitespace"); - -// These are used when `options.locations` is on, for the -// `startLoc` and `endLoc` properties. - -var Position = (function () { - function Position(line, col) { - _classCallCheck(this, Position); - - this.line = line; - this.column = col; - } - - Position.prototype.offset = function offset(n) { - return new Position(this.line, this.column + n); - }; - - return Position; -})(); - -exports.Position = Position; - -var SourceLocation = function SourceLocation(p, start, end) { - _classCallCheck(this, SourceLocation); - - this.start = start; - this.end = end; - if (p.sourceFile !== null) this.source = p.sourceFile; -} - -// The `getLineInfo` function is mostly useful when the -// `locations` option is off (for performance reasons) and you -// want to find the line/column position for a given character -// offset. `input` should be the code string that the offset refers -// into. - -; - -exports.SourceLocation = SourceLocation; - -function getLineInfo(input, offset) { - for (var line = 1, cur = 0;;) { - _whitespace.lineBreakG.lastIndex = cur; - var match = _whitespace.lineBreakG.exec(input); - if (match && match.index < offset) { - ++line; - cur = match.index + match[0].length; - } else { - return new Position(line, offset - cur); - } - } -} - -},{"./whitespace":16}],6:[function(_dereq_,module,exports){ -"use strict"; - -var _tokentype = _dereq_("./tokentype"); - -var _state = _dereq_("./state"); - -var _util = _dereq_("./util"); - -var pp = _state.Parser.prototype; - -// Convert existing expression atom to assignable pattern -// if possible. - -pp.toAssignable = function (node, isBinding) { - if (this.options.ecmaVersion >= 6 && node) { - switch (node.type) { - case "Identifier": - case "ObjectPattern": - case "ArrayPattern": - break; - - case "ObjectExpression": - node.type = "ObjectPattern"; - for (var i = 0; i < node.properties.length; i++) { - var prop = node.properties[i]; - if (prop.kind !== "init") this.raise(prop.key.start, "Object pattern can't contain getter or setter"); - this.toAssignable(prop.value, isBinding); - } - break; - - case "ArrayExpression": - node.type = "ArrayPattern"; - this.toAssignableList(node.elements, isBinding); - break; - - case "AssignmentExpression": - if (node.operator === "=") { - node.type = "AssignmentPattern"; - delete node.operator; - // falls through to AssignmentPattern - } else { - this.raise(node.left.end, "Only '=' operator can be used for specifying default value."); - break; - } - - case "AssignmentPattern": - if (node.right.type === "YieldExpression") this.raise(node.right.start, "Yield expression cannot be a default value"); - break; - - case "ParenthesizedExpression": - node.expression = this.toAssignable(node.expression, isBinding); - break; - - case "MemberExpression": - if (!isBinding) break; - - default: - this.raise(node.start, "Assigning to rvalue"); - } - } - return node; -}; - -// Convert list of expression atoms to binding list. - -pp.toAssignableList = function (exprList, isBinding) { - var end = exprList.length; - if (end) { - var last = exprList[end - 1]; - if (last && last.type == "RestElement") { - --end; - } else if (last && last.type == "SpreadElement") { - last.type = "RestElement"; - var arg = last.argument; - this.toAssignable(arg, isBinding); - if (arg.type !== "Identifier" && arg.type !== "MemberExpression" && arg.type !== "ArrayPattern") this.unexpected(arg.start); - --end; - } - - if (isBinding && last.type === "RestElement" && last.argument.type !== "Identifier") this.unexpected(last.argument.start); - } - for (var i = 0; i < end; i++) { - var elt = exprList[i]; - if (elt) this.toAssignable(elt, isBinding); - } - return exprList; -}; - -// Parses spread element. - -pp.parseSpread = function (refDestructuringErrors) { - var node = this.startNode(); - this.next(); - node.argument = this.parseMaybeAssign(refDestructuringErrors); - return this.finishNode(node, "SpreadElement"); -}; - -pp.parseRest = function (allowNonIdent) { - var node = this.startNode(); - this.next(); - - // RestElement inside of a function parameter must be an identifier - if (allowNonIdent) node.argument = this.type === _tokentype.types.name ? this.parseIdent() : this.unexpected();else node.argument = this.type === _tokentype.types.name || this.type === _tokentype.types.bracketL ? this.parseBindingAtom() : this.unexpected(); - - return this.finishNode(node, "RestElement"); -}; - -// Parses lvalue (assignable) atom. - -pp.parseBindingAtom = function () { - if (this.options.ecmaVersion < 6) return this.parseIdent(); - switch (this.type) { - case _tokentype.types.name: - return this.parseIdent(); - - case _tokentype.types.bracketL: - var node = this.startNode(); - this.next(); - node.elements = this.parseBindingList(_tokentype.types.bracketR, true, true); - return this.finishNode(node, "ArrayPattern"); - - case _tokentype.types.braceL: - return this.parseObj(true); - - default: - this.unexpected(); - } -}; - -pp.parseBindingList = function (close, allowEmpty, allowTrailingComma, allowNonIdent) { - var elts = [], - first = true; - while (!this.eat(close)) { - if (first) first = false;else this.expect(_tokentype.types.comma); - if (allowEmpty && this.type === _tokentype.types.comma) { - elts.push(null); - } else if (allowTrailingComma && this.afterTrailingComma(close)) { - break; - } else if (this.type === _tokentype.types.ellipsis) { - var rest = this.parseRest(allowNonIdent); - this.parseBindingListItem(rest); - elts.push(rest); - this.expect(close); - break; - } else { - var elem = this.parseMaybeDefault(this.start, this.startLoc); - this.parseBindingListItem(elem); - elts.push(elem); - } - } - return elts; -}; - -pp.parseBindingListItem = function (param) { - return param; -}; - -// Parses assignment pattern around given atom if possible. - -pp.parseMaybeDefault = function (startPos, startLoc, left) { - left = left || this.parseBindingAtom(); - if (this.options.ecmaVersion < 6 || !this.eat(_tokentype.types.eq)) return left; - var node = this.startNodeAt(startPos, startLoc); - node.left = left; - node.right = this.parseMaybeAssign(); - return this.finishNode(node, "AssignmentPattern"); -}; - -// Verify that a node is an lval — something that can be assigned -// to. - -pp.checkLVal = function (expr, isBinding, checkClashes) { - switch (expr.type) { - case "Identifier": - if (this.strict && this.reservedWordsStrictBind.test(expr.name)) this.raise(expr.start, (isBinding ? "Binding " : "Assigning to ") + expr.name + " in strict mode"); - if (checkClashes) { - if (_util.has(checkClashes, expr.name)) this.raise(expr.start, "Argument name clash"); - checkClashes[expr.name] = true; - } - break; - - case "MemberExpression": - if (isBinding) this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " member expression"); - break; - - case "ObjectPattern": - for (var i = 0; i < expr.properties.length; i++) { - this.checkLVal(expr.properties[i].value, isBinding, checkClashes); - }break; - - case "ArrayPattern": - for (var i = 0; i < expr.elements.length; i++) { - var elem = expr.elements[i]; - if (elem) this.checkLVal(elem, isBinding, checkClashes); - } - break; - - case "AssignmentPattern": - this.checkLVal(expr.left, isBinding, checkClashes); - break; - - case "RestElement": - this.checkLVal(expr.argument, isBinding, checkClashes); - break; - - case "ParenthesizedExpression": - this.checkLVal(expr.expression, isBinding, checkClashes); - break; - - default: - this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " rvalue"); - } -}; - -},{"./state":10,"./tokentype":14,"./util":15}],7:[function(_dereq_,module,exports){ -"use strict"; - -exports.__esModule = true; - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var _state = _dereq_("./state"); - -var _locutil = _dereq_("./locutil"); - -var Node = function Node(parser, pos, loc) { - _classCallCheck(this, Node); - - this.type = ""; - this.start = pos; - this.end = 0; - if (parser.options.locations) this.loc = new _locutil.SourceLocation(parser, loc); - if (parser.options.directSourceFile) this.sourceFile = parser.options.directSourceFile; - if (parser.options.ranges) this.range = [pos, 0]; -} - -// Start an AST node, attaching a start offset. - -; - -exports.Node = Node; -var pp = _state.Parser.prototype; - -pp.startNode = function () { - return new Node(this, this.start, this.startLoc); -}; - -pp.startNodeAt = function (pos, loc) { - return new Node(this, pos, loc); -}; - -// Finish an AST node, adding `type` and `end` properties. - -function finishNodeAt(node, type, pos, loc) { - node.type = type; - node.end = pos; - if (this.options.locations) node.loc.end = loc; - if (this.options.ranges) node.range[1] = pos; - return node; -} - -pp.finishNode = function (node, type) { - return finishNodeAt.call(this, node, type, this.lastTokEnd, this.lastTokEndLoc); -}; - -// Finish node at given position - -pp.finishNodeAt = function (node, type, pos, loc) { - return finishNodeAt.call(this, node, type, pos, loc); -}; - -},{"./locutil":5,"./state":10}],8:[function(_dereq_,module,exports){ -"use strict"; - -exports.__esModule = true; -exports.getOptions = getOptions; - -var _util = _dereq_("./util"); - -var _locutil = _dereq_("./locutil"); - -// A second optional argument can be given to further configure -// the parser process. These options are recognized: - -var defaultOptions = { - // `ecmaVersion` indicates the ECMAScript version to parse. Must - // be either 3, or 5, or 6. This influences support for strict - // mode, the set of reserved words, support for getters and - // setters and other features. - ecmaVersion: 5, - // Source type ("script" or "module") for different semantics - sourceType: "script", - // `onInsertedSemicolon` can be a callback that will be called - // when a semicolon is automatically inserted. It will be passed - // th position of the comma as an offset, and if `locations` is - // enabled, it is given the location as a `{line, column}` object - // as second argument. - onInsertedSemicolon: null, - // `onTrailingComma` is similar to `onInsertedSemicolon`, but for - // trailing commas. - onTrailingComma: null, - // By default, reserved words are only enforced if ecmaVersion >= 5. - // Set `allowReserved` to a boolean value to explicitly turn this on - // an off. When this option has the value "never", reserved words - // and keywords can also not be used as property names. - allowReserved: null, - // When enabled, a return at the top level is not considered an - // error. - allowReturnOutsideFunction: false, - // When enabled, import/export statements are not constrained to - // appearing at the top of the program. - allowImportExportEverywhere: false, - // When enabled, hashbang directive in the beginning of file - // is allowed and treated as a line comment. - allowHashBang: false, - // When `locations` is on, `loc` properties holding objects with - // `start` and `end` properties in `{line, column}` form (with - // line being 1-based and column 0-based) will be attached to the - // nodes. - locations: false, - // A function can be passed as `onToken` option, which will - // cause Acorn to call that function with object in the same - // format as tokens returned from `tokenizer().getToken()`. Note - // that you are not allowed to call the parser from the - // callback—that will corrupt its internal state. - onToken: null, - // A function can be passed as `onComment` option, which will - // cause Acorn to call that function with `(block, text, start, - // end)` parameters whenever a comment is skipped. `block` is a - // boolean indicating whether this is a block (`/* */`) comment, - // `text` is the content of the comment, and `start` and `end` are - // character offsets that denote the start and end of the comment. - // When the `locations` option is on, two more parameters are - // passed, the full `{line, column}` locations of the start and - // end of the comments. Note that you are not allowed to call the - // parser from the callback—that will corrupt its internal state. - onComment: null, - // Nodes have their start and end characters offsets recorded in - // `start` and `end` properties (directly on the node, rather than - // the `loc` object, which holds line/column data. To also add a - // [semi-standardized][range] `range` property holding a `[start, - // end]` array with the same numbers, set the `ranges` option to - // `true`. - // - // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 - ranges: false, - // It is possible to parse multiple files into a single AST by - // passing the tree produced by parsing the first file as - // `program` option in subsequent parses. This will add the - // toplevel forms of the parsed file to the `Program` (top) node - // of an existing parse tree. - program: null, - // When `locations` is on, you can pass this to record the source - // file in every node's `loc` object. - sourceFile: null, - // This value, if given, is stored in every node, whether - // `locations` is on or off. - directSourceFile: null, - // When enabled, parenthesized expressions are represented by - // (non-standard) ParenthesizedExpression nodes - preserveParens: false, - plugins: {} -}; - -exports.defaultOptions = defaultOptions; -// Interpret and default an options object - -function getOptions(opts) { - var options = {}; - for (var opt in defaultOptions) { - options[opt] = opts && _util.has(opts, opt) ? opts[opt] : defaultOptions[opt]; - }if (options.allowReserved == null) options.allowReserved = options.ecmaVersion < 5; - - if (_util.isArray(options.onToken)) { - (function () { - var tokens = options.onToken; - options.onToken = function (token) { - return tokens.push(token); - }; - })(); - } - if (_util.isArray(options.onComment)) options.onComment = pushComment(options, options.onComment); - - return options; -} - -function pushComment(options, array) { - return function (block, text, start, end, startLoc, endLoc) { - var comment = { - type: block ? 'Block' : 'Line', - value: text, - start: start, - end: end - }; - if (options.locations) comment.loc = new _locutil.SourceLocation(this, startLoc, endLoc); - if (options.ranges) comment.range = [start, end]; - array.push(comment); - }; -} - -},{"./locutil":5,"./util":15}],9:[function(_dereq_,module,exports){ -"use strict"; - -var _tokentype = _dereq_("./tokentype"); - -var _state = _dereq_("./state"); - -var _whitespace = _dereq_("./whitespace"); - -var pp = _state.Parser.prototype; - -// ## Parser utilities - -// Test whether a statement node is the string literal `"use strict"`. - -pp.isUseStrict = function (stmt) { - return this.options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" && stmt.expression.type === "Literal" && stmt.expression.raw.slice(1, -1) === "use strict"; -}; - -// Predicate that tests whether the next token is of the given -// type, and if yes, consumes it as a side effect. - -pp.eat = function (type) { - if (this.type === type) { - this.next(); - return true; - } else { - return false; - } -}; - -// Tests whether parsed token is a contextual keyword. - -pp.isContextual = function (name) { - return this.type === _tokentype.types.name && this.value === name; -}; - -// Consumes contextual keyword if possible. - -pp.eatContextual = function (name) { - return this.value === name && this.eat(_tokentype.types.name); -}; - -// Asserts that following token is given contextual keyword. - -pp.expectContextual = function (name) { - if (!this.eatContextual(name)) this.unexpected(); -}; - -// Test whether a semicolon can be inserted at the current position. - -pp.canInsertSemicolon = function () { - return this.type === _tokentype.types.eof || this.type === _tokentype.types.braceR || _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start)); -}; - -pp.insertSemicolon = function () { - if (this.canInsertSemicolon()) { - if (this.options.onInsertedSemicolon) this.options.onInsertedSemicolon(this.lastTokEnd, this.lastTokEndLoc); - return true; - } -}; - -// Consume a semicolon, or, failing that, see if we are allowed to -// pretend that there is a semicolon at this position. - -pp.semicolon = function () { - if (!this.eat(_tokentype.types.semi) && !this.insertSemicolon()) this.unexpected(); -}; - -pp.afterTrailingComma = function (tokType) { - if (this.type == tokType) { - if (this.options.onTrailingComma) this.options.onTrailingComma(this.lastTokStart, this.lastTokStartLoc); - this.next(); - return true; - } -}; - -// Expect a token of a given type. If found, consume it, otherwise, -// raise an unexpected token error. - -pp.expect = function (type) { - this.eat(type) || this.unexpected(); -}; - -// Raise an unexpected token error. - -pp.unexpected = function (pos) { - this.raise(pos != null ? pos : this.start, "Unexpected token"); -}; - -pp.checkPatternErrors = function (refDestructuringErrors, andThrow) { - var pos = refDestructuringErrors && refDestructuringErrors.trailingComma; - if (!andThrow) return !!pos; - if (pos) this.raise(pos, "Trailing comma is not permitted in destructuring patterns"); -}; - -pp.checkExpressionErrors = function (refDestructuringErrors, andThrow) { - var pos = refDestructuringErrors && refDestructuringErrors.shorthandAssign; - if (!andThrow) return !!pos; - if (pos) this.raise(pos, "Shorthand property assignments are valid only in destructuring patterns"); -}; - -},{"./state":10,"./tokentype":14,"./whitespace":16}],10:[function(_dereq_,module,exports){ -"use strict"; - -exports.__esModule = true; - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var _identifier = _dereq_("./identifier"); - -var _tokentype = _dereq_("./tokentype"); - -var _whitespace = _dereq_("./whitespace"); - -var _options = _dereq_("./options"); - -// Registered plugins -var plugins = {}; - -exports.plugins = plugins; -function keywordRegexp(words) { - return new RegExp("^(" + words.replace(/ /g, "|") + ")$"); -} - -var Parser = (function () { - function Parser(options, input, startPos) { - _classCallCheck(this, Parser); - - this.options = options = _options.getOptions(options); - this.sourceFile = options.sourceFile; - this.keywords = keywordRegexp(_identifier.keywords[options.ecmaVersion >= 6 ? 6 : 5]); - var reserved = options.allowReserved ? "" : _identifier.reservedWords[options.ecmaVersion] + (options.sourceType == "module" ? " await" : ""); - this.reservedWords = keywordRegexp(reserved); - var reservedStrict = (reserved ? reserved + " " : "") + _identifier.reservedWords.strict; - this.reservedWordsStrict = keywordRegexp(reservedStrict); - this.reservedWordsStrictBind = keywordRegexp(reservedStrict + " " + _identifier.reservedWords.strictBind); - this.input = String(input); - - // Used to signal to callers of `readWord1` whether the word - // contained any escape sequences. This is needed because words with - // escape sequences must not be interpreted as keywords. - this.containsEsc = false; - - // Load plugins - this.loadPlugins(options.plugins); - - // Set up token state - - // The current position of the tokenizer in the input. - if (startPos) { - this.pos = startPos; - this.lineStart = Math.max(0, this.input.lastIndexOf("\n", startPos)); - this.curLine = this.input.slice(0, this.lineStart).split(_whitespace.lineBreak).length; - } else { - this.pos = this.lineStart = 0; - this.curLine = 1; - } - - // Properties of the current token: - // Its type - this.type = _tokentype.types.eof; - // For tokens that include more information than their type, the value - this.value = null; - // Its start and end offset - this.start = this.end = this.pos; - // And, if locations are used, the {line, column} object - // corresponding to those offsets - this.startLoc = this.endLoc = this.curPosition(); - - // Position information for the previous token - this.lastTokEndLoc = this.lastTokStartLoc = null; - this.lastTokStart = this.lastTokEnd = this.pos; - - // The context stack is used to superficially track syntactic - // context to predict whether a regular expression is allowed in a - // given position. - this.context = this.initialContext(); - this.exprAllowed = true; - - // Figure out if it's a module code. - this.strict = this.inModule = options.sourceType === "module"; - - // Used to signify the start of a potential arrow function - this.potentialArrowAt = -1; - - // Flags to track whether we are in a function, a generator. - this.inFunction = this.inGenerator = false; - // Labels in scope. - this.labels = []; - - // If enabled, skip leading hashbang line. - if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === '#!') this.skipLineComment(2); - } - - // DEPRECATED Kept for backwards compatibility until 3.0 in case a plugin uses them - - Parser.prototype.isKeyword = function isKeyword(word) { - return this.keywords.test(word); - }; - - Parser.prototype.isReservedWord = function isReservedWord(word) { - return this.reservedWords.test(word); - }; - - Parser.prototype.extend = function extend(name, f) { - this[name] = f(this[name]); - }; - - Parser.prototype.loadPlugins = function loadPlugins(pluginConfigs) { - for (var _name in pluginConfigs) { - var plugin = plugins[_name]; - if (!plugin) throw new Error("Plugin '" + _name + "' not found"); - plugin(this, pluginConfigs[_name]); - } - }; - - Parser.prototype.parse = function parse() { - var node = this.options.program || this.startNode(); - this.nextToken(); - return this.parseTopLevel(node); - }; - - return Parser; -})(); - -exports.Parser = Parser; - -},{"./identifier":2,"./options":8,"./tokentype":14,"./whitespace":16}],11:[function(_dereq_,module,exports){ -"use strict"; - -var _tokentype = _dereq_("./tokentype"); - -var _state = _dereq_("./state"); - -var _whitespace = _dereq_("./whitespace"); - -var pp = _state.Parser.prototype; - -// ### Statement parsing - -// Parse a program. Initializes the parser, reads any number of -// statements, and wraps them in a Program node. Optionally takes a -// `program` argument. If present, the statements will be appended -// to its body instead of creating a new node. - -pp.parseTopLevel = function (node) { - var first = true; - if (!node.body) node.body = []; - while (this.type !== _tokentype.types.eof) { - var stmt = this.parseStatement(true, true); - node.body.push(stmt); - if (first) { - if (this.isUseStrict(stmt)) this.setStrict(true); - first = false; - } - } - this.next(); - if (this.options.ecmaVersion >= 6) { - node.sourceType = this.options.sourceType; - } - return this.finishNode(node, "Program"); -}; - -var loopLabel = { kind: "loop" }, - switchLabel = { kind: "switch" }; - -// Parse a single statement. -// -// If expecting a statement and finding a slash operator, parse a -// regular expression literal. This is to handle cases like -// `if (foo) /blah/.exec(foo)`, where looking at the previous token -// does not help. - -pp.parseStatement = function (declaration, topLevel) { - var starttype = this.type, - node = this.startNode(); - - // Most types of statements are recognized by the keyword they - // start with. Many are trivial to parse, some require a bit of - // complexity. - - switch (starttype) { - case _tokentype.types._break:case _tokentype.types._continue: - return this.parseBreakContinueStatement(node, starttype.keyword); - case _tokentype.types._debugger: - return this.parseDebuggerStatement(node); - case _tokentype.types._do: - return this.parseDoStatement(node); - case _tokentype.types._for: - return this.parseForStatement(node); - case _tokentype.types._function: - if (!declaration && this.options.ecmaVersion >= 6) this.unexpected(); - return this.parseFunctionStatement(node); - case _tokentype.types._class: - if (!declaration) this.unexpected(); - return this.parseClass(node, true); - case _tokentype.types._if: - return this.parseIfStatement(node); - case _tokentype.types._return: - return this.parseReturnStatement(node); - case _tokentype.types._switch: - return this.parseSwitchStatement(node); - case _tokentype.types._throw: - return this.parseThrowStatement(node); - case _tokentype.types._try: - return this.parseTryStatement(node); - case _tokentype.types._let:case _tokentype.types._const: - if (!declaration) this.unexpected(); // NOTE: falls through to _var - case _tokentype.types._var: - return this.parseVarStatement(node, starttype); - case _tokentype.types._while: - return this.parseWhileStatement(node); - case _tokentype.types._with: - return this.parseWithStatement(node); - case _tokentype.types.braceL: - return this.parseBlock(); - case _tokentype.types.semi: - return this.parseEmptyStatement(node); - case _tokentype.types._export: - case _tokentype.types._import: - if (!this.options.allowImportExportEverywhere) { - if (!topLevel) this.raise(this.start, "'import' and 'export' may only appear at the top level"); - if (!this.inModule) this.raise(this.start, "'import' and 'export' may appear only with 'sourceType: module'"); - } - return starttype === _tokentype.types._import ? this.parseImport(node) : this.parseExport(node); - - // If the statement does not start with a statement keyword or a - // brace, it's an ExpressionStatement or LabeledStatement. We - // simply start parsing an expression, and afterwards, if the - // next token is a colon and the expression was a simple - // Identifier node, we switch to interpreting it as a label. - default: - var maybeName = this.value, - expr = this.parseExpression(); - if (starttype === _tokentype.types.name && expr.type === "Identifier" && this.eat(_tokentype.types.colon)) return this.parseLabeledStatement(node, maybeName, expr);else return this.parseExpressionStatement(node, expr); - } -}; - -pp.parseBreakContinueStatement = function (node, keyword) { - var isBreak = keyword == "break"; - this.next(); - if (this.eat(_tokentype.types.semi) || this.insertSemicolon()) node.label = null;else if (this.type !== _tokentype.types.name) this.unexpected();else { - node.label = this.parseIdent(); - this.semicolon(); - } - - // Verify that there is an actual destination to break or - // continue to. - for (var i = 0; i < this.labels.length; ++i) { - var lab = this.labels[i]; - if (node.label == null || lab.name === node.label.name) { - if (lab.kind != null && (isBreak || lab.kind === "loop")) break; - if (node.label && isBreak) break; - } - } - if (i === this.labels.length) this.raise(node.start, "Unsyntactic " + keyword); - return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); -}; - -pp.parseDebuggerStatement = function (node) { - this.next(); - this.semicolon(); - return this.finishNode(node, "DebuggerStatement"); -}; - -pp.parseDoStatement = function (node) { - this.next(); - this.labels.push(loopLabel); - node.body = this.parseStatement(false); - this.labels.pop(); - this.expect(_tokentype.types._while); - node.test = this.parseParenExpression(); - if (this.options.ecmaVersion >= 6) this.eat(_tokentype.types.semi);else this.semicolon(); - return this.finishNode(node, "DoWhileStatement"); -}; - -// Disambiguating between a `for` and a `for`/`in` or `for`/`of` -// loop is non-trivial. Basically, we have to parse the init `var` -// statement or expression, disallowing the `in` operator (see -// the second parameter to `parseExpression`), and then check -// whether the next token is `in` or `of`. When there is no init -// part (semicolon immediately after the opening parenthesis), it -// is a regular `for` loop. - -pp.parseForStatement = function (node) { - this.next(); - this.labels.push(loopLabel); - this.expect(_tokentype.types.parenL); - if (this.type === _tokentype.types.semi) return this.parseFor(node, null); - if (this.type === _tokentype.types._var || this.type === _tokentype.types._let || this.type === _tokentype.types._const) { - var _init = this.startNode(), - varKind = this.type; - this.next(); - this.parseVar(_init, true, varKind); - this.finishNode(_init, "VariableDeclaration"); - if ((this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of")) && _init.declarations.length === 1 && !(varKind !== _tokentype.types._var && _init.declarations[0].init)) return this.parseForIn(node, _init); - return this.parseFor(node, _init); - } - var refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 }; - var init = this.parseExpression(true, refDestructuringErrors); - if (this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of")) { - this.checkPatternErrors(refDestructuringErrors, true); - this.toAssignable(init); - this.checkLVal(init); - return this.parseForIn(node, init); - } else { - this.checkExpressionErrors(refDestructuringErrors, true); - } - return this.parseFor(node, init); -}; - -pp.parseFunctionStatement = function (node) { - this.next(); - return this.parseFunction(node, true); -}; - -pp.parseIfStatement = function (node) { - this.next(); - node.test = this.parseParenExpression(); - node.consequent = this.parseStatement(false); - node.alternate = this.eat(_tokentype.types._else) ? this.parseStatement(false) : null; - return this.finishNode(node, "IfStatement"); -}; - -pp.parseReturnStatement = function (node) { - if (!this.inFunction && !this.options.allowReturnOutsideFunction) this.raise(this.start, "'return' outside of function"); - this.next(); - - // In `return` (and `break`/`continue`), the keywords with - // optional arguments, we eagerly look for a semicolon or the - // possibility to insert one. - - if (this.eat(_tokentype.types.semi) || this.insertSemicolon()) node.argument = null;else { - node.argument = this.parseExpression();this.semicolon(); - } - return this.finishNode(node, "ReturnStatement"); -}; - -pp.parseSwitchStatement = function (node) { - this.next(); - node.discriminant = this.parseParenExpression(); - node.cases = []; - this.expect(_tokentype.types.braceL); - this.labels.push(switchLabel); - - // Statements under must be grouped (by label) in SwitchCase - // nodes. `cur` is used to keep the node that we are currently - // adding statements to. - - for (var cur, sawDefault = false; this.type != _tokentype.types.braceR;) { - if (this.type === _tokentype.types._case || this.type === _tokentype.types._default) { - var isCase = this.type === _tokentype.types._case; - if (cur) this.finishNode(cur, "SwitchCase"); - node.cases.push(cur = this.startNode()); - cur.consequent = []; - this.next(); - if (isCase) { - cur.test = this.parseExpression(); - } else { - if (sawDefault) this.raise(this.lastTokStart, "Multiple default clauses"); - sawDefault = true; - cur.test = null; - } - this.expect(_tokentype.types.colon); - } else { - if (!cur) this.unexpected(); - cur.consequent.push(this.parseStatement(true)); - } - } - if (cur) this.finishNode(cur, "SwitchCase"); - this.next(); // Closing brace - this.labels.pop(); - return this.finishNode(node, "SwitchStatement"); -}; - -pp.parseThrowStatement = function (node) { - this.next(); - if (_whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start))) this.raise(this.lastTokEnd, "Illegal newline after throw"); - node.argument = this.parseExpression(); - this.semicolon(); - return this.finishNode(node, "ThrowStatement"); -}; - -// Reused empty array added for node fields that are always empty. - -var empty = []; - -pp.parseTryStatement = function (node) { - this.next(); - node.block = this.parseBlock(); - node.handler = null; - if (this.type === _tokentype.types._catch) { - var clause = this.startNode(); - this.next(); - this.expect(_tokentype.types.parenL); - clause.param = this.parseBindingAtom(); - this.checkLVal(clause.param, true); - this.expect(_tokentype.types.parenR); - clause.body = this.parseBlock(); - node.handler = this.finishNode(clause, "CatchClause"); - } - node.finalizer = this.eat(_tokentype.types._finally) ? this.parseBlock() : null; - if (!node.handler && !node.finalizer) this.raise(node.start, "Missing catch or finally clause"); - return this.finishNode(node, "TryStatement"); -}; - -pp.parseVarStatement = function (node, kind) { - this.next(); - this.parseVar(node, false, kind); - this.semicolon(); - return this.finishNode(node, "VariableDeclaration"); -}; - -pp.parseWhileStatement = function (node) { - this.next(); - node.test = this.parseParenExpression(); - this.labels.push(loopLabel); - node.body = this.parseStatement(false); - this.labels.pop(); - return this.finishNode(node, "WhileStatement"); -}; - -pp.parseWithStatement = function (node) { - if (this.strict) this.raise(this.start, "'with' in strict mode"); - this.next(); - node.object = this.parseParenExpression(); - node.body = this.parseStatement(false); - return this.finishNode(node, "WithStatement"); -}; - -pp.parseEmptyStatement = function (node) { - this.next(); - return this.finishNode(node, "EmptyStatement"); -}; - -pp.parseLabeledStatement = function (node, maybeName, expr) { - for (var i = 0; i < this.labels.length; ++i) { - if (this.labels[i].name === maybeName) this.raise(expr.start, "Label '" + maybeName + "' is already declared"); - }var kind = this.type.isLoop ? "loop" : this.type === _tokentype.types._switch ? "switch" : null; - for (var i = this.labels.length - 1; i >= 0; i--) { - var label = this.labels[i]; - if (label.statementStart == node.start) { - label.statementStart = this.start; - label.kind = kind; - } else break; - } - this.labels.push({ name: maybeName, kind: kind, statementStart: this.start }); - node.body = this.parseStatement(true); - this.labels.pop(); - node.label = expr; - return this.finishNode(node, "LabeledStatement"); -}; - -pp.parseExpressionStatement = function (node, expr) { - node.expression = expr; - this.semicolon(); - return this.finishNode(node, "ExpressionStatement"); -}; - -// Parse a semicolon-enclosed block of statements, handling `"use -// strict"` declarations when `allowStrict` is true (used for -// function bodies). - -pp.parseBlock = function (allowStrict) { - var node = this.startNode(), - first = true, - oldStrict = undefined; - node.body = []; - this.expect(_tokentype.types.braceL); - while (!this.eat(_tokentype.types.braceR)) { - var stmt = this.parseStatement(true); - node.body.push(stmt); - if (first && allowStrict && this.isUseStrict(stmt)) { - oldStrict = this.strict; - this.setStrict(this.strict = true); - } - first = false; - } - if (oldStrict === false) this.setStrict(false); - return this.finishNode(node, "BlockStatement"); -}; - -// Parse a regular `for` loop. The disambiguation code in -// `parseStatement` will already have parsed the init statement or -// expression. - -pp.parseFor = function (node, init) { - node.init = init; - this.expect(_tokentype.types.semi); - node.test = this.type === _tokentype.types.semi ? null : this.parseExpression(); - this.expect(_tokentype.types.semi); - node.update = this.type === _tokentype.types.parenR ? null : this.parseExpression(); - this.expect(_tokentype.types.parenR); - node.body = this.parseStatement(false); - this.labels.pop(); - return this.finishNode(node, "ForStatement"); -}; - -// Parse a `for`/`in` and `for`/`of` loop, which are almost -// same from parser's perspective. - -pp.parseForIn = function (node, init) { - var type = this.type === _tokentype.types._in ? "ForInStatement" : "ForOfStatement"; - this.next(); - node.left = init; - node.right = this.parseExpression(); - this.expect(_tokentype.types.parenR); - node.body = this.parseStatement(false); - this.labels.pop(); - return this.finishNode(node, type); -}; - -// Parse a list of variable declarations. - -pp.parseVar = function (node, isFor, kind) { - node.declarations = []; - node.kind = kind.keyword; - for (;;) { - var decl = this.startNode(); - this.parseVarId(decl); - if (this.eat(_tokentype.types.eq)) { - decl.init = this.parseMaybeAssign(isFor); - } else if (kind === _tokentype.types._const && !(this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of"))) { - this.unexpected(); - } else if (decl.id.type != "Identifier" && !(isFor && (this.type === _tokentype.types._in || this.isContextual("of")))) { - this.raise(this.lastTokEnd, "Complex binding patterns require an initialization value"); - } else { - decl.init = null; - } - node.declarations.push(this.finishNode(decl, "VariableDeclarator")); - if (!this.eat(_tokentype.types.comma)) break; - } - return node; -}; - -pp.parseVarId = function (decl) { - decl.id = this.parseBindingAtom(); - this.checkLVal(decl.id, true); -}; - -// Parse a function declaration or literal (depending on the -// `isStatement` parameter). - -pp.parseFunction = function (node, isStatement, allowExpressionBody) { - this.initFunction(node); - if (this.options.ecmaVersion >= 6) node.generator = this.eat(_tokentype.types.star); - if (isStatement || this.type === _tokentype.types.name) node.id = this.parseIdent(); - this.parseFunctionParams(node); - this.parseFunctionBody(node, allowExpressionBody); - return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); -}; - -pp.parseFunctionParams = function (node) { - this.expect(_tokentype.types.parenL); - node.params = this.parseBindingList(_tokentype.types.parenR, false, false, true); -}; - -// Parse a class declaration or literal (depending on the -// `isStatement` parameter). - -pp.parseClass = function (node, isStatement) { - this.next(); - this.parseClassId(node, isStatement); - this.parseClassSuper(node); - var classBody = this.startNode(); - var hadConstructor = false; - classBody.body = []; - this.expect(_tokentype.types.braceL); - while (!this.eat(_tokentype.types.braceR)) { - if (this.eat(_tokentype.types.semi)) continue; - var method = this.startNode(); - var isGenerator = this.eat(_tokentype.types.star); - var isMaybeStatic = this.type === _tokentype.types.name && this.value === "static"; - this.parsePropertyName(method); - method["static"] = isMaybeStatic && this.type !== _tokentype.types.parenL; - if (method["static"]) { - if (isGenerator) this.unexpected(); - isGenerator = this.eat(_tokentype.types.star); - this.parsePropertyName(method); - } - method.kind = "method"; - var isGetSet = false; - if (!method.computed) { - var key = method.key; - - if (!isGenerator && key.type === "Identifier" && this.type !== _tokentype.types.parenL && (key.name === "get" || key.name === "set")) { - isGetSet = true; - method.kind = key.name; - key = this.parsePropertyName(method); - } - if (!method["static"] && (key.type === "Identifier" && key.name === "constructor" || key.type === "Literal" && key.value === "constructor")) { - if (hadConstructor) this.raise(key.start, "Duplicate constructor in the same class"); - if (isGetSet) this.raise(key.start, "Constructor can't have get/set modifier"); - if (isGenerator) this.raise(key.start, "Constructor can't be a generator"); - method.kind = "constructor"; - hadConstructor = true; - } - } - this.parseClassMethod(classBody, method, isGenerator); - if (isGetSet) { - var paramCount = method.kind === "get" ? 0 : 1; - if (method.value.params.length !== paramCount) { - var start = method.value.start; - if (method.kind === "get") this.raise(start, "getter should have no params");else this.raise(start, "setter should have exactly one param"); - } - if (method.kind === "set" && method.value.params[0].type === "RestElement") this.raise(method.value.params[0].start, "Setter cannot use rest params"); - } - } - node.body = this.finishNode(classBody, "ClassBody"); - return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression"); -}; - -pp.parseClassMethod = function (classBody, method, isGenerator) { - method.value = this.parseMethod(isGenerator); - classBody.body.push(this.finishNode(method, "MethodDefinition")); -}; - -pp.parseClassId = function (node, isStatement) { - node.id = this.type === _tokentype.types.name ? this.parseIdent() : isStatement ? this.unexpected() : null; -}; - -pp.parseClassSuper = function (node) { - node.superClass = this.eat(_tokentype.types._extends) ? this.parseExprSubscripts() : null; -}; - -// Parses module export declaration. - -pp.parseExport = function (node) { - this.next(); - // export * from '...' - if (this.eat(_tokentype.types.star)) { - this.expectContextual("from"); - node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected(); - this.semicolon(); - return this.finishNode(node, "ExportAllDeclaration"); - } - if (this.eat(_tokentype.types._default)) { - // export default ... - var expr = this.parseMaybeAssign(); - var needsSemi = true; - if (expr.type == "FunctionExpression" || expr.type == "ClassExpression") { - needsSemi = false; - if (expr.id) { - expr.type = expr.type == "FunctionExpression" ? "FunctionDeclaration" : "ClassDeclaration"; - } - } - node.declaration = expr; - if (needsSemi) this.semicolon(); - return this.finishNode(node, "ExportDefaultDeclaration"); - } - // export var|const|let|function|class ... - if (this.shouldParseExportStatement()) { - node.declaration = this.parseStatement(true); - node.specifiers = []; - node.source = null; - } else { - // export { x, y as z } [from '...'] - node.declaration = null; - node.specifiers = this.parseExportSpecifiers(); - if (this.eatContextual("from")) { - node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected(); - } else { - // check for keywords used as local names - for (var i = 0; i < node.specifiers.length; i++) { - if (this.keywords.test(node.specifiers[i].local.name) || this.reservedWords.test(node.specifiers[i].local.name)) { - this.unexpected(node.specifiers[i].local.start); - } - } - - node.source = null; - } - this.semicolon(); - } - return this.finishNode(node, "ExportNamedDeclaration"); -}; - -pp.shouldParseExportStatement = function () { - return this.type.keyword; -}; - -// Parses a comma-separated list of module exports. - -pp.parseExportSpecifiers = function () { - var nodes = [], - first = true; - // export { x, y as z } [from '...'] - this.expect(_tokentype.types.braceL); - while (!this.eat(_tokentype.types.braceR)) { - if (!first) { - this.expect(_tokentype.types.comma); - if (this.afterTrailingComma(_tokentype.types.braceR)) break; - } else first = false; - - var node = this.startNode(); - node.local = this.parseIdent(this.type === _tokentype.types._default); - node.exported = this.eatContextual("as") ? this.parseIdent(true) : node.local; - nodes.push(this.finishNode(node, "ExportSpecifier")); - } - return nodes; -}; - -// Parses import declaration. - -pp.parseImport = function (node) { - this.next(); - // import '...' - if (this.type === _tokentype.types.string) { - node.specifiers = empty; - node.source = this.parseExprAtom(); - } else { - node.specifiers = this.parseImportSpecifiers(); - this.expectContextual("from"); - node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected(); - } - this.semicolon(); - return this.finishNode(node, "ImportDeclaration"); -}; - -// Parses a comma-separated list of module imports. - -pp.parseImportSpecifiers = function () { - var nodes = [], - first = true; - if (this.type === _tokentype.types.name) { - // import defaultObj, { x, y as z } from '...' - var node = this.startNode(); - node.local = this.parseIdent(); - this.checkLVal(node.local, true); - nodes.push(this.finishNode(node, "ImportDefaultSpecifier")); - if (!this.eat(_tokentype.types.comma)) return nodes; - } - if (this.type === _tokentype.types.star) { - var node = this.startNode(); - this.next(); - this.expectContextual("as"); - node.local = this.parseIdent(); - this.checkLVal(node.local, true); - nodes.push(this.finishNode(node, "ImportNamespaceSpecifier")); - return nodes; - } - this.expect(_tokentype.types.braceL); - while (!this.eat(_tokentype.types.braceR)) { - if (!first) { - this.expect(_tokentype.types.comma); - if (this.afterTrailingComma(_tokentype.types.braceR)) break; - } else first = false; - - var node = this.startNode(); - node.imported = this.parseIdent(true); - if (this.eatContextual("as")) { - node.local = this.parseIdent(); - } else { - node.local = node.imported; - if (this.isKeyword(node.local.name)) this.unexpected(node.local.start); - if (this.reservedWordsStrict.test(node.local.name)) this.raise(node.local.start, "The keyword '" + node.local.name + "' is reserved"); - } - this.checkLVal(node.local, true); - nodes.push(this.finishNode(node, "ImportSpecifier")); - } - return nodes; -}; - -},{"./state":10,"./tokentype":14,"./whitespace":16}],12:[function(_dereq_,module,exports){ -// The algorithm used to determine whether a regexp can appear at a -// given point in the program is loosely based on sweet.js' approach. -// See https://github.com/mozilla/sweet.js/wiki/design - -"use strict"; - -exports.__esModule = true; - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var _state = _dereq_("./state"); - -var _tokentype = _dereq_("./tokentype"); - -var _whitespace = _dereq_("./whitespace"); - -var TokContext = function TokContext(token, isExpr, preserveSpace, override) { - _classCallCheck(this, TokContext); - - this.token = token; - this.isExpr = !!isExpr; - this.preserveSpace = !!preserveSpace; - this.override = override; -}; - -exports.TokContext = TokContext; -var types = { - b_stat: new TokContext("{", false), - b_expr: new TokContext("{", true), - b_tmpl: new TokContext("${", true), - p_stat: new TokContext("(", false), - p_expr: new TokContext("(", true), - q_tmpl: new TokContext("`", true, true, function (p) { - return p.readTmplToken(); - }), - f_expr: new TokContext("function", true) -}; - -exports.types = types; -var pp = _state.Parser.prototype; - -pp.initialContext = function () { - return [types.b_stat]; -}; - -pp.braceIsBlock = function (prevType) { - if (prevType === _tokentype.types.colon) { - var _parent = this.curContext(); - if (_parent === types.b_stat || _parent === types.b_expr) return !_parent.isExpr; - } - if (prevType === _tokentype.types._return) return _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start)); - if (prevType === _tokentype.types._else || prevType === _tokentype.types.semi || prevType === _tokentype.types.eof || prevType === _tokentype.types.parenR) return true; - if (prevType == _tokentype.types.braceL) return this.curContext() === types.b_stat; - return !this.exprAllowed; -}; - -pp.updateContext = function (prevType) { - var update = undefined, - type = this.type; - if (type.keyword && prevType == _tokentype.types.dot) this.exprAllowed = false;else if (update = type.updateContext) update.call(this, prevType);else this.exprAllowed = type.beforeExpr; -}; - -// Token-specific context update code - -_tokentype.types.parenR.updateContext = _tokentype.types.braceR.updateContext = function () { - if (this.context.length == 1) { - this.exprAllowed = true; - return; - } - var out = this.context.pop(); - if (out === types.b_stat && this.curContext() === types.f_expr) { - this.context.pop(); - this.exprAllowed = false; - } else if (out === types.b_tmpl) { - this.exprAllowed = true; - } else { - this.exprAllowed = !out.isExpr; - } -}; - -_tokentype.types.braceL.updateContext = function (prevType) { - this.context.push(this.braceIsBlock(prevType) ? types.b_stat : types.b_expr); - this.exprAllowed = true; -}; - -_tokentype.types.dollarBraceL.updateContext = function () { - this.context.push(types.b_tmpl); - this.exprAllowed = true; -}; - -_tokentype.types.parenL.updateContext = function (prevType) { - var statementParens = prevType === _tokentype.types._if || prevType === _tokentype.types._for || prevType === _tokentype.types._with || prevType === _tokentype.types._while; - this.context.push(statementParens ? types.p_stat : types.p_expr); - this.exprAllowed = true; -}; - -_tokentype.types.incDec.updateContext = function () { - // tokExprAllowed stays unchanged -}; - -_tokentype.types._function.updateContext = function () { - if (this.curContext() !== types.b_stat) this.context.push(types.f_expr); - this.exprAllowed = false; -}; - -_tokentype.types.backQuote.updateContext = function () { - if (this.curContext() === types.q_tmpl) this.context.pop();else this.context.push(types.q_tmpl); - this.exprAllowed = false; -}; - -},{"./state":10,"./tokentype":14,"./whitespace":16}],13:[function(_dereq_,module,exports){ -"use strict"; - -exports.__esModule = true; - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var _identifier = _dereq_("./identifier"); - -var _tokentype = _dereq_("./tokentype"); - -var _state = _dereq_("./state"); - -var _locutil = _dereq_("./locutil"); - -var _whitespace = _dereq_("./whitespace"); - -// Object type used to represent tokens. Note that normally, tokens -// simply exist as properties on the parser object. This is only -// used for the onToken callback and the external tokenizer. - -var Token = function Token(p) { - _classCallCheck(this, Token); - - this.type = p.type; - this.value = p.value; - this.start = p.start; - this.end = p.end; - if (p.options.locations) this.loc = new _locutil.SourceLocation(p, p.startLoc, p.endLoc); - if (p.options.ranges) this.range = [p.start, p.end]; -} - -// ## Tokenizer - -; - -exports.Token = Token; -var pp = _state.Parser.prototype; - -// Are we running under Rhino? -var isRhino = typeof Packages == "object" && Object.prototype.toString.call(Packages) == "[object JavaPackage]"; - -// Move to the next token - -pp.next = function () { - if (this.options.onToken) this.options.onToken(new Token(this)); - - this.lastTokEnd = this.end; - this.lastTokStart = this.start; - this.lastTokEndLoc = this.endLoc; - this.lastTokStartLoc = this.startLoc; - this.nextToken(); -}; - -pp.getToken = function () { - this.next(); - return new Token(this); -}; - -// If we're in an ES6 environment, make parsers iterable -if (typeof Symbol !== "undefined") pp[Symbol.iterator] = function () { - var self = this; - return { next: function next() { - var token = self.getToken(); - return { - done: token.type === _tokentype.types.eof, - value: token - }; - } }; -}; - -// Toggle strict mode. Re-reads the next number or string to please -// pedantic tests (`"use strict"; 010;` should fail). - -pp.setStrict = function (strict) { - this.strict = strict; - if (this.type !== _tokentype.types.num && this.type !== _tokentype.types.string) return; - this.pos = this.start; - if (this.options.locations) { - while (this.pos < this.lineStart) { - this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1; - --this.curLine; - } - } - this.nextToken(); -}; - -pp.curContext = function () { - return this.context[this.context.length - 1]; -}; - -// Read a single token, updating the parser object's token-related -// properties. - -pp.nextToken = function () { - var curContext = this.curContext(); - if (!curContext || !curContext.preserveSpace) this.skipSpace(); - - this.start = this.pos; - if (this.options.locations) this.startLoc = this.curPosition(); - if (this.pos >= this.input.length) return this.finishToken(_tokentype.types.eof); - - if (curContext.override) return curContext.override(this);else this.readToken(this.fullCharCodeAtPos()); -}; - -pp.readToken = function (code) { - // Identifier or keyword. '\uXXXX' sequences are allowed in - // identifiers, so '\' also dispatches to that. - if (_identifier.isIdentifierStart(code, this.options.ecmaVersion >= 6) || code === 92 /* '\' */) return this.readWord(); - - return this.getTokenFromCode(code); -}; - -pp.fullCharCodeAtPos = function () { - var code = this.input.charCodeAt(this.pos); - if (code <= 0xd7ff || code >= 0xe000) return code; - var next = this.input.charCodeAt(this.pos + 1); - return (code << 10) + next - 0x35fdc00; -}; - -pp.skipBlockComment = function () { - var startLoc = this.options.onComment && this.curPosition(); - var start = this.pos, - end = this.input.indexOf("*/", this.pos += 2); - if (end === -1) this.raise(this.pos - 2, "Unterminated comment"); - this.pos = end + 2; - if (this.options.locations) { - _whitespace.lineBreakG.lastIndex = start; - var match = undefined; - while ((match = _whitespace.lineBreakG.exec(this.input)) && match.index < this.pos) { - ++this.curLine; - this.lineStart = match.index + match[0].length; - } - } - if (this.options.onComment) this.options.onComment(true, this.input.slice(start + 2, end), start, this.pos, startLoc, this.curPosition()); -}; - -pp.skipLineComment = function (startSkip) { - var start = this.pos; - var startLoc = this.options.onComment && this.curPosition(); - var ch = this.input.charCodeAt(this.pos += startSkip); - while (this.pos < this.input.length && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { - ++this.pos; - ch = this.input.charCodeAt(this.pos); - } - if (this.options.onComment) this.options.onComment(false, this.input.slice(start + startSkip, this.pos), start, this.pos, startLoc, this.curPosition()); -}; - -// Called at the start of the parse and after every token. Skips -// whitespace and comments, and. - -pp.skipSpace = function () { - loop: while (this.pos < this.input.length) { - var ch = this.input.charCodeAt(this.pos); - switch (ch) { - case 32:case 160: - // ' ' - ++this.pos; - break; - case 13: - if (this.input.charCodeAt(this.pos + 1) === 10) { - ++this.pos; - } - case 10:case 8232:case 8233: - ++this.pos; - if (this.options.locations) { - ++this.curLine; - this.lineStart = this.pos; - } - break; - case 47: - // '/' - switch (this.input.charCodeAt(this.pos + 1)) { - case 42: - // '*' - this.skipBlockComment(); - break; - case 47: - this.skipLineComment(2); - break; - default: - break loop; - } - break; - default: - if (ch > 8 && ch < 14 || ch >= 5760 && _whitespace.nonASCIIwhitespace.test(String.fromCharCode(ch))) { - ++this.pos; - } else { - break loop; - } - } - } -}; - -// Called at the end of every token. Sets `end`, `val`, and -// maintains `context` and `exprAllowed`, and skips the space after -// the token, so that the next one's `start` will point at the -// right position. - -pp.finishToken = function (type, val) { - this.end = this.pos; - if (this.options.locations) this.endLoc = this.curPosition(); - var prevType = this.type; - this.type = type; - this.value = val; - - this.updateContext(prevType); -}; - -// ### Token reading - -// This is the function that is called to fetch the next token. It -// is somewhat obscure, because it works in character codes rather -// than characters, and because operator parsing has been inlined -// into it. -// -// All in the name of speed. -// -pp.readToken_dot = function () { - var next = this.input.charCodeAt(this.pos + 1); - if (next >= 48 && next <= 57) return this.readNumber(true); - var next2 = this.input.charCodeAt(this.pos + 2); - if (this.options.ecmaVersion >= 6 && next === 46 && next2 === 46) { - // 46 = dot '.' - this.pos += 3; - return this.finishToken(_tokentype.types.ellipsis); - } else { - ++this.pos; - return this.finishToken(_tokentype.types.dot); - } -}; - -pp.readToken_slash = function () { - // '/' - var next = this.input.charCodeAt(this.pos + 1); - if (this.exprAllowed) { - ++this.pos;return this.readRegexp(); - } - if (next === 61) return this.finishOp(_tokentype.types.assign, 2); - return this.finishOp(_tokentype.types.slash, 1); -}; - -pp.readToken_mult_modulo = function (code) { - // '%*' - var next = this.input.charCodeAt(this.pos + 1); - if (next === 61) return this.finishOp(_tokentype.types.assign, 2); - return this.finishOp(code === 42 ? _tokentype.types.star : _tokentype.types.modulo, 1); -}; - -pp.readToken_pipe_amp = function (code) { - // '|&' - var next = this.input.charCodeAt(this.pos + 1); - if (next === code) return this.finishOp(code === 124 ? _tokentype.types.logicalOR : _tokentype.types.logicalAND, 2); - if (next === 61) return this.finishOp(_tokentype.types.assign, 2); - return this.finishOp(code === 124 ? _tokentype.types.bitwiseOR : _tokentype.types.bitwiseAND, 1); -}; - -pp.readToken_caret = function () { - // '^' - var next = this.input.charCodeAt(this.pos + 1); - if (next === 61) return this.finishOp(_tokentype.types.assign, 2); - return this.finishOp(_tokentype.types.bitwiseXOR, 1); -}; - -pp.readToken_plus_min = function (code) { - // '+-' - var next = this.input.charCodeAt(this.pos + 1); - if (next === code) { - if (next == 45 && this.input.charCodeAt(this.pos + 2) == 62 && _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.pos))) { - // A `-->` line comment - this.skipLineComment(3); - this.skipSpace(); - return this.nextToken(); - } - return this.finishOp(_tokentype.types.incDec, 2); - } - if (next === 61) return this.finishOp(_tokentype.types.assign, 2); - return this.finishOp(_tokentype.types.plusMin, 1); -}; - -pp.readToken_lt_gt = function (code) { - // '<>' - var next = this.input.charCodeAt(this.pos + 1); - var size = 1; - if (next === code) { - size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2; - if (this.input.charCodeAt(this.pos + size) === 61) return this.finishOp(_tokentype.types.assign, size + 1); - return this.finishOp(_tokentype.types.bitShift, size); - } - if (next == 33 && code == 60 && this.input.charCodeAt(this.pos + 2) == 45 && this.input.charCodeAt(this.pos + 3) == 45) { - if (this.inModule) this.unexpected(); - // `` line comment - this.skipLineComment(3) - this.skipSpace() - return this.nextToken() - } - return this.finishOp(tt.incDec, 2) - } - if (next === 61) return this.finishOp(tt.assign, 2) - return this.finishOp(tt.plusMin, 1) -} - -pp.readToken_lt_gt = function(code) { // '<>' - let next = this.input.charCodeAt(this.pos + 1) - let size = 1 - if (next === code) { - size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2 - if (this.input.charCodeAt(this.pos + size) === 61) return this.finishOp(tt.assign, size + 1) - return this.finishOp(tt.bitShift, size) - } - if (next == 33 && code == 60 && this.input.charCodeAt(this.pos + 2) == 45 && - this.input.charCodeAt(this.pos + 3) == 45) { - if (this.inModule) this.unexpected() - // `