diff --git a/.eslintignore b/.eslintignore index 93bcf0b4061d..59229ab0f141 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,3 +8,4 @@ /tests/performance/** /tmp/** test.js +!.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000000..ea75710e81a9 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,31 @@ +"use strict"; + +const path = require("path"); +const rulesDirPlugin = require("eslint-plugin-rulesdir"); + +rulesDirPlugin.RULES_DIR = path.join(__dirname, "tools/internal-rules"); + +module.exports = { + root: true, + plugins: [ + "eslint-plugin", + "node", + "rulesdir" + ], + extends: [ + "./packages/eslint-config-eslint/default.yml", + "plugin:node/recommended", + "plugin:eslint-plugin/recommended" + ], + rules: { + "eslint-plugin/consistent-output": "error", + "eslint-plugin/no-deprecated-context-methods": "error", + "eslint-plugin/prefer-output-null": "error", + "eslint-plugin/prefer-placeholders": "error", + "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], + "eslint-plugin/test-case-property-ordering": "error", + "eslint-plugin/test-case-shorthand-strings": "error", + "rulesdir/multiline-comment-style": "error", + "rulesdir/no-useless-catch": "error" + } +}; diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index c857caa671fb..000000000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,13 +0,0 @@ -root: true - -plugins: - - eslint-plugin - - node -extends: - - "./packages/eslint-config-eslint/default.yml" - - "plugin:node/recommended" - - "plugin:eslint-plugin/recommended" -rules: - eslint-plugin/consistent-output: "error" - eslint-plugin/prefer-placeholders: "error" - eslint-plugin/report-message-format: ["error", '[^a-z].*\.$'] diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index e36b1828617a..3cf719784e0e 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,11 +1,14 @@ **Tell us about your environment** @@ -18,22 +21,25 @@ Note that leaving sections blank will make it difficult for us to troubleshoot a **Please show your full configuration:** - -``` - - +
+Configuration + +```js ``` -**What did you do? Please include the actual source code causing the issue.** +
+ +**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.** ```js +``` - - + +```bash ``` diff --git a/.github/ISSUE_TEMPLATE/CHANGE.md b/.github/ISSUE_TEMPLATE/CHANGE.md new file mode 100644 index 000000000000..b8181f56cfec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/CHANGE.md @@ -0,0 +1,25 @@ + + +**Tell us about your environment** + +* **ESLint Version:** +* **Node Version:** +* **npm Version:** + +**What did you do?** + +**What happened?** + +**What did you expect to happen?** + diff --git a/.github/ISSUE_TEMPLATE/NEW_RULE.md b/.github/ISSUE_TEMPLATE/NEW_RULE.md new file mode 100644 index 000000000000..81248324be64 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/NEW_RULE.md @@ -0,0 +1,31 @@ + + +**Please describe what the rule should do:** + +**What category of rule is this? (place an "X" next to just one item)** + +[ ] Enforces code style +[ ] Warns about a potential error +[ ] Suggests an alternate way of doing something +[ ] Other (please specify:) + +**Provide 2-3 code examples that this rule will warn about:** + + +```js + +``` + +**Why should this rule be included in ESLint (instead of a plugin)?** + diff --git a/.github/ISSUE_TEMPLATE/RULE_CHANGE.md b/.github/ISSUE_TEMPLATE/RULE_CHANGE.md new file mode 100644 index 000000000000..b68b90714760 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/RULE_CHANGE.md @@ -0,0 +1,30 @@ + + +**What rule do you want to change?** + +**Does this change cause the rule to produce more or fewer warnings?** + +**How will the change be implemented? (New option, new default behavior, etc.)?** + +**Please provide some example code that this change will affect:** + + +```js + +``` + +**What does the rule currently do for this code?** + +**What will the rule do after it's changed?** + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3b0da9eff908..ca5e285ec393 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,7 @@ + + **What is the purpose of this pull request? (put an "X" next to item)** [ ] Documentation update @@ -16,7 +20,7 @@ diff --git a/.gitignore b/.gitignore index 400e6646a9b8..eae2b21d1ab1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ npm-debug.log .DS_Store tmp/ +debug/ .idea jsdoc/ versions.json diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000000..c1ca392feaa4 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock = false diff --git a/.travis.yml b/.travis.yml index f140463ffe55..d481188d3595 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,18 @@ node_js: - "5" - "6" - "7" + - "8" + - "9" sudo: false -script: "npm test && npm run docs" +branches: + only: + - master + +# Run npm test always +script: + - "npm test" after_success: - - npm run coveralls + - 'if [ "$node_js" = "8" ]; then npm run coveralls; fi' addons: code_climate: repo_token: 1945f7420d920a59f1ff8bf8d1a7b60ccd9e2838a692f73a5a74accd8df30146 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f4b3bbdb3fb..c22ac17bfecf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,641 @@ +v4.18.2 - March 2, 2018 + +* 6b71fd0 Fix: table@4.0.2, because 4.0.3 needs "ajv": "^6.0.1" (#10022) (Mathieu Seiler) +* 3c697de Chore: fix incorrect comment about linter.verify return value (#10030) (Teddy Katz) +* 9df8653 Chore: refactor parser-loading out of linter.verify (#10028) (Teddy Katz) +* f6901d0 Fix: remove catastrophic backtracking vulnerability (fixes #10002) (#10019) (Jamie Davis) +* e4f52ce Chore: Simplify dataflow in linter.verify (#10020) (Teddy Katz) +* 33177cd Chore: make library files non-executable (#10021) (Teddy Katz) +* 558ccba Chore: refactor directive comment processing (#10007) (Teddy Katz) +* 18e15d9 Chore: avoid useless catch clauses that just rethrow errors (#10010) (Teddy Katz) +* a1c3759 Chore: refactor populating configs with defaults in linter (#10006) (Teddy Katz) +* aea07dc Fix: Make max-len ignoreStrings ignore JSXText (fixes #9954) (#9985) (Rachael Sim) + +v4.18.1 - February 20, 2018 + +* f417506 Fix: ensure no-await-in-loop reports the correct node (fixes #9992) (#9993) (Teddy Katz) +* 3e99363 Docs: Fixed typo in key-spacing rule doc (#9987) (Jaid) +* 7c2cd70 Docs: deprecate experimentalObjectRestSpread (#9986) (Toru Nagashima) + +v4.18.0 - February 16, 2018 + +* 70f22f3 Chore: Apply memoization to config creation within glob utils (#9944) (Kenton Jacobsen) +* 0e4ae22 Update: fix indent bug with binary operators/ignoredNodes (fixes #9882) (#9951) (Teddy Katz) +* 47ac478 Update: add named imports and exports for object-curly-newline (#9876) (Nicholas Chua) +* e8efdd0 Fix: support Rest/Spread Properties (fixes #9885) (#9943) (Toru Nagashima) +* f012b8c Fix: support Async iteration (fixes #9891) (#9957) (Toru Nagashima) +* 74fa253 Docs: Clarify no-mixed-operators options (fixes #9962) (#9964) (Ivan Hayes) +* 426868f Docs: clean up key-spacing docs (fixes #9900) (#9963) (Abid Uzair) +* 4a6f22e Update: support eslint-disable-* block comments (fixes #8781) (#9745) (Erin) +* 777283b Docs: Propose fix typo for function (#9965) (John Eismeier) +* bf3d494 Docs: Fix typo in max-len ignorePattern example. (#9956) (Tim Martin) +* d64fbb4 Docs: fix typo in prefer-destructuring.md example (#9930) (Vse Mozhet Byt) +* f8d343f Chore: Fix default issue template (#9946) (Kai Cataldo) + +v4.17.0 - February 2, 2018 + +* 1da1ada Update: Add "multiline" type to padding-line-between-statements (#8668) (Matthew Bennett) +* bb213dc Chore: Use messageIds in some of the core rules (#9648) (Jed Fox) +* 1aa1970 Docs: remove outdated rule naming convention (#9925) (Teddy Katz) +* 3afaff6 Docs: Add prefer-destructuring variable reassignment example (#9873) (LePirlouit) +* d20f6b4 Fix: Typo in error message when running npm (#9866) (Maciej Kasprzyk) +* 51ec6a7 Docs: Use GitHub Multiple PR/Issue templates (#9911) (Kai Cataldo) +* dc80487 Update: space-unary-ops uses astUtils.canTokensBeAdjacent (fixes #9907) (#9906) (Kevin Partington) +* 084351b Docs: Fix the messageId example (fixes #9889) (#9892) (Jed Fox) +* 9cbb487 Docs: Mention the `globals` key in the no-undef docs (#9867) (Dan Dascalescu) + +v4.16.0 - January 19, 2018 + +* e26a25f Update: allow continue instead of if wrap in guard-for-in (fixes #7567) (#9796) (Michael Ficarra) +* af043eb Update: Add NewExpression support to comma-style (#9591) (Frazer McLean) +* 4f898c7 Build: Fix JSDoc syntax errors (#9813) (Matija Marohnić) +* 13bcf3c Fix: Removing curly quotes in no-eq-null report message (#9852) (Kevin Partington) +* b96fb31 Docs: configuration hierarchy for CLIEngine options (fixes #9526) (#9855) (PiIsFour) +* 8ccbdda Docs: Clarify that -c configs merge with `.eslintrc.*` (fixes #9535) (#9847) (Kevin Partington) +* 978574f Docs: Fix examples for no-useless-escape (#9853) (Toru Kobayashi) +* cd5681d Chore: Deactivate consistent-docs-url in internal rules folder (#9815) (Kevin Partington) +* 2e87ddd Docs: Sync messageId examples' style with other examples (#9816) (Kevin Partington) +* 1d61930 Update: use doctrine range information in valid-jsdoc (#9831) (Teddy Katz) +* 133336e Update: fix indent behavior on template literal arguments (fixes #9061) (#9820) (Teddy Katz) +* ea1b15d Fix: avoid crashing on malformed configuration comments (fixes #9373) (#9819) (Teddy Katz) +* add1e70 Update: fix indent bug on comments in ternary expressions (fixes #9729) (#9818) (Teddy Katz) +* 6a5cd32 Fix: prefer-destructuring error with computed properties (fixes #9784) (#9817) (Teddy Katz) +* 601f851 Docs: Minor modification to code comments for clarity (#9821) (rgovind92) +* b9da067 Docs: fix misleading info about RuleTester column numbers (#9830) (Teddy Katz) +* 2cf4522 Update: Rename and deprecate object-property-newline option (#9570) (Jonathan Pool) +* acde640 Docs: Add ES 2018 to Configuring ESLint (#9829) (Kai Cataldo) +* ccfce15 Docs: Minor tweaks to working with rules page (#9824) (Kevin Partington) +* 54b329a Docs: fix substitution of {{ name }} (#9822) (Andres Kalle) + +v4.15.0 - January 6, 2018 + +* 6ab04b5 New: Add context.report({ messageId }) (fixes #6740) (#9165) (Jed Fox) +* fc7f404 Docs: add url to each of the rules (refs #6582) (#9788) (Patrick McElhaney) +* fc44da9 Docs: fix sort-imports rule block language (#9805) (ferhat elmas) +* 65f0176 New: CLIEngine#getRules() (refs #6582) (#9782) (Patrick McElhaney) +* c64195f Update: More detailed assert message for rule-tester (#9769) (Weijia Wang) +* 9fcfabf Fix: no-extra-parens false positive (fixes: #9755) (#9795) (Erin) +* 61e5fa0 Docs: Add table of contents to Node.js API docs (#9785) (Patrick McElhaney) +* 4c87f42 Fix: incorrect error messages of no-unused-vars (fixes #9774) (#9791) (akouryy) +* bbabf34 Update: add `ignoreComments` option to `indent` rule (fixes #9018) (#9752) (Kevin Partington) +* db431cb Docs: HTTP -> HTTPS (fixes #9768) (#9768) (Ronald Eddy Jr) +* cbf0fb9 Docs: describe how to feature-detect scopeManager/visitorKeys support (#9764) (Teddy Katz) +* f7dcb70 Docs: Add note about "patch release pending" label to maintainer guide (#9763) (Teddy Katz) + +v4.14.0 - December 23, 2017 + +* be2f57e Update: support separate requires in one-var. (fixes #6175) (#9441) (薛定谔的猫) +* 370d614 Docs: Fix typos (#9751) (Jed Fox) +* 8196c45 Chore: Reorganize CLI options and associated docs (#9758) (Kevin Partington) +* 75c7419 Update: Logical-and is counted in `complexity` rule (fixes #8535) (#9754) (Kevin Partington) +* eb4b1e0 Docs: reintroduce misspelling in `valid-typeof` example (#9753) (Teddy Katz) +* ae51eb2 New: Add allowImplicit option to array-callback-return (fixes #8539) (#9344) (James C. Davis) +* e9d5dfd Docs: improve no-extra-parens formatting (#9747) (Rich Trott) +* 37d066c Chore: Add unit tests for overrides glob matching. (#9744) (Robert Jackson) +* 805a94e Chore: Fix typo in CLIEngine test name (#9741) (@scriptdaemon) +* 1c2aafd Update: Improve parser integrations (fixes #8392) (#8755) (Toru Nagashima) +* 4ddc131 Upgrade: debug@^3.1.0 (#9731) (Kevin Partington) +* f252c19 Docs: Make the lint message `source` property a little more subtle (#9735) (Jed Fox) +* 5a5c23c Docs: fix the link to contributing page (#9727) (Victor Hom) +* f44ce11 Docs: change beginner to good first issue label text (#9726) (Victor Hom) +* 14baa2e Chore: improve arrow-body-style error message (refs #5498) (#9718) (Teddy Katz) +* f819920 Docs: fix typos (#9723) (Thomas Broadley) +* 43d4ba8 Fix: false positive on rule`lines-between-class-members` (fixes #9665) (#9680) (sakabar) + +v4.13.1 - December 11, 2017 + +* b72dc83 Fix: eol-last allow empty-string to always pass (refs #9534) (#9696) (Kevin Partington) +* d80aa7c Fix: camelcase destructure leading/trailing underscore (fixes #9700) (#9701) (Kevin Partington) +* d49d9d0 Docs: Add missing period to the README (#9702) (Kevin Partington) +* 4564fe0 Chore: no-invalid-meta crash if no export assignment (refs #9534) (#9698) (Kevin Partington) + +v4.13.0 - December 8, 2017 + +* 256481b Update: update handling of destructuring in camelcase (fixes #8511) (#9468) (Erin) +* d067ae1 Docs: Don’t use undocumented array-style configuration for max-len (#9690) (Jed Fox) +* 1ad3091 Chore: fix test-suite to work with node master (#9688) (Myles Borins) +* cdb1488 Docs: Adds an example with try/catch. (#9672) (Jaap Taal) + +v4.12.1 - November 30, 2017 + +* 1e362a0 Revert "Fix: Use XML 1.1 on XML formatters (fixes #9607) (#9608)" (#9667) (Kevin Partington) + +v4.12.0 - November 25, 2017 + +* 76dab18 Upgrade: doctrine@^2.0.2 (#9656) (Kevin Partington) +* 28c9c8e New: add a Linter#defineParser function (#9321) (Ives van Hoorne) +* 5619910 Update: Add autofix for `sort-vars` (#9496) (Trevin Hofmann) +* 71eedbf Update: add `beforeStatementContinuationChars` to semi (fixes #9521) (#9594) (Toru Nagashima) +* 4118f14 New: Adds implicit-arrow-linebreak rule (refs #9510) (#9629) (Sharmila Jesupaul) +* 208fb0f Fix: Use XML 1.1 on XML formatters (fixes #9607) (#9608) (Daniel Reigada) +* 6e04f14 Upgrade: `globals` to 11.0.1 (fixes #9614) (#9632) (Toru Nagashima) +* e13d439 Fix: space-in-parens crash (#9655) (Toru Nagashima) +* 92171cc Docs: Updating migration guide for single-line disable (#9385) (Justin Helmer) +* f39ffe7 Docs: remove extra punctuation from readme (#9640) (Teddy Katz) +* a015234 Fix: prefer-destructuring false positive on "super" (fixes #9625) (#9626) (Kei Ito) +* 0cf081e Update: add importNames option to no-restricted-imports (#9506) (Benjamin R Gibson) +* 332c214 Docs: Add @platinumazure to TSC (#9618) (Ilya Volodin) + +v4.11.0 - November 10, 2017 + +* d4557a6 Docs: disallow use of the comma operator using no-restricted-syntax (#9585) (薛定谔的猫) +* d602f9e Upgrade: espree v3.5.2 (#9611) (Kai Cataldo) +* 4def876 Chore: avoid handling rules instances in config-validator (#9364) (Teddy Katz) +* fe5ac7e Chore: fix incorrect comment in safe-emitter.js (#9605) (Teddy Katz) +* 6672fae Docs: Fixed a typo on lines-between-class-members doc (#9603) (Moinul Hossain) +* 980ecd3 Chore: Update copyright and license info (#9599) (薛定谔的猫) +* cc2c7c9 Build: use Node 8 in appveyor (#9595) (薛定谔的猫) +* 2542f04 Docs: Add missing options for `lines-around-comment` (#9589) (Clément Fiorio) +* b6a7490 Build: ensure fuzzer tests get run with `npm test` (#9590) (Teddy Katz) +* 1073bc5 Build: remove shelljs-nodecli (refs #9533) (#9588) (Teddy Katz) +* 7e3bf6a Fix: edge-cases of semi-style (#9560) (Toru Nagashima) +* e5a37ce Fix: object-curly-newline for flow code (#9458) (Tiddo Langerak) +* 9064b9c Chore: add equalTokens in ast-utils. (#9500) (薛定谔的猫) +* b7c5b19 Fix: Correct [object Object] output of error.data. (#9561) (Jonathan Pool) +* 51c8cf0 Docs: Disambiguate definition of Update tag (#9584) (Jonathan Pool) +* afc3c75 Docs: clarify what eslint-config-eslint is (#9582) (Teddy Katz) +* aedae9d Docs: fix spelling in valid-typeof example (#9574) (Maksim Degtyarev) +* 4c5aaf3 Docs: Fix typo in no-underscore-dangle rule (#9567) (Fabien Lucas) +* 3623600 Chore: upgrade ajv@5.3.0 (#9557) (薛定谔的猫) +* 1b606cd Chore: Remove an indirect dependency on jsonify (#9444) (Rouven Weßling) +* 4d7d7ab Update: Resolve npm installed formatters (#5900) (#9464) (Tom Erik Støwer) +* accc490 Fix: Files with no failures get "passing" testcase (#9547) (Samuel Levy) +* ab0f66d Docs: Add examples to better show rule coverage. (#9548) (Jonathan Pool) +* 88d2303 Chore: Add object-property-newline tests to increase coverage. (#9553) (Jonathan Pool) +* 7f37b1c Build: test Node 9 on Travis (#9556) (Teddy Katz) +* acccfbd Docs: Minor rephrase in `no-invalid-this`. (#9542) (Francisc) +* 8f9c0fe Docs: improve id-match usage advice (#9544) (Teddy Katz) +* a9606a3 Fix: invalid tests with super (fixes #9539) (#9545) (Teddy Katz) +* 8e1a095 Chore: enable a modified version of multiline-comment-style on codebase (#9452) (Teddy Katz) +* cb60285 Chore: remove commented test for HTML formatter (#9532) (Teddy Katz) +* 06b491e Docs: fix duplicate entries in changelog (#9530) (Teddy Katz) +* 2224733 Chore: use eslint-plugin-rulesdir instead of --rulesdir for self-linting (#9164) (Teddy Katz) +* 9cf4ebe Docs: add .md to link(for github users) (#9529) (薛定谔的猫) + +v4.10.0 - October 27, 2017 + +* bb6e60a Fix: Improve the doc for no-restricted-modules rule (fixes #9437) (#9495) (vibss2397) +* c529de9 Docs: Amend rule document to correct and complete it (refs #6251). (#9498) (Jonathan Pool) +* f9c6673 Chore: Add tests to cover array and object values and leading commas. (#9502) (Jonathan Pool) +* 9169258 Chore: remove `npm run check-commit` script (#9513) (Teddy Katz) +* 7d390b2 Docs: Revise contributor documentation on issue labels. (#9469) (Jonathan Pool) +* d80b9d0 Fix: no-var don't fix globals (fixes #9520) (#9525) (Toru Nagashima) +* b8aa071 Fix: allow linting the empty string from stdin (fixes #9515) (#9517) (Teddy Katz) +* 350a72c Chore: regex.test => string.startsWith (#9518) (薛定谔的猫) +* de0bef4 Chore: remove obsolete eslintbot templates (#9512) (Teddy Katz) +* 720b6d5 Docs: Update ISSUE_TEMPLATE.md (#9504) (薛定谔的猫) +* 2fa64b7 Fix: should not convert non-consecutive line comments to a single blo… (#9475) (薛定谔的猫) +* 9725146 Fix: multiline-comment-style fix produces invalid code (fixes #9461). (#9463) (薛定谔的猫) +* b12cff8 Fix: Expected order of jsdoc tags (fixes #9412) (#9451) (Orlando Wenzinger) +* f054ab5 Docs: add `.md` to link (for github users) (#9501) (薛定谔的猫) +* 5ed9cfc Docs: Correct violations of “Variable Declarations” in Code Conventions (#9447) (Jonathan Pool) +* 3171097 Docs: Clears confusion on usage of global and local plugins.(#9492) (Vasili Sviridov) +* 3204773 Chore: enable max-len. (#9414) (薛定谔的猫) +* 0f71fef Docs: Unquote booleans in lines-between-class-members docs (#9497) (Brandon Mills) +* b3d7532 Docs: use consistent terminology & fix link etc. (#9490) (薛定谔的猫) +* 87db8ae Docs: Fix broken links (#9488) (gpiress) +* 51bdb2f Docs: Incorrect link to related rule (#9477) (Gavin King) +* 1a962e8 Docs: Add FAQ for when ESLint cannot find plugin (#9467) (Kevin Partington) +* 8768b2d Fix: multiline-comment-style autofixer added trailing space (#9454) (Teddy Katz) +* e830aa1 Fix: multiline-comment-style reports block comments followed by code (#9450) (Teddy Katz) +* b12e5fe Docs: Repair broken links and add migration links. (#9473) (Jonathan Pool) +* eca01ed Docs: Add missing info about special status of home-dir config files. (#9472) (Jonathan Pool) +* eb8cfb1 Fix: change err report in constant condition (fixes #9398) (#9436) (Victor Hom) +* da77eb4 Chore: Revise no-config-file test to prevent false failure. (#9443) (Jonathan Pool) +* 47e5f6f Docs: ensure "good commit message" examples actually follow guidelines (#9466) (Teddy Katz) +* ebb530d Update: Don't ignore comments (no-trailing-spaces) (#9416) (Chris van Marle) +* 5012661 Build: fix `npm run profile` script (fixes #9397) (#9455) (Teddy Katz) +* ecac0fd Docs: Remove blockBindings references (#9446) (Jan Pilzer) +* 0b89865 Chore: ensure tests for internal rules get run (#9453) (Teddy Katz) +* 052c504 Docs: suggest deleting branches after merging PRs (#9449) (Teddy Katz) +* b31e55a Chore: move internal rules out of lib/ (#9448) (Teddy Katz) +* a7521e3 Docs: improve examples for multiline-comment-style (#9440) (Teddy Katz) + +v4.9.0 - October 14, 2017 + +* 85388fb Fix: Correct error and test messages to fit config search path (#9428) (Jonathan Pool) +* 62a323c Fix: Add class options for `lines-around-comment` (fixes #8564) (#8565) (Ed Lee) +* 8eb4aae New: multiline-comment-style rule (fixes #8320) (#9389) (薛定谔的猫) +* db41408 Chore: avoid applying eslint-env comments twice (#9278) (Teddy Katz) +* febb897 Chore: avoid loose equality assertions (#9415) (Teddy Katz) +* 2247efa Update: Add FunctionExpression to require-jsdoc (fixes #5867) (#9395) (Kai Cataldo) +* 6791d18 Docs: Corrected noun to verb. (#9438) (Jonathan Pool) +* b02fbb6 Update: custom messages for no-restricted-* (refs #8400) (Maja Wichrowska) +* 02732bd Docs: Reorganized to avoid misunderstandings. (#9434) (Jonathan Pool) +* d9466b8 Docs: Correct time forecast for tests. (#9432) (Jonathan Pool) +* f7ed84f Docs: Add instruction re home-directory config files (refs #7729) (#9426) (Jonathan Pool) +* 30d018b Chore: Add Aladdin-ADD & VictorHom to README (#9424) (Kai Cataldo) +* 2d8a303 Docs: fix examples for prefer-numeric-literals (#9155) (Lutz Lengemann) +* d7610f5 Docs: Add jquery warning to prefer-destructuring (#9409) (Thomas Grainger) +* e835dd1 Docs: clarify no-mixed-operators (fixes #8051) (Ruxandra Fediuc) +* 51360c8 Docs: update block-spacing details (fixes #8743) (#9375) (Victor Hom) +* 6767857 Update: fix ignored nodes in indent rule when using tabs (fixes #9392) (#9393) (Robin Houston) +* 37dde77 Chore: Refactor SourceCode#getJSDocComment (#9403) (Kai Cataldo) +* 9fedd51 Chore: Add missing space in blog post template (#9407) (Kevin Partington) +* 7654c99 Docs: add installing prerequisites in readme. (#9401) (薛定谔的猫) +* 786cc73 Update: Add "consistent" option to array-bracket-newline (fixes #9136) (#9206) (Ethan Rutherford) +* e171f6b Docs: add installing prerequisites. (#9394) (薛定谔的猫) +* 74dfc87 Docs: update doc for class-methods-use-this (fixes #8910) (#9374) (Victor Hom) +* b4a9dbf Docs: show console call with no-restricted-syntax (fixes #7806) (#9376) (Victor Hom) +* 8da525f Fix: recognise multiline comments as multiline arrays (fixes #9211) (#9369) (Phil Quinn) +* c581b77 Chore: Error => TypeError (#9390) (薛定谔的猫) +* ee99876 New: lines-between-class-members rule (fixes #5949) (#9141) (薛定谔的猫) +* 9d3f5ad Chore: report unused eslint-disable directives in ESLint codebase (#9371) (Teddy Katz) +* 1167638 Update: add allowElseIf option to no-else-return (fixes #9228) (#9229) (Thomas Grainger) +* 4567ab1 New: Add the fix-dry-run flag (fixes #9076) (#9073) (Rafał Ruciński) + +v4.8.0 - September 29, 2017 + +* 3f2b908 New: add option to report unused eslint-disable directives (fixes #9249) (#9250) (Teddy Katz) +* ff2be59 Fix: dot notation rule failing to catch string template (fixes #9350) (#9357) (Phil Quinn) +* b1372da Chore: remove sourceCode property from Linter (refs #9161) (#9363) (Teddy Katz) +* cef6f8c Docs: remove line about removing rules from semver policy (#9367) (Teddy Katz) +* 06efe87 Fix: Add meta element with charset attribute. (#9365) (H1Gdev) +* 458ca67 Docs: update architecture page (fixes #9337) (#9345) (Victor Hom) +* 1c6bc67 Fix: special EventEmitter keys leak information about other rules (#9328) (Teddy Katz) +* d593e61 Docs: update eslint.org links to use https (#9358) (Teddy Katz) +* 38d0cb2 Fix: fix wrong code-path about try-for-in (fixes #8848) (#9348) (Toru Nagashima) +* 434d9e2 Fix: Invalid font-size property value issue. (#9341) (H1Gdev) +* a7668c2 Chore: Remove unnecessary slice from logging utility (#9343) (Gyandeep Singh) +* 2ff6fb6 Chore: remove unused arguments in codebase (#9340) (Teddy Katz) + +v4.7.2 - September 21, 2017 + +* 4f87732 Fix: Revert setting node.parent early (fixes #9331) (#9336) (Teddy Katz) + +v4.7.1 - September 18, 2017 + +* 08656db Fix: Handle nested disable directive correctly (fixes #9318) (#9322) (Gyandeep Singh) +* 9226495 Revert "Chore: rewrite parseListConfig for a small perf gain." (#9325) (薛定谔的猫) + +v4.7.0 - September 15, 2017 + +* 787b78b Upgrade: Espree v3.5.1 (fixes #9153) (#9314) (Brandon Mills) +* 1488b51 Update: run rules after `node.parent` is already set (fixes #9122) (#9283) (Teddy Katz) +* 4431d68 Docs: fix wrong config in max-len example. (#9309) (薛定谔的猫) +* 7d24dde Docs: Fix code snippet to refer to the correct option (#9313) (Ruben Tytgat) +* 12388d4 Chore: rewrite parseListConfig for a small perf gain. (#9300) (薛定谔的猫) +* ce1f084 Update: fix MemberExpression handling in no-extra-parens (fixes #9156) (jackyho112) +* 0c720a3 Update: allow autofixing when using processors (fixes #7510) (#9090) (Teddy Katz) +* 838df76 Chore: upgrade deps. (#9289) (薛定谔的猫) +* f12def6 Update: indent flatTernary option to handle `return` (fixes #9285) (#9296) (Teddy Katz) +* e220687 Fix: remove autofix for var undef inits (fixes #9231) (#9288) (Victor Hom) +* 002e199 Docs: fix no-restricted-globals wrong config. (#9305) (薛定谔的猫) +* fcfe91a Docs: fix wrong config in id-length example. (#9303) (薛定谔的猫) +* 2731f94 Update: make newline-per-chained-call fixable (#9149) (João Granado) +* 61f1093 Chore: avoid monkeypatching Linter instances in RuleTester (#9276) (Teddy Katz) +* 28929cb Chore: remove Linter#reset (refs #9161) (#9268) (Teddy Katz) +* abc8634 Build: re-run browserify when generating site (#9275) (Teddy Katz) +* 7685fed Fix: IIFE and arrow functions in no-invalid-this (fixes #9126) (#9258) (Toru Nagashima) +* 2b1eba2 Chore: enable eslint-plugin/no-deprecated-context-methods (#9279) (Teddy Katz) +* 981f933 Fix: reuse the AST of source code object in verify (#9256) (Toru Nagashima) +* cd698ba Docs: move RuleTester documentation to Node.js API page (#9273) (Teddy Katz) +* 4ae7ad3 Docs: fix inaccuracy in `npm run perf` description (#9274) (Teddy Katz) +* cad45bd Docs: improve documentation for rule contexts (#9272) (Teddy Katz) +* 3b0c6fd Chore: remove extraneous linter properties (refs #9161) (#9267) (Teddy Katz) +* c3231b3 Docs: Fix typo in array-bracket-newline.md (#9269) (宋文强) +* 51132d6 Fix: Formatters keep trailing '.' if preceded by a space (fixes #9154) (#9247) (i-ron-y) +* 88d5d4d Chore: remove undocumented Linter#markVariableAsUsed method (refs #9161) (#9266) (Teddy Katz) +* 09414cf Chore: remove internal Linter#getDeclaredVariables method (refs #9161) (#9264) (Teddy Katz) +* f31f59d Chore: prefer smaller scope for variables in codebase (#9265) (Teddy Katz) +* 3693e4e Chore: remove undocumented Linter#getScope method (#9253) (Teddy Katz) +* 5d7eb81 Chore: refactor config hash caching in CLIEngine (#9260) (Teddy Katz) +* 1a76c4d Chore: remove SourceCode passthroughs from Linter.prototype (refs #9161) (#9263) (Teddy Katz) +* 40ae27b Chore: avoid relying on Linter#getScope/markVariableAsUsed in tests (#9252) (Teddy Katz) +* b383d81 Chore: make executeOnFile a pure function in CLIEngine (#9262) (Teddy Katz) +* 5e0e579 Chore: avoid internal SourceCode methods in Linter tests (refs #9161) (#9223) (Teddy Katz) +* adab827 Chore: remove unused eslint-disable comment (#9251) (Teddy Katz) +* 31e4ec8 Chore: use consistent names for apply-disable-directives in tests (#9246) (Teddy Katz) +* 7ba46e6 Fix: shebang error in eslint-disable-new-line; add tests (fixes #9238) (#9240) (i-ron-y) +* 8f6546c Chore: remove undocumented defaults() method (refs #9161) (#9237) (Teddy Katz) +* 82d8b73 Docs: Fix error in example code for sort-imports (fixes #8734) (#9245) (i-ron-y) +* a32ec36 Update: refactor eslint-disable comment processing (#9216) (Teddy Katz) +* 583f0b8 Chore: avoid using globals in CLIEngine tests (#9242) (Teddy Katz) +* c8bf687 Chore: upgrade eslint-plugin-eslint-plugin@1.0.0 (#9234) (薛定谔的猫) +* 3c41a05 Chore: always normalize rules to new API in rules.js (#9236) (Teddy Katz) +* c5f4227 Chore: move logic for handling missing rules to rules.js (#9235) (Teddy Katz) +* bf1e344 Chore: create report translators lazily (#9221) (Teddy Katz) +* 2eedc1f Chore: remove currentFilename prop from Linter instances (refs #9161) (#9219) (Teddy Katz) +* 5566e94 Docs: Replace misleading CLA links (#9133) (#9232) (i-ron-y) +* c991630 Chore: remove ConfigOps.normalize in favor of ConfigOps.getRuleSeverity (#9224) (Teddy Katz) +* 171962a Chore: remove internal Linter#getAncestors helper (refs #9161) (#9222) (Teddy Katz) +* a567499 Chore: avoid storing list of problems on Linter instance (refs #9161) (#9214) (Teddy Katz) +* ed6d088 Chore: avoid relying on undocumented Linter#getFilename API in tests (#9218) (Teddy Katz) + +v4.6.1 - September 3, 2017 + +* bdec46d Build: avoid process leak when generating website (#9217) (Teddy Katz) +* cb74b87 Fix: avoid adding globals when an env is used with `false` (fixes #9202) (#9203) (Teddy Katz) +* f9b7544 Docs: Correct a typo in generator-star-spacing documentation (#9205) (Ethan Rutherford) +* e5c5e83 Build: Fixing issue with docs generation (Fixes #9199) (#9200) (Ilya Volodin) + +v4.6.0 - September 1, 2017 + +* 56dd769 Docs: fix link format in prefer-arrow-callback.md (#9198) (Vse Mozhet Byt) +* 6becf91 Update: add eslint version to error output. (fixes #9037) (#9071) (薛定谔的猫) +* 0e09973 New: function-paren-newline rule (fixes #6074) (#8102) (Teddy Katz) +* 88a64cc Chore: Make parseJsonConfig() a pure function in Linter (#9186) (Teddy Katz) +* 1bbac51 Fix: avoid breaking eslint-plugin-eslint-comments (fixes #9193) (#9196) (Teddy Katz) +* 3e8b70a Fix: off-by-one error in eslint-disable comment checking (#9195) (Teddy Katz) +* 73815f6 Docs: rewrite prefer-arrow-callback documentation (fixes #8950) (#9077) (Charles E. Morgan) +* 0d3a854 Chore: avoid mutating report descriptors in report-translator (#9189) (Teddy Katz) +* 2db356b Update: no-unused-vars Improve message to include the allowed patterns (#9176) (Eli White) +* 8fbaf0a Update: Add configurability to generator-star-spacing (#8985) (Ethan Rutherford) +* 8ed779c Chore: remove currentScopes property from Linter instances (refs #9161) (#9187) (Teddy Katz) +* af4ad60 Fix: Handle error when running init without npm (#9169) (Gabriel Aumala) +* 4b94c6c Chore: make parse() a pure function in Linter (refs #9161) (#9183) (Teddy Katz) +* 1be5634 Chore: don't make Linter a subclass of EventEmitter (refs #9161) (#9177) (Teddy Katz) +* e95af9b Chore: don't include internal test helpers in npm package (#9160) (Teddy Katz) +* 6fb32e1 Chore: avoid using private Linter APIs in astUtils tests (refs #9161) (#9173) (Teddy Katz) +* de6dccd Docs: add documentation for Linter methods (refs #6525) (#9151) (Teddy Katz) +* 2d90030 Chore: remove unused assignment. (#9182) (薛定谔的猫) +* d672aef Chore: refactor reporting logic (refs #9161) (#9168) (Teddy Katz) +* 5ab0434 Fix: indent crash on sparse arrays with "off" option (fixes #9157) (#9166) (Teddy Katz) +* c147b97 Chore: Make SourceCodeFixer accept text instead of a SourceCode instance (#9178) (Teddy Katz) +* f127423 Chore: avoid using private Linter APIs in Linter tests (refs #9161) (#9175) (Teddy Katz) +* 2334335 Chore: avoid using private Linter APIs in SourceCode tests (refs #9161) (#9174) (Teddy Katz) +* 2dc243a Chore: avoid using internal Linter APIs in RuleTester (refs #9161) (#9172) (Teddy Katz) +* d6e436f Fix: no-extra-parens reported some parenthesized IIFEs (fixes #9140) (#9158) (Teddy Katz) +* e6b115c Build: Add an edit link to the rule docs’ metadata (#9049) (Jed Fox) +* fcb7bb4 Chore: avoid unnecessarily complex forEach calls in no-extra-parens (#9159) (Teddy Katz) +* ffa021e Docs: quotes rule - when does \n require backticks (#9135) (avimar) +* 60c5148 Chore: improve coverage in lib/*.js (#9130) (Teddy Katz) + +v4.5.0 - August 18, 2017 + +* decdd2c Update: allow arbitrary nodes to be ignored in `indent` (fixes #8594) (#9105) (Teddy Katz) +* 79062f3 Update: fix indentation of multiline `new.target` expressions (#9116) (Teddy Katz) +* d00e24f Upgrade: `chalk` to 2.x release (#9115) (Stephen Edgar) +* 6ef734a Docs: add missing word in processor documentation (#9106) (Teddy Katz) +* a4f53ba Fix: Include files with no messages in junit results (#9093) (#9094) (Sean DuBois) +* 1d6a9c0 Chore: enable eslint-plugin/test-case-shorthand-strings (#9067) (薛定谔的猫) +* f8add8f Fix: don't autofix with linter.verifyAndFix when `fix: false` is used (#9098) (Teddy Katz) +* 77bcee4 Docs: update instructions for adding TSC members (#9086) (Teddy Katz) +* bd09cd5 Update: avoid requiring NaN spaces of indentation (fixes #9083) (#9085) (Teddy Katz) +* c93a853 Chore: Remove extra space in blogpost template (#9088) (Kai Cataldo) + +v4.4.1 - August 7, 2017 + +* ec93614 Fix: no-multi-spaces to avoid reporting consecutive tabs (fixes #9079) (#9087) (Teddy Katz) + +v4.4.0 - August 5, 2017 + +* 89196fd Upgrade: Espree to 3.5.0 (#9074) (Gyandeep Singh) +* b3e4598 Fix: clarify AST and don't use `node.start`/`node.end` (fixes #8956) (#8984) (Toru Nagashima) +* 62911e4 Update: Add ImportDeclaration option to indent rule (#8955) (David Irvine) +* de75f9b Chore: enable object-curly-newline & object-property-newline.(fixes #9042) (#9068) (薛定谔的猫) +* 5ae8458 Docs: fix typo in object-shorthand.md (#9066) (Jon Berry) +* c3d5b39 Docs: clarify options descriptions (fixes #8875) (#9060) (Brandon Mailhiot) +* 37158c5 Docs: clarified behavior of globalReturn option (fixes #8953) (#9058) (Brandon Mailhiot) +* c2f3553 Docs: Update example for MemberExpression option of indent (fixes #9056) (#9057) (Jeff) +* 78a85e0 Fix: no-extra-parens incorrectly reports async function expressions (#9035) (薛定谔的猫) +* c794f86 Fix: getter-return reporting method named 'get' (fixes #8919) (#9004) (薛定谔的猫) +* d0f78ec Docs: update rule deprecation policy (fixes #8635) (#9033) (Teddy Katz) +* 5ab282f Fix: Print error message in bin/eslint.js (fixes #9011) (#9041) (Victor Hom) +* 50e3cf3 Docs: Update sort-keys doc to define natural ordering (fixes #9043) (#9045) (Karan Sharma) +* 7ecfe6a Chore: enable eslint-plugin/test-case-property-ordering (#9040) (薛定谔的猫) +* ad32697 Upgrade: js-yaml to 3.9.1 (refs #9011) (#9044) (Teddy Katz) +* 66c1d43 Docs: Create SUPPORT.md (#9031) (Teddy Katz) +* 7247b6c Update: handle indentation of custom destructuring syntax (fixes #8990) (#9027) (Teddy Katz) +* cdb82f2 Fix: padding-line-between-statements crash on semicolons after blocks (#8748) (Alexander Madyankin) +* 3141872 Chore: remove unnecessary eslint-disable comments in codebase (#9032) (Teddy Katz) +* 0f97279 Fix: refactor no-multi-spaces to avoid regex backtracking (fixes #9001) (#9008) (Teddy Katz) +* b74514d Fix: refactor RuleContext to not modify report locations (fixes #8980) (#8997) (Teddy Katz) +* 31d7fd2 Fix: inconsistent `indent` behavior on computed properties (fixes #8989) (#8999) (Teddy Katz) +* 3393894 Fix: avoid reporting the entire AST for missing rules (#8998) (Teddy Katz) +* b3b95b8 Chore: enable additional rules on ESLint codebase (#9013) (Teddy Katz) +* 9b6c552 Upgrade: eslint-plugin-eslint-plugin@0.8.0 (#9012) (薛定谔的猫) +* acbe86a Chore: disallow .substr and .substring in favor of .slice (#9010) (Teddy Katz) +* d0536d6 Chore: Optimizes adding Linter methods (fixes #9000) (#9007) (Sean C Denison) +* 0a0401f Chore: fix spelling error. (#9003) (薛定谔的猫) +* 3d020b9 Update: emit a warning for ecmaFeatures rather than throwing an error (#8974) (Teddy Katz) +* d2f8f9f Fix: include name of invalid config in validation messages (fixes #8963) (#8973) (Teddy Katz) +* c3ee46b Chore: fix misleading comment in RuleTester (#8995) (Teddy Katz) + +v4.3.0 - July 21, 2017 + +* 91dccdf Update: support more options in prefer-destructuring (#8796) (Victor Hom) +* 3bebcfd Update: Support generator yields in no constant condition (#8762) (Victor Hom) +* 96df8c9 Fix: Handle fixing objects containing comments (fixes #8484) (#8944) (Brian Schemp) +* e39d41d Docs: Make `peerDependencies` package.json snippet valid JSON (#8971) (Sam Adams) +* a5fd101 Fix: duplicated error message if a crash occurs (fixes #8964) (#8965) (Teddy Katz) +* f8d122c Docs: trailing commas not allowed in json (#8969) (Scott Fletcher) +* d09288a Chore: Use `output: null` to assert that a test case is not autofixed. (#8960) (薛定谔的猫) +* e639358 Update: add question to confirm downgrade (fixes #8870) (#8911) (Toru Nagashima) +* 601039d Docs: fix badge in eslint-config-eslint readme (#8954) (Teddy Katz) +* 3c231fa Update: add enforceInMethodNames to no-underscore-dangle (fixes #7065) (#7234) (Gabriele Petronella) +* 128591f Update: prefer-numeric-literals warns Number.parseInt (fixes #8913) (#8929) (Kevin Partington) +* 846f8b1 Docs: Clarified that core PRs require issue in maintainer guide (#8927) (Kevin Partington) +* 55bc35d Fix: Avoid shell mangling during eslint --init (#8936) (Anders Kaseorg) +* 10c3d78 Chore: fix misleading `indent` test (#8925) (Teddy Katz) +* fb8005d Update: no-restricted-globals custom error messages (fixes #8315) (#8932) (Kevin Partington) +* a747b6f Chore: make minor improvements to `indent` internals (#8947) (Teddy Katz) +* 1ea3723 Update: fix indentation of parenthesized MemberExpressions (fixes #8924) (#8928) (Teddy Katz) +* 9abc6f7 Update: fix BinaryExpression indentation edge case (fixes #8914) (#8930) (Teddy Katz) +* 0e90453 Docs: Fixing broken cyclomatic complexity link (fixes #8396) (#8937) (Chris Bargren) +* a8a8350 Chore: improve performance of `indent` rule (#8905) (Teddy Katz) +* 764b2a9 Chore: update header info in `indent` (#8926) (Teddy Katz) +* 597c217 Fix: confusing error if plugins from config is not an array (#8888) (Calvin Freitas) +* 3c1dd6d Docs: add description of no-sync `allowAtRootLevel` option (fixes #8902) (#8906) (Teddy Katz) +* 933a9cf Chore: add a fuzzer to detect bugs in core rules (#8422) (Teddy Katz) +* 45f8cd9 Docs: fix verifyAndFix result property name (#8903) (Tino Vyatkin) +* 1a89e1c Docs: Fix always-multiline example in multiline-ternary docs (#8904) (Nathan Woltman) + +v4.2.0 - July 8, 2017 + +* e0f0101 Update: fix indentation of nested function parameters (fixes #8892) (#8900) (Teddy Katz) +* 9f95a3e Chore: remove unused helper method from `indent` (#8901) (Teddy Katz) +* 11ffe6b Fix: no-regex-spaces rule incorrectly fixes quantified spaces (#8773) (Keri Warr) +* 975dacf Update: fix indentation of EmptyStatements (fixes #8882) (#8885) (Teddy Katz) +* 88ed041 Build: Turnoff CI branch build (fixes #8804) (#8873) (Gyandeep Singh) +* 72f22eb Chore: replace is-my-json-valid with Ajv (#8852) (Gajus Kuizinas) +* 7c8de92 Docs: Clarified PR guidelines in maintainer guide (#8876) (Kevin Partington) +* d1fc408 Docs: Update CLA link in Contributing docs (#8883) (Calvin Freitas) +* 931a9f1 Fix: indent false positive with multi-line await expression (#8837) (薛定谔的猫) +* 3767cda Update: add no-sync option to allow at root level (fixes #7985) (#8859) (Victor Hom) +* 1ce553d Docs: Fix wording of minProperties in object-curly-newline (fixes #8874) (#8878) (solmsted) +* f00854e Fix: --quiet no longer fixes warnings (fixes #8675) (#8858) (Kevin Partington) +* b678535 Chore: Add collapsible block for config in ISSUE_TEMPLATE (#8872) (Gyandeep Singh) +* 1f5bfc2 Update: Add always-multiline option to multiline-ternary (fixes #8770) (#8841) (Nathan Woltman) +* 22116f2 Fix: correct comma-dangle JSON schema (#8864) (Evgeny Poberezkin) +* 676af9e Update: fix indentation of JSXExpressionContainer contents (fixes #8832) (#8850) (Teddy Katz) +* 330dd58 Chore: fix title of linter test suite (#8861) (Teddy Katz) +* 60099ed Chore: enable for-direction rule on ESLint codebase (#8853) (薛定谔的猫) +* e0d1a84 Chore: upgrade eslint-plugin-eslint-plugin & eslint-plugin-node (#8856) (薛定谔的猫) +* 0780d86 Chore: remove identical tests (#8851) (Teddy Katz) +* 5c3ac8e Fix: arrow-parens fixer gets tripped up with trailing comma in args (#8838) (薛定谔的猫) +* c4f2e29 Build: fix race condition in demo (#8827) (Teddy Katz) +* c693be5 New: Allow passing a function as `fix` option (fixes #8039) (#8730) (Ian VanSchooten) +* 8796d55 Docs: add missing item to 4.0 migration guide table of contents (#8835) (薛定谔的猫) +* 742998c doc md update: false -> `false` (#8825) (Erik Vold) +* ce969f9 Docs: add guidelines for patch release communication (fixes #7277) (#8823) (Teddy Katz) +* 5c83c99 Docs: Clarify arrow function parens in no-extra-parens (fixes #8741) (#8822) (Kevin Partington) +* 84d921d Docs: Added note about Node/CJS scoping to no-redeclare (fixes #8814) (#8820) (Kevin Partington) +* 85c9327 Update: fix parenthesized CallExpression indentation (fixes #8790) (#8802) (Teddy Katz) +* be8d354 Update: simplify variable declarator indent handling (fixes #8785) (#8801) (Teddy Katz) +* 9417818 Fix: no-debugger autofixer produced invalid syntax (#8806) (Teddy Katz) +* 8698a92 New: getter-return rule (fixes #8449) (#8460) (薛定谔的猫) +* eac06f2 Fix: no-extra-parens false positives for variables called "let" (#8808) (Teddy Katz) +* 616587f Fix: dot-notation autofix produces syntax errors for object called "let" (#8807) (Teddy Katz) +* a53ef7e Fix: don't require a third argument in linter.verifyAndFix (fixes #8805) (#8809) (Teddy Katz) +* 5ad8b70 Docs: add minor formatting improvement to paragraph about parsers (#8816) (Teddy Katz) + +v4.1.1 - June 25, 2017 + +* f307aa0 Fix: ensure configs from a plugin are cached separately (fixes #8792) (#8798) (Teddy Katz) +* 8b48ae8 Docs: Add doc on parser services (fixes #8390) (#8795) (Victor Hom) +* 0d041e7 Fix: avoid crashing when using baseConfig with extends (fixes #8791) (#8797) (Teddy Katz) +* 03213bb Chore: improve comment explanation of `indent` internal functions (#8800) (Teddy Katz) +* d2e88ed Chore: Fix misleading comment in ConfigCache.js (#8799) (Teddy Katz) + +v4.1.0 - June 23, 2017 + +* e8f1362 Docs: Remove wrong descriptions in `padded-block` rule (#8783) (Plusb Preco) +* 291a783 Update: `enforceForArrowConditionals` to `no-extra-parens` (fixes #6196) (#8439) (Evilebot Tnawi) +* a21dd32 New: Add `overrides`/`files` options for glob-based config (fixes #3611) (#8081) (Sylvan Mably) +* 879688c Update: Add ignoreComments option to no-trailing-spaces (#8061) (Jake Roussel) +* b58ae2e Chore: Only instantiate fileEntryCache when cache flage set (perf) (#8763) (Gyandeep Singh) +* 9851288 Update: fix indent errors on multiline destructure (fixes #8729) (#8756) (Victor Hom) +* 3608f06 Docs: Increase visibility of code of conduct (fixes #8758) (#8764) (Kai Cataldo) +* 673a58b Update: support multiple fixes in a report (fixes #7348) (#8101) (Toru Nagashima) +* 7a1bc38 Fix: don't pass default parserOptions to custom parsers (fixes #8744) (#8745) (Teddy Katz) +* c5b4052 Chore: enable computed-property-spacing on ESLint codebase (#8760) (Teddy Katz) +* 3419f64 Docs: describe how to use formatters on the formatter demo page (#8754) (Teddy Katz) +* a3ff8f2 Chore: combine tests in tests/lib/eslint.js and tests/lib/linter.js (#8746) (Teddy Katz) +* b7cc1e6 Fix: Space-infix-ops should ignore type annotations in TypeScript (#8341) (Reyad Attiyat) +* 46e73ee Fix: eslint --init installs wrong dependencies of popular styles (fixes #7338) (#8713) (Toru Nagashima) +* a82361b Chore: Prevent package-lock.json files from being created (fixes #8742) (#8747) (Teddy Katz) +* 5f81a68 New: Add eslintIgnore support to package.json (fixes #8458) (#8690) (Victor Hom) +* b5a70b4 Update: fix multiline binary operator/parentheses indentation (#8719) (Teddy Katz) +* ab8b016 Update: fix MemberExpression indentation with "off" option (fixes #8721) (#8724) (Teddy Katz) +* eb5d12c Update: Add Fixer method to Linter API (#8631) (Gyandeep Singh) +* 26a2daa Chore: Cache fs reads in ignored-paths (fixes #8363) (#8706) (Victor Hom) + +v4.0.0 - June 11, 2017 + +* 4aefb49 Chore: avoid using deprecated rules on ESLint codebase (#8708) (Teddy Katz) +* 389feba Chore: upgrade deps. (#8684) (薛定谔的猫) +* 3da7b5e Fix: Semi-Style only check for comments when tokens exist (fixes #8696) (#8697) (Reyad Attiyat) +* 3cfe9ee Fix: Add space between async and param on fix (fixes #8682) (#8693) (Reyad Attiyat) +* c702858 Chore: enable no-multiple-empty-lines on ESLint codebase (#8694) (Teddy Katz) +* 34c4020 Update: Add support for parens on left side for-loops (fixes: #8393) (#8679) (Victor Hom) +* 735cd09 Docs: Correct the comment in an example for `no-mixed-requires` (#8686) (Fangzhou Li) +* 026f048 Chore: remove dead code from prefer-const (#8683) (Teddy Katz) + +v4.0.0-rc.0 - June 2, 2017 + +* 0058b0f8 Update: add --fix to no-debugger (#8660) (薛定谔的猫) +* b4daa225 Docs: Note to --fix option for strict rule (#8680) (Vitaliy Potapov) +* 4df33e7c Chore: check for root:true in project sooner (fixes #8561) (#8638) (Victor Hom) +* c9b980ce Build: Add Node 8 on travis (#8669) (Gyandeep Singh) +* 95248336 Fix: Don't check object destructing in integer property (fixes #8654) (#8657) (flowmemo) +* c4ac969c Update: fix parenthesized ternary expression indentation (fixes #8637) (#8649) (Teddy Katz) +* 4f2f9fcb Build: update license checker to allow LGPL (fixes #8647) (#8652) (Teddy Katz) +* b0c83bd1 Docs: suggest pushing new commits to a PR instead of amending (#8632) (Teddy Katz) +* d0e9fd2d Fix: Config merge to correctly account for extends (fixes #8193) (#8636) (Gyandeep Singh) +* 705d88f7 Docs: Update CLA link on Pull Requests page (#8642) (Teddy Katz) +* 794d4d6c Docs: missing paren on readme (#8640) (Dan Beam) +* 7ebd9d6f New: array-element-newline rule (fixes #6075) (#8375) (Jan Peer Stöcklmair) +* f62cff66 Chore: Remove dependency to user-home (fixes #8604) (#8629) (Pavol Madar) +* 936bc174 Docs: Add missing documentation for scoped modules in sharable config developer-guide (#8610) (Jonathan Samines) + +v4.0.0-beta.0 - May 19, 2017 + +* 2f7015b6 New: semi-style rule (fixes #8169) (#8542) (Toru Nagashima) +* 1eaef580 Revert "Breaking: Traverse into type annotations (fixes #7129) (#8365)" (#8584) (Kai Cataldo) +* eb14584a Fix: no-unneeded-ternary change code behavior after fix (fixes #8507) (#8624) (Jan Peer Stöcklmair) +* 3ec436ee Breaking: New Linter API (fixes #8454) (#8465) (Gyandeep Singh) +* 3fc9653a Fix: Call expression consistency in variable declaration (fixes #8607) (#8619) (Reyad Attiyat) +* 5b6093ef Docs: Remove .eslintignore reference to transpiled file filtering (#8622) (Alex Summer) +* 729bbcdb Chore: Fix lgtm alerts. (#8611) (Max Schaefer) +* 3418479a Update: improve indent of `flatTernaryExpressions` (fixes #8481) (#8587) (Toru Nagashima) +* 268d52ef Update: Use sane defaults for JSX indentation (fixes #8425) (#8593) (Teddy Katz) +* d21f5283 Chore: make shelljs a devDependency instead of a dependency (#8608) (Teddy Katz) +* 11493781 Docs: Rephrase in about section (#8609) (Sudarsan G P) +* 23401626 Chore: remove strip-bom dependency (refs #8603) (#8606) (Teddy Katz) +* a93a2f95 New: padding-line-between-statements rule (fixes #7356) (#8099) (Toru Nagashima) +* 0ef09ea0 New: for-direction rule (fixes #8387) (#8519) (薛定谔的猫) +* a73e6c09 Fix: Fix failing uknown node test since #8569 indents class bodies (#8588) (Reyad Attiyat) +* c6c639d6 Fix: Ignore unknown nodes for Indent rule (fixes #8440) (#8504) (Reyad Attiyat) +* df17bc87 Fix: object-shorthand crash on some computed keys (fixes #8576) (#8577) (Teddy Katz) +* 482d5720 New: switch-colon-spacing rule (fixes #7981) (#8540) (Toru Nagashima) +* afa35c68 Update: check allman-style classes correctly in indent (fixes #8493) (#8569) (Teddy Katz) +* de0b4ad7 Fix: Indent Ignore Variable Declaration init operator (fixes #8546) (#8563) (Reyad Attiyat) +* 927ca0dc Fix: invalid syntax from prefer-arrow-callback autofixer (fixes #8541) (#8555) (Teddy Katz) +* 25db3d22 Chore: avoid skipping test for env overrides (refs #8291) (#8556) (Teddy Katz) +* 456f519b Update: make indent MemberExpression handling more robust (fixes #8552) (#8554) (Teddy Katz) +* 873310e5 Fix: run no-unexpected-multiline only if needed (fixes #8550) (#8551) (Ruben Bridgewater) +* 833a0cad Fix: confusing RuleTester error message when options is not an array (#8557) (Teddy Katz) + +v4.0.0-alpha.2 - May 5, 2017 + +* 74ab344 Update: check allman-style blocks correctly in indent rule (fixes #8493) (#8499) (Teddy Katz) +* f6256d4 Update: no-extend-native checks global scope refs only (fixes #8461) (#8528) (Kevin Partington) +* b463045 Docs: add typescript-eslint-parser (#8388) (#8534) (薛定谔的猫) +* 99c56d5 Update: handle multiline parents consistently in indent (fixes #8455) (#8498) (Teddy Katz) +* cf940c6 Update: indent `from` tokens in import statements (fixes #8438) (#8466) (Teddy Katz) +* 0a9a90f Fix: max-len doesn't allow comments longer than code (#8532) (Ken Gregory) +* 734846b Breaking: validate eslintrc properties (fixes #8213) (#8295) (alberto) +* 025e97a Chore: delete duplicated test. (#8527) (薛定谔的猫) +* 6a333ff Upgrade: espree@^3.4.2 (#8526) (Kevin Partington) +* e52d998 Docs: Configuring Cascading and Hierarchy example correction (#8512) (Cheong Yip) +* e135aa5 Docs: Correct code of conduct link on Readme.md (#8517) (Zander Mackie) +* 37e3ba1 Chore: Add license report and scan status (#8503) (Kevin Wang) +* afbea78 Chore: don't pull default options from eslint:recommended (fixes #8374) (#8381) (Teddy Katz) +* d49acc3 Update: fix no-self-compare false negative on non-literals (fixes #7677) (#8492) (Teddy Katz) +* aaa1a81 Fix: avoid creating extra whitespace in brace-style fixer (fixes #7621) (#8491) (Teddy Katz) +* 9c3da77 Docs: list another related rule in no-undefined (#8467) (Ethan) +* f987814 Docs: Update CHANGELOG.md for v4.0.0-alpha.1 release (#8488) (Kai Cataldo) + +v4.0.0-alpha.1 - April 21, 2017 + +* b0dadfe3 Docs: Update comments section of Migrating to v4.0.0 (#8486) (Kai Cataldo) +* b337738f Update: Add `consistent` option to `object-curly-newline` (fixes #6488) (#7720) (Evilebot Tnawi) +* 53fefb3b Update: add fix for no-confusing-arrow (#8347) (Mordy Tikotzky) +* 735d02d5 Update: Deprecate sourceCode.getComments() (fixes #8408) (#8434) (Kai Cataldo) +* ac39e3b0 Update: no-unexpected-multiline to flag confusing division (fixes #8469) (#8475) (Teddy Katz) +* e35107f0 Fix: indent crash on arrow functions without parens at start of line (#8477) (Teddy Katz) +* 973adeb6 Docs: State that functions option only applies in ES2017 (fixes #7809) (#8468) (Thenaesh Elango) +* 7bc6fe0a New: array-bracket-newline rule (#8314) (Jan Peer Stöcklmair) +* 10a1a2d7 Chore: Do not use cache when testing (#8464) (Kai Cataldo) +* 9f540fd2 Update: no-unused-vars false negative about destructuring (fixes #8442) (#8459) (Toru Nagashima) +* 741ed393 Docs: Clarify how to run local ESLint installation (#8463) (Kai Cataldo) +* fac53890 Breaking: Remove array-callback-return from recommended (fixes #8428) (#8433) (Kai Cataldo) +* 288c96c1 Upgrade: dependencies (#8304) (alberto) +* 48700fc8 Docs: Remove extra header line from LICENSE (#8448) (Teddy Katz) +* 161ee4ea Chore: avoid cloning comments array in TokenStore (#8436) (Teddy Katz) +* 0c2a386e Docs: clarify new indent behavior with MemberExpressions (#8432) (Teddy Katz) +* 446b8876 Docs: update space-before-function-paren docs for 4.0 (fixes #8430) (#8431) (Teddy Katz) + +v4.0.0-alpha.0 - April 7, 2017 + +* 950874f Docs: add 4.0.0 migration guide (fixes #8306) (#8313) (Teddy Katz) +* 2754141 Fix: more autofix token-combining bugs (#8394) (Teddy Katz) +* f5a7e42 Breaking: log number of fixable problems (fixes #7364) (#8324) (alberto) +* 769b121 Chore: Fix indentation errors in indent-legacy (#8424) (Kai Cataldo) +* 8394e48 Update: add deprecated indent-legacy rule as v3.x indent rule snapshot (#8286) (Teddy Katz) +* 3c87e85 Fix: no-multi-spaces false positive with irregular indent whitespace (#8412) (Teddy Katz) +* cc53481 Breaking: rewrite indent (fixes #1801, #3737, #3845, #6007, ...16 more) (#7618) (Teddy Katz) +* 867dd2e Breaking: Calculate leading/trailing comments in core (#7516) (Kai Cataldo) +* de9f1a0 Docs: ES6 syntax vs globals configuration (fixes #7984) (#8350) (Zander Mackie) +* 66af53e Breaking: Traverse into type annotations (fixes #7129) (#8365) (Kai Cataldo) +* 86cf3e4 New: no-buffer-constructor rule (fixes #5614) (#8413) (Teddy Katz) +* f560c06 Update: fix space-unary-ops behavior with postfix UpdateExpressions (#8391) (Teddy Katz) +* 936af66 Fix: no-multiple-empty-lines crash on space after last \n (fixes #8401) (#8402) (Teddy Katz) +* e395919 Breaking: Resolve patterns from .eslintignore directory (fixes #6759) (#7678) (Ian VanSchooten) +* c778676 Breaking: convert RuleTester to ES6 class (refs #8231) (#8263) (Teddy Katz) +* 6f7757e Breaking: convert SourceCode to ES6 class (refs #8231) (#8264) (Teddy Katz) +* 8842d7e Chore: fix comment spacing in tests (#8405) (Teddy Katz) +* 9a9d916 Breaking: update eslint:recommended for 4.0.0 (fixes #8236) (#8372) (Teddy Katz) +* b0c63f0 Breaking: infer endLine and endColumn from a reported node (fixes #8004) (#8234) (Teddy Katz) +* 40b8c69 Breaking: no-multi-spaces check around inline comments (fixes #7693) (#7696) (Kai Cataldo) +* 034a575 Breaking: convert CLIEngine to ES6 class (refs #8231) (#8262) (Teddy Katz) +* 7dd890d Breaking: tweak space-before-function-paren default option (fixes #8267) (#8285) (Teddy Katz) +* 0e0dd27 Breaking: Remove `ecmaFeatures` from `eslint:recommended` (#8239) (alberto) +* 2fa7502 Breaking: disallow scoped plugin references without scope (fixes #6362) (#8233) (Teddy Katz) +* 4673f6e Chore: Switch to eslint-scope from escope (#8280) (Corbin Uselton) +* e232464 Breaking: change defaults for padded-blocks (fixes #7879) (#8134) (alberto) + v3.19.0 - March 31, 2017 * e09132f Fix: no-extra-parens false positive with exports and object literals (#8359) (Teddy Katz) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000000..2fc80ddcd823 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +This project adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f288ce8009c..411e7fb523b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,20 +2,24 @@ Please be sure to read the contribution guidelines before making or requesting a change. +## Code of Conduct + +This project adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct). We kindly request that you read over our code of conduct before contributing. + ## Filing Issues Before filing an issue, please be sure to read the guidelines for what you're reporting: -* [Bug Report](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) -* [Propose a New Rule](http://eslint.org/docs/developer-guide/contributing/new-rules) -* [Proposing a Rule Change](http://eslint.org/docs/developer-guide/contributing/rule-changes) -* [Request a Change](http://eslint.org/docs/developer-guide/contributing/changes) +* [Bug Report](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) +* [Propose a New Rule](https://eslint.org/docs/developer-guide/contributing/new-rules) +* [Proposing a Rule Change](https://eslint.org/docs/developer-guide/contributing/rule-changes) +* [Request a Change](https://eslint.org/docs/developer-guide/contributing/changes) ## Contributing Code -Please sign our [Contributor License Agreement](https://contribute.jquery.org/CLA/) and read over the [Pull Request Guidelines](http://eslint.org/docs/developer-guide/contributing/pull-requests). +Please sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint) and read over the [Pull Request Guidelines](https://eslint.org/docs/developer-guide/contributing/pull-requests). ## Full Documentation Our full contribution guidelines can be found at: -http://eslint.org/docs/developer-guide/contributing/ +https://eslint.org/docs/developer-guide/contributing/ diff --git a/LICENSE b/LICENSE index 777939e8fc1a..7fe552a86615 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,3 @@ -ESLint Copyright JS Foundation and other contributors, https://js.foundation Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/Makefile.js b/Makefile.js index 60f02b58c442..02363aa6641f 100644 --- a/Makefile.js +++ b/Makefile.js @@ -3,7 +3,7 @@ * @author nzakas */ -/* global cat, cd, cp, echo, exec, exit, find, ls, mkdir, pwd, rm, target, test*/ +/* global target */ /* eslint no-use-before-define: "off", no-console: "off" */ "use strict"; @@ -20,14 +20,27 @@ const lodash = require("lodash"), fs = require("fs"), glob = require("glob"), markdownlint = require("markdownlint"), - nodeCLI = require("shelljs-nodecli"), os = require("os"), path = require("path"), semver = require("semver"), + shell = require("shelljs"), ejs = require("ejs"), loadPerf = require("load-perf"), yaml = require("js-yaml"); +const cat = shell.cat; +const cd = shell.cd; +const cp = shell.cp; +const echo = shell.echo; +const exec = shell.exec; +const exit = shell.exit; +const find = shell.find; +const ls = shell.ls; +const mkdir = shell.mkdir; +const pwd = shell.pwd; +const rm = shell.rm; +const test = shell.test; + //------------------------------------------------------------------------------ // Settings //------------------------------------------------------------------------------ @@ -41,7 +54,7 @@ const lodash = require("lodash"), const PERF_MULTIPLIER = 13e6; const OPEN_SOURCE_LICENSES = [ - /MIT/, /BSD/, /Apache/, /ISC/, /WTF/, /Public Domain/ + /MIT/, /BSD/, /Apache/, /ISC/, /WTF/, /Public Domain/, /LGPL/ ]; //------------------------------------------------------------------------------ @@ -51,6 +64,7 @@ const OPEN_SOURCE_LICENSES = [ const NODE = "node ", // intentional extra space NODE_MODULES = "./node_modules/", TEMP_DIR = "./tmp/", + DEBUG_DIR = "./debug/", BUILD_DIR = "./build/", DOCS_DIR = "../eslint.github.io/docs", SITE_DIR = "../eslint.github.io/", @@ -58,11 +72,11 @@ const NODE = "node ", // intentional extra space // Utilities - intentional extra space at the end of each string MOCHA = `${NODE_MODULES}mocha/bin/_mocha `, - ESLINT = `${NODE} bin/eslint.js --rulesdir lib/internal-rules/ `, + ESLINT = `${NODE} bin/eslint.js --report-unused-disable-directives `, // Files MAKEFILE = "./Makefile.js", - JS_FILES = "\"lib/**/*.js\" \"conf/**/*.js\" \"bin/**/*.js\"", + JS_FILES = "\"lib/**/*.js\" \"conf/**/*.js\" \"bin/**/*.js\" \"tools/**/*.js\"", JSON_FILES = find("conf/").filter(fileType("json")), MARKDOWN_FILES_ARRAY = find("docs/").concat(ls(".")).filter(fileType("md")), TEST_FILES = getTestFilePatterns(), @@ -70,9 +84,6 @@ const NODE = "node ", // intentional extra space PERF_MULTIFILES_TARGET_DIR = path.join(PERF_TMP_DIR, "eslint"), PERF_MULTIFILES_TARGETS = `"${PERF_MULTIFILES_TARGET_DIR + path.sep}{lib,tests${path.sep}lib}${path.sep}**${path.sep}*.js"`, - // Regex - TAG_REGEX = /^(?:Breaking|Build|Chore|Docs|Fix|New|Update|Upgrade):/, - // Settings MOCHA_TIMEOUT = 10000; @@ -86,16 +97,12 @@ const NODE = "node ", // intentional extra space * @private */ function getTestFilePatterns() { - const testLibPath = "tests/lib/", - testTemplatesPath = "tests/templates/", - testBinPath = "tests/bin/"; - - return ls(testLibPath).filter(pathToCheck => test("-d", testLibPath + pathToCheck)).reduce((initialValue, currentValues) => { + return ls("tests/lib/").filter(pathToCheck => test("-d", `tests/lib/${pathToCheck}`)).reduce((initialValue, currentValues) => { if (currentValues !== "rules") { - initialValue.push(`"${testLibPath + currentValues}/**/*.js"`); + initialValue.push(`"tests/lib/${currentValues}/**/*.js"`); } return initialValue; - }, [`"${testLibPath}rules/**/*.js"`, `"${testLibPath}*.js"`, `"${testTemplatesPath}*.js"`, `"${testBinPath}**/*.js"`]).join(" "); + }, ["\"tests/lib/rules/**/*.js\"", "\"tests/lib/*.js\"", "\"tests/bin/**/*.js\"", "\"tests/tools/**/*.js\""]).join(" "); } /** @@ -118,7 +125,7 @@ function validateJsonFile(filePath) { */ function fileType(extension) { return function(filename) { - return filename.substring(filename.lastIndexOf(".") + 1) === extension; + return filename.slice(filename.lastIndexOf(".") + 1) === extension; }; } @@ -149,7 +156,7 @@ function generateRulesIndex(basedir) { * @returns {string} The result of the executed command. */ function execSilent(cmd) { - return exec(cmd, { silent: true }).output; + return exec(cmd, { silent: true }).stdout; } /** @@ -332,7 +339,7 @@ function getFirstCommitOfFile(filePath) { * @param {string} filePath The file path to check. * @returns {string} The tag name. */ -function getTagOfFirstOccurrence(filePath) { +function getFirstVersionOfFile(filePath) { const firstCommit = getFirstCommitOfFile(filePath); let tags = execSilent(`git tag --contains ${firstCommit}`); @@ -346,15 +353,6 @@ function getTagOfFirstOccurrence(filePath) { }, []).sort(semver.compare)[0]; } -/** - * Gets the version number where a given file was introduced first. - * @param {string} filePath The file path to check. - * @returns {string} The version number. - */ -function getFirstVersionOfFile(filePath) { - return getTagOfFirstOccurrence(filePath); -} - /** * Gets the commit that deleted a file. * @param {string} filePath The path to the deleted file. @@ -381,24 +379,6 @@ function getFirstVersionOfDeletion(filePath) { .sort(semver.compare)[0]; } - -/** - * Returns all the branch names - * @returns {string[]} branch names - * @private - */ -function getBranches() { - const branchesRaw = splitCommandResultToLines(execSilent("git branch --list")), - branches = []; - - for (let i = 0; i < branchesRaw.length; i++) { - const branchName = branchesRaw[i].replace(/^\*(.*)/, "$1").trim(); - - branches.push(branchName); - } - return branches; -} - /** * Lints Markdown files. * @param {array} files Array of file names to lint. @@ -412,7 +392,7 @@ function lintMarkdown(files) { // Exclusions for deliberate/widespread violations MD001: false, // Header levels should only increment by one level at a time MD002: false, // First header should be a h1 header - MD007: { // Unordered list indentation + MD007: { // Unordered list indentation indent: 4 }, MD012: false, // Multiple consecutive blank lines @@ -427,7 +407,7 @@ function lintMarkdown(files) { MD033: false, // Allow inline HTML MD034: false, // Bare URL used MD040: false, // Fenced code blocks should have a language specified - MD041: false // First line in file should be a top level header + MD041: false // First line in file should be a top level header }, result = markdownlint.sync({ files, @@ -443,25 +423,13 @@ function lintMarkdown(files) { return { code: returnCode }; } -/** - * Check if the branch name is valid - * @param {string} branchName Branch name to check - * @returns {boolean} true is branch exists - * @private - */ -function hasBranch(branchName) { - const branches = getBranches(); - - return branches.indexOf(branchName) !== -1; -} - /** * Gets linting results from every formatter, based on a hard-coded snippet and config * @returns {Object} Output from each formatter */ function getFormatterResults() { const CLIEngine = require("./lib/cli-engine"), - chalk = require("chalk"); + stripAnsi = require("strip-ansi"); const formatterFiles = fs.readdirSync("./lib/formatters/"), cli = new CLIEngine({ @@ -492,13 +460,22 @@ function getFormatterResults() { if (fileExt === ".js") { data.formatterResults[name] = { - result: chalk.stripColor(cli.getFormatter(name)(rawMessages.results)) + result: stripAnsi(cli.getFormatter(name)(rawMessages.results)) }; } return data; }, { formatterResults: {} }); } +/** + * Gets a path to an executable in node_modules/.bin + * @param {string} command The executable name + * @returns {string} The executable path + */ +function getBinFile(command) { + return path.join("node_modules", ".bin", command); +} + //------------------------------------------------------------------------------ // Tasks //------------------------------------------------------------------------------ @@ -509,20 +486,16 @@ target.all = function() { target.lint = function() { let errors = 0, - makeFileCache = " ", - jsCache = " ", - testCache = " ", lastReturn; - // using the cache locally to speed up linting process - if (!process.env.TRAVIS) { - makeFileCache = " --cache --cache-file .cache/makefile_cache "; - jsCache = " --cache --cache-file .cache/js_cache "; - testCache = " --cache --cache-file .cache/test_cache "; + echo("Validating Makefile.js"); + lastReturn = exec(`${ESLINT} ${MAKEFILE}`); + if (lastReturn.code !== 0) { + errors++; } - echo("Validating Makefile.js"); - lastReturn = exec(ESLINT + makeFileCache + MAKEFILE); + echo("Validating .eslintrc.js"); + lastReturn = exec(`${ESLINT} .eslintrc.js`); if (lastReturn.code !== 0) { errors++; } @@ -537,13 +510,13 @@ target.lint = function() { } echo("Validating JavaScript files"); - lastReturn = exec(ESLINT + jsCache + JS_FILES); + lastReturn = exec(`${ESLINT} ${JS_FILES}`); if (lastReturn.code !== 0) { errors++; } echo("Validating JavaScript test files"); - lastReturn = exec(`${ESLINT}${testCache}"tests/**/*.js"`); + lastReturn = exec(`${ESLINT} "tests/**/*.js"`); if (lastReturn.code !== 0) { errors++; } @@ -553,27 +526,62 @@ target.lint = function() { } }; +target.fuzz = function() { + const fuzzerRunner = require("./tools/fuzzer-runner"); + const fuzzResults = fuzzerRunner.run({ amount: process.env.CI ? 1000 : 300 }); + + if (fuzzResults.length) { + echo(`The fuzzer reported ${fuzzResults.length} error${fuzzResults.length === 1 ? "" : "s"}.`); + + const formattedResults = JSON.stringify({ results: fuzzResults }, null, 4); + + if (process.env.CI) { + echo("More details can be found below."); + echo(formattedResults); + } else { + if (!test("-d", DEBUG_DIR)) { + mkdir(DEBUG_DIR); + } + + let fuzzLogPath; + let fileSuffix = 0; + + // To avoid overwriting any existing fuzzer log files, append a numeric suffix to the end of the filename. + do { + fuzzLogPath = path.join(DEBUG_DIR, `fuzzer-log-${fileSuffix}.json`); + fileSuffix++; + } while (test("-f", fuzzLogPath)); + + formattedResults.to(fuzzLogPath); + + // TODO: (not-an-aardvark) Create a better way to isolate and test individual fuzzer errors from the log file + echo(`More details can be found in ${fuzzLogPath}.`); + } + + exit(1); + } +}; + target.test = function() { target.lint(); target.checkRuleFiles(); let errors = 0, lastReturn; - // exec(ISTANBUL + " cover " + MOCHA + "-- -c " + TEST_FILES); - lastReturn = nodeCLI.exec("istanbul", "cover", MOCHA, `-- -R progress -t ${MOCHA_TIMEOUT}`, "-c", TEST_FILES); + lastReturn = exec(`${getBinFile("istanbul")} cover ${MOCHA} -- -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`); if (lastReturn.code !== 0) { errors++; } - // exec(ISTANBUL + "check-coverage --statement 99 --branch 98 --function 99 --lines 99"); - lastReturn = nodeCLI.exec("istanbul", "check-coverage", "--statement 99 --branch 98 --function 99 --lines 99"); + lastReturn = exec(`${getBinFile("istanbul")} check-coverage --statement 99 --branch 98 --function 99 --lines 99`); + if (lastReturn.code !== 0) { errors++; } target.browserify(); - lastReturn = nodeCLI.exec("karma", "start karma.conf.js"); + lastReturn = exec(`${getBinFile("karma")} start karma.conf.js`); if (lastReturn.code !== 0) { errors++; } @@ -587,7 +595,7 @@ target.test = function() { target.docs = function() { echo("Generating documentation"); - nodeCLI.exec("jsdoc", "-d jsdoc lib"); + exec(`${getBinFile("jsdoc")} -d jsdoc lib`); echo("Documentation has been output to /jsdoc"); }; @@ -608,11 +616,13 @@ target.gensite = function(prereleaseVersion) { } // 1. create temp and build directory + echo("> Creating a temporary directory (Step 1)"); if (!test("-d", TEMP_DIR)) { mkdir(TEMP_DIR); } // 2. remove old files from the site + echo("> Removing old files (Step 2)"); docFiles.forEach(filePath => { const fullPath = path.join(DOCS_DIR, filePath), htmlFullPath = fullPath.replace(".md", ".html"); @@ -628,6 +638,7 @@ target.gensite = function(prereleaseVersion) { }); // 3. Copy docs folder to a temporary directory + echo("> Copying the docs folder (Step 3)"); cp("-rf", "docs/*", TEMP_DIR); let versions = test("-f", "./versions.json") ? JSON.parse(cat("./versions.json")) : {}; @@ -645,7 +656,11 @@ target.gensite = function(prereleaseVersion) { const FIXABLE_TEXT = "\n\n(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule."; // 4. Loop through all files in temporary directory - find(TEMP_DIR).forEach(filename => { + process.stdout.write("> Updating files (Steps 4-9): 0/... - ...\r"); + const tempFiles = find(TEMP_DIR); + const length = tempFiles.length; + + tempFiles.forEach((filename, i) => { if (test("-f", filename) && path.extname(filename) === ".md") { const rulesUrl = "https://github.com/eslint/eslint/tree/master/lib/rules/", @@ -653,10 +668,13 @@ target.gensite = function(prereleaseVersion) { baseName = path.basename(filename), sourceBaseName = `${path.basename(filename, ".md")}.js`, sourcePath = path.join("lib/rules", sourceBaseName), - ruleName = path.basename(filename, ".md"); + ruleName = path.basename(filename, ".md"), + filePath = path.join("docs", path.relative("tmp", filename)); let text = cat(filename), title; + process.stdout.write(`> Updating files (Steps 4-9): ${i}/${length} - ${filePath + " ".repeat(30)}\r`); + // 5. Prepend page title and layout variables at the top of rules if (path.dirname(filename).indexOf("rules") >= 0) { @@ -672,7 +690,7 @@ target.gensite = function(prereleaseVersion) { text = `${ruleHeading}${isRecommended ? RECOMMENDED_TEXT : ""}${isFixable ? FIXABLE_TEXT : ""}\n${ruleDocsContent}`; - text = `---\ntitle: ${ruleName} - Rules\nlayout: doc\n---\n\n\n${text}`; + title = `${ruleName} - Rules`; } else { // extract the title from the file itself @@ -682,9 +700,19 @@ target.gensite = function(prereleaseVersion) { } else { title = "Documentation"; } - text = `---\ntitle: ${title}\nlayout: doc\n---\n\n\n${text}`; } + text = [ + "---", + `title: ${title}`, + "layout: doc", + `edit_link: https://github.com/eslint/eslint/edit/master/${filePath}`, + "---", + "", + "", + text + ].join("\n"); + // 6. Remove .md extension for relative links and change README to empty string text = text.replace(/\((?!https?:\/\/)([^)]*?)\.md.*?\)/g, "($1)").replace("README.html", ""); @@ -722,8 +750,10 @@ target.gensite = function(prereleaseVersion) { } }); JSON.stringify(versions).to("./versions.json"); + echo(`> Updating files (Steps 4-9)${" ".repeat(50)}`); // 10. Copy temporary directory to site's docs folder + echo("> Copying the temporary directory the site (Step 10)"); let outputDir = DOCS_DIR; if (prereleaseVersion) { @@ -732,18 +762,27 @@ target.gensite = function(prereleaseVersion) { cp("-rf", `${TEMP_DIR}*`, outputDir); // 11. Generate rule listing page + echo("> Generating the rule listing (Step 11)"); generateRuleIndexPage(process.cwd()); // 12. Delete temporary directory + echo("> Removing the temporary directory (Step 12)"); rm("-r", TEMP_DIR); // 13. Update demos, but only for non-prereleases if (!prereleaseVersion) { + echo("> Updating the demos (Step 13)"); + target.browserify(); cp("-f", "build/eslint.js", `${SITE_DIR}js/app/eslint.js`); + } else { + echo("> Skipped updating the demos (Step 13)"); } // 14. Create Example Formatter Output Page + echo("> Creating the formatter examples (Step 14)"); generateFormatterExamples(getFormatterResults(), prereleaseVersion); + + echo("Done generating eslint.org"); }; target.browserify = function() { @@ -767,10 +806,10 @@ target.browserify = function() { generateRulesIndex(TEMP_DIR); // 5. browserify the temp directory - nodeCLI.exec("browserify", "-x espree", `${TEMP_DIR}eslint.js`, "-o", `${BUILD_DIR}eslint.js`, "-s eslint", "-t [ babelify --presets [ es2015 ] ]"); + exec(`${getBinFile("browserify")} -x espree ${TEMP_DIR}linter.js -o ${BUILD_DIR}eslint.js -s eslint --global-transform [ babelify --presets [ es2015 ] ]`); // 6. Browserify espree - nodeCLI.exec("browserify", "-r espree", "-o", `${TEMP_DIR}espree.js`); + exec(`${getBinFile("browserify")} -r espree -o ${TEMP_DIR}espree.js`); // 7. Concatenate Babel polyfill, Espree, and ESLint files together cat("./node_modules/babel-polyfill/dist/polyfill.js", `${TEMP_DIR}espree.js`, `${BUILD_DIR}eslint.js`).to(`${BUILD_DIR}eslint.js`); @@ -811,9 +850,11 @@ target.checkRuleFiles = function() { const docText = cat(docFilename); const idOldAtEndOfTitleRegExp = new RegExp(`^# (.*?) \\(${id}\\)`); // original format const idNewAtBeginningOfTitleRegExp = new RegExp(`^# ${id}: `); // new format is same as rules index - // 1. Added support for new format. - // 2. Will remove support for old format after all docs files have new format. - // 3. Will remove this check when the main heading is automatically generated from rule metadata. + /* + * 1. Added support for new format. + * 2. Will remove support for old format after all docs files have new format. + * 3. Will remove this check when the main heading is automatically generated from rule metadata. + */ return idNewAtBeginningOfTitleRegExp.test(docText) || idOldAtEndOfTitleRegExp.test(docText); } @@ -884,7 +925,8 @@ target.checkLicenses = function() { if (impermissible.length) { impermissible.forEach(dependency => { - console.error("%s license for %s is impermissible.", + console.error( + "%s license for %s is impermissible.", dependency.licenses, dependency.name ); @@ -894,57 +936,6 @@ target.checkLicenses = function() { }); }; -target.checkGitCommit = function() { - let commitMsgs, - failed; - - if (hasBranch("master")) { - commitMsgs = splitCommandResultToLines(execSilent("git log HEAD --not master --format=format:%s --no-merges")); - } else { - commitMsgs = [execSilent("git log -1 --format=format:%s --no-merges")]; - } - - echo("Validating Commit Message"); - - // No commit since master should not cause test to fail - if (commitMsgs[0] === "") { - return; - } - - // Check for more than one commit - if (commitMsgs.length > 1) { - echo(" - More than one commit found, please squash."); - failed = true; - } - - // Only check non-release messages - if (!semver.valid(commitMsgs[0]) && !/^Revert /.test(commitMsgs[0])) { - if (commitMsgs[0].split(/\r?\n/)[0].length > 72) { - echo(" - First line of commit message must not exceed 72 characters"); - failed = true; - } - - // Check for tag at start of message - if (!TAG_REGEX.test(commitMsgs[0])) { - echo([" - Commit summary must start with one of:", - " 'Fix:'", - " 'Update:'", - " 'Breaking:'", - " 'Docs:'", - " 'Build:'", - " 'New:'", - " 'Upgrade:'", - " 'Chore:'", - " Please refer to the contribution guidelines for more details."].join("\n")); - failed = true; - } - } - - if (failed) { - exit(1); - } -}; - /** * Downloads a repository which has many js files to test performance with multi files. * Here, it's eslint@1.10.3 (450 files) @@ -1042,7 +1033,7 @@ function runPerformanceTest(title, targets, multiplier, cb) { echo(" CPU Speed is %d with multiplier %d", cpuSpeed, multiplier); time(cmd, 5, 1, [], results => { - if (!results || results.length === 0) { // No results? Something is wrong. + if (!results || results.length === 0) { // No results? Something is wrong. throw new Error("Performance test failed."); } diff --git a/README.md b/README.md index 1ccdbb9bfd42..c333edd83bfa 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,16 @@ [![Downloads][downloads-image]][downloads-url] [![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=282608)](https://www.bountysource.com/trackers/282608-eslint?utm_source=282608&utm_medium=shield&utm_campaign=TRACKER_BADGE) [![Join the chat at https://gitter.im/eslint/eslint](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/eslint/eslint?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_shield) # 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://js.foundation/conduct/) | +[Website](https://eslint.org) | +[Configuring](https://eslint.org/docs/user-guide/configuring) | +[Rules](https://eslint.org/docs/rules/) | +[Contributing](https://eslint.org/docs/developer-guide/contributing) | +[Reporting Bugs](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) | +[Code of Conduct](https://js.foundation/community/code-of-conduct) | [Twitter](https://twitter.com/geteslint) | [Mailing List](https://groups.google.com/group/eslint) | [Chat Room](https://gitter.im/eslint/eslint) @@ -26,6 +27,8 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J ## Installation and Usage +Prerequisites: [Node.js](https://nodejs.org/en/) (>=4.x), npm version 2+. + There are two ways to install ESLint: globally and locally. ### Local Installation and Usage @@ -87,17 +90,17 @@ After running `eslint --init`, you'll have a `.eslintrc` file in your directory. } ``` -The names `"semi"` and `"quotes"` are the names of [rules](http://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values: +The names `"semi"` and `"quotes"` are the names of [rules](https://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values: * `"off"` or `0` - turn the rule off * `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) * `"error"` or `2` - turn the rule on as an error (exit code will be 1) -The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](http://eslint.org/docs/user-guide/configuring)). +The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](https://eslint.org/docs/user-guide/configuring)). ## Sponsors -* Site search ([eslint.org](http://eslint.org)) is sponsored by [Algolia](https://www.algolia.com) +* Site search ([eslint.org](https://eslint.org)) is sponsored by [Algolia](https://www.algolia.com) ## Team @@ -113,6 +116,7 @@ These folks keep the project moving and are resources for help. * Alberto Rodríguez ([@alberto](https://github.com/alberto)) * Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo)) * Teddy Katz ([@not-an-aardvark](https://github.com/not-an-aardvark)) +* Kevin Partington ([@platinumazure](https://github.com/platinumazure)) ### Development Team @@ -123,31 +127,36 @@ These folks keep the project moving and are resources for help. * Michael Ficarra ([@michaelficarra](https://github.com/michaelficarra)) * Mark Pedrotti ([@pedrottimark](https://github.com/pedrottimark)) * Oleg Gaidarenko ([@markelog](https://github.com/markelog)) -* Mike Sherov [@mikesherov](https://github.com/mikesherov)) +* Mike Sherov ([@mikesherov](https://github.com/mikesherov)) * Henry Zhu ([@hzoo](https://github.com/hzoo)) * Marat Dulin ([@mdevils](https://github.com/mdevils)) * Alexej Yaroshevich ([@zxqfox](https://github.com/zxqfox)) -* Kevin Partington ([@platinumazure](https://github.com/platinumazure)) * Vitor Balocco ([@vitorbal](https://github.com/vitorbal)) * James Henry ([@JamesHenry](https://github.com/JamesHenry)) * Reyad Attiyat ([@soda0289](https://github.com/soda0289)) +* 薛定谔的猫 ([@Aladdin-ADD](https://github.com/Aladdin-ADD)) +* Victor Hom ([@VictorHom](https://github.com/VictorHom)) ## Releases We have scheduled releases every two weeks on Friday or Saturday. +## Code of Conduct + +ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct). + ## Filing Issues Before filing an issue, please be sure to read the guidelines for what you're reporting: -* [Bug Report](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) -* [Propose a New Rule](http://eslint.org/docs/developer-guide/contributing/new-rules) -* [Proposing a Rule Change](http://eslint.org/docs/developer-guide/contributing/rule-changes) -* [Request a Change](http://eslint.org/docs/developer-guide/contributing/changes) +* [Bug Report](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) +* [Propose a New Rule](https://eslint.org/docs/developer-guide/contributing/new-rules) +* [Proposing a Rule Change](https://eslint.org/docs/developer-guide/contributing/rule-changes) +* [Request a Change](https://eslint.org/docs/developer-guide/contributing/changes) ## Semantic Versioning Policy -ESLint follows [semantic versioning](http://semver.org). However, due to the nature of ESLint as a code quality tool, it's not always clear when a minor or major version bump occurs. To help clarify this for everyone, we've defined the following semantic versioning policy for ESLint: +ESLint follows [semantic versioning](https://semver.org). However, due to the nature of ESLint as a code quality tool, it's not always clear when a minor or major version bump occurs. To help clarify this for everyone, we've defined the following semantic versioning policy for ESLint: * Patch release (intended to not break your lint build) * A bug fix in a rule that results in ESLint reporting fewer errors. @@ -166,17 +175,20 @@ ESLint follows [semantic versioning](http://semver.org). However, due to the nat * Major release (likely to break your lint build) * `eslint:recommended` is updated. * A new option to an existing rule that results in ESLint reporting more errors by default. - * An existing rule is removed. * An existing formatter is removed. * Part of the public API is removed or changed in an incompatible way. According to our policy, any minor update may report more errors than the previous release (ex: from a bug fix). As such, we recommend using the tilde (`~`) in `package.json` e.g. `"eslint": "~3.1.0"` to guarantee the results of your builds. +## License + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_large) + ## Frequently Asked Questions ### How is ESLint different from JSHint? -The most significant difference is that ESlint has pluggable linting rules. That means you can use the rules it comes with, or you can extend it with rules created by others or by yourself! +The most significant difference is that ESLint has pluggable linting rules. That means you can use the rules it comes with, or you can extend it with rules created by others or by yourself! ### How does ESLint performance compare to JSHint? @@ -186,36 +198,48 @@ Despite being slower, we believe that ESLint is fast enough to replace JSHint wi ### 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. +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](https://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? -Maybe, depending on how much you need it. [JSCS has reached end of life](http://eslint.org/blog/2016/07/jscs-end-of-life), but if it is working for you then there is no reason to move 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. +Maybe, depending on how much you need it. [JSCS has reached end of life](https://eslint.org/blog/2016/07/jscs-end-of-life), but if it is working for you then there is no reason to move 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. If you are having issues with JSCS, you can try to move to ESLint. We are focusing our time and energy on JSCS compatibility issues. - ### Is ESLint just linting or does it also check style? ESLint does both traditional linting (looking for problematic patterns) and style checking (enforcement of conventions). You can use it for both. +### Why can't ESLint find my plugins? + +ESLint can be [globally or locally installed](#installation-and-usage). If you install ESLint globally, your plugins must also be installed globally; if you install ESLint locally, your plugins must also be installed locally. + +If you are trying to run globally, make sure your plugins are installed globally (use `npm ls -g`). + +If you are trying to run locally: + +* Make sure your plugins (and ESLint) are both in your project's `package.json` as devDependencies (or dependencies, if your project uses ESLint at runtime). +* Make sure you have run `npm install` and all your dependencies are installed. + +In all cases, make sure your plugins' peerDependencies have been installed as well. You can use `npm view eslint-plugin-myplugin peerDepencies` to see what peer dependencies `eslint-plugin-myplugin` has. + ### Does ESLint support JSX? -Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](http://eslint.org/docs/user-guide/configuring).). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics. +Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/user-guide/configuring)). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics. ### What about ECMAScript 6 support? -ESLint has full support for ECMAScript 6. By default, this support is off. You can enable ECMAScript 6 support through [configuration](http://eslint.org/docs/user-guide/configuring). +ESLint has full support for ECMAScript 6. By default, this support is off. You can enable ECMAScript 6 syntax and global variables through [configuration](https://eslint.org/docs/user-guide/configuring). ### What about experimental features? ESLint doesn't natively support experimental ECMAScript language features. You can use [babel-eslint](https://github.com/babel/babel-eslint) to use any option available in Babel. -Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](http://eslint.org/docs/developer-guide/contributing). Until then, please use the appropriate parser and plugin(s) for your experimental feature. +Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/developer-guide/contributing). Until then, please use the appropriate parser and plugin(s) for your experimental feature. ### Where to ask for help? -Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint) +Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint). [npm-image]: https://img.shields.io/npm/v/eslint.svg?style=flat-square diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 000000000000..08942601a15d --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1 @@ +If you have a question about how to use ESLint, please ask it in our [Gitter channel](https://gitter.im/eslint/eslint). diff --git a/appveyor.yml b/appveyor.yml index e0e77131a196..966263e293f2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,11 @@ version: "{build}" # What combinations to test environment: matrix: - - nodejs_version: 4 + - nodejs_version: 8 + +branches: + only: + - master install: # Get the latest stable version of Node.js diff --git a/bin/eslint.js b/bin/eslint.js index bf534971f24d..1a298047aed3 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -43,12 +43,14 @@ process.once("uncaughtException", err => { if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) { const template = lodash.template(fs.readFileSync(path.resolve(__dirname, `../messages/${err.messageTemplate}.txt`), "utf-8")); + const pkg = require("../package.json"); - console.log("\nOops! Something went wrong! :("); - console.log(`\n${template(err.messageData || {})}`); + console.error("\nOops! Something went wrong! :("); + console.error(`\nESLint: ${pkg.version}.\n${template(err.messageData || {})}`); } else { - console.log(err.message); - console.log(err.stack); + + console.error(err.message); + console.error(err.stack); } process.exitCode = 1; @@ -61,14 +63,12 @@ if (useStdIn) { } else if (init) { const configInit = require("../lib/config/config-initializer"); - configInit.initializeConfig(err => { - if (err) { - process.exitCode = 1; - console.error(err.message); - console.error(err.stack); - } else { - process.exitCode = 0; - } + configInit.initializeConfig().then(() => { + process.exitCode = 0; + }).catch(err => { + process.exitCode = 1; + console.error(err.message); + console.error(err.stack); }); } else { process.exitCode = cli.execute(process.argv); diff --git a/conf/category-list.json b/conf/category-list.json index b5020c1f00d8..5427667b09e3 100644 --- a/conf/category-list.json +++ b/conf/category-list.json @@ -10,12 +10,12 @@ ], "deprecated": { "name": "Deprecated", - "description": "These rules have been deprecated and replaced by newer rules:", + "description": "These rules have been deprecated in accordance with the [deprecation policy](/docs/user-guide/rule-deprecation), and replaced by newer rules:", "rules": [] }, "removed": { "name": "Removed", - "description": "These rules from older versions of ESLint have been replaced by newer rules:", + "description": "These rules from older versions of ESLint (before the [deprecation policy](/docs/user-guide/rule-deprecation) existed) have been replaced by newer rules:", "rules": [ { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] }, { "removed": "global-strict", "replacedBy": ["strict"] }, diff --git a/conf/config-schema.js b/conf/config-schema.js new file mode 100644 index 000000000000..626e1d54c520 --- /dev/null +++ b/conf/config-schema.js @@ -0,0 +1,70 @@ +/** + * @fileoverview Defines a schema for configs. + * @author Sylvan Mably + */ + +"use strict"; + +const baseConfigProperties = { + env: { type: "object" }, + globals: { type: "object" }, + parser: { type: ["string", "null"] }, + parserOptions: { type: "object" }, + plugins: { type: "array" }, + rules: { type: "object" }, + settings: { type: "object" }, + + ecmaFeatures: { type: "object" } // deprecated; logs a warning when used +}; + +const overrideProperties = Object.assign( + {}, + baseConfigProperties, + { + files: { + oneOf: [ + { type: "string" }, + { + type: "array", + items: { type: "string" }, + minItems: 1 + } + ] + }, + excludedFiles: { + oneOf: [ + { type: "string" }, + { + type: "array", + items: { type: "string" } + } + ] + } + } +); + +const topLevelConfigProperties = Object.assign( + {}, + baseConfigProperties, + { + extends: { type: ["string", "array"] }, + root: { type: "boolean" }, + overrides: { + type: "array", + items: { + type: "object", + properties: overrideProperties, + required: ["files"], + additionalProperties: false + } + } + } +); + +const configSchema = { + type: "object", + properties: topLevelConfigProperties, + additionalProperties: false +}; + +module.exports = configSchema; diff --git a/conf/cli-options.js b/conf/default-cli-options.js similarity index 62% rename from conf/cli-options.js rename to conf/default-cli-options.js index b377f3da7f39..7d46d46c5210 100644 --- a/conf/cli-options.js +++ b/conf/default-cli-options.js @@ -12,18 +12,19 @@ module.exports = { useEslintrc: true, envs: [], globals: [], - rules: {}, extensions: [".js"], ignore: true, ignorePath: null, - parser: "", // must be empty cache: false, - // in order to honor the cacheFile option if specified - // this option should not have a default value otherwise - // it will always be used + /* + * in order to honor the cacheFile option if specified + * this option should not have a default value otherwise + * it will always be used + */ cacheLocation: "", cacheFile: ".eslintcache", fix: false, - allowInlineConfig: true + allowInlineConfig: true, + reportUnusedDisableDirectives: false }; diff --git a/conf/environments.js b/conf/environments.js index a11f2963dc97..13020ebb5256 100644 --- a/conf/environments.js +++ b/conf/environments.js @@ -15,12 +15,130 @@ const globals = require("globals"); //------------------------------------------------------------------------------ module.exports = { - builtin: globals.es5, + builtin: { + globals: globals.es5 + }, browser: { - globals: globals.browser + + /* + * For backward compatibility. + * Remove those on the next major release. + */ + globals: Object.assign( + { + AutocompleteErrorEvent: false, + CDATASection: false, + ClientRect: false, + ClientRectList: false, + CSSAnimation: false, + CSSTransition: false, + CSSUnknownRule: false, + CSSViewportRule: false, + Debug: false, + DocumentTimeline: false, + DOMSettableTokenList: false, + ElementTimeControl: false, + FederatedCredential: false, + FileError: false, + HTMLAppletElement: false, + HTMLBlockquoteElement: false, + HTMLIsIndexElement: false, + HTMLKeygenElement: false, + HTMLLayerElement: false, + IDBEnvironment: false, + InputMethodContext: false, + MediaKeyError: false, + MediaKeyEvent: false, + MediaKeys: false, + opera: false, + PasswordCredential: false, + ReadableByteStream: false, + SharedKeyframeList: false, + showModalDialog: false, + SiteBoundCredential: false, + SVGAltGlyphDefElement: false, + SVGAltGlyphElement: false, + SVGAltGlyphItemElement: false, + SVGAnimateColorElement: false, + SVGAnimatedPathData: false, + SVGAnimatedPoints: false, + SVGColor: false, + SVGColorProfileElement: false, + SVGColorProfileRule: false, + SVGCSSRule: false, + SVGCursorElement: false, + SVGDocument: false, + SVGElementInstance: false, + SVGElementInstanceList: false, + SVGEvent: false, + SVGExternalResourcesRequired: false, + SVGFilterPrimitiveStandardAttributes: false, + SVGFitToViewBox: false, + SVGFontElement: false, + SVGFontFaceElement: false, + SVGFontFaceFormatElement: false, + SVGFontFaceNameElement: false, + SVGFontFaceSrcElement: false, + SVGFontFaceUriElement: false, + SVGGlyphElement: false, + SVGGlyphRefElement: false, + SVGHKernElement: false, + SVGICCColor: false, + SVGLangSpace: false, + SVGLocatable: false, + SVGMissingGlyphElement: false, + SVGPaint: false, + SVGPathSeg: false, + SVGPathSegArcAbs: false, + SVGPathSegArcRel: false, + SVGPathSegClosePath: false, + SVGPathSegCurvetoCubicAbs: false, + SVGPathSegCurvetoCubicRel: false, + SVGPathSegCurvetoCubicSmoothAbs: false, + SVGPathSegCurvetoCubicSmoothRel: false, + SVGPathSegCurvetoQuadraticAbs: false, + SVGPathSegCurvetoQuadraticRel: false, + SVGPathSegCurvetoQuadraticSmoothAbs: false, + SVGPathSegCurvetoQuadraticSmoothRel: false, + SVGPathSegLinetoAbs: false, + SVGPathSegLinetoHorizontalAbs: false, + SVGPathSegLinetoHorizontalRel: false, + SVGPathSegLinetoRel: false, + SVGPathSegLinetoVerticalAbs: false, + SVGPathSegLinetoVerticalRel: false, + SVGPathSegList: false, + SVGPathSegMovetoAbs: false, + SVGPathSegMovetoRel: false, + SVGRenderingIntent: false, + SVGStylable: false, + SVGTests: false, + SVGTransformable: false, + SVGTRefElement: false, + SVGURIReference: false, + SVGViewSpec: false, + SVGVKernElement: false, + SVGZoomAndPan: false, + SVGZoomEvent: false, + TimeEvent: false, + XDomainRequest: false, + XMLHttpRequestProgressEvent: false, + XPathException: false, + XPathNamespace: false, + XPathNSResolver: false + }, + globals.browser + ) }, node: { - globals: globals.node, + + /* + * For backward compatibility. + * Remove those on the next major release. + */ + globals: Object.assign( + { arguments: false, GLOBAL: false, root: false }, + globals.node + ), parserOptions: { ecmaFeatures: { globalReturn: true @@ -51,7 +169,15 @@ module.exports = { globals: globals.jasmine }, jest: { - globals: globals.jest + + /* + * For backward compatibility. + * Remove those on the next major release. + */ + globals: Object.assign( + { check: false, gen: false }, + globals.jest + ) }, phantomjs: { globals: globals.phantomjs @@ -96,7 +222,7 @@ module.exports = { globals: globals.webextensions }, es6: { - globals: globals.es6, + globals: globals.es2015, parserOptions: { ecmaVersion: 6 } diff --git a/conf/eslint-all.js b/conf/eslint-all.js index 28d745a9210c..43db54fb7187 100644 --- a/conf/eslint-all.js +++ b/conf/eslint-all.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const load = require("../lib/load-rules"), - rules = require("../lib/rules"); + Rules = require("../lib/rules"); +const rules = new Rules(); //------------------------------------------------------------------------------ // Helpers diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js old mode 100755 new mode 100644 index 63c2fc770d21..0acee234a56b --- a/conf/eslint-recommended.js +++ b/conf/eslint-recommended.js @@ -6,19 +6,15 @@ "use strict"; -/* eslint sort-keys: ["error", "asc"], quote-props: ["error", "consistent"] */ -/* eslint-disable sort-keys */ +/* eslint sort-keys: ["error", "asc"] */ module.exports = { - parser: "espree", - ecmaFeatures: {}, - rules: { - - /* eslint-enable sort-keys */ "accessor-pairs": "off", + "array-bracket-newline": "off", "array-bracket-spacing": "off", "array-callback-return": "off", + "array-element-newline": "off", "arrow-body-style": "off", "arrow-parens": "off", "arrow-spacing": "off", @@ -26,35 +22,40 @@ module.exports = { "block-spacing": "off", "brace-style": "off", "callback-return": "off", - "camelcase": "off", + camelcase: "off", "capitalized-comments": "off", "class-methods-use-this": "off", "comma-dangle": "off", "comma-spacing": "off", "comma-style": "off", - "complexity": "off", + complexity: "off", "computed-property-spacing": "off", "consistent-return": "off", "consistent-this": "off", "constructor-super": "error", - "curly": "off", + curly: "off", "default-case": "off", "dot-location": "off", "dot-notation": "off", "eol-last": "off", - "eqeqeq": "off", + eqeqeq: "off", + "for-direction": "off", "func-call-spacing": "off", "func-name-matching": "off", "func-names": "off", "func-style": "off", + "function-paren-newline": "off", "generator-star-spacing": "off", + "getter-return": "off", "global-require": "off", "guard-for-in": "off", "handle-callback-err": "off", "id-blacklist": "off", "id-length": "off", "id-match": "off", - "indent": "off", + "implicit-arrow-linebreak": "off", + indent: "off", + "indent-legacy": "off", "init-declarations": "off", "jsx-quotes": "off", "key-spacing": "off", @@ -63,6 +64,7 @@ module.exports = { "linebreak-style": "off", "lines-around-comment": "off", "lines-around-directive": "off", + "lines-between-class-members": "off", "max-depth": "off", "max-len": "off", "max-lines": "off", @@ -70,6 +72,7 @@ module.exports = { "max-params": "off", "max-statements": "off", "max-statements-per-line": "off", + "multiline-comment-style": "off", "multiline-ternary": "off", "new-cap": "off", "new-parens": "off", @@ -80,11 +83,12 @@ module.exports = { "no-array-constructor": "off", "no-await-in-loop": "off", "no-bitwise": "off", + "no-buffer-constructor": "off", "no-caller": "off", "no-case-declarations": "error", "no-catch-shadow": "off", "no-class-assign": "error", - "no-compare-neg-zero": "off", + "no-compare-neg-zero": "error", "no-cond-assign": "error", "no-confusing-arrow": "off", "no-console": "error", @@ -202,7 +206,7 @@ module.exports = { "no-useless-computed-key": "off", "no-useless-concat": "off", "no-useless-constructor": "off", - "no-useless-escape": "off", + "no-useless-escape": "error", "no-useless-rename": "off", "no-useless-return": "off", "no-var": "off", @@ -212,7 +216,7 @@ module.exports = { "no-with": "off", "nonblock-statement-body-position": "off", "object-curly-newline": "off", - "object-curly-spacing": ["off", "never"], + "object-curly-spacing": "off", "object-property-newline": "off", "object-shorthand": "off", "one-var": "off", @@ -220,6 +224,7 @@ module.exports = { "operator-assignment": "off", "operator-linebreak": "off", "padded-blocks": "off", + "padding-line-between-statements": "off", "prefer-arrow-callback": "off", "prefer-const": "off", "prefer-destructuring": "off", @@ -230,14 +235,15 @@ module.exports = { "prefer-spread": "off", "prefer-template": "off", "quote-props": "off", - "quotes": "off", - "radix": "off", + quotes: "off", + radix: "off", "require-await": "off", "require-jsdoc": "off", "require-yield": "error", "rest-spread-spacing": "off", - "semi": "off", + semi: "off", "semi-spacing": "off", + "semi-style": "off", "sort-imports": "off", "sort-keys": "off", "sort-vars": "off", @@ -247,7 +253,8 @@ module.exports = { "space-infix-ops": "off", "space-unary-ops": "off", "spaced-comment": "off", - "strict": "off", + strict: "off", + "switch-colon-spacing": "off", "symbol-description": "off", "template-curly-spacing": "off", "template-tag-spacing": "off", @@ -259,6 +266,6 @@ module.exports = { "wrap-iife": "off", "wrap-regex": "off", "yield-star-spacing": "off", - "yoda": "off" + yoda: "off" } }; diff --git a/conf/json-schema-schema.json b/conf/json-schema-schema.json deleted file mode 100644 index 85eb502a680e..000000000000 --- a/conf/json-schema-schema.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "id": "http://json-schema.org/draft-04/schema#", - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Core schema meta-schema", - "definitions": { - "schemaArray": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#" } - }, - "positiveInteger": { - "type": "integer", - "minimum": 0 - }, - "positiveIntegerDefault0": { - "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] - }, - "simpleTypes": { - "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] - }, - "stringArray": { - "type": "array", - "items": { "type": "string" }, - "minItems": 1, - "uniqueItems": true - } - }, - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uri" - }, - "$schema": { - "type": "string", - "format": "uri" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "default": {}, - "multipleOf": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true - }, - "maximum": { - "type": "number" - }, - "exclusiveMaximum": { - "type": "boolean", - "default": false - }, - "minimum": { - "type": "number" - }, - "exclusiveMinimum": { - "type": "boolean", - "default": false - }, - "maxLength": { "$ref": "#/definitions/positiveInteger" }, - "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, - "pattern": { - "type": "string", - "format": "regex" - }, - "additionalItems": { - "anyOf": [ - { "type": "boolean" }, - { "$ref": "#" } - ], - "default": {} - }, - "items": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/schemaArray" } - ], - "default": {} - }, - "maxItems": { "$ref": "#/definitions/positiveInteger" }, - "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, - "uniqueItems": { - "type": "boolean", - "default": false - }, - "maxProperties": { "$ref": "#/definitions/positiveInteger" }, - "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, - "required": { "$ref": "#/definitions/stringArray" }, - "additionalProperties": { - "anyOf": [ - { "type": "boolean" }, - { "$ref": "#" } - ], - "default": {} - }, - "definitions": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "properties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "patternProperties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "dependencies": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/stringArray" } - ] - } - }, - "enum": { - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "type": { - "anyOf": [ - { "$ref": "#/definitions/simpleTypes" }, - { - "type": "array", - "items": { "$ref": "#/definitions/simpleTypes" }, - "minItems": 1, - "uniqueItems": true - } - ] - }, - "allOf": { "$ref": "#/definitions/schemaArray" }, - "anyOf": { "$ref": "#/definitions/schemaArray" }, - "oneOf": { "$ref": "#/definitions/schemaArray" }, - "not": { "$ref": "#" } - }, - "dependencies": { - "exclusiveMaximum": [ "maximum" ], - "exclusiveMinimum": [ "minimum" ] - }, - "default": {} -} diff --git a/docs/about/index.md b/docs/about/index.md index 88655d68e92c..a9c5acb46389 100644 --- a/docs/about/index.md +++ b/docs/about/index.md @@ -8,8 +8,8 @@ The primary reason ESLint was created was to allow developers to create their ow ESLint is written using Node.js to provide a fast runtime environment and easy installation via [npm][]. -[linting]: http://en.wikipedia.org/wiki/Lint_(software) -[npm]: http://npmjs.org/ +[linting]: https://en.wikipedia.org/wiki/Lint_(software) +[npm]: https://npmjs.org/ ## Philosophy @@ -23,8 +23,8 @@ Everything is pluggable: Every rule: * Is standalone -* Can be able to be turned off or on (nothing can be deemed "too important to turn off") -* Can be set to be a warning or error individually +* Can be turned off or on (nothing can be deemed "too important to turn off") +* Can be set to a warning or error individually Additionally: diff --git a/docs/developer-guide/README.md b/docs/developer-guide/README.md index 5eba2229c6e0..406568c1c47f 100644 --- a/docs/developer-guide/README.md +++ b/docs/developer-guide/README.md @@ -14,27 +14,27 @@ In order to work with ESLint as a developer, it's recommended that: If that sounds like you, then continue reading to get started. -## Section 1: Get the [Source Code](source-code) +## Section 1: Get the [Source Code](source-code.md) Before you can get started, you'll need to get a copy of the ESLint source code. This section explains how to do that and a little about the source code structure. -## Section 2: Set up a [Development Environment](development-environment) +## Section 2: Set up a [Development Environment](development-environment.md) Developing for ESLint is a bit different than running it on the command line. This section shows you how to set up a development environment and get you ready to write code. -## Section 3: Run the [Unit Tests](unit-tests) +## Section 3: Run the [Unit Tests](unit-tests.md) There are a lot of unit tests included with ESLint to make sure that we're keeping on top of code quality. This section explains how to run the unit tests. -## Section 4: [Working with Rules](working-with-rules) +## Section 4: [Working with Rules](working-with-rules.md) You're finally ready to start working with rules. You may want to fix an existing rule or create a new one. This section explains how to do all of that. -## Section 5: [Working with Plugins](working-with-plugins) +## Section 5: [Working with Plugins](working-with-plugins.md) You've developed library-specific rules for ESLint and you want to share it with the community. You can publish an ESLint plugin on npm. -## Section 6: [Node.js API](nodejs-api) +## Section 6: [Node.js API](nodejs-api.md) If you're interested in writing a tool that uses ESLint, then you can use the Node.js API to get programmatic access to functionality. diff --git a/docs/developer-guide/architecture.md b/docs/developer-guide/architecture.md index 06403c20f11d..2c7ae4419aa9 100644 --- a/docs/developer-guide/architecture.md +++ b/docs/developer-guide/architecture.md @@ -4,7 +4,10 @@ At a high level, there are a few key parts to ESLint: * `bin/eslint.js` - this is the file that actually gets executed with the command line utility. It's a dumb wrapper that does nothing more than bootstrap ESLint, passing the command line arguments to `cli`. This is intentionally small so as not to require heavy testing. * `lib/cli.js` - this is the heart of the ESLint CLI. It takes an array of arguments and then uses `eslint` to execute the commands. By keeping this as a separate utility, it allows others to effectively call ESLint from within another Node.js program as if it were done on the command line. The main call is `cli.execute()`. This is also the part that does all the file reading, directory traversing, input, and output. -* `lib/eslint.js` - this is the core `eslint` object that does code verifying based on configuration options. This file does no file I/O and does not interact with the `console` at all. For other Node.js programs that have JavaScript text to verify, they would be able to use this interface directly. +* `lib/linter.js` - this is the core Linter class that does code verifying based on configuration options. This file does no file I/O and does not interact with the `console` at all. For other Node.js programs that have JavaScript text to verify, they would be able to use this interface directly. +* `lib/api.js` - this exposes an object that contains Linter, CLIEngine, RuleTester, and SourceCode. +* `lib/testers/rule-tester.js` - this is a wrapper around Mocha, so that rules can be unit tested. This class lets us write consistently formatted tests for each rule that is implemented and be confident that each of the rules work. The RuleTester interface was modeled after Mocha and works with Mocha's global testing methods. RuleTester can also be modified to work with other testing frameworks. +* `lib/util/source-code.js` - this contains a SourceCode class that is used to represent the parsed source code. It takes in source code and the Program node of the AST representing the code. ## The `cli` object diff --git a/docs/developer-guide/code-conventions.md b/docs/developer-guide/code-conventions.md index c9a78ab89621..610be23b225e 100644 --- a/docs/developer-guide/code-conventions.md +++ b/docs/developer-guide/code-conventions.md @@ -1,6 +1,6 @@ # Code Conventions -Programming language style guides are important for the long-term maintainability of software. This guide is based on the [Code Conventions for the Java Programming Language](http://java.sun.com/docs/codeconv/) and [Douglas Crockford's Code Conventions for the JavaScript Programming Language](http://javascript.crockford.com/code.html). Modifications have been made due to my personal experience and preferences. +Programming language style guides are important for the long-term maintainability of software. This guide is based on the [Code Conventions for the Java Programming Language](https://java.sun.com/docs/codeconv/) and [Douglas Crockford's Code Conventions for the JavaScript Programming Language](http://javascript.crockford.com/code.html). Modifications have been made due to my personal experience and preferences. ## File Format @@ -262,7 +262,7 @@ Make frequent use of comments to aid others in understanding your code. Use comm ### Single-Line Comments -Single-line comments should be used to documentation one line of code or a group of related lines of code. A single-line comment may be used in three ways: +Single-line comments should be used to document one line of code or a group of related lines of code. A single-line comment may be used in three ways: 1. On a separate line, describing the code beneath it. 1. At the end of a line, describing the code before it. diff --git a/docs/developer-guide/contributing/README.md b/docs/developer-guide/contributing/README.md index dcd2751879ba..a4e331ef9d63 100644 --- a/docs/developer-guide/contributing/README.md +++ b/docs/developer-guide/contributing/README.md @@ -4,30 +4,34 @@ One of the great things about open source projects is that anyone can contribute This guide is intended for anyone who wants to contribute to an ESLint project. Please read it carefully as it answers a lot of the questions many newcomers have when first working with our projects. -## [Signing the CLA](https://contribute.jquery.org/cla) +## Read the [Code of Conduct](https://js.foundation/community/code-of-conduct) -In order to submit code or documentation to an ESLint project, please electronically sign the [Contributor License Agreement](https://contribute.jquery.org/cla). The CLA is you giving us permission to use your contribution. +ESLint welcomes contributions from everyone and adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct). We kindly request that you read over our code of conduct before contributing. + +## [Signing the CLA](https://js.foundation/CLA) + +In order to submit code or documentation to an ESLint project, you will need to electronically sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint). The CLA is you giving us permission to use your contribution. ## [Bug Reporting](reporting-bugs) Think you found a problem? We'd love to hear about it. This section explains how to submit a bug, the type of information we need to properly verify it, and the overall process. -## Proposing a [New Rule](new-rules) +## Proposing a [New Rule](new-rules.md) We get a lot of proposals for new rules in ESLint. This section explains how we determine which rules are accepted and what information you should provide to help us evaluate your proposal. -## Proposing a [Rule Change](rule-changes) +## Proposing a [Rule Change](rule-changes.md) Want to make a change to an existing rule? This section explains the process and how we evaluate such proposals. -## Requesting a [Change](changes) +## Requesting a [Change](changes.md) If you'd like to request a change other than a bug fix or new rule, this section explains that process. -## [Working on Issues](working-on-issues) +## [Working on Issues](working-on-issues.md) Have some extra time and want to contribute? This section talks about the process of working on issues. -## Submitting a [Pull Request](pull-requests) +## Submitting a [Pull Request](pull-requests.md) We're always looking for contributions from the community. This section explains the requirements for pull requests and the process of contributing code. diff --git a/docs/developer-guide/contributing/changes.md b/docs/developer-guide/contributing/changes.md index 957f1286edc0..6b5f1590107f 100644 --- a/docs/developer-guide/contributing/changes.md +++ b/docs/developer-guide/contributing/changes.md @@ -1,6 +1,6 @@ # Change Requests -If you'd like to request a change to ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new?body=**What%20version%20are%20you%20using%3F**%0A%0A**What%20did%20you%20do%3F**%0A%0A**What%20happened%3F**%0A%0A**What%20did%20you%20expect%20to%20happen%3F**%0A%0A) on GitHub. Be sure to include the following information: +If you'd like to request a change to ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new?template=CHANGE.md) on GitHub. Be sure to include the following information: 1. The version of ESLint you are using. 1. The problem you want to solve. diff --git a/docs/developer-guide/contributing/new-rules.md b/docs/developer-guide/contributing/new-rules.md index 9f88d0513faf..c3fe9f4549b4 100644 --- a/docs/developer-guide/contributing/new-rules.md +++ b/docs/developer-guide/contributing/new-rules.md @@ -19,7 +19,7 @@ Even though these are the formal criteria for inclusion, each rule is evaluated ## Proposing a Rule -If you want to propose a new rule, [create a pull request](/docs/developer-guide/contributing/pull-requests) or new issue and paste the questions from the [rule proposal template](https://github.com/eslint/eslint/blob/master/templates/rule-proposal.md) into the description. +If you want to propose a new rule, [create a pull request](/docs/developer-guide/contributing/pull-requests.md) or [new issue](https://github.com/eslint/eslint/issues/new?template=NEW_RULE.md) and fill out the template. We need all of this information in order to determine whether or not the rule is a good core rule candidate. @@ -39,4 +39,4 @@ The ESLint team doesn't implement new rules that are suggested by users because ## Alternative: Creating Your Own Rules -Remember that ESLint is completely pluggable, which means you can create your own rules and distribute them using plugins. We did this on purpose because we don't want to be the gatekeepers for all possible rules. Even if we don't accept a rule into the core, that doesn't mean you can't have the exact rule that you want. See the [working with rules](../working-with-rules) and [working with plugins](../working-with-plugins) documentation for more information. +Remember that ESLint is completely pluggable, which means you can create your own rules and distribute them using plugins. We did this on purpose because we don't want to be the gatekeepers for all possible rules. Even if we don't accept a rule into the core, that doesn't mean you can't have the exact rule that you want. See the [working with rules](../working-with-rules.md) and [working with plugins](../working-with-plugins.md) documentation for more information. diff --git a/docs/developer-guide/contributing/pull-requests.md b/docs/developer-guide/contributing/pull-requests.md index ff6c864d999f..a1bc8f6bffc4 100644 --- a/docs/developer-guide/contributing/pull-requests.md +++ b/docs/developer-guide/contributing/pull-requests.md @@ -6,9 +6,9 @@ If you want to contribute to an ESLint repo, please use a GitHub pull request. T If you'd like to work on a pull request and you've never submitted code before, follow these steps: -1. Sign our [Contributor License Agreement](https://contribute.jquery.org/cla). -1. Set up a [development environment](../development-environment). -1. If you want to implement a breaking change or a change to the core, ensure there's an issue that describes what you're doing and the issue has been accepted. You can create a new issue or just indicate you're [working on an existing issue](working-on-issues). Bug fixes, documentation changes, and other pull requests do not require an issue. +1. Sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint). +1. Set up a [development environment](../development-environment.md). +1. If you want to implement a breaking change or a change to the core, ensure there's an issue that describes what you're doing and the issue has been accepted. You can create a new issue or just indicate you're [working on an existing issue](working-on-issues.md). Bug fixes, documentation changes, and other pull requests do not require an issue. After that, you're ready to start working on code. @@ -40,7 +40,7 @@ You should do all of your development for the issue in this branch. ### Step 2: Make your changes -Make the changes to the code and tests, following the [code conventions](../code-conventions) as you go. Once you have finished, commit the changes to your branch: +Make the changes to the code and tests, following the [code conventions](../code-conventions.md) as you go. Once you have finished, commit the changes to your branch: ``` $ git add -A @@ -60,7 +60,7 @@ The first line of the commit message (the summary) must have a specific format. The `Tag` is one of the following: * `Fix` - for a bug fix. -* `Update` - for a backwards-compatible enhancement or a change to a rule that increases the number of reported problems. +* `Update` - either for a backwards-compatible enhancement or for a rule change that adds reported problems. * `New` - implemented a new feature. * `Breaking` - for a backwards-incompatible enhancement or feature. * `Docs` - changes to documentation only. @@ -68,7 +68,7 @@ The `Tag` is one of the following: * `Upgrade` - for a dependency upgrade. * `Chore` - for refactoring, adding tests, etc. (anything that isn't user-facing). -Use the [labels of the issue you are working on](working-on-issues#issue-labels) to determine the best tag. +Use the [labels of the issue you are working on](working-on-issues.md#issue-labels) to determine the best tag. The message summary should be a one-sentence description of the change, and it must be 72 characters in length or shorter. If the pull request addresses an issue, then the issue number should be mentioned at the end. If the commit doesn't completely fix the issue, then use `(refs #1234)` instead of `(fixes #1234)`. @@ -77,7 +77,7 @@ Here are some good commit message summary examples: ``` Build: Update Travis to only test Node 0.10 (refs #734) Fix: Semi rule incorrectly flagging extra semicolon (fixes #840) -Upgrade: Esprima to 1.2, switch to using Esprima comment attachment (fixes #730) +Upgrade: Esprima to 1.2, switch to using comment attachment (fixes #730) ``` The commit message format is important because these messages are used to create a changelog for each release. The tag and issue number help to create more consistent and useful changelogs. @@ -105,7 +105,7 @@ If there are any failing tests, update your code until all tests pass. With your code ready to go, this is a good time to double-check your submission to make sure it follows our conventions. Here are the things to check: -* Run `npm run check-commit` to double-check that your commit is formatted correctly. +* Make sure your commit is formatted correctly. * The pull request must have a description. The description should explain what you did and how its effects can be seen. * The commit message is properly formatted. * The change introduces no functional regression. Be sure to run `npm test` to verify your changes before submitting a pull request. @@ -160,15 +160,15 @@ If we ask you to make code changes, there's no need to close the pull request an ``` $ git add -A -$ git commit --amend --no-edit -$ git push origin issue1234 -f +$ git commit +$ git push origin issue1234 ``` -This snippets adds all your new changes, then amends the previous commit with them. The `--no-edit` means you don't want to edit the commit message; you can omit that option if you need to make commit message changes as well. +When updating the code, it's usually better to add additional commits to your branch rather than amending the original commit, because reviewers can easily tell which changes were made in response to a particular review. When we merge pull requests, we will squash all the commits from your branch into a single commit on the `master` branch. ### Rebasing -If your code is out-of-date, we might ask you to rebase. That means we want you to apply your changes on top of the latest upstream code. Make sure you have set up a [development environment](../development-environment) and then you can rebase using these commands: +If your code is out-of-date, we might ask you to rebase. That means we want you to apply your changes on top of the latest upstream code. Make sure you have set up a [development environment](../development-environment.md) and then you can rebase using these commands: ``` $ git fetch upstream diff --git a/docs/developer-guide/contributing/reporting-bugs.md b/docs/developer-guide/contributing/reporting-bugs.md index 153dd269519d..81f5e8562e7e 100644 --- a/docs/developer-guide/contributing/reporting-bugs.md +++ b/docs/developer-guide/contributing/reporting-bugs.md @@ -1,6 +1,6 @@ # Reporting Bugs -If you think you've found a bug in ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new) or a [pull request](/docs/developer-guide/contributing/pull-requests) on GitHub. Be sure to copy the questions from the [bug report template](https://github.com/eslint/eslint/blob/master/templates/bug-report.md). +If you think you've found a bug in ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new) or a [pull request](/docs/developer-guide/contributing/pull-requests.md) on GitHub. Please include as much detail as possible to help us properly address your issue. If we need to triage issues and constantly ask people for more detail, that's time taken away from actually fixing issues. Help us be as efficient as possible by including a lot of detail in your issues. diff --git a/docs/developer-guide/contributing/rule-changes.md b/docs/developer-guide/contributing/rule-changes.md index 8eaa19d1c7aa..d45ddbe449d9 100644 --- a/docs/developer-guide/contributing/rule-changes.md +++ b/docs/developer-guide/contributing/rule-changes.md @@ -4,7 +4,7 @@ Occasionally, a core ESLint rule needs to be changed. This is not necessarily a ## Proposing a Rule Change -To propose a change to an existing rule, [create a new issue](https://github.com/eslint/eslint/issues/new) or a [pull request](/docs/developer-guide/contributing/pull-requests) on GitHub. Be sure to copy the questions from the [rule change proposal template](https://github.com/eslint/eslint/blob/master/templates/rule-change-proposal.md). +To propose a change to an existing rule, [create a pull request](/docs/developer-guide/contributing/pull-requests.md) or [new issue](https://github.com/eslint/eslint/issues/new?template=RULE_CHANGE.md) and fill out the template. We need all of this information in order to determine whether or not the change is a good candidate for inclusion. @@ -12,7 +12,7 @@ We need all of this information in order to determine whether or not the change In order for a rule change to be accepted into ESLint, it must: -1. Adhere to the [Core Rule Guidelines](new-rules#core-rule-guidelines) +1. Adhere to the [Core Rule Guidelines](new-rules.md#core-rule-guidelines) 1. Have an ESLint team member champion the change 1. Be important enough that rule is deemed incomplete without this change diff --git a/docs/developer-guide/contributing/working-on-issues.md b/docs/developer-guide/contributing/working-on-issues.md index 95282aa9cb63..9e666d649b3f 100644 --- a/docs/developer-guide/contributing/working-on-issues.md +++ b/docs/developer-guide/contributing/working-on-issues.md @@ -4,29 +4,19 @@ Our public [issues tracker](https://github.com/eslint/eslint/issues) lists all o ## Issue Labels -We use labels to indicate the status of issues. The most important labels are: +We use labels to indicate the status of issues. The most complete documentation on the labels is found in the [Maintainer Guide](https://eslint.org/docs/maintainer-guide/issues.html#when-an-issue-is-opened), but most contributors should find the information on this page sufficient. The most important questions that labels can help you, as a contributor, answer are: -1. [`triage`](https://github.com/eslint/eslint/issues?labels=triage&milestone=&page=1&state=open) - When an issue is first submitted, it is labeled as `triage`, which means the ESLint team needs to investigate and determine if the request is a bug report, a feature request, or something else. It's best not to work on tickets marked as `triage`, because we're not yet sure if we will accept the issue to work on. -1. [`accepted`](https://github.com/eslint/eslint/issues?labels=accepted&milestone=&page=1&state=open) - Once an issue has been properly triaged and the team decides it must be addressed, someone will assign the `accepted` label to an issue. When an issue is accepted, anyone is free to work on it. -1. [`bug`](https://github.com/eslint/eslint/issues?labels=bug&milestone=&page=1&state=open) - Indicates that the issue is reporting a problem. When submitting a pull request to work on this type of issue, be sure to prefix the commit message with "Fix:". -1. [`feature`](https://github.com/eslint/eslint/issues?labels=feature&milestone=&page=1&state=open) - Indicates that the issue is requesting a new feature. Features are functionality that doesn't already exist in the project. When submitting a pull request to work on this type of issue, be sure to prefix the commit message with "New:". -1. [`enhancement`](https://github.com/eslint/eslint/issues?labels=enhancement&milestone=&page=1&state=open) - Indicates that the issue is requesting a change to existing functionality. When submitting a pull request to work on this type of issue, be sure to prefix the commit message with "Update:". -1. [`beginner`](https://github.com/eslint/eslint/issues?labels=beginner&milestone=&page=1&state=open) - Indicates that the issue is simple enough that it would be a good first contribution for a new contributor. If you're looking to get started helping out with ESLint, take a look at the beginner issues. -1. [`help wanted`](https://github.com/eslint/eslint/issues?labels=help%20wanted&milestone=&page=1&state=open) - Indicates that the core team won't be working on this issue, however, we will accept pull requests from contributors. This basically means the issue isn't on the formal roadmap but it will be accepted if a contributor wants to implement it. +1. Is this issue available for me to work on? If you have little or no experience contributing to ESLint, the [`good first issue`](https://github.com/eslint/eslint/labels/good%20first%20issue) label marks appropriate issues. Otherwise, the [`help wanted`](https://github.com/eslint/eslint/labels/help%20wanted) label is an invitation to work on the issue. If you have more experience, you can try working on other issues labeled [`accepted`](https://github.com/eslint/eslint/labels/accepted). Conversely, issues not yet ready to work on are labeled `triage`, `evaluating`, and/or `needs bikeshedding`, and issues that cannot currently be worked on because of something else, such as a bug in a dependency, are labeled `blocked`. +1. What is this issue about? Labels describing the nature of issues include `bug`, `enhancement`, `feature`, `question`, `rule`, `documentation`, `core`, `build`, `cli`, `infrastructure`, `breaking`, and `chore`. These are documented in the [Maintainer Guide](https://eslint.org/docs/maintainer-guide/issues.html#types-of-issues). +1. What is the priority of this issue? Because we have a lot of issues, we prioritize certain issues above others. The following is the list of priorities, from highest to lowest: -## Bounty Issues + 1. **Bugs** - problems with the project are actively affecting users. We want to get these resolved as quickly as possible. + 1. **Documentation** - documentation issues are a type of bug in that they actively affect current users. As such, we want to address documentation issues as quickly as possible. + 1. **Features** - new functionality that will aid users in the future. + 1. **Enhancements** - requested improvements for existing functionality. + 1. **Other** - anything else. -We accept and assign issue bounties using [BountySource](https://www.bountysource.com/teams/eslint/issues). - -## Issue Priority - -Because we have a lot of issues, we prioritize certain issues above others. The following is the list of priorities, from highest to lowest: - -1. **Bugs** - problems with the project are actively affecting users. We want to get these resolved as quickly as possible. -1. **Documentation** - documentation issues are a type of bug in that they actively affect current users. As such, we want to address documentation issues as quickly as possible. -1. **Features** - new functionality that will aid users in the future. -1. **Enhancements** - requested improvements for existing functionality. -1. **Other** - anything else. + Some issues have had monetary rewards attached to them. Those are labeled `bounty`. Bounties are assigned via [BountySource](https://www.bountysource.com/teams/eslint/issues). ## Starting Work diff --git a/docs/developer-guide/development-environment.md b/docs/developer-guide/development-environment.md index 538e297a3f9d..3b3a12fb2792 100644 --- a/docs/developer-guide/development-environment.md +++ b/docs/developer-guide/development-environment.md @@ -4,9 +4,9 @@ ESLint has a very lightweight development environment that makes updating code f ## Step 1: Install Node.js -Go to to download and install the latest stable version for your operating system. +Go to to download and install the latest stable version for your operating system. -Most of the installers come with [npm](http://npmjs.org/) already installed, but if for some reason it doesn't work on your system, you can install it manually using the instructions on the website. +Most of the installers come with [npm](https://www.npmjs.com/) already installed, but if for some reason it doesn't work on your system, you can install it manually using the instructions on the site. ## Step 2: Fork and checkout your own ESLint repository @@ -53,15 +53,19 @@ Running the tests is the best way to ensure you have correctly set up your devel npm test ``` -The testing takes a few seconds to complete. If any tests fail, that likely means one or more parts of the environment setup didn't complete correctly. The upstream tests always pass. +The testing takes a few minutes to complete. If any tests fail, that likely means one or more parts of the environment setup didn't complete correctly. The upstream tests always pass. +## Reference Information +### Workflow -## Build Scripts +Once you have your development environment installed, you can make and submit changes to the ESLint source files. Doing this successfully requires careful adherence to our [pull-request submission workflow](contributing/pull-requests.md). + +### Build Scripts ESLint has several build scripts that help with various parts of development. -### npm test +#### npm test The primary script to use is `npm test`, which does several things: @@ -75,19 +79,19 @@ Be sure to run this after making changes and before sending a pull request with **Note:** The full code coverage report is output into `/coverage`. -### npm run lint +#### npm run lint Runs just the JavaScript and JSON linting on the repository -### npm run browserify +#### npm run browserify Generates `build/eslint.js`, a version of ESLint for use in the browser -### npm run docs +#### npm run docs Generates JSDoc documentation and places it into `/jsdoc`. -### npm run profile +#### npm run profile This command is used for intensive profiling of ESLint using Chrome Developer Tools. It starts a development server that runs through three profiles: @@ -101,12 +105,3 @@ Your browser should automatically open to the page in question. When that happen 1. Click on Profiles You should start to see profiles for each run show up on the left side. If not, reload the page in the browser. Once all three profiles have completed, they will be available for inspection. - -## Workflow - -Whenever you make changes to the ESLint source files, you'll need to run `npm test` to rerun the tests. The workflow is: - -1. Make changes -2. Run `npm test` to run tests on the command line - -You'll have to do this each time you make a change. The tests are run automatically whenever a pull request is received, so make sure to verify your changes work before submitting them. diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md index 60148ce4ef90..4988f2696801 100644 --- a/docs/developer-guide/nodejs-api.md +++ b/docs/developer-guide/nodejs-api.md @@ -4,6 +4,35 @@ While ESLint is designed to be run on the command line, it's possible to use ESL **Note:** Use undocumented parts of the API at your own risk. Only those parts that are specifically mentioned in this document are approved for use and will remain stable and reliable. Anything left undocumented is unstable and may change or be removed at any point. +## Table of Contents + +* [SourceCode](#sourcecode) + * [splitLines()](#sourcecodesplitlines) +* [Linter](#linter) + * [verify()](#linterverify) + * [verifyAndFix()](#linterverifyandfix) + * [defineRule()](#linterdefinerule) + * [defineRules()](#linterdefinerules) + * [getRules()](#lintergetrules) + * [defineParser()](#linterdefineparser) + * [version](#linterversion) +* [linter (deprecated)](#linter-1) +* [CLIEngine](#cliengine) + * [executeOnFiles()](#cliengineexecuteonfiles) + * [resolveFileGlobPatterns()](#cliengineresolvefileglobpatterns) + * [getConfigForFile()](#clienginegetconfigforfile) + * [executeOnText()](#cliengineexecuteontext) + * [addPlugin()](#cliengineaddplugin) + * [isPathIgnored()](#cliengineispathignored) + * [getFormatter()](#clienginegetformatter) + * [getErrorResults()](#clienginegeterrorresults) + * [outputFixes()](#cliengineoutputfixes) + * [getRules()](#clienginegetrules) + * [version](#cliengineversion) +* [RuleTester](#ruletester) + * [Customizing RuleTester](#customizing-ruletester) +* [Deprecated APIs](#deprecated-apis) + ## SourceCode The `SourceCode` type represents the parsed source code that ESLint executes on. It's used internally in ESLint and is also available so that already-parsed code can be used. You can create a new instance of `SourceCode` by passing in the text string representing the code and an abstract syntax tree (AST) in [ESTree](https://github.com/estree/estree) format (including location information, range information, comments, and tokens): @@ -28,7 +57,7 @@ assert(code.hasBOM === true); assert(code.text === "var foo = bar;"); ``` -### splitLines() +### SourceCode#splitLines() This is a static function on `SourceCode` that is used to split the source code text into an array of lines. @@ -49,29 +78,36 @@ var codeLines = SourceCode.splitLines(code); */ ``` -## linter +## Linter -The `linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `linter` object does not process configuration objects or files. You can retrieve `linter` like this: +The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration objects or files. You can retrieve instances of `Linter` like this: ```js -var linter = require("eslint").linter; +var Linter = require("eslint").Linter; +var linter = new Linter(); ``` -The most important method on `linter` is `verify()`, which initiates linting of the given text. This method accepts four arguments: +### Linter#verify + +The most important method on `Linter` is `verify()`, which initiates linting of the given text. This method accepts three arguments: * `code` - the source code to lint (a string or instance of `SourceCode`). * `config` - a configuration object that has been processed and normalized by CLIEngine using eslintrc files and/or other configuration arguments. - * **Note**: If you want to lint text and have your configuration be read and processed, use CLIEngine's [`executeOnFiles`](#executeonfiles) or [`executeOnText`](#executeontext) instead. -* `optionsOrFilename` - (optional) Additional options for this run or a string representing the filename to associate with the code being linted. + * **Note**: If you want to lint text and have your configuration be read and processed, use CLIEngine's [`executeOnFiles`](#cliengineexecuteonfiles) or [`executeOnText`](#cliengineexecuteontext) instead. +* `options` - (optional) Additional options for this run. * `filename` - (optional) the filename to associate with the source code. - * `saveState` - (optional) see below. This will override any value passed as the fourth argument if an options object is used here instead of the filename. - * `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing eslint rules. -* `saveState` - (optional) set to true to maintain the internal state of `linter` after linting (mostly used for testing purposes) + * `preprocess` - (optional) A function that accepts a string containing source text, and returns an array of strings containing blocks of code to lint. Also see: [Processors in Plugins](/docs/developer-guide/working-with-plugins.md#processors-in-plugins) + * `postprocess` - (optional) A function that accepts an array of problem lists (one list of problems for each block of code from `preprocess`), and returns a one-dimensional array of problems containing problems for the original, unprocessed text. Also see: [Processors in Plugins](/docs/developer-guide/working-with-plugins.md#processors-in-plugins) + * `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing ESLint rules. + * `reportUnusedDisableDirectives` - (optional) when set to `true`, adds reported errors for unused `eslint-disable` directives when no problems would be reported in the disabled area anyway. + +If the third argument is a string, it is interpreted as the `filename`. You can call `verify()` like this: ```js -var linter = require("eslint").linter; +var Linter = require("eslint").Linter; +var linter = new Linter(); var messages = linter.verify("var foo;", { rules: { @@ -119,17 +155,17 @@ The information available for each linting message is: * `nodeType` - the node or token type that was reported with the problem. * `ruleId` - the ID of the rule that triggered the messages (or null if `fatal` is true). * `severity` - either 1 or 2, depending on your configuration. -* `source` - the line of code where the problem is (or empty string if it can't be found). * `endColumn` - the end column of the range on which the error occurred (this property is omitted if it's not range). * `endLine` - the end line of the range on which the error occurred (this property is omitted if it's not range). * `fix` - an object describing the fix for the problem (this property is omitted if no fix is available). -**Please note**: the `source` property will be removed from the linting messages in an upcoming breaking release. If you depend on this property, you can still use the `getSourceCode` method described below to get the line of code for each message. +Linting message objects have a deprecated `source` property. This property **will be removed** from linting messages in an upcoming breaking release. If you depend on this property, you should now use the `SourceCode` instance provided by the linter. You can also get an instance of the `SourceCode` object used inside of `linter` by using the `getSourceCode()` method: ```js -var linter = require("eslint").linter; +var Linter = require("eslint").Linter; +var linter = new Linter(); var messages = linter.verify("var foo = bar;", { rules: { @@ -144,6 +180,143 @@ console.log(code.text); // "var foo = bar;" In this way, you can retrieve the text and AST used for the last run of `linter.verify()`. +### Linter#verifyAndFix() + +This method is similar to verify except that it also runs autofixing logic, similar to the `--fix` flag on the command line. The result object will contain the autofixed code, along with any remaining linting messages for the code that were not autofixed. + +```js +var Linter = require("eslint").Linter; +var linter = new Linter(); + +var messages = linter.verifyAndFix("var foo", { + rules: { + semi: 2 + } +}); +``` + +Output object from this method: + +```js +{ + fixed: true, + output: "var foo;", + messages: [] +} +``` + +The information available is: + +* `fixed` - True, if the code was fixed. +* `output` - Fixed code text (might be the same as input if no fixes were applied). +* `messages` - Collection of all messages for the given code (It has the same information as explained above under `verify` block). + +### Linter#defineRule + +Each `Linter` instance holds a map of rule names to loaded rule objects. By default, all ESLint core rules are loaded. If you want to use `Linter` with custom rules, you should use the `defineRule` method to register your rules by ID. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +linter.defineRule("my-custom-rule", { + // (an ESLint rule) + + create(context) { + // ... + } +}); + +const results = linter.verify("// some source text", { rules: { "my-custom-rule": "error" } }); +``` + +### Linter#defineRules + +This is a convenience method similar to `Linter#defineRule`, except that it allows you to define many rules at once using an object. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +linter.defineRules({ + "my-custom-rule": { /* an ESLint rule */ create() {} }, + "another-custom-rule": { /* an ESLint rule */ create() {} } +}); + +const results = linter.verify("// some source text", { + rules: { + "my-custom-rule": "error", + "another-custom-rule": "warn" + } +}); +``` + +### Linter#getRules + +This method returns a map of all loaded rules. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +linter.getRules(); + +/* +Map { + 'accessor-pairs' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, + 'array-bracket-newline' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, + ... +} +*/ +``` + +### Linter#defineParser + +Each instance of `Linter` holds a map of custom parsers. If you want to define a parser programmatically you can add this function +with the name of the parser as first argument and the [parser object](/docs/developer-guide/working-with-plugins.md#working-with-custom-parsers) as second argument. + +If during linting the parser is not found, it will fallback to `require(parserId)`. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +linter.defineParser("my-custom-parser", { + parse(code, options) { + // ... + } +}); + +const results = linter.verify("// some source text", { parser: "my-custom-parser" }); +``` + +### Linter#version + +Each instance of `Linter` has a `version` property containing the semantic version number of ESLint that the `Linter` instance is from. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +linter.version; // => '4.5.0' +``` + +## linter + +The `eslint.linter` object (deprecated) is an instance of the `Linter` class as defined [above](#linter). `eslint.linter` exists for backwards compatibility, but we do not recommend using it because any mutations to it are shared among every module that uses `eslint`. Instead, please create your own instance of `eslint.Linter`. + +```js +var linter = require("eslint").linter; + +var messages = linter.verify("var foo;", { + rules: { + semi: 2 + } +}, { filename: "foo.js" }); +``` + +Note: This API is deprecated as of 4.0.0. + ## CLIEngine The primary Node.js API is `CLIEngine`, which is the underlying utility that runs the ESLint command line interface. This object will read the filesystem for configuration and file information but will not output any results. Instead, it allows you direct access to the important information so you can deal with the output yourself. @@ -156,16 +329,16 @@ var CLIEngine = require("eslint").CLIEngine; The `CLIEngine` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are: -* `allowInlineConfig` - Set to false to disable the use of configuration comments (such as `/*eslint-disable*/`). Corresponds to `--no-inline-config`. +* `allowInlineConfig` - Set to `false` to disable the use of configuration comments (such as `/*eslint-disable*/`). Corresponds to `--no-inline-config`. * `baseConfig` - Set to false to disable use of base config. Could be set to an object to override default base config as well. * `cache` - Operate only on changed files (default: `false`). Corresponds to `--cache`. * `cacheFile` - Name of the file where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-file`. Deprecated: use `cacheLocation` instead. * `cacheLocation` - Name of the file or directory where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-location`. -* `configFile` - The configuration file to use (default: null). Corresponds to `-c`. +* `configFile` - The configuration file to use (default: null). If `useEslintrc` is true or not specified, this configuration will be merged with any configuration defined in `.eslintrc.*` files, with options in this configuration having precedence. Corresponds to `-c`. * `cwd` - Path to a directory that should be considered as the current working directory. * `envs` - An array of environments to load (default: empty array). Corresponds to `--env`. * `extensions` - An array of filename extensions that should be checked for code. The default is an array containing just `".js"`. Corresponds to `--ext`. It is only used in conjunction with directories, not with filenames or glob patterns. -* `fix` - True indicates that fixes should be included with the output report, and that errors and warnings should not be listed if they can be fixed. However, the files on disk will not be changed. To persist changes to disk, call [`outputFixes()`](#outputfixes). +* `fix` - This can be a boolean or a function which will be provided each linting message and should return a boolean. True indicates that fixes should be included with the output report, and that errors and warnings should not be listed if they can be fixed. However, the files on disk will not be changed. To persist changes to disk, call [`outputFixes()`](#cliengineoutputfixes). * `globals` - An array of global variables to declare (default: empty array). Corresponds to `--global`. * `ignore` - False disables use of `.eslintignore`, `ignorePath` and `ignorePattern` (default: true). Corresponds to `--no-ignore`. * `ignorePath` - The ignore file to use instead of `.eslintignore` (default: null). Corresponds to `--ignore-path`. @@ -173,6 +346,7 @@ The `CLIEngine` is a constructor, and you can create a new instance by passing i * `parser` - Specify the parser to be used (default: `espree`). Corresponds to `--parser`. * `parserOptions` - An object containing parser options (default: empty object). Corresponds to `--parser-options`. * `plugins` - An array of plugins to load (default: empty array). Corresponds to `--plugin`. +* `reportUnusedDisableDirectives` - When set to `true`, adds reported errors for unused `eslint-disable` directives when no problems would be reported in the disabled area anyway (default: false). Corresponds to `--report-unused-disable-directives`. * `rulePaths` - An array of directories to load custom rules from (default: empty array). Corresponds to `--rulesdir`. * `rules` - An object of rules to use (default: null). Corresponds to `--rule`. * `useEslintrc` - Set to false to disable use of `.eslintrc` files (default: true). Corresponds to `--no-eslintrc`. @@ -195,7 +369,7 @@ var cli = new CLIEngine({ In this code, a new `CLIEngine` instance is created that sets two environments, `"browser"` and `"mocha"`, disables loading of `.eslintrc` and `package.json` files, and enables the `semi` rule as an error. You can then call methods on `cli` and these options will be used to perform the correct action. -### executeOnFiles() +### CLIEngine#executeOnFiles() If you want to lint one or more files, use the `executeOnFiles()` method. This method accepts a single argument, which is an array of files and/or directories to traverse for files. You can pass the same values as you would using the ESLint command line interface, such as `"."` to search all JavaScript files in the current directory. Here's an example: @@ -228,16 +402,19 @@ The return value is an object containing the results of the linting operation. H line: 1, column: 13, nodeType: "ExpressionStatement", - source: "\"use strict\"", // Deprecated: see "please note" paragraph below. fix: { range: [12, 12], text: ";" } }], errorCount: 1, warningCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, source: "\"use strict\"\n" } ], errorCount: 1, - warningCount: 0 + warningCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0 } ``` @@ -273,7 +450,6 @@ var report = cli.executeOnFiles(["myfile.js", "lib/"]); line: 1, column: 13, nodeType: "ExpressionStatement", - source: "\"use strict\"", // Deprecated: see "please note" paragraph below. fix: { range: [12, 12], text: ";" } }, { @@ -282,17 +458,20 @@ var report = cli.executeOnFiles(["myfile.js", "lib/"]); message: "Function name `bar` should match variable name `foo`", line: 2, column: 5, - nodeType: "VariableDeclarator", - source: "var foo = function bar() {};" + nodeType: "VariableDeclarator" } ], - errorCount: 1, + errorCount: 2, warningCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, output: "\"use strict\";\nvar foo = function bar() {};\nfoo();\n" } ], - errorCount: 1, - warningCount: 0 + errorCount: 2, + warningCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, } ``` @@ -308,7 +487,6 @@ If the operation ends with a parsing error, you will get a single message for th ruleId: null, fatal: true, severity: 2, - source: "fucntion foo() {}", message: "Parsing error: Unexpected token foo", line: 1, column: 10 @@ -316,11 +494,15 @@ If the operation ends with a parsing error, you will get a single message for th ], errorCount: 1, warningCount: 0, - source: "fucntion foo() {}" + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "function foo() {}" } ], errorCount: 1, - warningCount: 0 + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, } ``` @@ -334,11 +516,11 @@ The top-level report object has a `results` array containing all linting results The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files. -**Please note**: the `source` property will be removed from the linting messages returned in `messages` in an upcoming breaking release. If you depend on this property, you should now use the top-level `source` or `output` properties instead. +Report message objects have a deprecated `source` property. This property **will be removed** from the linting messages returned in `messages` in an upcoming breaking release. If you depend on this property, you should now use the top-level `source` or `output` properties instead. -Once you get a report object, it's up to you to determine how to output the results. Fixes will not be automatically applied to the files, even if you set `fix: true` when constructing the `CLIEngine` instance. To apply fixes to the files, call [`outputFixes`](#outputfixes). +Once you get a report object, it's up to you to determine how to output the results. Fixes will not be automatically applied to the files, even if you set `fix: true` when constructing the `CLIEngine` instance. To apply fixes to the files, call [`outputFixes`](#cliengineoutputfixes). -### resolveFileGlobPatterns() +### CLIEngine#resolveFileGlobPatterns() You can pass filesystem-style or glob patterns to ESLint and have it function properly. In order to achieve this, ESLint must resolve non-glob patterns into glob patterns before determining which files to execute on. The `resolveFileGlobPatterns()` methods uses the current settings from `CLIEngine` to resolve non-glob patterns into glob patterns. Pass an array of patterns that might be passed to the ESLint CLI and it will return an array of glob patterns that mean the same thing. Here's an example: @@ -353,7 +535,7 @@ var globPatterns = cli.resolveFileGlobPatterns(["."]); console.log(globPatterns[i]); // ["**/*.js"] ``` -### getConfigForFile() +### CLIEngine#getConfigForFile() If you want to retrieve a configuration object for a given file, use the `getConfigForFile()` method. This method accepts one argument, a file path, and returns an object represented the calculated configuration of the file. Here's an example: @@ -390,7 +572,7 @@ var config = cli.getConfigForFile("myfile.js"); var messages = linter.verify('var foo;', config); ``` -### executeOnText() +### CLIEngine#executeOnText() If you already have some text to lint, then you can use the `executeOnText()` method to lint that text. The linter will assume that the text is a file in the current working directory, and so will still obey any `.eslintrc` and `.eslintignore` files that may be present. Here's an example: @@ -414,7 +596,7 @@ The `report` returned from `executeOnText()` is in the same format as from `exec If a filename in the optional second parameter matches a file that is configured to be ignored, then this function returns no errors or warnings. To return a warning instead, call the method with true as the optional third parameter. -### addPlugin() +### CLIEngine#addPlugin() Loads a plugin from configuration object with specified name. Name can include plugin prefix ("eslint-plugin-") @@ -437,7 +619,7 @@ cli.addPlugin("eslint-plugin-processor", { }); ``` -### isPathIgnored() +### CLIEngine#isPathIgnored() Checks if a given path is ignored by ESLint. @@ -452,7 +634,7 @@ var cli = new CLIEngine({ var isIgnored = cli.isPathIgnored("foo/bar.js"); ``` -### getFormatter() +### CLIEngine#getFormatter() Retrieves a formatter, which you can then use to format a report object. The argument is either the name of a built-in formatter: @@ -504,7 +686,7 @@ var formatter = CLIEngine.getFormatter(); **Important:** You must pass in the `results` property of the report. Passing in `report` directly will result in an error. -### getErrorResults() +### CLIEngine#getErrorResults() This is a static function on `CLIEngine`. It can be used to filter out all the non error messages from the report object. @@ -528,7 +710,7 @@ var errorReport = CLIEngine.getErrorResults(report.results) **Important:** You must pass in the `results` property of the report. Passing in `report` directly will result in an error. -### outputFixes() +### CLIEngine#outputFixes() This is a static function on `CLIEngine` that is used to output fixes from `report` to disk. It does by looking for files that have an `output` property in their results. Here's an example: @@ -551,6 +733,154 @@ var report = cli.executeOnFiles(["myfile.js", "lib/"]); CLIEngine.outputFixes(report); ``` +### CLIEngine#getRules() + +This method returns a map of all loaded rules. Under the hood, it calls [Linter#getRules](#lintergetrules). + +```js +const CLIEngine = require("eslint").CLIEngine; +const cli = new CLIEngine(); + +cli.getRules(); + +/* +Map { + 'accessor-pairs' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, + 'array-bracket-newline' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, + ... +} +*/ +``` + + +### CLIEngine.version + +`CLIEngine` has a static `version` property containing the semantic version number of ESLint that it comes from. + +```js +require("eslint").CLIEngine.version; // '4.5.0' +``` + +## RuleTester + +`eslint.RuleTester` is a utility to write tests for ESLint rules. It is used internally for the bundled rules that come with ESLint, and it can also be used by plugins. + +Example usage: + +```js +"use strict"; + +const rule = require("../../../lib/rules/my-rule"), + RuleTester = require("eslint").RuleTester; + +const ruleTester = new RuleTester(); + +ruleTester.run("my-rule", rule, { + valid: [ + { + code: "var foo = true", + options: [{ allowFoo: true }] + } + ], + + invalid: [ + { + code: "var invalidVariable = true", + errors: [{ message: "Unexpected invalid variable." }] + }, + { + code: "var invalidVariable = true", + errors: [{ message: /^Unexpected.+variable/ }] + } + ] +}); +``` + +The `RuleTester` constructor accepts an optional object argument, which can be used to specify defaults for your test cases. For example, if all of your test cases use ES2015, you can set it as a default: + +```js +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); +``` + +The `RuleTester#run()` method is used to run the tests. It should be passed the following arguments: + +* The name of the rule (string) +* The rule object itself (see ["working with rules"](./working-with-rules)) +* An object containing `valid` and `invalid` properties, each of which is an array containing test cases. + +A test case is an object with the following properties: + +* `code` (string, required): The source code that the rule should be run on +* `options` (array, optional): The options passed to the rule. The rule severity should not be included in this list. +* `filename` (string, optional): The filename for the given case (useful for rules that make assertions about filenames). + +In addition to the properties above, invalid test cases can also have the following properties: + +* `errors` (number or array, required): Asserts some properties of the errors that the rule is expected to produce when run on this code. If this is a number, asserts the number of errors produced. Otherwise, this should be a list of objects, each containing information about a single reported error. The following properties can be used for an error (all are optional): + * `message` (string/regexp): The message for the error + * `type` (string): The type of the reported AST node + * `line` (number): The 1-based line number of the reported location + * `column` (number): The 1-based column number of the reported location + * `endLine` (number): The 1-based line number of the end of the reported location + * `endColumn` (number): The 1-based column number of the end of the reported location + + If a string is provided as an error instead of an object, the string is used to assert the `message` of the error. +* `output` (string, optional): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the `--fix` command line flag). If this is `null`, asserts that none of the reported problems suggest autofixes. + +Any additional properties of a test case will be passed directly to the linter as config options. For example, a test case can have a `parserOptions` property to configure parser behavior: + +```js +{ + code: "let foo;", + parserOptions: { ecmaVersion: 2015 } +} +``` + +If a valid test case only uses the `code` property, it can optionally be provided as a string containing the code, rather than an object with a `code` key. + +### Customizing RuleTester + +`RuleTester` depends on two functions to run tests: `describe` and `it`. These functions can come from various places: + +1. If `RuleTester.describe` and `RuleTester.it` have been set to function values, `RuleTester` will use `RuleTester.describe` and `RuleTester.it` to run tests. You can use this to customize the behavior of `RuleTester` to match a test framework that you're using. +1. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `global.describe` and `global.it` to run tests. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration. +1. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `node`, without needing a testing framework. + +`RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.it` and `RuleTester.describe`.) + +Example of customizing `RuleTester`: + +```js +"use strict"; + +const RuleTester = require("eslint").RuleTester, + test = require("my-test-runner"), + myRule = require("../../../lib/rules/my-rule"); + +RuleTester.describe = function(text, method) { + RuleTester.it.title = text; + return method.call(this); +}; + +RuleTester.it = function(text, method) { + test(RuleTester.it.title + ": " + text, method); +}; + +// then use RuleTester as documented + +const ruleTester = new RuleTester(); + +ruleTester.run("my-rule", myRule, { + valid: [ + // valid test cases + ], + invalid: [ + // invalid test cases + ] +}) +``` + ## Deprecated APIs * `cli` - the `cli` object has been deprecated in favor of `CLIEngine`. As of v1.0.0, `cli` is no longer exported and should not be used by external tools. +* `linter` - the `linter` object has been deprecated in favor of `Linter` as of v4.0.0. diff --git a/docs/developer-guide/scope-manager-interface.md b/docs/developer-guide/scope-manager-interface.md new file mode 100644 index 000000000000..08986caa258e --- /dev/null +++ b/docs/developer-guide/scope-manager-interface.md @@ -0,0 +1,388 @@ +# ScopeManager + +This document was written based on the implementation of [eslint-scope](https://github.com/eslint/eslint-scope), a fork of [escope](https://github.com/estools/escope), and deprecates some members ESLint is not using. + +---- + +## ScopeManager interface + +`ScopeManager` object has all variable scopes. + +### Fields + +#### scopes + +* **Type:** `Scope[]` +* **Description:** All scopes. + +#### globalScope + +* **Type:** `Scope` +* **Description:** The root scope. + +### Methods + +#### acquire(node, inner = false) + +* **Parameters:** + * `node` (`ASTNode`) ... An AST node to get their scope. + * `inner` (`boolean`) ... If the node has multiple scope, this returns the outermost scope normally. If `inner` is `true` then this returns the innermost scope. Default is `false`. +* **Return type:** `Scope | null` +* **Description:** Get the scope of a given AST node. The gotten scope's `block` property is the node. This method never returns `function-expression-name` scope and `TDZ` scope. If the node does not have their scope, this returns `null`. + +#### getDeclaredVariables(node) + +* **Parameters:** + * `node` (`ASTNode`) ... An AST node to get their variables. +* **Return type:** `Variable[]` +* **Description:** Get the variables that a given AST node defines. The gotten variables' `def[].node`/`def[].parent` property is the node. If the node does not define any variable, this returns an empty array. + +### Deprecated members + +Those members are defined but not used in ESLint. + +#### isModule() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this program is module. + +#### isImpliedStrict() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this program is strict mode implicitly. I.e., `options.impliedStrict === true`. + +#### isStrictModeSupported() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this program supports strict mode. I.e., `options.ecmaVersion >= 5`. + +#### acquireAll(node) + +* **Parameters:** + * `node` (`ASTNode`) ... An AST node to get their scope. +* **Return type:** `Scope[] | null` +* **Description:** Get the scopes of a given AST node. The gotten scopes' `block` property is the node. If the node does not have their scope, this returns `null`. + +---- + +## Scope interface + +`Scope` object has all variables and references in the scope. + +### Fields + +#### type + +* **Type:** `string` +* **Description:** The type of this scope. This is one of `"block"`, `"catch"`, `"class"`, `"for"`, `"function"`, `"function-expression-name"`, `"global"`, `"module"`, `"switch"`, `"with"`, `"TDZ"` + +#### isStrict + +* **Type:** `boolean` +* **Description:** `true` if this scope is strict mode. + +#### upper + +* **Type:** `Scope | null` +* **Description:** The parent scope. If this is the global scope then this property is `null`. + +#### childScopes + +* **Type:** `Scope[]` +* **Description:** The array of child scopes. This does not include grandchild scopes. + +#### variableScope + +* **Type:** `Scope` +* **Description:** The scope which hosts variables which are defined by `var` declarations. + +#### block + +* **Type:** `ASTNode` +* **Description:** The AST node which created this scope. + +#### variables + +* **Type:** `Variable[]` +* **Description:** The array of all variables which are defined on this scope. This does not include variables which are defined in child scopes. + +#### set + +* **Type:** `Map` +* **Description:** The map from variable names to variable objects. + +> I hope to rename `set` field or replace by a method. + +#### references + +* **Type:** `Reference[]` +* **Description:** The array of all references on this scope. This does not include references in child scopes. + +#### through + +* **Type:** `Reference[]` +* **Description:** The array of references which could not be resolved in this scope. + +#### functionExpressionScope + +* **Type:** `boolean` +* **Description:** `true` if this scope is `"function-expression-name"` scope. + +> I hope to deprecate `functionExpressionScope` field as replacing by `scope.type === "function-expression-name"`. + +### Deprecated members + +Those members are defined but not used in ESLint. + +#### taints + +* **Type:** `Map` +* **Description:** The map from variable names to `tainted` flag. + +#### dynamic + +* **Type:** `boolean` +* **Description:** `true` if this scope is dynamic. I.e., the type of this scope is `"global"` or `"with"`. + +#### directCallToEvalScope + +* **Type:** `boolean` +* **Description:** `true` if this scope contains `eval()` invocations. + +#### thisFound + +* **Type:** `boolean` +* **Description:** `true` if this scope contains `this`. + +#### resolve(node) + +* **Parameters:** + * `node` (`ASTNode`) ... An AST node to get their reference object. The type of the node must be `"Identifier"`. +* **Return type:** `Reference | null` +* **Description:** Returns `this.references.find(r => r.identifier === node)`. + +#### isStatic() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** Returns `!this.dynamic`. + +#### isArgumentsMaterialized() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this is a `"function"` scope which has used `arguments` variable. + +#### isThisMaterialized() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** Returns `this.thisFound`. + +#### isUsedName(name) + +* **Parameters:** + * `name` (`string`) ... The name to check. +* **Return type:** `boolean` +* **Description:** `true` if a given name is used in variable names or reference names. + +---- + +## Variable interface + +`Variable` object is variable's information. + +### Fields + +#### name + +* **Type:** `string` +* **Description:** The name of this variable. + +#### identifiers + +* **Type:** `ASTNode[]` +* **Description:** The array of `Identifier` nodes which define this variable. If this variable is redeclared, this array includes two or more nodes. + +> I hope to deprecate `identifiers` field as replacing by `defs[].name` field. + +#### references + +* **Type:** `Reference[]` +* **Description:** The array of the references of this variable. + +#### defs + +* **Type:** `Definition[]` +* **Description:** The array of the definitions of this variable. + +### Deprecated members + +Those members are defined but not used in ESLint. + +#### tainted + +* **Type:** `boolean` +* **Description:** The `tainted` flag. (always `false`) + +#### stack + +* **Type:** `boolean` +* **Description:** The `stack` flag. (I'm not sure what this means.) + +---- + +## Reference interface + +`Reference` object is reference's information. + +### Fields + +#### identifier + +* **Type:** `ASTNode` +* **Description:** The `Identifier` node of this reference. + +#### from + +* **Type:** `Scope` +* **Description:** The `Scope` object that this reference is on. + +#### resolved + +* **Type:** `Variable | null` +* **Description:** The `Variable` object that this reference refers. If such variable was not defined, this is `null`. + +#### writeExpr + +* **Type:** `ASTNode | null` +* **Description:** The ASTNode object which is right-hand side. + +#### init + +* **Type:** `boolean` +* **Description:** `true` if this writing reference is a variable initializer or a default value. + +### Methods + +#### isWrite() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is writing. + +#### isRead() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is reading. + +#### isWriteOnly() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is writing but not reading. + +#### isReadOnly() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is reading but not writing. + +#### isReadWrite() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is reading and writing. + +### Deprecated members + +Those members are defined but not used in ESLint. + +#### tainted + +* **Type:** `boolean` +* **Description:** The `tainted` flag. (always `false`) + +#### flag + +* **Type:** `number` +* **Description:** `1` is reading, `2` is writing, `3` is reading/writing. + +#### partial + +* **Type:** `boolean` +* **Description:** The `partial` flag. + +#### isStatic() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is resolved statically. + +---- + +## Definition interface + +`Definition` object is variable definition's information. + +### Fields + +#### type + +* **Type:** `string` +* **Description:** The type of this definition. One of `"CatchClause"`, `"ClassName"`, `"FunctionName"`, `"ImplicitGlobalVariable"`, `"ImportBinding"`, `"Parameter"`, `"TDZ"`, and `"Variable"`. + +#### name + +* **Type:** `ASTNode` +* **Description:** The `Identifier` node of this definition. + +#### node + +* **Type:** `ASTNode` +* **Description:** The enclosing node of the name. + +| type | node | +|:---------------------------|:-----| +| `"CatchClause"` | `CatchClause` +| `"ClassName"` | `ClassDeclaration` or `ClassExpression` +| `"FunctionName"` | `FunctionDeclaration` or `FunctionExpression` +| `"ImplicitGlobalVariable"` | `Program` +| `"ImportBinding"` | `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier` +| `"Parameter"` | `FunctionDeclaration`, `FunctionExpression`, or `ArrowFunctionExpression` +| `"TDZ"` | ? +| `"Variable"` | `VariableDeclarator` + +#### parent + +* **Type:** `ASTNode | undefined | null` +* **Description:** The enclosing statement node of the name. + +| type | parent | +|:---------------------------|:-------| +| `"CatchClause"` | `null` +| `"ClassName"` | `null` +| `"FunctionName"` | `null` +| `"ImplicitGlobalVariable"` | `null` +| `"ImportBinding"` | `ImportDeclaration` +| `"Parameter"` | `null` +| `"TDZ"` | `null` +| `"Variable"` | `VariableDeclaration` + +### Deprecated members + +Those members are defined but not used in ESLint. + +#### index + +* **Type:** `number | undefined | null` +* **Description:** The index in the declaration statement. + +#### kind + +* **Type:** `string | undefined | null` +* **Description:** The kind of the declaration statement. diff --git a/docs/developer-guide/selectors.md b/docs/developer-guide/selectors.md index e5f5ec9da16d..ac1ae0d3f42e 100644 --- a/docs/developer-guide/selectors.md +++ b/docs/developer-guide/selectors.md @@ -32,7 +32,7 @@ The following selectors are supported: * attribute existence: `[attr]` * attribute value: `[attr="foo"]` or `[attr=123]` * attribute regex: `[attr=/foo.*/]` -* attribute conditons: `[attr!="foo"]`, `[attr>2]`, `[attr<3]`, `[attr>=2]`, or `[attr<=3]` +* attribute conditions: `[attr!="foo"]`, `[attr>2]`, `[attr<3]`, `[attr>=2]`, or `[attr<=3]` * nested attribute: `[attr.level2="foo"]` * field: `FunctionDeclaration > Identifier.id` * First or last child: `:first-child` or `:last-child` @@ -90,7 +90,7 @@ If multiple selectors have equal specificity, their listeners will be called in ### Restricting syntax with selectors -With the [no-restricted-syntax](/docs/rules/no-restricted-syntax) rule, you can restrict the usage of particular syntax in your code. For example, you can use the following configuration to disallow using `if` statements that do not have block statements as their body: +With the [no-restricted-syntax](/docs/rules/no-restricted-syntax.md) rule, you can restrict the usage of particular syntax in your code. For example, you can use the following configuration to disallow using `if` statements that do not have block statements as their body: ```json { diff --git a/docs/developer-guide/shareable-configs.md b/docs/developer-guide/shareable-configs.md index 10a9231c7b23..cecc74589326 100644 --- a/docs/developer-guide/shareable-configs.md +++ b/docs/developer-guide/shareable-configs.md @@ -1,10 +1,14 @@ # Shareable Configs -The configuration that you have in your `.eslintrc` file is an important part of your project, and as such, you may want to share it with other projects or people. Shareable configs allow you to publish your configuration settings on [npm](https://npmjs.com) and have others download and use it in their ESLint projects. +The configuration that you have in your `.eslintrc` file is an important part of your project, and as such, you may want to share it with other projects or people. Shareable configs allow you to publish your configuration settings on [npm](https://www.npmjs.com/) and have others download and use it in their ESLint projects. ## Creating a Shareable Config -Shareable configs are simply npm packages that export a configuration object. To start, [create a Node.js module](https://docs.npmjs.com/getting-started/creating-node-modules) like you normally would. Make sure the module name begins with `eslint-config-`, such as `eslint-config-myconfig`. Create a new `index.js` file and export an object containing your settings: +Shareable configs are simply npm packages that export a configuration object. To start, [create a Node.js module](https://docs.npmjs.com/getting-started/creating-node-modules) like you normally would. Make sure the module name begins with `eslint-config-`, such as `eslint-config-myconfig`. + +npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported, by naming or prefixing the module with `@scope/eslint-config`, such as `@scope/eslint-config` or `@scope/eslint-config-myconfig`. + +Create a new `index.js` file and export an object containing your settings: ```js module.exports = { @@ -26,10 +30,10 @@ Since `index.js` is just JavaScript, you can optionally read these settings from Once your shareable config is ready, you can [publish to npm](https://docs.npmjs.com/getting-started/publishing-npm-packages) to share with others. We recommend using the `eslint` and `eslintconfig` keywords so others can easily find your module. -You should declare your dependency on eslint in `package.json` using the [peerDependencies](https://docs.npmjs.com/files/package.json#peerdependencies) field. The recommended way to declare a dependency for future proof compatibility is with the ">=" range syntax, using the lowest required eslint version. For example: +You should declare your dependency on ESLint in `package.json` using the [peerDependencies](https://docs.npmjs.com/files/package.json#peerdependencies) field. The recommended way to declare a dependency for future proof compatibility is with the ">=" range syntax, using the lowest required ESLint version. For example: ``` -peerDependencies: { +"peerDependencies": { "eslint": ">= 3" } ``` @@ -66,6 +70,35 @@ You can also omit the `eslint-config-` and it will be automatically assumed by E } ``` +### npm scoped modules + +npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported in a number of ways. + + +By using the module name: + +```json +{ + "extends": "@scope/eslint-config" +} +``` + +You can also omit the `eslint-config` and it will be automatically assumed by ESLint: + +```json +{ + "extends": "@scope" +} +``` + +The module name can also be customized, just note that when using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config-` prefix. Doing so would result in package naming conflicts, and thus in resolution errors in most of cases. For example a package named `@scope/eslint-config-myconfig` vs `@scope/my-config`, since both are valid scoped package names, the configuration should be specified as: + +```json +{ + "extends": "@scope/eslint-config-myconfig" +} +``` + You can override settings from the shareable config by adding them directly into your `.eslintrc` file. ## Sharing Multiple Configs @@ -90,6 +123,14 @@ Then, assuming you're using the package name `eslint-config-myconfig`, you can a } ``` +When using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config` namespace. Doing so would result in resolution errors as explained above. Assuming the package name is `@scope/eslint-config`, the additional config can be accessed as: + +```json +{ + "extends": "@scope/eslint-config/my-special-config" +} +``` + Note that you can leave off the `.js` from the filename. In this way, you can add as many additional configs to your package as you'd like. **Important:** We strongly recommend always including a default config for your plugin to avoid errors. diff --git a/docs/developer-guide/source-code.md b/docs/developer-guide/source-code.md index ae32c81f4369..a7e9434cdbbb 100644 --- a/docs/developer-guide/source-code.md +++ b/docs/developer-guide/source-code.md @@ -1,19 +1,19 @@ # Source Code -ESLint is hosted at [GitHub](https://github.com/eslint/eslint) and uses [Git](http://git-scm.com/) for source control. In order to obtain the source code, you must first install Git on your system. Instructions for installing and setting up Git can be found at [http://help.github.com/set-up-git-redirect](http://help.github.com/set-up-git-redirect). +ESLint is hosted at [GitHub](https://github.com/eslint/eslint) and uses [Git](https://git-scm.com/) for source control. In order to obtain the source code, you must first install Git on your system. Instructions for installing and setting up Git can be found at [https://help.github.com/articles/set-up-git/](https://help.github.com/articles/set-up-git/). If you simply want to create a local copy of the source to play with, you can clone the main repository using this command: git clone git://github.com/eslint/eslint.git -If you're planning on contributing to ESLint, then it's a good idea to fork the repository. You can find instructions for forking a repository at [http://help.github.com/fork-a-repo/](http://help.github.com/fork-a-repo/). After forking the ESLint repository, you'll want to create a local copy of your fork. +If you're planning on contributing to ESLint, then it's a good idea to fork the repository. You can find instructions for forking a repository at [https://help.github.com/articles/fork-a-repo/](https://help.github.com/articles/fork-a-repo/). After forking the ESLint repository, you'll want to create a local copy of your fork. ## Start Developing Before you can get started developing, you'll need to have a couple of things installed: -* [Node.JS](http://nodejs.org) -* [npm](http://npmjs.org) +* [Node.JS](https://nodejs.org) +* [npm](https://www.npmjs.com/) Once you have a local copy and have Node.JS and npm installed, you'll need to install the ESLint dependencies: diff --git a/docs/developer-guide/unit-tests.md b/docs/developer-guide/unit-tests.md index 863bd444f886..3c45d67a4180 100644 --- a/docs/developer-guide/unit-tests.md +++ b/docs/developer-guide/unit-tests.md @@ -1,6 +1,6 @@ # Unit Tests -Most parts of ESLint have unit tests associated with them. Unit tests are written using [Mocha](http://mochajs.org/) and are required when making contributions to ESLint. You'll find all of the unit tests in the `tests` directory. +Most parts of ESLint have unit tests associated with them. Unit tests are written using [Mocha](https://mochajs.org/) and are required when making contributions to ESLint. You'll find all of the unit tests in the `tests` directory. When you first get the source code, you need to run `npm install` once initially to set ESLint for development. Once you've done that, you can run the tests via: diff --git a/docs/developer-guide/working-with-custom-formatters.md b/docs/developer-guide/working-with-custom-formatters.md index 0a358d513ab0..772e15994d00 100644 --- a/docs/developer-guide/working-with-custom-formatters.md +++ b/docs/developer-guide/working-with-custom-formatters.md @@ -1,6 +1,6 @@ # Working with Custom Formatters -Writing an ESlint custom formatter is simple. All that is needed is a module that exports a function that will receive the results from the execution of ESLint. +Writing an ESLint custom formatter is simple. All that is needed is a module that exports a function that will receive the results from the execution of ESLint. The simplest formatter will be something like: @@ -67,7 +67,7 @@ the list of messages for `errors` and/or `warnings`. The following are the fields of the result object: -- **filePath**: The path to the file relative to the current working directory (the path from which eslint was executed). +- **filePath**: The path to the file relative to the current working directory (the path from which ESLint was executed). - **messages**: An array of message objects. See below for more info about messages. - **errorCount**: The number of errors for the given file. - **warningCount**: The number of warnings for the given file. diff --git a/docs/developer-guide/working-with-plugins.md b/docs/developer-guide/working-with-plugins.md index b8667d33afaa..e0b20bedef55 100644 --- a/docs/developer-guide/working-with-plugins.md +++ b/docs/developer-guide/working-with-plugins.md @@ -4,7 +4,7 @@ Each plugin is an npm module with a name in the format of `eslint-plugin-= 3.0.0. There is also a [deprecated rule format](./working-with-rules-deprecated). +**Note:** This page covers the most recent rule format for ESLint >= 3.0.0. There is also a [deprecated rule format](./working-with-rules-deprecated.md). -Each rule in ESLint has two files named with its identifier (for example, `no-extra-semi`). +Each rule in ESLint has three files named with its identifier (for example, `no-extra-semi`). * in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`) * in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`) +* in the `docs/rules` directory: a Markdown documentation file (for example, `no-extra-semi.md`) **Important:** If you submit a **core** rule to the ESLint repository, you **must** follow some conventions explained below. @@ -28,7 +29,8 @@ module.exports = { docs: { description: "disallow unnecessary semicolons", category: "Possible Errors", - recommended: true + recommended: true, + url: "https://eslint.org/docs/rules/no-extra-semi" }, fixable: "code", schema: [] // no options @@ -51,27 +53,28 @@ The source file for a rule exports an object with the following properties. * `description` (string) provides the short description of the rule in the [rules index](../rules/) * `category` (string) specifies the heading under which the rule is listed in the [rules index](../rules/) - * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../user-guide/configuring#extending-configuration-files) enables the rule + * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../user-guide/configuring.md#extending-configuration-files) enables the rule + * `url` (string) specifies the URL at which the full documentation can be accessed In a custom rule or plugin, you can omit `docs` or include any properties that you need in it. -* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixes problems reported by the rule +* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface.md#fix) automatically fixes problems reported by the rule **Important:** Without the `fixable` property, ESLint does not [apply fixes](#applying-fixes) even if the rule implements `fix` functions. Omit the `fixable` property if the rule is not fixable. -* `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring#configuring-rules) +* `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring.md#configuring-rules) * `deprecated` (boolean) indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. `create` (function) returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: -* if a key is a node type or a [selector](./selectors), ESLint calls that **visitor** function while going **down** the tree -* if a key is a node type or a [selector](./selectors) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree +* if a key is a node type or a [selector](./selectors.md), ESLint calls that **visitor** function while going **down** the tree +* if a key is a node type or a [selector](./selectors.md) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree * if a key is an event name, ESLint calls that **handler** function for [code path analysis](./code-path-analysis.md) A rule can use the current node and its surrounding tree to report or fix problems. -Here are methods for the [array-callback-return](../rules/array-callback-return) rule: +Here are methods for the [array-callback-return](../rules/array-callback-return.md) rule: ```js function checkLastSegment (node) { @@ -104,21 +107,31 @@ module.exports = { The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties: -* `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring#specifying-parser-options)). +* `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring.md#specifying-parser-options)). * `id` - the rule ID. -* `options` - an array of rule options. -* `settings` - the `settings` from configuration. -* `parserPath` - the full path to the `parser` from configuration. +* `options` - an array of the [configured options](/docs/user-guide/configuring.md#configuring-rules) for this rule. This array does not include the rule severity. For more information, see [here](#contextoptions). +* `settings` - the [shared settings](/docs/user-guide/configuring.md#adding-shared-settings) from configuration. +* `parserPath` - the name of the `parser` from configuration. +* `parserServices` - an object containing parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) Additionally, the `context` object has the following methods: -* `getAncestors()` - returns an array of ancestor nodes based on the current traversal. -* `getDeclaredVariables(node)` - returns the declared variables on the given node. +* `getAncestors()` - returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. +* `getDeclaredVariables(node)` - returns a list of [variables](./scope-manager-interface.md#variable-interface) declared by the given node. This information can be used to track references to variables. + * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. + * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. + * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. + * If the node is an `ArrowFunctionExpression`, variables for the parameters are returned. + * If the node is a `ClassDeclaration` or a `ClassExpression`, the variable for the class name is returned. + * If the node is a `CatchClause`, the variable for the exception is returned. + * If the node is an `ImportDeclaration`, variables for all of its specifiers are returned. + * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. + * Otherwise, if the node does not declare any variables, an empty array is returned. * `getFilename()` - returns the filename associated with the source. -* `getScope()` - returns the current scope. -* `getSourceCode()` - returns a `SourceCode` object that you can use to work with the source that was passed to ESLint -* `markVariableAsUsed(name)` - marks the named variable in scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule. -* `report(descriptor)` - reports a problem in the code. +* `getScope()` - returns the [scope](./scope-manager-interface.md#scope-interface) of the currently-traversed node. This information can be used track references to variables. +* `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint. +* `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. +* `report(descriptor)` - reports a problem in the code (see the [dedicated section](#contextreport)). **Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. @@ -135,8 +148,8 @@ The main method you'll use is `context.report()`, which publishes a warning or e * `end` - An object of the end location. * `line` - the 1-based line number at which the problem occurred. * `column` - the 0-based column number at which the problem occurred. -* `data` - (optional) placeholder data for `message`. -* `fix` - (optional) a function that applies a fix to resolve the problem. +* `data` - (optional) [placeholder](#using-message-placeholders) data for `message`. +* `fix` - (optional) a function that applies a [fix](#applying-fixes) to resolve the problem. Note that at least one of `node` or `loc` is required. @@ -151,6 +164,8 @@ context.report({ The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. +### Using message placeholders + You can also use placeholders in the message and provide `data`: ```js @@ -169,6 +184,68 @@ Note that leading and trailing whitespace is optional in message parameters. The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. +### `messageId`s + +Instead of typing out messages in both the `context.report()` call and your tests, you can use `messageId`s instead. + +This allows you to avoid retyping error messages. It also prevents errors reported in different sections of your rule from having out-of-date messages. + +```js +{% raw %} +// in your rule +module.exports = { + meta: { + messages: { + avoidName: "Avoid using variables named '{{ name }}'" + } + }, + create(context) { + return { + Identifier(node) { + if (node.name === "foo") { + context.report({ + node, + messageId: "avoidName", + data: { + name: "foo", + } + }); + } + } + }; + } +}; + +// in the file to lint: + +var foo = 2; +// ^ error: Avoid using variables named 'foo' + +// In your tests: +var rule = require("../../../lib/rules/my-rule"); +var RuleTester = require("eslint").RuleTester; + +var ruleTester = new RuleTester(); +ruleTester.run("my-rule", rule, { + valid: ["bar", "baz"], + + invalid: [ + { + code: "foo", + errors: [ + { + messageId: "avoidName", + data: { + name: "foo" + } + } + ] + } + ] +}); +{% endraw %} +``` + ### Applying Fixes If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example: @@ -198,6 +275,15 @@ The `fixer` object has the following methods: * `replaceText(nodeOrToken, text)` - replaces the text in the given node or token * `replaceTextRange(range, text)` - replaces the text in the given range +The above methods return a `fixing` object. +The `fix()` function can return the following values: + +* A `fixing` object. +* An array which includes `fixing` objects. +* An iterable object which enumerates `fixing` objects. Especially, the `fix()` function can be a generator. + +If you make a `fix()` function which returns multiple `fixing` objects, those `fixing` objects must not be overlapped. + Best practices for fixes: 1. Avoid any fixes that could change the runtime behavior of code and cause it to stop working. @@ -217,7 +303,7 @@ Best practices for fixes: ({ "foo": 1 }) ``` - * This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](/docs/rules/quotes) rule. + * This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](/docs/rules/quotes.md) rule. ### context.options @@ -243,7 +329,7 @@ module.exports = { Since `context.options` is just an array, you can use it to determine how many options have been passed as well as retrieving the actual options themselves. Keep in mind that the error level is not part of `context.options`, as the error level cannot be known or modified from inside a rule. -When using options, make sure that your rule has some logic defaults in case the options are not provided. +When using options, make sure that your rule has some logical defaults in case the options are not provided. ### context.getSourceCode() @@ -263,7 +349,9 @@ Once you have an instance of `SourceCode`, you can use the methods on it to work * `getText(node)` - returns the source code for the given node. Omit `node` to get the whole source. * `getAllComments()` - returns an array of all comments in the source. -* `getComments(node)` - returns the leading and trailing comments arrays for the given node. +* `getCommentsBefore(nodeOrToken)` - returns an array of comment tokens that occur directly before the given node or token. +* `getCommentsAfter(nodeOrToken)` - returns an array of comment tokens that occur directly after the given node or token. +* `getCommentsInside(node)` - returns an array of all comment tokens inside a given node. * `getJSDocComment(node)` - returns the JSDoc comment for a given node or `null` if there is none. * `isSpaceBetweenTokens(first, second)` - returns true if there is a whitespace character between the two tokens. * `getFirstToken(node, skipOptions)` - returns the first token representing the given node. @@ -284,6 +372,7 @@ Once you have an instance of `SourceCode`, you can use the methods on it to work * `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index. * `getLocFromIndex(index)` - returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. * `getIndexFromLoc(loc)` - returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. +* `commentsExistBetween(nodeOrToken1, nodeOrToken2)` - returns `true` if comments exist between two nodes. > `skipOptions` is an object which has 3 properties; `skip`, `includeComments`, and `filter`. Default is `{skip: 0, includeComments: false, filter: null}`. > - `skip` is a positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. @@ -303,10 +392,20 @@ There are also some properties you can access: * `hasBOM` - the flag to indicate whether or not the source code has Unicode BOM. * `text` - the full text of the code being linted. Unicode BOM has been stripped from this text. * `ast` - the `Program` node of the AST for the code being linted. +* `scopeManager` - the [ScopeManager](./scope-manager-interface.md#scopemanager-interface) object of the code. +* `visitorKeys` - the visitor keys to traverse this AST. * `lines` - an array of lines, split according to the specification's definition of line breaks. You should use a `SourceCode` object whenever you need to get more information about the code being linted. +#### Deprecated + +Please note that the following methods have been deprecated and will be removed in a future version of ESLint: + +* `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` +* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option +* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option + ### Options Schemas Rules may export a `schema` property, which is a [JSON schema](http://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. @@ -341,6 +440,8 @@ In the preceding example, the error level is assumed to be the first argument. I To learn more about JSON Schema, we recommend looking at some [examples](http://json-schema.org/examples.html) to start, and also reading [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/) (a free ebook). +**Note:** Currently you need to use full JSON Schema object rather than array in case your schema has references ($ref), because in case of array format ESLint transforms this array into a single schema without updating references that makes them incorrect (they are ignored). + ### Getting the Source If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: @@ -362,290 +463,40 @@ var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as location of commas, semicolons, parentheses, etc.). -### Accessing comments - -If you need to access comments for a specific node you can use `sourceCode.getComments(node)`: - -```js -// the "comments" variable has a "leading" and "trailing" property containing -// its leading and trailing comments, respectively -var comments = sourceCode.getComments(node); -``` - -Keep in mind that comments are technically not a part of the AST and are only attached to it on demand, i.e. when you call `getComments()`. +### Accessing Comments -**Note:** One of the libraries adds AST node properties for comments - do not use these properties. Always use `sourceCode.getComments()` as this is the only guaranteed API for accessing comments (we will likely change how comments are handled later). +While comments are not technically part of the AST, ESLint provides a few ways for rules to access them: -### Accessing Code Paths +#### sourceCode.getAllComments() -ESLint analyzes code paths while traversing AST. -You can access that code path objects with five events related to code paths. +This method returns an array of all the comments found in the program. This is useful for rules that need to check all comments regardless of location. -[details here](./code-path-analysis.md) +#### sourceCode.getCommentsBefore(), sourceCode.getCommentsAfter(), and sourceCode.getCommentsInside() -## Rule Unit Tests +These methods return an array of comments that appear directly before, directly after, and inside nodes, respectively. They are useful for rules that need to check comments in relation to a given node or token. -Each rule must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if your rule source file is `lib/rules/foo.js` then your test file should be `tests/lib/rules/foo.js`. +Keep in mind that the results of this method are calculated on demand. -For your rule, be sure to test: +#### Token traversal methods -1. All instances that should be flagged as warnings. -1. At least one pattern that should **not** be flagged as a warning. - -The basic pattern for a rule unit test file is: - -```js -/** - * @fileoverview Tests for no-with rule. - * @author Nicholas C. Zakas - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -var rule = require("../../../lib/rules/no-with"), - RuleTester = require("../../../lib/testers/rule-tester"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -var ruleTester = new RuleTester(); -ruleTester.run("no-with", rule, { - valid: [ - "foo.bar()" - ], - invalid: [ - { - code: "with(foo) { bar() }", - errors: [{ message: "Unexpected use of 'with' statement.", type: "WithStatement"}] - } - ] -}); -``` - -Be sure to replace the value of `"no-with"` with your rule's ID. There are plenty of examples in the `tests/lib/rules/` directory. - -### Valid Code - -Each valid case can be either a string or an object. The object form is used when you need to specify additional global variables or arguments for the rule. For example, the following defines `window` as a global variable for code that should not trigger the rule being tested: - -```js -valid: [ - { - code: "window.alert()", - globals: [ "window" ] - } -] -``` - -You can also pass options to the rule (if it accepts them). These arguments are equivalent to how people can configure rules in their `.eslintrc` file. For example: - -```js -valid: [ - { - code: "var msg = 'Hello';", - options: [ "single" ] - } -] -``` +Finally, comments can be accessed through many of `sourceCode`'s methods using the `includeComments` option. -The `options` property must be an array of options. This gets passed through to `context.options` in the rule. +### Accessing Shebangs -### Invalid Code +Shebangs are represented by tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined above. -Each invalid case must be an object containing the code to test and at least one message that is produced by the rule. The `errors` key specifies an array of objects, each containing a message (your rule may trigger multiple messages for the same code). You should also specify the type of AST node you expect to receive back using the `type` key. The AST node should represent the actual spot in the code where there is a problem. For example: - -```js -invalid: [ - { - code: "function doSomething() { var f; if (true) { var build = true; } f = build; }", - errors: [ - { message: "build used outside of binding context.", type: "Identifier" } - ] - } -] -``` - -In this case, the message is specific to the variable being used and the AST node type is `Identifier`. - -You can also check that the rule returns the correct line and column numbers for the message by adding `line` and `column` properties as needed (both are optional, but highly recommend): - -```js -invalid: [ - { - code: "function doSomething() { var f; if (true) { var build = true; } f = build; }", - errors: [ - { - message: "build used outside of binding context.", - type: "Identifier", - line: 1, - column: 68 - } - ] - } -] -``` - -The test fails if the line or column reported by the rule doesn't match the options specified in the test. - -Similar to the valid cases, you can also specify `options` to be passed to the rule: - -```js -invalid: [ - { - code: "function doSomething() { var f; if (true) { var build = true; } f = build; }", - options: [ "double" ], - errors: [ - { message: "build used outside of binding context.", type: "Identifier" } - ] - } -] -``` - -For simpler cases where the only thing that really matters is the error message, you can also specify any `errors` as strings. You can also have some strings and some objects, if you like. - -```js -invalid: [ - { - code: "'single quotes'", - options: ["double"], - errors: ["Strings must use doublequote."] - } -] -``` - -### Specifying Globals - -If your rule relies on globals to be specified, you can provide global variable declarations by using the `globals` property. For example: - -```js -valid: [ - { - code: "for (x of a) doSomething();", - globals: { window: true } - } -] -``` - -The same works on invalid tests: - -```js -invalid: [ - { - code: "'single quotes'", - globals: { window: true }, - errors: ["Strings must use doublequote."] - } -] -``` - -### Specifying Settings - -If your rule relies on `context.settings` to be specified, you can provide those settings by using the `settings` property. For example: - -```js -valid: [ - { - code: "for (x of a) doSomething();", - settings: { message: "hi" } - } -] -``` - -The same works on invalid tests: - -```js -invalid: [ - { - code: "'single quotes'", - settings: { message: "hi" }, - errors: ["Strings must use doublequote."] - } -] -``` - -You can then access `context.settings.message` inside of the rule. - -### Specifying Filename - -If your rule relies on `context.getFilename()` to be specified, you can provide the filename by using the `filename` property. For example: - -```js -valid: [ - { - code: "for (x of a) doSomething();", - filename: "foo/bar.js" - } -] -``` - -The same works on invalid tests: - -```js -invalid: [ - { - code: "'single quotes'", - filename: "foo/bar.js", - errors: ["Strings must use doublequote."] - } -] -``` - -You can then access `context.getFilename()` inside of the rule. - -### Specifying Parser and Parser Options - -Some tests require that a certain parser configuration must be used. This can be specified in test specifications via the `parser` and `parserOptions` properties. While the following examples show usage in `valid` tests, you can use the same options in `invalid` tests as well. - -For example, to set `ecmaVersion` to 6 (in order to use constructs like `for-of`): - -```js -valid: [ - { - code: "for (x of a) doSomething();", - parserOptions: { ecmaVersion: 6 } - } -] -``` - -If you are working with ES6 modules: - -```js -valid: [ - { - code: "export default function () {};", - parserOptions: { ecmaVersion: 6, sourceType: "module" } - } -] -``` +### Accessing Code Paths -For non-version specific features such as JSX: +ESLint analyzes code paths while traversing AST. +You can access that code path objects with five events related to code paths. -```js -valid: [ - { - code: "var foo =
{bar}
", - parserOptions: { ecmaFeatures: { jsx: true } } - } -] -``` +[details here](./code-path-analysis.md) -To use a different parser: +## Rule Unit Tests -```js -valid: [ - { - code: "var foo =
{bar}
", - parser: "my-custom-parser" - } -] -``` +Each bundled rule for ESLint core must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if the rule source file is `lib/rules/foo.js` then the test file should be `tests/lib/rules/foo.js`. -The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../user-guide/configuring#specifying-parser-options). +ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api.md#ruletester) utility to make it easy to write tests for rules. ## Performance Testing @@ -653,7 +504,7 @@ To keep the linting process efficient and unobtrusive, it is useful to verify th ### Overall Performance -The `npm run perf` command gives a high-level overview of ESLint running time with default rules (`eslint:recommended`) enabled. +When developing in the ESLint core repository, the `npm run perf` command gives a high-level overview of ESLint running time with all core rules enabled. ```bash $ git checkout master @@ -716,7 +567,6 @@ The rule naming conventions for ESLint are fairly simple: * If your rule is disallowing something, prefix it with `no-` such as `no-eval` for disallowing `eval()` and `no-debugger` for disallowing `debugger`. * If your rule is enforcing the inclusion of something, use a short name without a special prefix. -* Keep your rule names as short as possible, use abbreviations where appropriate, and no more than four words. * Use dashes between words. ## Runtime Rules @@ -726,5 +576,5 @@ The thing that makes ESLint different from other linters is the ability to defin Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps: 1. Place all of your runtime rules in the same directory (i.e., `eslint_rules`). -2. Create a [configuration file](../user-guide/configuring) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file. -3. Run the [command line interface](../user-guide/command-line-interface) using the `--rulesdir` option to specify the location of your runtime rules. +2. Create a [configuration file](../user-guide/configuring.md) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file. +3. Run the [command line interface](../user-guide/command-line-interface.md) using the `--rulesdir` option to specify the location of your runtime rules. diff --git a/docs/maintainer-guide/README.md b/docs/maintainer-guide/README.md index 845a5bade8d3..19c1fb9eeccc 100644 --- a/docs/maintainer-guide/README.md +++ b/docs/maintainer-guide/README.md @@ -2,18 +2,18 @@ This guide is intended for those who work as part of the ESLint project team. -## [Managing Issues](issues.html) +## [Managing Issues](issues.md) Describes how to deal with issues when they're opened, when interacting with users, and how to close them effectively. -## [Reviewing Pull Requests](pullrequests.html) +## [Reviewing Pull Requests](pullrequests.md) Describes how to review incoming pull requests. -## [Managing Releases](releases.html) +## [Managing Releases](releases.md) Describes how to do an ESLint project release. -## [Governance](governance.html) +## [Governance](governance.md) Describes the governance policy for ESLint, including the rights and privileges of individuals inside the project. diff --git a/docs/maintainer-guide/governance.md b/docs/maintainer-guide/governance.md index 098f11598459..15e5a6340b25 100644 --- a/docs/maintainer-guide/governance.md +++ b/docs/maintainer-guide/governance.md @@ -92,13 +92,14 @@ A Committer is invited to become a TSC member by existing TSC members. A nominat #### Process for Adding TSC Members 1. Add the GitHub user to the "ESLint TSC" team -2. Set the GitHub user to be have the "Owner" role for the ESLint organization -3. Send welcome email with link to maintainer guide -4. Add TSC member to the README -5. Invite to Gitter TSC chatroom -6. Make TSC member an admin on the ESLint team mailing list -7. Add TSC member as an admin to ESLint Twitter Account on Tweetdeck -8. Tweet congratulations to the new TSC member from the ESLint Twitter account +1. Set the GitHub user to be have the "Owner" role for the ESLint organization +1. Send welcome email with link to maintainer guide +1. Add the TSC member to the README +1. Invite to the Gitter TSC chatroom +1. Make the TSC member an admin on the ESLint team mailing list +1. Add the TSC member to the recurring TSC meeting event on Google Calendar +1. Add the TSC member as an admin to ESLint Twitter Account on Tweetdeck +1. Tweet congratulations to the new TSC member from the ESLint Twitter account #### TSC Meetings @@ -135,7 +136,7 @@ agenda item and sending it as a pull request after the meeting. ## Consensus Seeking Process The TSC follows a -[Consensus Seeking](http://en.wikipedia.org/wiki/Consensus-seeking_decision-making) +[Consensus Seeking](https://en.wikipedia.org/wiki/Consensus-seeking_decision-making) decision making model. When an agenda item has appeared to reach a consensus, the moderator @@ -151,4 +152,4 @@ or else the discussion will continue. Simple majority wins. This work is a derivative of [YUI Contributor Model](https://github.com/yui/yui3/wiki/Contributor-Model) and the [Node.js Project Governance Model](https://github.com/nodejs/node/blob/master/GOVERNANCE.md). -This work is licensed under a [Creative Commons Attribution-ShareAlike 2.0 UK: England & Wales License](http://creativecommons.org/licenses/by-sa/2.0/uk/). +This work is licensed under a [Creative Commons Attribution-ShareAlike 2.0 UK: England & Wales License](https://creativecommons.org/licenses/by-sa/2.0/uk/). diff --git a/docs/maintainer-guide/issues.md b/docs/maintainer-guide/issues.md index 4f045513f1f6..ad610253d151 100644 --- a/docs/maintainer-guide/issues.md +++ b/docs/maintainer-guide/issues.md @@ -41,10 +41,10 @@ The steps for triaging an issue are: * "documentation" - related to content on eslint.org * "infrastructure" - related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) 1. Once it's clear what type of issue it is, make sure all of the relevant information is provided: - * **Bugs**: See [bug reporting guidelines](/docs/developer-guide/contributing/reporting-bugs) - * **New Rules:** See [rule proposal guidelines](/docs/developer-guide/contributing/new-rules) - * **Rule Changes:** See [rule change proposal guidelines](/docs/developer-guide/contributing/rule-changes) - * **Other Changes:** See [change proposal guidelines](http://eslint.org/docs/developer-guide/contributing/changes) + * **Bugs**: See [bug reporting guidelines](/docs/developer-guide/contributing/reporting-bugs.md) + * **New Rules:** See [rule proposal guidelines](/docs/developer-guide/contributing/new-rules.md) + * **Rule Changes:** See [rule change proposal guidelines](/docs/developer-guide/contributing/rule-changes.md) + * **Other Changes:** See [change proposal guidelines](/docs/developer-guide/contributing/changes.md) 1. Next steps: * **Questions:** answer the question and close the issue when the conversation is over. * **Bugs:** if you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request. @@ -54,7 +54,7 @@ The steps for triaging an issue are: * **Duplicates:** if you can verify the issue is a duplicate, add a comment mentioning the duplicate issue (such as, "Duplicate of #1234") and close the issue. 1. Regardless of the above, always leave a comment. Don't just add labels, engage with the person who opened the issue by asking a question (request more information if necessary) or stating your opinion of the issue. If it's a verified bug, ask if the user would like to submit a pull request. -**Note:** "Beginner" issues are intended to help new contributors feel welcome and empowered to make a contribution to ESLint. To ensure that new contributors are given a chance to work on these issues, issues labeled "beginner" must be open for 30 days *from the day the issue was labeled* before a team member is permitted to work on them. +**Note:** "Good first issue" issues are intended to help new contributors feel welcome and empowered to make a contribution to ESLint. To ensure that new contributors are given a chance to work on these issues, issues labeled "good first issue" must be open for 30 days *from the day the issue was labeled* before a team member is permitted to work on them. ## Accepting Issues @@ -72,7 +72,7 @@ New rules and rule changes require a champion. As champion, it's your job to: * Gain [consensus](#consensus) from the ESLint team on inclusion * Guide the rule creation process until it's complete (so only champion a rule that you have time to implement or help another contributor implement) -Once consensus has been reached on inclusion, add the "accepted" and, optionally, "help wanted" and "beginner" labels, as necessary. +Once consensus has been reached on inclusion, add the "accepted" and, optionally, "help wanted" and "good first issue" labels, as necessary. ## Consensus diff --git a/docs/maintainer-guide/pullrequests.md b/docs/maintainer-guide/pullrequests.md index fffeb75b5fdf..2d3b13bf0d9f 100644 --- a/docs/maintainer-guide/pullrequests.md +++ b/docs/maintainer-guide/pullrequests.md @@ -11,14 +11,15 @@ Anyone, both team members and the public, may leave comments on pull requests. When a pull request is opened, the bot will check the following: 1. Has the submitter signed a CLA? -1. Is the commit message summary in the correct format? Double-check that the tag ("Fix:", "New:", etc.) is correct based on the issue. Documentation-only pull requests do not require an issue. -1. Does the commit summary reference an issue? +1. Is the commit message summary in the correct format? 1. Is the commit summary too long? The bot will add a comment specifying the problems that it finds. You do not need to look at the pull request any further until those problems have been addressed (there's no need to comment on the pull request to ask the submitter to do what the bot asked - that's why we have the bot!). Once the bot checks have been satisfied, you check the following: +1. Double-check that the commit message tag ("Fix:", "New:", etc.) is correct based on the issue (or, if no issue is referenced, based on the stated problem). +1. If the pull request makes a change to core, ensure that an issue exists and the pull request references the issue in the commit message. 1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the conventions document. 1. For code changes: * Are there tests that verify the change? If not, please ask for them. @@ -37,7 +38,8 @@ Committers may merge a pull request if it is a non-breaking change and is: 1. A documentation change 1. A bug fix (for either rules or core) 1. A dependency upgrade -1. Related to the build system +1. Related to the build tool +1. A chore In addition, committers may merge any non-breaking pull request if it has been approved by at least one TSC member. @@ -57,7 +59,7 @@ Team members may merge a pull request immediately if it: 1. Makes a small documentation change 1. Is a chore -1. Fixes a block of other work on the repo (build-related, test-related, dependency-related, etc.) +1. Fixes a block of other work on the repository (build-related, test-related, dependency-related, etc.) 1. Is an important fix to get into a patch release Otherwise, team members should observe a waiting period before merging a pull request: @@ -67,6 +69,8 @@ Otherwise, team members should observe a waiting period before merging a pull re The waiting period ensures that other team members have a chance to review the pull request before it is merged. +If the pull request was created from a branch on the `eslint/eslint` repository (as opposed to a fork), delete the branch after merging the pull request. (GitHub will display a "Delete branch" button after the pull request is merged.) + **Note:** You should not merge your own pull request unless you're received feedback from at least one other team member. ## When to Close a Pull Request diff --git a/docs/maintainer-guide/releases.md b/docs/maintainer-guide/releases.md index cf92d7f561b0..e8faab5f63a7 100644 --- a/docs/maintainer-guide/releases.md +++ b/docs/maintainer-guide/releases.md @@ -2,7 +2,7 @@ Releases are when a project formally publishes a new version so the community can use it. There are two types of releases: -* Regular releases that follow [semantic versioning](http://semver.org/) and are considered production-ready. +* Regular releases that follow [semantic versioning](https://semver.org/) and are considered production-ready. * Prereleases that are not considered production-ready and are intended to give the community a preview of upcoming changes. ## Release Team @@ -16,6 +16,10 @@ A two-person release team is assigned to each scheduled release. This two-person The two-person team should seek input from the whole team on the Monday following a release to double-check if a patch release is necessary. +## Release Communication + +Each scheduled release should be associated with a release issue ([example](https://github.com/eslint/eslint/issues/8138)). The release issue is the source of information for the team about the status of a release. Be sure the release issue has the "release" label so that it's easy to find. + ## Process On the day of a scheduled release, the release team should follow these steps: @@ -27,10 +31,11 @@ On the day of a scheduled release, the release team should follow these steps: * Small bug fixes written by a team member. 1. Log into Jenkins and schedule a build for the "ESLint Release" job. 1. Wait for the "ESLint Release" job to complete. -1. Update the release blog post with a "Highlights", including new rules and anything else that's important. -1. Make release announcement in the chatroom. -1. Make release announcement on Twitter. -1. Remind the team not to merge anything other than documentation changes and bug fixes. +1. Update the release blog post with a "Highlights" section, including new rules and anything else that's important. +1. Make a release announcement in the public chatroom. +1. Make a release announcement on Twitter. +1. Make a release announcement on the release issue. Document any problems that occurred during the release, and remind the team not to merge anything other than documentation changes and bug fixes. Leave the release issue open. +1. Add the `patch release pending` label to the release issue. (When this label is present, `eslint-github-bot` will create a pending status check on non-semver-patch pull requests, to ensure that they aren't accidentally merged while a patch release is pending.) On the Monday following the scheduled release, the release team needs to determine if a patch release is necessary. A patch release is considered necessary if any of the following occurred since the scheduled release: @@ -39,7 +44,9 @@ On the Monday following the scheduled release, the release team needs to determi The patch release decision should be made as early on Monday as possible. If a patch release is necessary, then follow the same steps as the scheduled release process. -After the patch release has been published (or no patch release is necessary), inform the team that they can start merging in changes again. +In rare cases, a second patch release might be necessary if the release is known to have a severe regression that hasn't been fixed by Monday. If this occurs, the release team should announce the situation on the release issue, and leave the issue open until all patch releases are complete. However, it's usually better to fix bugs for the next release cycle rather than doing a second patch release. + +After the patch release has been published (or no patch release is necessary), close the release issue and inform the team that they can start merging in semver-minor changes again. ## Emergency Releases diff --git a/docs/rules/array-bracket-newline.md b/docs/rules/array-bracket-newline.md new file mode 100644 index 000000000000..72e0041d89a2 --- /dev/null +++ b/docs/rules/array-bracket-newline.md @@ -0,0 +1,281 @@ +# enforce line breaks after opening and before closing array brackets (array-bracket-newline) + +A number of style guides require or disallow line breaks inside of array brackets. + +## Rule Details + +This rule enforces line breaks after opening and before closing array brackets. + +## Options + +This rule has either a string option: + +* `"always"` requires line breaks inside brackets +* `"never"` disallows line breaks inside brackets +* `"consistent"` requires consistent usage of linebreaks for each pair of brackets. It reports an error if one bracket in the pair has a linebreak inside it and the other bracket does not. + +Or an object option (Requires line breaks if any of properties is satisfied. Otherwise, disallows line breaks): + +* `"multiline": true` (default) requires line breaks if there are line breaks inside elements or between elements. If this is false, this condition is disabled. +* `"minItems": null` (default) requires line breaks if the number of elements is at least the given integer. If this is 0, this condition will act the same as the option `"always"`. If this is `null` (the default), this condition is disabled. + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint array-bracket-newline: ["error", "always"]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, + 2]; +var e = [function foo() { + dosomething(); +}]; +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint array-bracket-newline: ["error", "always"]*/ + +var a = [ +]; +var b = [ + 1 +]; +var c = [ + 1, 2 +]; +var d = [ + 1, + 2 +]; +var e = [ + function foo() { + dosomething(); + } +]; +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint array-bracket-newline: ["error", "never"]*/ + +var a = [ +]; +var b = [ + 1 +]; +var c = [ + 1, 2 +]; +var d = [ + 1, + 2 +]; +var e = [ + function foo() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint array-bracket-newline: ["error", "never"]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, + 2]; +var e = [function foo() { + dosomething(); +}]; +``` + +### consistent + +Examples of **incorrect** code for this rule with the `"consistent"` option: + +```js +/*eslint array-bracket-newline: ["error", "consistent"]*/ + +var a = [1 +]; +var b = [ + 1]; +var c = [function foo() { + dosomething(); +} +] +var d = [ + function foo() { + dosomething(); + }] +``` + +Examples of **correct** code for this rule with the `"consistent"` option: + +```js +/*eslint array-bracket-newline: ["error", "consistent"]*/ + +var a = []; +var b = [ +]; +var c = [1]; +var d = [ + 1 +]; +var e = [function foo() { + dosomething(); +}]; +var f = [ + function foo() { + dosomething(); + } +]; +``` + +### multiline + +Examples of **incorrect** code for this rule with the default `{ "multiline": true }` option: + +```js +/*eslint array-bracket-newline: ["error", { "multiline": true }]*/ + +var a = [ +]; +var b = [ + 1 +]; +var c = [ + 1, 2 +]; +var d = [1, + 2]; +var e = [function foo() { + dosomething(); +}]; +``` + +Examples of **correct** code for this rule with the default `{ "multiline": true }` option: + +```js +/*eslint array-bracket-newline: ["error", { "multiline": true }]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [ + 1, + 2 +]; +var e = [ + function foo() { + dosomething(); + } +]; +``` + +### minItems + +Examples of **incorrect** code for this rule with the `{ "minItems": 2 }` option: + +```js +/*eslint array-bracket-newline: ["error", { "minItems": 2 }]*/ + +var a = [ +]; +var b = [ + 1 +]; +var c = [1, 2]; +var d = [1, + 2]; +var e = [ + function foo() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `{ "minItems": 2 }` option: + +```js +/*eslint array-bracket-newline: ["error", { "minItems": 2 }]*/ + +var a = []; +var b = [1]; +var c = [ + 1, 2 +]; +var d = [ + 1, + 2 +]; +var e = [function foo() { + dosomething(); +}]; +``` + +### multiline and minItems + +Examples of **incorrect** code for this rule with the `{ "multiline": true, "minItems": 2 }` options: + +```js +/*eslint array-bracket-newline: ["error", { "multiline": true, "minItems": 2 }]*/ + +var a = [ +]; +var b = [ + 1 +]; +var c = [1, 2]; +var d = [1, + 2]; +var e = [function foo() { + dosomething(); +}]; +``` + +Examples of **correct** code for this rule with the `{ "multiline": true, "minItems": 2 }` options: + +```js +/*eslint array-bracket-newline: ["error", { "multiline": true, "minItems": 2 }]*/ + +var a = []; +var b = [1]; +var c = [ + 1, 2 +]; +var d = [ + 1, + 2 +]; +var e = [ + function foo() { + dosomething(); + } +]; +``` + + +## When Not To Use It + +If you don't want to enforce line breaks after opening and before closing array brackets, don't enable this rule. + +## Compatibility + +* **JSCS:** [validateNewlineAfterArrayElements](http://jscs.info/rule/validateNewlineAfterArrayElements) + +## Related Rules + +* [array-bracket-spacing](array-bracket-spacing.md) diff --git a/docs/rules/array-callback-return.md b/docs/rules/array-callback-return.md index 9b1e9838d78e..c1ed80de948e 100644 --- a/docs/rules/array-callback-return.md +++ b/docs/rules/array-callback-return.md @@ -16,16 +16,16 @@ This rule enforces usage of `return` statement in callbacks of array's methods. This rule finds callback functions of the following methods, then checks usage of `return` statement. -* [`Array.from`](http://www.ecma-international.org/ecma-262/6.0/#sec-array.from) -* [`Array.prototype.every`](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.every) -* [`Array.prototype.filter`](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.filter) -* [`Array.prototype.find`](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.find) -* [`Array.prototype.findIndex`](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.findIndex ) -* [`Array.prototype.map`](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.map) -* [`Array.prototype.reduce`](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.reduce) -* [`Array.prototype.reduceRight`](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.reduceRight) -* [`Array.prototype.some`](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.some) -* [`Array.prototype.sort`](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.sort) +* [`Array.from`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.from) +* [`Array.prototype.every`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.every) +* [`Array.prototype.filter`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.filter) +* [`Array.prototype.find`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.find) +* [`Array.prototype.findIndex`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.findIndex ) +* [`Array.prototype.map`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.map) +* [`Array.prototype.reduce`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.reduce) +* [`Array.prototype.reduceRight`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.reduceRight) +* [`Array.prototype.some`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.some) +* [`Array.prototype.sort`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.sort) * And above of typed arrays. Examples of **incorrect** code for this rule: @@ -72,6 +72,21 @@ var foo = Array.from(nodes, function(node) { var bar = foo.map(node => node.getAttribute("id")); ``` +## Options + +This rule has an object option: + +* `"allowImplicit": false` (default) When set to true, allows implicitly returning `undefined` with a `return` statement containing no expression. + +Examples of **correct** code for the `{ "allowImplicit": true }` option: + +```js +/*eslint array-callback-return: ["error", { allowImplicit: true }]*/ +var undefAllTheThings = myArray.map(function(item) { + return; +}); +``` + ## Known Limitations This rule checks callback functions of methods with the given names, *even if* the object which has the method is *not* an array. diff --git a/docs/rules/array-element-newline.md b/docs/rules/array-element-newline.md new file mode 100644 index 000000000000..21d957101553 --- /dev/null +++ b/docs/rules/array-element-newline.md @@ -0,0 +1,240 @@ +# enforce line breaks between array elements (array-element-newline) + +A number of style guides require or disallow line breaks between array elements. + +## Rule Details + +This rule enforces line breaks between array elements. + +## Options + +This rule has either a string option: + +* `"always"` (default) requires line breaks between array elements +* `"never"` disallows line breaks between array elements + +Or an object option (Requires line breaks if any of properties is satisfied. Otherwise, disallows line breaks): + +* `"multiline": ` requires line breaks if there are line breaks inside elements. If this is false, this condition is disabled. +* `"minItems": ` requires line breaks if the number of elements is at least the given integer. If this is 0, this condition will act the same as the option `"always"`. If this is `null` (the default), this condition is disabled. + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint array-element-newline: ["error", "always"]*/ + +var c = [1, 2]; +var d = [1, 2, 3]; +var e = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint array-element-newline: ["error", "always"]*/ + +var a = []; +var b = [1]; +var c = [1, + 2]; +var d = [1, + 2, + 3]; +var e = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; +``` + +### never + +Examples of **incorrect** code for this rule with the default `"never"` option: + +```js +/*eslint array-element-newline: ["error", "never"]*/ + +var c = [ + 1, + 2 +]; +var d = [ + 1, + 2, + 3 +]; +var e = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint array-element-newline: ["error", "never"]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, 2, 3]; +var e = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; +``` + +### multiline + +Examples of **incorrect** code for this rule with the `{ "multiline": true }` option: + +```js +/*eslint array-element-newline: ["error", { "multiline": true }]*/ + +var d = [1, + 2, 3]; +var e = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `{ "multiline": true }` option: + +```js +/*eslint array-element-newline: ["error", { "multiline": true }]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, 2, 3]; +var e = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; +``` + +### minItems + +Examples of **incorrect** code for this rule with the `{ "minItems": 3 }` option: + +```js +/*eslint array-element-newline: ["error", { "minItems": 3 }]*/ + +var c = [1, + 2]; +var d = [1, 2, 3]; +var e = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `{ "minItems": 3 }` option: + +```js +/*eslint array-element-newline: ["error", { "minItems": 3 }]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, + 2, + 3]; +var e = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; +``` + +### multiline and minItems + +Examples of **incorrect** code for this rule with the `{ "multiline": true, "minItems": 3 }` options: + +```js +/*eslint array-element-newline: ["error", { "multiline": true, "minItems": 3 }]*/ + +var c = [1, +2]; +var d = [1, 2, 3]; +var e = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `{ "multiline": true, "minItems": 3 }` options: + +```js +/*eslint array-element-newline: ["error", { "multiline": true, "minItems": 3 }]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, + 2, + 3]; +var e = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; +``` + + +## When Not To Use It + +If you don't want to enforce linebreaks between array elements, don't enable this rule. + +## Compatibility + +* **JSCS:** [validateNewlineAfterArrayElements](http://jscs.info/rule/validateNewlineAfterArrayElements) + +## Related Rules + +* [array-bracket-spacing](array-bracket-spacing.md) +* [array-bracket-newline](array-bracket-newline.md) +* [object-property-newline](object-property-newline.md) +* [object-curly-spacing](object-curly-spacing.md) +* [object-curly-newline](object-curly-newline.md) +* [max-statements-per-line](max-statements-per-line.md) +* [block-spacing](block-spacing.md) +* [brace-style](brace-style.md) diff --git a/docs/rules/block-spacing.md b/docs/rules/block-spacing.md index 2e37fe54b7d0..aef7c333cd31 100644 --- a/docs/rules/block-spacing.md +++ b/docs/rules/block-spacing.md @@ -1,8 +1,8 @@ -# Disallow or enforce spaces inside of single line blocks (block-spacing) +# Disallow or enforce spaces inside of blocks after opening block and before closing block (block-spacing) ## Rule Details -This rule enforces consistent spacing inside single-line blocks. +This rule enforces consistent spacing inside an open block token and the next token on the same line. This rule also enforces consistent spacing inside a close block token and previous token on the same line. ## Options @@ -20,6 +20,9 @@ Examples of **incorrect** code for this rule with the default `"always"` option: function foo() {return true;} if (foo) { bar = 0;} +function baz() {let i = 0; + return i; +} ``` Examples of **correct** code for this rule with the default `"always"` option: diff --git a/docs/rules/brace-style.md b/docs/rules/brace-style.md index 01a1d29e6a91..37098b1cd490 100644 --- a/docs/rules/brace-style.md +++ b/docs/rules/brace-style.md @@ -1,6 +1,6 @@ # Require Brace Style (brace-style) -Brace style is closely related to [indent style](http://en.wikipedia.org/wiki/Indent_style) in programming and describes the placement of braces relative to their control statement and body. There are probably a dozen, if not more, brace styles in the world. +Brace style is closely related to [indent style](https://en.wikipedia.org/wiki/Indent_style) in programming and describes the placement of braces relative to their control statement and body. There are probably a dozen, if not more, brace styles in the world. The *one true brace style* is one of the most common brace styles in JavaScript, in which the opening brace of a block is placed on the same line as its corresponding statement or declaration. For example: @@ -301,4 +301,4 @@ If you don't want to enforce a particular brace style, don't enable this rule. ## Further Reading -* [Indent style](http://en.wikipedia.org/wiki/Indent_style) +* [Indent style](https://en.wikipedia.org/wiki/Indent_style) diff --git a/docs/rules/callback-return.md b/docs/rules/callback-return.md index 46b24547b3b7..ba27e23340da 100644 --- a/docs/rules/callback-return.md +++ b/docs/rules/callback-return.md @@ -167,7 +167,7 @@ There are some cases where you might want to call a callback function more than ## Further Reading * [The Art Of Node: Callbacks](https://github.com/maxogden/art-of-node#callbacks) -* [Nodejitsu: What are the error conventions?](http://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions) +* [Nodejitsu: What are the error conventions?](https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions/) ## Related Rules diff --git a/docs/rules/camelcase.md b/docs/rules/camelcase.md index 66abcccf8c0f..faeb92398565 100644 --- a/docs/rules/camelcase.md +++ b/docs/rules/camelcase.md @@ -1,6 +1,6 @@ -# Require Camelcase (camelcase) +# Require CamelCase (camelcase) -When it comes to naming variables, style guides generally fall into one of two camps: camelcase (`variableName`) and underscores (`variable_name`). This rule focuses on using the camelcase approach. If your style guide calls for camelcasing your variable names, then this rule is for you! +When it comes to naming variables, style guides generally fall into one of two camps: camelcase (`variableName`) and underscores (`variable_name`). This rule focuses on using the camelcase approach. If your style guide calls for camelCasing your variable names, then this rule is for you! ## Rule Details @@ -32,9 +32,27 @@ obj.do_something = function() { // ... }; +function foo({ no_camelcased }) { + // ... +}; + +function foo({ isCamelcased: no_camelcased }) { + // ... +} + +function foo({ no_camelcased = 'default value' }) { + // ... +}; + var obj = { my_pref: 1 }; + +var { category_id = 1 } = query; + +var { foo: no_camelcased } = bar; + +var { foo: bar_baz = 1 } = quz; ``` Examples of **correct** code for this rule with the default `{ "properties": "always" }` option: @@ -56,6 +74,25 @@ do_something(); new do_something(); var { category_id: category } = query; + +function foo({ isCamelCased }) { + // ... +}; + +function foo({ isCamelCased: isAlsoCamelCased }) { + // ... +} + +function foo({ isCamelCased = 'default value' }) { + // ... +}; + +var { categoryId = 1 } = query; + +var { foo: isCamelCased } = bar; + +var { foo: isCamelCased = 1 } = quz; + ``` ### never diff --git a/docs/rules/class-methods-use-this.md b/docs/rules/class-methods-use-this.md index d24925fbd2f8..89f6762b2221 100644 --- a/docs/rules/class-methods-use-this.md +++ b/docs/rules/class-methods-use-this.md @@ -1,6 +1,6 @@ # Enforce that class methods utilize `this` (class-methods-use-this) -If a class method does not use `this`, it can safely be made a static function. +If a class method does not use `this`, it can *sometimes* be made into a static function. If you do convert the method into a static function, instances of the class that call that particular method have to be converted to a static call as well (`MyClass.callStaticMethod()`) It's possible to have a class method which doesn't use `this`, such as: @@ -43,7 +43,7 @@ class A { A.sayHi(); // => "hi" ``` -Also note in the above examples that the code calling the function on an *instance* of the class (`let a = new A(); a.sayHi();`) changes to calling it on the *class* itself (`A.sayHi();`). +Also note in the above examples that if you switch a method to a static method, *instances* of the class that call the static method (`let a = new A(); a.sayHi();`) have to be updated to being a static call (`A.sayHi();`) instead of having the instance of the *class* call the method ## Rule Details @@ -94,7 +94,7 @@ class A { "class-methods-use-this": [, { "exceptMethods": [<...exceptions>] }] ``` -The `exceptMethods` option allows you to pass an array of method names for which you would like to ignore warnings. +The `exceptMethods` option allows you to pass an array of method names for which you would like to ignore warnings. For example, you might have a spec from an external library that requires you to overwrite a method as a regular function (and not as a static method) and does not use `this` inside the function body. In this case, you can add that method to ignore in the warnings. Examples of **incorrect** code for this rule when used without exceptMethods: @@ -117,3 +117,8 @@ class A { } } ``` + +## Further Reading + +* [Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) +* [Static Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) diff --git a/docs/rules/comma-dangle.md b/docs/rules/comma-dangle.md index 1897f66612b8..275fd0363ff2 100644 --- a/docs/rules/comma-dangle.md +++ b/docs/rules/comma-dangle.md @@ -48,7 +48,7 @@ This rule has a string option or an object option: "objects": "never", "imports": "never", "exports": "never", - "functions": "ignore", + "functions": "ignore" }] } ``` @@ -68,8 +68,9 @@ The default for each option is `"never"` unless otherwise specified. * `objects` is for object literals and object patterns of destructuring. (e.g. `let {a,} = {a: 1};`) * `imports` is for import declarations of ES Modules. (e.g. `import {a,} from "foo";`) * `exports` is for export declarations of ES Modules. (e.g. `export {a,};`) -* `functions` is for function declarations and function calls. (e.g. `(function(a,){ })(b,);`)
- `functions` is set to `"ignore"` by default for consistency with the string option. +* `functions` is for function declarations and function calls. (e.g. `(function(a,){ })(b,);`) + * `functions` is set to `"ignore"` by default for consistency with the string option. + * `functions` should only be enabled when linting ECMAScript 2017 or higher. ### never diff --git a/docs/rules/comma-spacing.md b/docs/rules/comma-spacing.md index 759494bf32e3..a04d5ae5ec78 100644 --- a/docs/rules/comma-spacing.md +++ b/docs/rules/comma-spacing.md @@ -124,6 +124,6 @@ If your project will not be following a consistent comma-spacing pattern, turn t * [space-in-brackets](space-in-brackets.md) (deprecated) * [space-in-parens](space-in-parens.md) * [space-infix-ops](space-infix-ops.md) -* [space-after-keywords](space-after-keywords) -* [space-unary-ops](space-unary-ops) -* [space-return-throw-case](space-return-throw-case) +* [space-after-keywords](space-after-keywords.md) +* [space-unary-ops](space-unary-ops.md) +* [space-return-throw-case](space-return-throw-case.md) diff --git a/docs/rules/comma-style.md b/docs/rules/comma-style.md index 30338fdf29a7..2a55dbf58c4c 100644 --- a/docs/rules/comma-style.md +++ b/docs/rules/comma-style.md @@ -37,8 +37,9 @@ This rule also accepts an additional `exceptions` object: * `"ObjectExpression": true` ignores comma style in object literals * `"ObjectPattern": true` ignores comma style in object patterns of destructuring * `"VariableDeclaration": true` ignores comma style in variable declarations + * `"NewExpression": true` ignores comma style in the parameters of constructor expressions -A way to determine the node types as defined by [ESTree](https://github.com/estree/estree) is to use the [online demo](http://eslint.org/parser). +A way to determine the node types as defined by [ESTree](https://github.com/estree/estree) is to use the [online demo](https://eslint.org/parser). ### last diff --git a/docs/rules/complexity.md b/docs/rules/complexity.md index 4197c03c2791..4f4654b9d39f 100644 --- a/docs/rules/complexity.md +++ b/docs/rules/complexity.md @@ -70,8 +70,11 @@ If you can't determine an appropriate complexity limit for your code, then it's ## Further Reading -* [About Complexity](http://jscomplexity.org/complexity) +* [Cyclomatic Complexity](https://en.wikipedia.org/wiki/Cyclomatic_complexity) * [Complexity Analysis of JavaScript Code](http://ariya.ofilabs.com/2012/12/complexity-analysis-of-javascript-code.html) +* [More about Complexity in JavaScript](https://craftsmanshipforsoftware.com/2015/05/25/complexity-for-javascript/) +* [About Complexity](https://web.archive.org/web/20160808115119/http://jscomplexity.org/complexity) +* [Discussion about Complexity in ESLint and more links](https://github.com/eslint/eslint/issues/4808#issuecomment-167795140) ## Related Rules diff --git a/docs/rules/constructor-super.md b/docs/rules/constructor-super.md index dfe4a97ee733..bf43888d1eb8 100644 --- a/docs/rules/constructor-super.md +++ b/docs/rules/constructor-super.md @@ -2,7 +2,7 @@ Constructors of derived classes must call `super()`. Constructors of non derived classes must not call `super()`. -If this is not observed, the javascript engine will raise a runtime error. +If this is not observed, the JavaScript engine will raise a runtime error. This rule checks whether or not there is a valid `super()` call. diff --git a/docs/rules/dot-notation.md b/docs/rules/dot-notation.md index 881e819d839d..a13049a3f3f5 100644 --- a/docs/rules/dot-notation.md +++ b/docs/rules/dot-notation.md @@ -48,7 +48,7 @@ var x = foo["class"]; // Property name is a reserved word, square-bracket notati ### allowPattern -For example, when preparing data to be sent to an external API, it is often required to use property names that include underscores. If the `camelcase` rule is in effect, these [snake case](http://en.wikipedia.org/wiki/Snake_case) properties would not be allowed. By providing an `allowPattern` to the `dot-notation` rule, these snake case properties can be accessed with bracket notation. +For example, when preparing data to be sent to an external API, it is often required to use property names that include underscores. If the `camelcase` rule is in effect, these [snake case](https://en.wikipedia.org/wiki/Snake_case) properties would not be allowed. By providing an `allowPattern` to the `dot-notation` rule, these snake case properties can be accessed with bracket notation. Examples of **correct** code for the sample `{ "allowPattern": "^[a-z]+(_[a-z]+)+$" }` option: diff --git a/docs/rules/eol-last.md b/docs/rules/eol-last.md index abeef685173f..ce6203bb11f7 100644 --- a/docs/rules/eol-last.md +++ b/docs/rules/eol-last.md @@ -10,7 +10,7 @@ This rule enforces at least one newline (or absence thereof) at the end of non-empty files. Prior to v0.16.0 this rule also enforced that there was only a single line at -the end of the file. If you still want this behaviour, consider enabling +the end of the file. If you still want this behavior, consider enabling [no-multiple-empty-lines](no-multiple-empty-lines.md) with `maxEOF` and/or [no-trailing-spaces](no-trailing-spaces.md). diff --git a/docs/rules/eqeqeq.md b/docs/rules/eqeqeq.md index 8c02ca978a0c..4849f999967f 100644 --- a/docs/rules/eqeqeq.md +++ b/docs/rules/eqeqeq.md @@ -2,7 +2,7 @@ It is considered good practice to use the type-safe equality operators `===` and `!==` instead of their regular counterparts `==` and `!=`. -The reason for this is that `==` and `!=` do type coercion which follows the rather obscure [Abstract Equality Comparison Algorithm](http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3). +The reason for this is that `==` and `!=` do type coercion which follows the rather obscure [Abstract Equality Comparison Algorithm](https://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3). For instance, the following statements are all considered `true`: * `[] == false` @@ -114,7 +114,7 @@ foo == null ### allow-null -**Deprecated:** Instead of using this option use "always" and pass a "null" option property with value "ignore". This will tell eslint to always enforce strict equality except when comparing with the `null` literal. +**Deprecated:** Instead of using this option use "always" and pass a "null" option property with value "ignore". This will tell ESLint to always enforce strict equality except when comparing with the `null` literal. ```js ["error", "always", {"null": "ignore"}] diff --git a/docs/rules/for-direction.md b/docs/rules/for-direction.md new file mode 100644 index 000000000000..be24f7f73694 --- /dev/null +++ b/docs/rules/for-direction.md @@ -0,0 +1,24 @@ +# Enforce "for" loop update clause moving the counter in the right direction. (for-direction) + +## Rule Details + +A `for` loop with a stop condition that can never be reached, such as one with a counter that moves in the wrong direction, will run infinitely. While there are occasions when an infinite loop is intended, the convention is to construct such loops as `while` loops. More typically, an infinite for loop is a bug. + +Examples of **incorrect** code for this rule: + +```js +/*eslint for-direction: "error"*/ +for (var i = 0; i < 10; i--) { +} + +for (var i = 10; i >= 0; i++) { +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint for-direction: "error"*/ +for (var i = 0; i < 10; i++) { +} +``` diff --git a/docs/rules/func-names.md b/docs/rules/func-names.md index 26a414b13673..758abc13f862 100644 --- a/docs/rules/func-names.md +++ b/docs/rules/func-names.md @@ -103,7 +103,7 @@ Foo.prototype.bar = function() {}; ## Further Reading * [Functions Explained](http://markdaggett.com/blog/2013/02/15/functions-explained/) -* [Function Names in ES6](http://www.2ality.com/2015/09/function-names-es6.html) +* [Function Names in ES6](http://2ality.com/2015/09/function-names-es6.html) ## Compatibility diff --git a/docs/rules/function-paren-newline.md b/docs/rules/function-paren-newline.md new file mode 100644 index 000000000000..0c886bf53564 --- /dev/null +++ b/docs/rules/function-paren-newline.md @@ -0,0 +1,277 @@ +# enforce consistent line breaks inside function parentheses (function-paren-newline) + +Many style guides require or disallow newlines inside of function parentheses. + +## Rule Details + +This rule enforces consistent line breaks inside parentheses of function parameters or arguments. + +### Options + +This rule has a single option, which can either be a string or an object. + +* `"always"` requires line breaks inside all function parentheses. +* `"never"` disallows line breaks inside all function parentheses. +* `"multiline"` (default) requires linebreaks inside function parentheses if any of the parameters/arguments have a line break between them. Otherwise, it disallows linebreaks. +* `"consistent"` requires consistent usage of linebreaks for each pair of parentheses. It reports an error if one parenthesis in the pair has a linebreak inside it and the other parenthesis does not. +* `{ "minItems": value }` requires linebreaks inside function parentheses if the number of parameters/arguments is at least `value`. Otherwise, it disallows linebreaks. + +Example configurations: + +```json +{ + "rules": { + "function-paren-newline": ["error", "never"] + } +} +``` + +```json +{ + "rules": { + "function-paren-newline": ["error", { "minItems": 3 }] + } +} +``` + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/* eslint function-paren-newline: ["error", "always"] */ + +function foo(bar, baz) {} + +var foo = function(bar, baz) {}; + +var foo = (bar, baz) => {}; + +foo(bar, baz); +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/* eslint function-paren-newline: ["error", "always"] */ + +function foo( + bar, + baz +) {} + +var foo = function( + bar, baz +) {}; + +var foo = ( + bar, + baz +) => {}; + +foo( + bar, + baz +); +``` + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/* eslint function-paren-newline: ["error", "never"] */ + +function foo( + bar, + baz +) {} + +var foo = function( + bar, baz +) {}; + +var foo = ( + bar, + baz +) => {}; + +foo( + bar, + baz +); +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/* eslint function-paren-newline: ["error", "never"] */ + +function foo(bar, baz) {} + +function foo(bar, + baz) {} + +var foo = function(bar, baz) {}; + +var foo = (bar, baz) => {}; + +foo(bar, baz); + +foo(bar, + baz); +``` + +Examples of **incorrect** code for this rule with the default `"multiline"` option: + +```js +/* eslint function-paren-newline: ["error", "multiline"] */ + +function foo(bar, + baz +) {} + +var foo = function( + bar, baz +) {}; + +var foo = ( + bar, + baz) => {}; + +foo(bar, + baz); + +foo( + function() { + return baz; + } +); +``` + +Examples of **correct** code for this rule with the default `"multiline"` option: + +```js +/* eslint function-paren-newline: ["error", "multiline"] */ + +function foo(bar, baz) {} + +var foo = function( + bar, + baz +) {}; + +var foo = (bar, baz) => {}; + +foo(bar, baz, qux); + +foo( + bar, + baz, + qux +); + +foo(function() { + return baz; +}); +``` + +Examples of **incorrect** code for this rule with the `"consistent"` option: + +```js +/* eslint function-paren-newline: ["error", "consistent"] */ + +function foo(bar, + baz +) {} + +var foo = function(bar, + baz +) {}; + +var foo = ( + bar, + baz) => {}; + +foo( + bar, + baz); + +foo( + function() { + return baz; + }); +``` + +Examples of **correct** code for this rule with the consistent `"consistent"` option: + +```js +/* eslint function-paren-newline: ["error", "consistent"] */ + +function foo(bar, + baz) {} + +var foo = function(bar, baz) {}; + +var foo = ( + bar, + baz +) => {}; + +foo( + bar, baz +); + +foo( + function() { + return baz; + } +); +``` + +Examples of **incorrect** code for this rule with the `{ "minItems": 3 }` option: + +```js +/* eslint function-paren-newline: ["error", { "minItems": 3 }] */ + +function foo( + bar, + baz +) {} + +function foo(bar, baz, qux) {} + +var foo = function( + bar, baz +) {}; + +var foo = (bar, + baz) => {}; + +foo(bar, + baz); +``` + +Examples of **correct** code for this rule with the `{ "minItems": 3 }` option: + +```js +/* eslint function-paren-newline: ["error", { "minItems": 3 }] */ + +function foo(bar, baz) {} + +var foo = function( + bar, + baz, + qux +) {}; + +var foo = ( + bar, baz, qux +) => {}; + +foo(bar, baz); + +foo( + bar, baz, qux +); +``` + +## When Not To Use It + +If don't want to enforce consistent linebreaks inside function parentheses, do not turn on this rule. diff --git a/docs/rules/generator-star-spacing.md b/docs/rules/generator-star-spacing.md index 91fa1213bbd6..95af6de8097f 100644 --- a/docs/rules/generator-star-spacing.md +++ b/docs/rules/generator-star-spacing.md @@ -75,6 +75,27 @@ An example of shorthand configuration: "generator-star-spacing": ["error", "after"] ``` +Additionally, this rule allows further configurability via overrides per function type. + +* `named` provides overrides for named functions +* `anonymous` provides overrides for anonymous functions +* `method` provides overrides for class methods or property function shorthand + +An example of a configuration with overrides: + +```json +"generator-star-spacing": ["error", { + "before": false, + "after": true, + "anonymous": "neither", + "method": {"before": true, "after": true} +}] +``` + +In the example configuration above, the top level "before" and "after" options define the default behavior of +the rule, while the "anonymous" and "method" options override the default behavior. +Overrides can be either an object with "before" and "after", or a shorthand string as above. + ## Examples ### before @@ -137,6 +158,46 @@ var anonymous = function*() {}; var shorthand = { *generator() {} }; ``` +Examples of **incorrect** code for this rule with overrides present: + +```js +/*eslint generator-star-spacing: ["error", { + "before": false, + "after": true, + "anonymous": "neither", + "method": {"before": true, "after": true} +}]*/ +/*eslint-env es6*/ + +function * generator() {} + +var anonymous = function* () {}; + +var shorthand = { *generator() {} }; + +class Class { static* method() {} } +``` + +Examples of **correct** code for this rule with overrides present: + +```js +/*eslint generator-star-spacing: ["error", { + "before": false, + "after": true, + "anonymous": "neither", + "method": {"before": true, "after": true} +}]*/ +/*eslint-env es6*/ + +function* generator() {} + +var anonymous = function*() {}; + +var shorthand = { * generator() {} }; + +class Class { static * method() {} } +``` + ## When Not To Use It If your project will not be using generators or you are not concerned with spacing consistency, you do not need this rule. diff --git a/docs/rules/getter-return.md b/docs/rules/getter-return.md new file mode 100644 index 000000000000..57f92065d42e --- /dev/null +++ b/docs/rules/getter-return.md @@ -0,0 +1,97 @@ +# Enforces that a return statement is present in property getters (getter-return) + +The get syntax binds an object property to a function that will be called when that property is looked up. It was first introduced in ECMAScript 5: + +```js + var p = { + get name(){ + return "nicholas"; + } + }; + + Object.defineProperty(p, "age", { + get: function (){ + return 17; + } + }); +``` + +Note that every `getter` is expected to return a value. + +## Rule Details + +This rule enforces that a return statement is present in property getters. + +Examples of **incorrect** code for this rule: + +```js +/*eslint getter-return: "error"*/ + +p = { + get name(){ + // no returns. + } +}; + +Object.defineProperty(p, "age", { + get: function (){ + // no returns. + } +}); + +class P{ + get name(){ + // no returns. + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint getter-return: "error"*/ + +p = { + get name(){ + return "nicholas"; + } +}; + +Object.defineProperty(p, "age", { + get: function (){ + return 18; + } +}); + +class P{ + get name(){ + return "nicholas"; + } +} +``` + +## Options + +This rule has an object option: + +* `"allowImplicit": false` (default) disallows implicitly returning undefined with a return; statement. + +Examples of **correct** code for the `{ "allowImplicit": true }` option: + +```js +/*eslint getter-return: ["error", { allowImplicit: true }]*/ +p = { + get name(){ + return; // return undefined implicitly. + } +}; +``` + +## When Not To Use It + +If your project will not be using ES5 property getters you do not need this rule. + +## Further Reading + +* [MDN: Functions getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) +* [Understanding ES6: Accessor Properties](https://leanpub.com/understandinges6/read/#leanpub-auto-accessor-properties) diff --git a/docs/rules/guard-for-in.md b/docs/rules/guard-for-in.md index 3b282f8cd15d..ec7860b79f0a 100644 --- a/docs/rules/guard-for-in.md +++ b/docs/rules/guard-for-in.md @@ -45,5 +45,5 @@ for (key in foo) { ## Further Reading -* [Exploring JavaScript for-in loops](http://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/) -* [The pitfalls of using objects as maps in JavaScript](http://www.2ality.com/2012/01/objects-as-maps.html) +* [Exploring JavaScript for-in loops](https://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/) +* [The pitfalls of using objects as maps in JavaScript](http://2ality.com/2012/01/objects-as-maps.html) diff --git a/docs/rules/handle-callback-err.md b/docs/rules/handle-callback-err.md index 9862b5d0abd0..5733e421729e 100644 --- a/docs/rules/handle-callback-err.md +++ b/docs/rules/handle-callback-err.md @@ -78,4 +78,4 @@ confident that some other form of monitoring will help you catch the problem. ## Further Reading * [The Art Of Node: Callbacks](https://github.com/maxogden/art-of-node#callbacks) -* [Nodejitsu: What are the error conventions?](http://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions) +* [Nodejitsu: What are the error conventions?](https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions/) diff --git a/docs/rules/id-length.md b/docs/rules/id-length.md index b63a88b08039..a28407fecf07 100644 --- a/docs/rules/id-length.md +++ b/docs/rules/id-length.md @@ -192,7 +192,7 @@ data["y"] = 3; // excused because of calculated property access Examples of **incorrect** code for this rule with the `{ "max": 10 }` option: ```js -/*eslint id-length: ["error", { "max": "10" }]*/ +/*eslint id-length: ["error", { "max": 10 }]*/ /*eslint-env es6*/ var reallyLongVarName = 5; @@ -210,7 +210,7 @@ try { Examples of **correct** code for this rule with the `{ "max": 10 }` option: ```js -/*eslint id-length: ["error", { "max": "10" }]*/ +/*eslint id-length: ["error", { "max": 10 }]*/ /*eslint-env es6*/ var varName = 5; diff --git a/docs/rules/id-match.md b/docs/rules/id-match.md index 2c6796d5d386..bb56847f3edd 100644 --- a/docs/rules/id-match.md +++ b/docs/rules/id-match.md @@ -83,4 +83,4 @@ do_something(__dirname); ## When Not To Use It -If your rules are too complex, it is possible that you encounter performance issues due to the nature of the job. +If you don't want to enforce any particular naming convention for all identifiers, or your naming convention is too complex to be enforced by configuring this rule, then you should not enable this rule. diff --git a/docs/rules/implicit-arrow-linebreak.md b/docs/rules/implicit-arrow-linebreak.md new file mode 100644 index 000000000000..a1a23553549d --- /dev/null +++ b/docs/rules/implicit-arrow-linebreak.md @@ -0,0 +1,101 @@ +# Enforce the location of arrow function bodies with implicit returns (implicit-arrow-linebreak) + +An arrow function body can contain an implicit return as an expression instead of a block body. It can be useful to enforce a consistent location for the implicitly returned expression. + +## Rule Details + +This rule aims to enforce a consistent location for an arrow function containing an implicit return. + +See Also: + +- [`brace-style`](https://eslint.org/docs/rules/brace-style) which enforces this behavior for arrow functions with block bodies. + +### Options + +This rule accepts a string option: + +- `"beside"` (default) disallows a newline before an arrow function body. +- `"below"` requires a newline before an arrow function body. + +Examples of **incorrect** code for this rule with the default `"beside"` option: + +```js +/* eslint implicit-arrow-linebreak: ["error", "beside"] */ + +(foo) => + bar; + +(foo) => + (bar); + +(foo) => + bar => + baz; + +(foo) => +( + bar() +); +``` + +Examples of **correct** code for this rule with the default `"beside"` option: + +```js +/* eslint implicit-arrow-linebreak: ["error", "beside"] */ + +(foo) => bar; + +(foo) => (bar); + +(foo) => bar => baz; + +(foo) => ( + bar() +); + +// functions with block bodies allowed with this rule using any style +// to enforce a consistent location for this case, see the rule: `brace-style` +(foo) => { + return bar(); +} + +(foo) => +{ + return bar(); +} +``` + +Examples of **incorrect** code for this rule with the `"below"` option: + +```js +/* eslint implicit-arrow-linebreak: ["error", "below"] */ + +(foo) => bar; + +(foo) => (bar); + +(foo) => bar => baz; +``` + +Examples of **correct** code for this rule with the `"below"` option: + +```js +/* eslint implicit-arrow-linebreak: ["error", "below"] */ + + +(foo) => + bar; + +(foo) => + (bar); + +(foo) => + bar => + baz; +``` + +## When Not To Use It + +If you're not concerned about consistent locations of implicitly returned arrow function expressions, you should not turn on this rule. + +You can also disable this rule if you are using the `"always"` option for the [`arrow-body-style`](https://eslint.org/docs/rules/arrow-body-style), since this will disable the use of implicit returns in arrow functions. diff --git a/docs/rules/indent-legacy.md b/docs/rules/indent-legacy.md new file mode 100644 index 000000000000..28208de5db3e --- /dev/null +++ b/docs/rules/indent-legacy.md @@ -0,0 +1,533 @@ +# enforce consistent indentation (indent-legacy) + +ESLint 4.0.0 introduced a rewrite of the [`indent`](/docs/rules/indent) rule, which now reports more errors than it did in previous versions. To ease the process of migrating to 4.0.0, the `indent-legacy` rule was introduced as a snapshot of the `indent` rule from ESLint 3.x. If your build is failing after the upgrade to 4.0.0, you can disable `indent` and enable `indent-legacy` as a quick fix. Eventually, you should switch back to the `indent` rule to get bugfixes and improvements in future versions. + +--- + +There are several common guidelines which require specific indentation of nested blocks and statements, like: + +```js +function hello(indentSize, type) { + if (indentSize === 4 && type !== 'tab') { + console.log('Each next indentation will increase on 4 spaces'); + } +} +``` + +These are the most common scenarios recommended in different style guides: + +* Two spaces, not longer and no tabs: Google, npm, Node.js, Idiomatic, Felix +* Tabs: jQuery +* Four spaces: Crockford + +## Rule Details + +This rule enforces a consistent indentation style. The default style is `4 spaces`. + +## Options + +This rule has a mixed option: + +For example, for 2-space indentation: + +```json +{ + "indent": ["error", 2] +} +``` + +Or for tabbed indentation: + +```json +{ + "indent": ["error", "tab"] +} +``` + +Examples of **incorrect** code for this rule with the default options: + +```js +/*eslint indent: "error"*/ + +if (a) { + b=c; + function foo(d) { + e=f; + } +} +``` + +Examples of **correct** code for this rule with the default options: + +```js +/*eslint indent: "error"*/ + +if (a) { + b=c; + function foo(d) { + e=f; + } +} +``` + +This rule has an object option: + +* `"SwitchCase"` (default: 0) enforces indentation level for `case` clauses in `switch` statements +* `"VariableDeclarator"` (default: 1) enforces indentation level for `var` declarators; can also take an object to define separate rules for `var`, `let` and `const` declarations. +* `"outerIIFEBody"` (default: 1) enforces indentation level for file-level IIFEs. +* `"MemberExpression"` (off by default) enforces indentation level for multi-line property chains (except in variable declarations and assignments) +* `"FunctionDeclaration"` takes an object to define rules for function declarations. + * `parameters` (off by default) enforces indentation level for parameters in a function declaration. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the declaration must be aligned with the first parameter. + * `body` (default: 1) enforces indentation level for the body of a function declaration. +* `"FunctionExpression"` takes an object to define rules for function expressions. + * `parameters` (off by default) enforces indentation level for parameters in a function expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the expression must be aligned with the first parameter. + * `body` (default: 1) enforces indentation level for the body of a function expression. +* `"CallExpression"` takes an object to define rules for function call expressions. + * `arguments` (off by default) enforces indentation level for arguments in a call expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all arguments of the expression must be aligned with the first argument. +* `"ArrayExpression"` (default: 1) enforces indentation level for elements in arrays. It can also be set to the string `"first"`, indicating that all the elements in the array should be aligned with the first element. +* `"ObjectExpression"` (default: 1) enforces indentation level for properties in objects. It can be set to the string `"first"`, indicating that all properties in the object should be aligned with the first property. + +Level of indentation denotes the multiple of the indent specified. Example: + +* Indent of 4 spaces with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 8 spaces. +* Indent of 2 spaces with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 4 spaces. +* Indent of 2 spaces with `VariableDeclarator` set to `{"var": 2, "let": 2, "const": 3}` will indent the multi-line variable declarations with 4 spaces for `var` and `let`, 6 spaces for `const` statements. +* Indent of tab with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 2 tabs. +* Indent of 2 spaces with `SwitchCase` set to `0` will not indent `case` clauses with respect to `switch` statements. +* Indent of 2 spaces with `SwitchCase` set to `1` will indent `case` clauses with 2 spaces with respect to `switch` statements. +* Indent of 2 spaces with `SwitchCase` set to `2` will indent `case` clauses with 4 spaces with respect to `switch` statements. +* Indent of tab with `SwitchCase` set to `2` will indent `case` clauses with 2 tabs with respect to `switch` statements. +* Indent of 2 spaces with `MemberExpression` set to `0` will indent the multi-line property chains with 0 spaces. +* Indent of 2 spaces with `MemberExpression` set to `1` will indent the multi-line property chains with 2 spaces. +* Indent of 2 spaces with `MemberExpression` set to `2` will indent the multi-line property chains with 4 spaces. +* Indent of 4 spaces with `MemberExpression` set to `0` will indent the multi-line property chains with 0 spaces. +* Indent of 4 spaces with `MemberExpression` set to `1` will indent the multi-line property chains with 4 spaces. +* Indent of 4 spaces with `MemberExpression` set to `2` will indent the multi-line property chains with 8 spaces. + +### tab + +Examples of **incorrect** code for this rule with the `"tab"` option: + +```js +/*eslint indent: ["error", "tab"]*/ + +if (a) { + b=c; +function foo(d) { + e=f; + } +} +``` + +Examples of **correct** code for this rule with the `"tab"` option: + +```js +/*eslint indent: ["error", "tab"]*/ + +if (a) { +/*tab*/b=c; +/*tab*/function foo(d) { +/*tab*//*tab*/e=f; +/*tab*/} +} +``` + +### SwitchCase + +Examples of **incorrect** code for this rule with the `2, { "SwitchCase": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "SwitchCase": 1 }]*/ + +switch(a){ +case "a": + break; +case "b": + break; +} +``` + +Examples of **correct** code for this rule with the `2, { "SwitchCase": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "SwitchCase": 1 }]*/ + +switch(a){ + case "a": + break; + case "b": + break; +} +``` + +### VariableDeclarator + +Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **correct** code for this rule with the `2, { "VariableDeclarator": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **correct** code for this rule with the `2, { "VariableDeclarator": 2 }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": 2 }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **correct** code for this rule with the `2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +### outerIIFEBody + +Examples of **incorrect** code for this rule with the options `2, { "outerIIFEBody": 0 }`: + +```js +/*eslint indent: ["error", 2, { "outerIIFEBody": 0 }]*/ + +(function() { + + function foo(x) { + return x + 1; + } + +})(); + + +if(y) { +console.log('foo'); +} +``` + +Examples of **correct** code for this rule with the options `2, {"outerIIFEBody": 0}`: + +```js +/*eslint indent: ["error", 2, { "outerIIFEBody": 0 }]*/ + +(function() { + +function foo(x) { + return x + 1; +} + +})(); + + +if(y) { + console.log('foo'); +} +``` + +### MemberExpression + +Examples of **incorrect** code for this rule with the `2, { "MemberExpression": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "MemberExpression": 1 }]*/ + +foo +.bar +.baz() +``` + +Examples of **correct** code for this rule with the `2, { "MemberExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "MemberExpression": 1 }]*/ + +foo + .bar + .baz(); + +// Any indentation is permitted in variable declarations and assignments. +var bip = aardvark.badger + .coyote; +``` + +### FunctionDeclaration + +Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ + +function foo(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ + +function foo(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ + +function foo(bar, baz, + qux, boop) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ + +function foo(bar, baz, + qux, boop) { + qux(); +} +``` + +### FunctionExpression + +Examples of **incorrect** code for this rule with the `2, { "FunctionExpression": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ + +var foo = function(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionExpression": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ + +var foo = function(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **incorrect** code for this rule with the `2, { "FunctionExpression": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ + +var foo = function(bar, baz, + qux, boop) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionExpression": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ + +var foo = function(bar, baz, + qux, boop) { + qux(); +} +``` + +### CallExpression + +Examples of **incorrect** code for this rule with the `2, { "CallExpression": {"arguments": 1} }` option: + +```js +/*eslint indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ + +foo(bar, + baz, + qux +); +``` + +Examples of **correct** code for this rule with the `2, { "CallExpression": {"arguments": 1} }` option: + +```js +/*eslint indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ + +foo(bar, + baz, + qux +); +``` + +Examples of **incorrect** code for this rule with the `2, { "CallExpression": {"arguments": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ + +foo(bar, baz, + baz, boop, beep); +``` + +Examples of **correct** code for this rule with the `2, { "CallExpression": {"arguments": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ + +foo(bar, baz, + baz, boop, beep); +``` + +### ArrayExpression + +Examples of **incorrect** code for this rule with the `2, { "ArrayExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ArrayExpression": 1 }]*/ + +var foo = [ + bar, +baz, + qux +]; +``` + +Examples of **correct** code for this rule with the `2, { "ArrayExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ArrayExpression": 1 }]*/ + +var foo = [ + bar, + baz, + qux +]; +``` + +Examples of **incorrect** code for this rule with the `2, { "ArrayExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ArrayExpression": "first"}]*/ + +var foo = [bar, + baz, + qux +]; +``` + +Examples of **correct** code for this rule with the `2, { "ArrayExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ArrayExpression": "first"}]*/ + +var foo = [bar, + baz, + qux +]; +``` + +### ObjectExpression + +Examples of **incorrect** code for this rule with the `2, { "ObjectExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ObjectExpression": 1 }]*/ + +var foo = { + bar: 1, +baz: 2, + qux: 3 +}; +``` + +Examples of **correct** code for this rule with the `2, { "ObjectExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ObjectExpression": 1 }]*/ + +var foo = { + bar: 1, + baz: 2, + qux: 3 +}; +``` + +Examples of **incorrect** code for this rule with the `2, { "ObjectExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ObjectExpression": "first"}]*/ + +var foo = { bar: 1, + baz: 2 }; +``` + +Examples of **correct** code for this rule with the `2, { "ObjectExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ObjectExpression": "first"}]*/ + +var foo = { bar: 1, + baz: 2 }; +``` + + +## Compatibility + +* **JSHint**: `indent` +* **JSCS**: [validateIndentation](http://jscs.info/rule/validateIndentation) diff --git a/docs/rules/indent.md b/docs/rules/indent.md index 92969d7ed87a..37dcabbd3e47 100644 --- a/docs/rules/indent.md +++ b/docs/rules/indent.md @@ -71,17 +71,21 @@ This rule has an object option: * `"SwitchCase"` (default: 0) enforces indentation level for `case` clauses in `switch` statements * `"VariableDeclarator"` (default: 1) enforces indentation level for `var` declarators; can also take an object to define separate rules for `var`, `let` and `const` declarations. * `"outerIIFEBody"` (default: 1) enforces indentation level for file-level IIFEs. -* `"MemberExpression"` (off by default) enforces indentation level for multi-line property chains (except in variable declarations and assignments) +* `"MemberExpression"` (default: 1) enforces indentation level for multi-line property chains. This can also be set to `"off"` to disable checking for MemberExpression indentation. * `"FunctionDeclaration"` takes an object to define rules for function declarations. - * `parameters` (off by default) enforces indentation level for parameters in a function declaration. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the declaration must be aligned with the first parameter. + * `parameters` (default: 1) enforces indentation level for parameters in a function declaration. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the declaration must be aligned with the first parameter. This can also be set to `"off"` to disable checking for FunctionDeclaration parameters. * `body` (default: 1) enforces indentation level for the body of a function declaration. * `"FunctionExpression"` takes an object to define rules for function expressions. - * `parameters` (off by default) enforces indentation level for parameters in a function expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the expression must be aligned with the first parameter. + * `parameters` (default: 1) enforces indentation level for parameters in a function expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the expression must be aligned with the first parameter. This can also be set to `"off"` to disable checking for FunctionExpression parameters. * `body` (default: 1) enforces indentation level for the body of a function expression. * `"CallExpression"` takes an object to define rules for function call expressions. - * `arguments` (off by default) enforces indentation level for arguments in a call expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all arguments of the expression must be aligned with the first argument. -* `"ArrayExpression"` (default: 1) enforces indentation level for elements in arrays. It can also be set to the string `"first"`, indicating that all the elements in the array should be aligned with the first element. -* `"ObjectExpression"` (default: 1) enforces indentation level for properties in objects. It can be set to the string `"first"`, indicating that all properties in the object should be aligned with the first property. + * `arguments` (default: 1) enforces indentation level for arguments in a call expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all arguments of the expression must be aligned with the first argument. This can also be set to `"off"` to disable checking for CallExpression arguments. +* `"ArrayExpression"` (default: 1) enforces indentation level for elements in arrays. It can also be set to the string `"first"`, indicating that all the elements in the array should be aligned with the first element. This can also be set to `"off"` to disable checking for array elements. +* `"ObjectExpression"` (default: 1) enforces indentation level for properties in objects. It can be set to the string `"first"`, indicating that all properties in the object should be aligned with the first property. This can also be set to `"off"` to disable checking for object properties. +* `"ImportDeclaration"` (default: 1) enforces indentation level for import statements. It can be set to the string `"first"`, indicating that all imported members from a module should be aligned with the first member in the list. This can also be set to `"off"` to disable checking for imported module members. +* `"flatTernaryExpressions": true` (`false` by default) requires no indentation for ternary expressions which are nested in other ternary expressions. +* `"ignoredNodes"` accepts an array of [selectors](/docs/developer-guide/selectors.md). If an AST node is matched by any of the selectors, the indentation of tokens which are direct children of that node will be ignored. This can be used as an escape hatch to relax the rule if you disagree with the indentation that it enforces for a particular syntactic pattern. +* `"ignoreComments"` (default: false) can be used when comments do not need to be aligned with nodes on the previous or next line. Level of indentation denotes the multiple of the indent specified. Example: @@ -286,10 +290,6 @@ Examples of **correct** code for this rule with the `2, { "MemberExpression": 1 foo .bar .baz(); - -// Any indentation is permitted in variable declarations and assignments. -var bip = aardvark.badger - .coyote; ``` ### FunctionDeclaration @@ -522,6 +522,141 @@ var foo = { bar: 1, baz: 2 }; ``` +### ImportDeclaration + +Examples of **correct** code for this rule with the `4, { "ImportDeclaration": 1 }` option (the default): + +```js +/*eslint indent: ["error", 4, { ImportDeclaration: 1 }]*/ + +import { foo, + bar, + baz, +} from 'qux'; + +import { + foo, + bar, + baz, +} from 'qux'; +``` + +Examples of **incorrect** code for this rule with the `4, { ImportDeclaration: "first" }` option: + +```js +/*eslint indent: ["error", 4, { ImportDeclaration: "first" }]*/ + +import { foo, + bar, + baz, +} from 'qux'; +``` + +Examples of **correct** code for this rule with the `4, { ImportDeclaration: "first" }` option: + +```js +/*eslint indent: ["error", 4, { ImportDeclaration: "first" }]*/ + +import { foo, + bar, + baz, +} from 'qux'; +``` + +### flatTernaryExpressions + +Examples of **incorrect** code for this rule with the default `4, { "flatTernaryExpressions": false }` option: + +```js +/*eslint indent: ["error", 4, { "flatTernaryExpressions": false }]*/ + +var a = + foo ? bar : + baz ? qux : + boop; +``` + +Examples of **correct** code for this rule with the default `4, { "flatTernaryExpressions": false }` option: + +```js +/*eslint indent: ["error", 4, { "flatTernaryExpressions": false }]*/ + +var a = + foo ? bar : + baz ? qux : + boop; +``` + +Examples of **incorrect** code for this rule with the `4, { "flatTernaryExpressions": true }` option: + +```js +/*eslint indent: ["error", 4, { "flatTernaryExpressions": true }]*/ + +var a = + foo ? bar : + baz ? qux : + boop; +``` + +Examples of **correct** code for this rule with the `4, { "flatTernaryExpressions": true }` option: + +```js +/*eslint indent: ["error", 4, { "flatTernaryExpressions": true }]*/ + +var a = + foo ? bar : + baz ? qux : + boop; +``` + +### ignoredNodes + +The following configuration ignores the indentation of `ConditionalExpression` ("ternary expression") nodes: + +Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["ConditionalExpression"] }` option: + +```js +/*eslint indent: ["error", 4, { "ignoredNodes": ["ConditionalExpression"] }]*/ + +var a = foo + ? bar + : baz; + +var a = foo + ? bar +: baz; +``` + +The following configuration ignores indentation in the body of IIFEs. + +Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }` option: + +```js +/*eslint indent: ["error", 4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }]*/ + +(function() { + +foo(); +bar(); + +}) +``` + +### ignoreComments + +Examples of additional **correct** code for this rule with the `4, { "ignoreComments": true }` option: + +```js +/*eslint indent: ["error", 4, { "ignoreComments": true }] */ + +if (foo) { + doSomething(); + +// comment intentionally de-indented + doSomethingElse(); +} +``` + ## Compatibility diff --git a/docs/rules/key-spacing.md b/docs/rules/key-spacing.md index 2c4afae8cb47..c94b11f6db9f 100644 --- a/docs/rules/key-spacing.md +++ b/docs/rules/key-spacing.md @@ -10,14 +10,18 @@ This rule enforces consistent spacing between keys and values in object literal This rule has an object option: -* `"beforeColon": false` (default) disallows spaces between the key and the colon in object literals. -* `"beforeColon": true` requires at least one space between the key and the colon in object literals. -* `"afterColon": true` (default) requires at least one space between the colon and the value in object literals. -* `"afterColon": false` disallows spaces between the colon and the value in object literals. -* `"mode": "strict"` (default) enforces exactly one space before or after colons in object literals. -* `"mode": "minimum"` enforces one or more spaces before or after colons in object literals. -* `"align": "value"` enforces horizontal alignment of values in object literals. -* `"align": "colon"` enforces horizontal alignment of both colons and values in object literals. +* `"beforeColon": false (default) | true` + * `false`: disallows spaces between the key and the colon in object literals. + * `true`: requires at least one space between the key and the colon in object literals. +* `"afterColon": true (default) | false` + * `true`: requires at least one space between the colon and the value in object literals. + * `false`: disallows spaces between the colon and the value in object literals. +* `"mode": "strict" (default) | "minimum"` + * `"strict"`: enforces exactly one space before or after colons in object literals. + * `"minimum"`: enforces one or more spaces before or after colons in object literals. +* `"align": "value" | "colon"` + * `"value"`: enforces horizontal alignment of values in object literals. + * `"colon"` enforces horizontal alignment of both colons and values in object literals. * `"align"` with an object value allows for fine-grained spacing when values are being aligned in object literals. * `"singleLine"` specifies a spacing style for single-line object literals. * `"multiLine"` specifies a spacing style for multi-line object literals. diff --git a/docs/rules/keyword-spacing.md b/docs/rules/keyword-spacing.md index b8d97a73e4b9..becc9ef323a9 100644 --- a/docs/rules/keyword-spacing.md +++ b/docs/rules/keyword-spacing.md @@ -59,44 +59,44 @@ if (foo) { //... } -// no conflict with `array-bracket-spacing` +// Avoid conflict with `array-bracket-spacing` let a = [this]; let b = [function() {}]; -// no conflict with `arrow-spacing` +// Avoid conflict with `arrow-spacing` let a = ()=> this.foo; -// no conflict with `block-spacing` +// Avoid conflict with `block-spacing` {function foo() {}} -// no conflict with `comma-spacing` +// Avoid conflict with `comma-spacing` let a = [100,this.foo, this.bar]; -// not conflict with `computed-property-spacing` +// Avoid conflict with `computed-property-spacing` obj[this.foo] = 0; -// no conflict with `generator-star-spacing` +// Avoid conflict with `generator-star-spacing` function *foo() {} -// no conflict with `key-spacing` +// Avoid conflict with `key-spacing` let obj = { foo:function() {} }; -// no conflict with `object-curly-spacing` +// Avoid conflict with `object-curly-spacing` let obj = {foo: this}; -// no conflict with `semi-spacing` +// Avoid conflict with `semi-spacing` let a = this;function foo() {} -// no conflict with `space-in-parens` +// Avoid conflict with `space-in-parens` (function () {})(); -// no conflict with `space-infix-ops` +// Avoid conflict with `space-infix-ops` if ("foo"in {foo: 0}) {} if (10+this.foo<= this.bar) {} -// no conflict with `jsx-curly-spacing` +// Avoid conflict with `jsx-curly-spacing` let a = ``` @@ -157,57 +157,57 @@ if (foo) { //... } -// not conflict with `array-bracket-spacing` +// Avoid conflict with `array-bracket-spacing` let a = [this]; -// not conflict with `arrow-spacing` +// Avoid conflict with `arrow-spacing` let a = ()=> this.foo; -// not conflict with `comma-spacing` +// Avoid conflict with `comma-spacing` let a = [100, this.foo, this.bar]; -// not conflict with `computed-property-spacing` +// Avoid conflict with `computed-property-spacing` obj[this.foo] = 0; -// not conflict with `generator-star-spacing` +// Avoid conflict with `generator-star-spacing` function* foo() {} -// not conflict with `key-spacing` +// Avoid conflict with `key-spacing` let obj = { foo:function() {} }; -// not conflict with `func-call-spacing` +// Avoid conflict with `func-call-spacing` class A { constructor() { super(); } } -// not conflict with `object-curly-spacing` +// Avoid conflict with `object-curly-spacing` let obj = {foo: this}; -// not conflict with `semi-spacing` +// Avoid conflict with `semi-spacing` let a = this;function foo() {} -// not conflict with `space-before-function-paren` +// Avoid conflict with `space-before-function-paren` function() {} -// no conflict with `space-infix-ops` +// Avoid conflict with `space-infix-ops` if ("foo"in{foo: 0}) {} if (10+this.foo<= this.bar) {} -// no conflict with `space-unary-ops` +// Avoid conflict with `space-unary-ops` function* foo(a) { return yield+a; } -// no conflict with `yield-star-spacing` +// Avoid conflict with `yield-star-spacing` function* foo(a) { return yield* a; } -// no conflict with `jsx-curly-spacing` +// Avoid conflict with `jsx-curly-spacing` let a = ``` diff --git a/docs/rules/lines-around-comment.md b/docs/rules/lines-around-comment.md index f0b5c22576a9..61af59698130 100644 --- a/docs/rules/lines-around-comment.md +++ b/docs/rules/lines-around-comment.md @@ -21,6 +21,8 @@ This rule has an object option: * `"allowObjectEnd": true` allows comments to appear at the end of object literals * `"allowArrayStart": true` allows comments to appear at the start of array literals * `"allowArrayEnd": true` allows comments to appear at the end of array literals +* `"allowClassStart": true` allows comments to appear at the start of classes +* `"allowClassEnd": true` allows comments to appear at the end of classes * `"applyDefaultIgnorePatterns"` enables or disables the default comment patterns to be ignored by the rule * `"ignorePattern"` custom patterns to be ignored by the rule @@ -172,6 +174,101 @@ function foo(){ } ``` +### allowClassStart + +Examples of **incorrect** code for this rule with the `{ "beforeLineComment": true, "allowClassStart": false }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowClassStart": false }]*/ + +class foo { + // what a great and wonderful day + day() {} +}; +``` + +Examples of **correct** code for this rule with the `{ "beforeLineComment": true, "allowClassStart": false }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowClassStart": false }]*/ + +class foo { + + // what a great and wonderful day + day() {} +}; +``` + +Examples of **correct** code for this rule with the `{ "beforeLineComment": true, "allowClassStart": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowClassStart": true }]*/ + +class foo { + // what a great and wonderful day + day() {} +}; +``` + +Examples of **incorrect** code for this rule with the `{ "beforeBlockComment": true, "allowClassStart": false }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowClassStart": false }]*/ + +class foo { + /* what a great and wonderful day */ + day() {} +}; +``` + +Examples of **correct** code for this rule with the `{ "beforeBlockComment": true, "allowClassStart": false }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowClassStart": false }]*/ + +class foo { + + /* what a great and wonderful day */ + day() {} +}; +``` + +Examples of **correct** code for this rule with the `{ "beforeBlockComment": true, "allowClassStart": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowClassStart": true }]*/ + +class foo { + /* what a great and wonderful day */ + day() {} +}; +``` + +### allowClassEnd + +Examples of **correct** code for this rule with the `{ "afterLineComment": true, "allowClassEnd": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterLineComment": true, "allowClassEnd": true }]*/ + +class foo { + day() {} + // what a great and wonderful day +}; +``` + +Examples of **correct** code for this rule with the `{ "afterBlockComment": true, "allowClassEnd": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterBlockComment": true, "allowClassEnd": true }]*/ + +class foo { + day() {} + + /* what a great and wonderful day */ +}; +``` + ### allowObjectStart Examples of **correct** code for this rule with the `{ "beforeLineComment": true, "allowObjectStart": true }` option: diff --git a/docs/rules/lines-around-directive.md b/docs/rules/lines-around-directive.md index 16eb77645649..3f9890bc4f96 100644 --- a/docs/rules/lines-around-directive.md +++ b/docs/rules/lines-around-directive.md @@ -1,6 +1,8 @@ # require or disallow newlines around directives (lines-around-directive) -Directives are used in JavaScript to indicate to the execution environment that a script would like to opt into a feature such as `"strict mode"`. Directives are grouped together in a [directive prologue](http://www.ecma-international.org/ecma-262/7.0/#directive-prologue) at the top of either a file or function block and are applied to the scope in which they occur. +This rule was **deprecated** in ESLint v4.0.0 and replaced by the [padding-line-between-statements](padding-line-between-statements.md) rule. + +Directives are used in JavaScript to indicate to the execution environment that a script would like to opt into a feature such as `"strict mode"`. Directives are grouped together in a [directive prologue](https://www.ecma-international.org/ecma-262/7.0/#directive-prologue) at the top of either a file or function block and are applied to the scope in which they occur. ```js // Strict mode is invoked for the entire script diff --git a/docs/rules/lines-between-class-members.md b/docs/rules/lines-between-class-members.md new file mode 100644 index 000000000000..e0429fdf8dd2 --- /dev/null +++ b/docs/rules/lines-between-class-members.md @@ -0,0 +1,107 @@ +# require or disallow an empty line between class members (lines-between-class-members) + +This rule improves readability by enforcing lines between class members. It will not check empty lines before the first member and after the last member, since that is already taken care of by padded-blocks. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +/* eslint lines-between-class-members: ["error", "always"]*/ +class MyClass { + foo() { + //... + } + bar() { + //... + } +} +``` + +Examples of **correct** code for this rule: + +```js +/* eslint lines-between-class-members: ["error", "always"]*/ +class MyClass { + foo() { + //... + } + + bar() { + //... + } +} +``` + +### Options + +This rule has a string option and an object option. + +String option: + +* `"always"`(default) require an empty line after class members +* `"never"` disallows an empty line after class members + +Object option: + +* `"exceptAfterSingleLine": false`(default) **do not** skip checking empty lines after single-line class members +* `"exceptAfterSingleLine": true` skip checking empty lines after single-line class members + +Examples of **incorrect** code for this rule with the string option: + +```js +/* eslint lines-between-class-members: ["error", "always"]*/ +class Foo{ + bar(){} + baz(){} +} + +/* eslint lines-between-class-members: ["error", "never"]*/ +class Foo{ + bar(){} + + baz(){} +} +``` + +Examples of **correct** code for this rule with the string option: + +```js +/* eslint lines-between-class-members: ["error", "always"]*/ +class Foo{ + bar(){} + + baz(){} +} + +/* eslint lines-between-class-members: ["error", "never"]*/ +class Foo{ + bar(){} + baz(){} +} +``` + +Examples of **correct** code for this rule with the object option: + +```js +/* eslint lines-between-class-members: ["error", "always", { exceptAfterSingleLine: true }]*/ +class Foo{ + bar(){} // single line class member + baz(){ + // multi line class member + } + + qux(){} +} +``` + +## When Not To Use It + +If you don't want to enforce empty lines between class members, you can disable this rule. + +## Related Rules + +* [padded-blocks](padded-blocks.md) +* [padding-line-between-statements](padding-line-between-statements.md) +* [requirePaddingNewLinesAfterBlocks](http://jscs.info/rule/requirePaddingNewLinesAfterBlocks) +* [disallowPaddingNewLinesAfterBlocks](http://jscs.info/rule/disallowPaddingNewLinesAfterBlocks) diff --git a/docs/rules/max-len.md b/docs/rules/max-len.md index ef463b2eaa3d..7aa755a34b2d 100644 --- a/docs/rules/max-len.md +++ b/docs/rules/max-len.md @@ -30,7 +30,7 @@ This rule has a number or object option: Examples of **incorrect** code for this rule with the default `{ "code": 80 }` option: ```js -/*eslint max-len: ["error", 80]*/ +/*eslint max-len: ["error", { "code": 80 }]*/ var foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" }, "difficult": "to read" }; ``` @@ -38,7 +38,7 @@ var foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" }, "difficu Examples of **correct** code for this rule with the default `{ "code": 80 }` option: ```js -/*eslint max-len: ["error", 80]*/ +/*eslint max-len: ["error", { "code": 80 }]*/ var foo = { "bar": "This is a bar.", @@ -52,7 +52,7 @@ var foo = { Examples of **incorrect** code for this rule with the default `{ "tabWidth": 4 }` option: ```js -/*eslint max-len: ["error", 80, 4]*/ +/*eslint max-len: ["error", { "code": 80, "tabWidth": 4 }]*/ \t \t var foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" } }; ``` @@ -60,7 +60,7 @@ Examples of **incorrect** code for this rule with the default `{ "tabWidth": 4 } Examples of **correct** code for this rule with the default `{ "tabWidth": 4 }` option: ```js -/*eslint max-len: ["error", 80, 4]*/ +/*eslint max-len: ["error", { "code": 80, "tabWidth": 4 }]*/ \t \t var foo = { \t \t \t \t "bar": "This is a bar.", @@ -144,10 +144,10 @@ var longRegExpLiteral = /this is a really really really really really long regul ### ignorePattern -Examples of **correct** code for this rule with the `{ "ignorePattern": true }` option: +Examples of **correct** code for this rule with the `ignorePattern` option: ```js -/*eslint max-len: ["error", { "ignorePattern": "^\\s*var\\s.+=\\s*require\\s*\\(/" }]*/ +/*eslint max-len: ["error", { "ignorePattern": "^\\s*var\\s.+=\\s*require\\s*\\(" }]*/ var dep = require('really/really/really/really/really/really/really/really/long/module'); ``` diff --git a/docs/rules/max-nested-callbacks.md b/docs/rules/max-nested-callbacks.md index 734ca0e8e444..8c1f9c230f3a 100644 --- a/docs/rules/max-nested-callbacks.md +++ b/docs/rules/max-nested-callbacks.md @@ -71,8 +71,8 @@ function handleFoo4() { ## Further Reading * [Control flow in Node.js](http://book.mixu.net/node/ch7.html) -* [Control Flow in Node](http://howtonode.org/control-flow) -* [Control Flow in Node Part II](http://howtonode.org/control-flow-part-ii) +* [Control Flow in Node](https://howtonode.org/control-flow) +* [Control Flow in Node Part II](https://howtonode.org/control-flow-part-ii) ## Related Rules diff --git a/docs/rules/multiline-comment-style.md b/docs/rules/multiline-comment-style.md new file mode 100644 index 000000000000..32bedddd9367 --- /dev/null +++ b/docs/rules/multiline-comment-style.md @@ -0,0 +1,123 @@ +# enforce a particular style for multiline comments (multiline-comment-style) + +Many style guides require a particular style for comments that span multiple lines. For example, some style guides prefer the use of a single block comment for multiline comments, whereas other style guides prefer consecutive line comments. + +## Rule Details + +This rule aims to enforce a particular style for multiline comments. + +### Options + +This rule has a string option, which can have one of the following values: + +* `"starred-block"` (default): Disallows consecutive line comments in favor of block comments. Additionally, requires block comments to have an aligned `*` character before each line. +* `"bare-block"`: Disallows consecutive line comments in favor of block comments, and disallows block comments from having a `"*"` character before each line. +* `"separate-lines"`: Disallows block comments in favor of consecutive line comments + +The rule always ignores directive comments such as `/* eslint-disable */`. Additionally, unless the mode is `"starred-block"`, the rule ignores JSDoc comments. + +Examples of **incorrect** code for this rule with the default `"starred-block"` option: + +```js + +/* eslint multiline-comment-style: ["error", "starred-block"] */ + +// this line +// calls foo() +foo(); + +/* this line +calls foo() */ +foo(); + +/* this comment + * is missing a newline after /* + */ + +/* + * this comment + * is missing a newline at the end */ + +/* +* the star in this line should have a space before it + */ + +/* + * the star on the following line should have a space before it +*/ + +``` + +Examples of **correct** code for this rule with the default `"starred-block"` option: + +```js +/* eslint multiline-comment-style: ["error", "starred-block"] */ + +/* + * this line + * calls foo() + */ +foo(); + +// single-line comment +``` + +Examples of **incorrect** code for this rule with the `"bare-block"` option: + +```js +/* eslint multiline-comment-style: ["error", "bare-block"] */ + +// this line +// calls foo() +foo(); + +/* + * this line + * calls foo() + */ +foo(); +``` + +Examples of **correct** code for this rule with the `"bare-block"` option: + +```js +/* eslint multiline-comment-style: ["error", "bare-block"] */ + +/* this line + calls foo() */ +foo(); +``` + +Examples of **incorrect** code for this rule with the `"separate-lines"` option: + +```js + +/* eslint multiline-comment-style: ["error", "separate-lines"] */ + +/* This line +calls foo() */ +foo(); + +/* + * This line + * calls foo() + */ +foo(); + +``` + +Examples of **correct** code for this rule with the `"separate-lines"` option: + +```js +/* eslint multiline-comment-style: ["error", "separate-lines"] */ + +// This line +// calls foo() +foo(); + + +``` + +## When Not To Use It + +If you don't want to enforce a particular style for multiline comments, you can disable the rule. diff --git a/docs/rules/multiline-ternary.md b/docs/rules/multiline-ternary.md index e331078f3a49..0cb94643a241 100644 --- a/docs/rules/multiline-ternary.md +++ b/docs/rules/multiline-ternary.md @@ -26,6 +26,7 @@ Note: The location of the operators is not enforced by this rule. Please see the This rule has a string option: * `"always"` (default) enforces newlines between the operands of a ternary expression. +* `"always-multiline"` enforces newlines between the operands of a ternary expression if the expression spans multiple lines. * `"never"` disallows newlines between the operands of a ternary expression (enforcing that the entire ternary expression is on one line). ### always @@ -62,6 +63,50 @@ foo > bar ? value3; ``` +### always-multiline + +Examples of **incorrect** code for this rule with the `"always-multiline"` option: + +```js +/*eslint multiline-ternary: ["error", "always-multiline"]*/ + +foo > bar ? value1 : + value2; + +foo > bar ? + value1 : value2; + +foo > bar && + bar > baz ? value1 : value2; +``` + +Examples of **correct** code for this rule with the `"always-multiline"` option: + +```js +/*eslint multiline-ternary: ["error", "always-multiline"]*/ + +foo > bar ? value1 : value2; + +foo > bar ? + value1 : + value2; + +foo > bar ? + (baz > qux ? value1 : value2) : + value3; + +foo > bar ? + (baz > qux ? + value1 : + value2) : + value3; + +foo > bar && + bar > baz ? + value1 : + value2; +``` + ### never Examples of **incorrect** code for this rule with the `"never"` option: diff --git a/docs/rules/newline-after-var.md b/docs/rules/newline-after-var.md index 76af225decf8..43ec9ba8ac07 100644 --- a/docs/rules/newline-after-var.md +++ b/docs/rules/newline-after-var.md @@ -1,5 +1,7 @@ # require or disallow an empty line after variable declarations (newline-after-var) +This rule was **deprecated** in ESLint v4.0.0 and replaced by the [padding-line-between-statements](padding-line-between-statements.md) rule. + As of today there is no consistency in separating variable declarations from the rest of the code. Some developers leave an empty line between var statements and the rest of the code like: ```js diff --git a/docs/rules/newline-before-return.md b/docs/rules/newline-before-return.md index 1a3c48584ec2..670953404e17 100644 --- a/docs/rules/newline-before-return.md +++ b/docs/rules/newline-before-return.md @@ -1,5 +1,7 @@ # require an empty line before `return` statements (newline-before-return) +This rule was **deprecated** in ESLint v4.0.0 and replaced by the [padding-line-between-statements](padding-line-between-statements.md) rule. + There is no hard and fast rule about whether empty lines should precede `return` statements in JavaScript. However, clearly delineating where a function is returning can greatly increase the readability and clarity of the code. For example: ```js diff --git a/docs/rules/no-array-constructor.md b/docs/rules/no-array-constructor.md index 0ad39d88d5f9..863f56d0b77a 100644 --- a/docs/rules/no-array-constructor.md +++ b/docs/rules/no-array-constructor.md @@ -1,7 +1,7 @@ # disallow `Array` constructors (no-array-constructor) Use of the `Array` constructor to construct a new array is generally -discouraged in favour of array literal notation because of the single-argument +discouraged in favor of array literal notation because of the single-argument pitfall and because the `Array` global may be redefined. The exception is when the Array constructor is used to intentionally create sparse arrays of a specified size by giving the constructor a single numeric argument. diff --git a/docs/rules/no-buffer-constructor.md b/docs/rules/no-buffer-constructor.md new file mode 100644 index 000000000000..f287ce57343c --- /dev/null +++ b/docs/rules/no-buffer-constructor.md @@ -0,0 +1,41 @@ +# disallow use of the Buffer() constructor (no-buffer-constructor) + +In Node.js, the behavior of the `Buffer` constructor is different depending on the type of its argument. Passing an argument from user input to `Buffer()` without validating its type can lead to security vulnerabilities such as remote memory disclosure and denial of service. As a result, the `Buffer` constructor has been deprecated and should not be used. Use the producer methods `Buffer.from`, `Buffer.alloc`, and `Buffer.allocUnsafe` instead. + +## Rule Details + +This rule disallows calling and constructing the `Buffer()` constructor. + +Examples of **incorrect** code for this rule: + +```js +new Buffer(5); +new Buffer([1, 2, 3]); + +Buffer(5); +Buffer([1, 2, 3]); + +new Buffer(res.body.amount); +new Buffer(res.body.values); +``` + +Examples of **correct** code for this rule: + +```js +Buffer.alloc(5); +Buffer.allocUnsafe(5); +Buffer.from([1, 2, 3]); + +Buffer.alloc(res.body.amount); +Buffer.from(res.body.values); +``` + +## When Not To Use It + +If you don't use Node.js, or you still need to support versions of Node.js that lack methods like `Buffer.from`, then you should not enable this rule. + +## Further Reading + +* [Buffer API documentation](https://nodejs.org/api/buffer.html) +* [Let's fix Node.js Buffer API](https://github.com/ChALkeR/notes/blob/master/Lets-fix-Buffer-API.md) +* [Buffer(number) is unsafe](https://github.com/nodejs/node/issues/4660) diff --git a/docs/rules/no-console.md b/docs/rules/no-console.md index ab58cb846a86..1c36a85f974a 100644 --- a/docs/rules/no-console.md +++ b/docs/rules/no-console.md @@ -49,6 +49,46 @@ console.error("Log an error level message."); If you're using Node.js, however, `console` is used to output information to the user and so is not strictly used for debugging purposes. If you are developing for Node.js then you most likely do not want this rule enabled. +Another case where you might not use this rule is if you want to enforce console calls and not console overwrites. For example: + +```js +/*eslint no-console: ["error", { allow: ["warn"] }] */ +console.error = function (message) { + throw new Error(message); +}; +``` + +With the `no-console` rule in the above example, ESLint will report an error. For the above example, you can disable the rule: + +```js +// eslint-disable-next-line no-console +console.error = function (message) { + throw new Error(message); +}; + +// or + +console.error = function (message) { // eslint-disable-line no-console + throw new Error(message); +}; +``` + +However, you might not want to manually add `eslint-disable-next-line` or `eslint-disable-line`. You can achieve the effect of only receiving errors for console calls with the `no-restricted-syntax` rule: + +```json +{ + "rules": { + "no-restricted-syntax": [ + "error", + { + "selector": "CallExpression[callee.object.name='console'][callee.property.name=/^(log|warn|error|info|trace)$/]", + "message": "Unexpected property on console object was called" + } + ] + } +} +``` + ## Related Rules * [no-alert](no-alert.md) diff --git a/docs/rules/no-div-regex.md b/docs/rules/no-div-regex.md index 0b31b40eb4af..8555536e5476 100644 --- a/docs/rules/no-div-regex.md +++ b/docs/rules/no-div-regex.md @@ -1,4 +1,4 @@ -# Disallow Regexs That Look Like Division (no-div-regex) +# Disallow Regular Expressions That Look Like Division (no-div-regex) Require regex literals to escape division operators. diff --git a/docs/rules/no-else-return.md b/docs/rules/no-else-return.md index 3127e0f511fb..eb6f8fab349c 100644 --- a/docs/rules/no-else-return.md +++ b/docs/rules/no-else-return.md @@ -16,6 +16,23 @@ function foo() { This rule is aimed at highlighting an unnecessary block of code following an `if` containing a return statement. As such, it will warn when it encounters an `else` following a chain of `if`s, all of them containing a `return` statement. +## Options + +This rule has an object option: + +```json +{ + "no-else-return": ["error", { "allowElseIf": true }], + // or + "no-else-return": ["error", { "allowElseIf": false }] +} +``` + +* `allowElseIf: true` (default) allows `else if` blocks after a return +* `allowElseIf: false` disallows `else if` blocks after a return + +### `allowElseIf: true` + Examples of **incorrect** code for this rule: ```js @@ -49,6 +66,16 @@ function foo() { return t; } +function foo() { + if (error) { + return 'It failed'; + } else { + if (loading) { + return "It's still loading"; + } + } +} + // Two warnings for nested occurrences function foo() { if (x) { @@ -95,4 +122,44 @@ function foo() { return z; } } + +function foo() { + if (error) { + return 'It failed'; + } else if (loading) { + return "It's still loading"; + } +} +``` + +### `allowElseIf: false` + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-else-return: ["error", {allowElseIf: false}]*/ + +function foo() { + if (error) { + return 'It failed'; + } else if (loading) { + return "It's still loading"; + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-else-return: ["error", {allowElseIf: false}]*/ + +function foo() { + if (error) { + return 'It failed'; + } + + if (loading) { + return "It's still loading"; + } +} ``` diff --git a/docs/rules/no-eval.md b/docs/rules/no-eval.md index c3355ccc98ce..9f7b71ad620a 100644 --- a/docs/rules/no-eval.md +++ b/docs/rules/no-eval.md @@ -139,8 +139,8 @@ global.eval("var a = 0"); ## Further Reading -* [Eval is Evil, Part One](http://blogs.msdn.com/b/ericlippert/archive/2003/11/01/53329.aspx) -* [How evil is eval](http://javascriptweblog.wordpress.com/2010/04/19/how-evil-is-eval/) +* [Eval is Evil, Part One](https://blogs.msdn.com/b/ericlippert/archive/2003/11/01/53329.aspx) +* [How evil is eval](https://javascriptweblog.wordpress.com/2010/04/19/how-evil-is-eval/) ## Related Rules diff --git a/docs/rules/no-ex-assign.md b/docs/rules/no-ex-assign.md index 95d5e62c62b5..11745daf2628 100644 --- a/docs/rules/no-ex-assign.md +++ b/docs/rules/no-ex-assign.md @@ -33,4 +33,4 @@ try { ## Further Reading -* [The "catch" with try...catch](http://weblog.bocoup.com/the-catch-with-try-catch/) by Ben Alman explains how the exception identifier can leak into the outer scope in IE 6-8 +* [The "catch" with try...catch](https://bocoup.com/blog/the-catch-with-try-catch) by Ben Alman explains how the exception identifier can leak into the outer scope in IE 6-8 diff --git a/docs/rules/no-extra-bind.md b/docs/rules/no-extra-bind.md index 0286ad746ee0..0294d3e5c4aa 100644 --- a/docs/rules/no-extra-bind.md +++ b/docs/rules/no-extra-bind.md @@ -83,4 +83,4 @@ If you are not concerned about unnecessary calls to `bind()`, you can safely dis ## Further Reading * [Function.prototype.bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) -* [Understanding JavaScript's Function.prototype.bind](http://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/) +* [Understanding JavaScript's Function.prototype.bind](https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/) diff --git a/docs/rules/no-extra-parens.md b/docs/rules/no-extra-parens.md index 0a358388295a..61fb50dc3e0d 100644 --- a/docs/rules/no-extra-parens.md +++ b/docs/rules/no-extra-parens.md @@ -8,6 +8,7 @@ This rule always ignores extra parentheses around the following: * RegExp literals such as `(/abc/).test(var)` to avoid conflicts with the [wrap-regex](wrap-regex.md) rule * immediately-invoked function expressions (also known as IIFEs) such as `var x = (function () {})();` and `((function foo() {return 1;})())` to avoid conflicts with the [wrap-iife](wrap-iife.md) rule +* arrow function arguments to avoid conflicts with the [arrow-parens](arrow-parens.md) rule ## Options @@ -22,6 +23,7 @@ This rule has an object option for exceptions to the `"all"` option: * `"returnAssign": false` allows extra parentheses around assignments in `return` statements * `"nestedBinaryExpressions": false` allows extra parentheses in nested binary expressions * `"ignoreJSX": "none|all|multi-line|single-line"` allows extra parentheses around no/all/multi-line/single-line JSX components. Defaults to `none`. +* `"enforceForArrowConditionals": false` allows extra parentheses around ternary expressions which are the body of an arrow function ### all @@ -34,6 +36,12 @@ a = (b * c); (a * b) + c; +for (a in (b, c)); + +for (a in (b)); + +for (a of (b)); + typeof (a); (function(){} ? a() : b()); @@ -53,6 +61,14 @@ Examples of **correct** code for this rule with the default `"all"` option: (function(){}) ? a() : b(); (/^a$/).test(x); + +for (a of (b, c)); + +for (a of b); + +for (a in b, c); + +for (a in b); ``` ### conditionalAssign @@ -165,6 +181,17 @@ const Component = (
) const Component = (

) ``` +### enforceForArrowConditionals + +Examples of **correct** code for this rule with the `"all"` and `{ "enforceForArrowConditionals": false }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { "enforceForArrowConditionals": false }] */ + +const b = a => 1 ? 2 : 3; +const d = c => (1 ? 2 : 3); +``` + ### functions Examples of **incorrect** code for this rule with the `"functions"` option: @@ -205,5 +232,6 @@ typeof (a); ## Related Rules +* [arrow-parens](arrow-parens.md) * [no-cond-assign](no-cond-assign.md) * [no-return-assign](no-return-assign.md) diff --git a/docs/rules/no-extra-strict.md b/docs/rules/no-extra-strict.md index 50b5a936690c..a350f77a3912 100644 --- a/docs/rules/no-extra-strict.md +++ b/docs/rules/no-extra-strict.md @@ -47,4 +47,4 @@ Examples of **correct** code for this rule: ## Further Reading -* [The ECMAScript 5 Annotated Specification - Strict Mode](http://es5.github.io/#C) +* [The ECMAScript 5 Annotated Specification - Strict Mode](https://es5.github.io/#C) diff --git a/docs/rules/no-invalid-regexp.md b/docs/rules/no-invalid-regexp.md index 67502885f167..d656da025d0b 100644 --- a/docs/rules/no-invalid-regexp.md +++ b/docs/rules/no-invalid-regexp.md @@ -61,4 +61,4 @@ new RegExp('.', 'yu') ## Further Reading -* [Annotated ES5 §7.8.5 - Regular Expression Literals](http://es5.github.io/#x7.8.5) +* [Annotated ES5 §7.8.5 - Regular Expression Literals](https://es5.github.io/#x7.8.5) diff --git a/docs/rules/no-invalid-this.md b/docs/rules/no-invalid-this.md index 87d5387ac60a..b6733eded563 100644 --- a/docs/rules/no-invalid-this.md +++ b/docs/rules/no-invalid-this.md @@ -6,7 +6,7 @@ Under the strict mode, `this` keywords outside of classes or class-like objects This rule aims to flag usage of `this` keywords outside of classes or class-like objects. -Basically this rule checks whether or not a function which are containing `this` keywords is a constructor or a method. +Basically, this rule checks whether or not a function containing `this` keyword is a constructor or a method. This rule judges from following conditions whether or not the function is a constructor: diff --git a/docs/rules/no-irregular-whitespace.md b/docs/rules/no-irregular-whitespace.md index 21866b5caa44..e3f65159a0bc 100644 --- a/docs/rules/no-irregular-whitespace.md +++ b/docs/rules/no-irregular-whitespace.md @@ -2,13 +2,13 @@ Invalid or irregular whitespace causes issues with ECMAScript 5 parsers and also makes code harder to debug in a similar nature to mixed tabs and spaces. -Various whitespace characters can be inputted by programmers by mistake for example from copying or keyboard shortcuts. Pressing Alt + Space on OS X adds in a non breaking space character for example. +Various whitespace characters can be inputted by programmers by mistake for example from copying or keyboard shortcuts. Pressing Alt + Space on macOS adds in a non breaking space character for example. Known issues these spaces cause: * Zero Width Space * Is NOT considered a separator for tokens and is often parsed as an `Unexpected token ILLEGAL` - * Is NOT shown in modern browsers making code repository software expected to resolve the visualisation + * Is NOT shown in modern browsers making code repository software expected to resolve the visualization * Line Separator * Is NOT a valid character within JSON which would cause parse errors diff --git a/docs/rules/no-iterator.md b/docs/rules/no-iterator.md index 954ce56dfd6d..95b42a9ea1a6 100644 --- a/docs/rules/no-iterator.md +++ b/docs/rules/no-iterator.md @@ -40,5 +40,5 @@ var __iterator__ = foo; // Not using the `__iterator__` property. ## Further Reading * [MDN - Iterators and Generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) -* [ECMAScript 6 compatibility table - Iterators](http://kangax.github.io/es5-compat-table/es6/#Iterators) +* [ECMAScript 6 compatibility table - Iterators](https://kangax.github.io/es5-compat-table/es6/#Iterators) * [Deprecated and Obsolete Features](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features#Object_methods) diff --git a/docs/rules/no-lone-blocks.md b/docs/rules/no-lone-blocks.md index cbaef843e4f4..8cb45d6359fb 100644 --- a/docs/rules/no-lone-blocks.md +++ b/docs/rules/no-lone-blocks.md @@ -44,7 +44,7 @@ function bar() { } ``` -Examples of **correct** code for this rule with es6 environment: +Examples of **correct** code for this rule with ES6 environment: ```js /*eslint no-lone-blocks: "error"*/ @@ -80,7 +80,7 @@ aLabel: { } ``` -Examples of **correct** code for this rule with es6 environment and strict mode via `"parserOptions": { "sourceType": "module" }` in the ESLint configuration or `"use strict"` directive in the code: +Examples of **correct** code for this rule with ES6 environment and strict mode via `"parserOptions": { "sourceType": "module" }` in the ESLint configuration or `"use strict"` directive in the code: ```js /*eslint no-lone-blocks: "error"*/ diff --git a/docs/rules/no-mixed-operators.md b/docs/rules/no-mixed-operators.md index 8961c7dd867d..3c0bb2fab6b0 100644 --- a/docs/rules/no-mixed-operators.md +++ b/docs/rules/no-mixed-operators.md @@ -9,6 +9,21 @@ var foo = (a && b) || c || d; /*GOOD*/ var foo = a && (b || c || d); /*GOOD*/ ``` +**Note:** +It is expected for this rule to emit one error for each mixed operator in a pair. As a result, for each two consecutive mixed operators used, a distinct error will be displayed, pointing to where the specific operator that breaks the rule is used: + +```js +var foo = a && b || c || d; +``` + +will generate + +```sh +1:13 Unexpected mix of '&&' and '||'. (no-mixed-operators) +1:18 Unexpected mix of '&&' and '||'. (no-mixed-operators) +``` + + ## Rule Details This rule checks `BinaryExpression` and `LogicalExpression`. @@ -60,11 +75,8 @@ var foo = (a + b) * c; This rule has 2 options. -* `groups` (`string[][]`) - specifies groups to compare operators. - When this rule compares two operators, if both operators are included in a same group, this rule checks it. Otherwise, this rule ignores it. - This value is a list of groups. The group is a list of binary operators. - Default is the groups for each kind of operators. -* `allowSamePrecedence` (`boolean`) - specifies to allow mix of 2 operators if those have the same precedence. Default is `true`. +* `groups` (`string[][]`) - specifies operator groups to be checked. The `groups` option is a list of groups, and a group is a list of binary operators. Default operator groups are defined as arithmetic, bitwise, comparison, logical, and relational operators. +* `allowSamePrecedence` (`boolean`) - specifies whether to allow mixed operators if they are of equal precedence. Default is `true`. ### groups @@ -76,11 +88,10 @@ The following operators can be used in `groups` option: * Logical Operators: `"&&"`, `"||"` * Relational Operators: `"in"`, `"instanceof"` -Now, considers about `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}` configure. -This configure has 2 groups: bitwise operators and logical operators. -This rule checks only if both operators are included in a same group. -So, in this case, this rule comes to check between bitwise operators and between logical operators. -This rule ignores other operators. +Now, consider the following group configuration: `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}`. +There are 2 groups specified in this configuration: bitwise operators and logical operators. +This rule checks if the operators belong to the same group only. +In this case, this rule checks if bitwise operators and logical operators are mixed, but ignores all other operators. Examples of **incorrect** code for this rule with `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}` option: @@ -127,6 +138,15 @@ Examples of **incorrect** code for this rule with `{"allowSamePrecedence": false var foo = a + b - c; ``` +Examples of **correct** code for this rule with `{"allowSamePrecedence": false}` option: + +```js +/*eslint no-mixed-operators: ["error", {"allowSamePrecedence": false}]*/ + +// + and - have the same precedence. +var foo = (a + b) - c; +``` + ## When Not To Use It If you don't want to be notified about mixed operators, then it's safe to disable this rule. diff --git a/docs/rules/no-mixed-requires.md b/docs/rules/no-mixed-requires.md index b507b6c5e7d0..ee63c99090a2 100644 --- a/docs/rules/no-mixed-requires.md +++ b/docs/rules/no-mixed-requires.md @@ -76,7 +76,7 @@ Examples of **incorrect** code for this rule with the `{ "grouping": true }` opt ```js /*eslint no-mixed-requires: ["error", { "grouping": true }]*/ -// invalid because of mixed types "core" and "file" +// invalid because of mixed types "core" and "module" var fs = require('fs'), async = require('async'); @@ -118,7 +118,7 @@ var async = require('async'), If you use a pattern such as [UMD][4] where the `require`d modules are not loaded in variable declarations, this rule will obviously do nothing for you. -[1]: http://nodejs.org/api/modules.html#modules_core_modules -[2]: http://nodejs.org/api/modules.html#modules_file_modules -[3]: http://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders +[1]: https://nodejs.org/api/modules.html#modules_core_modules +[2]: https://nodejs.org/api/modules.html#modules_file_modules +[3]: https://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders [4]: https://github.com/umdjs/umd diff --git a/docs/rules/no-mixed-spaces-and-tabs.md b/docs/rules/no-mixed-spaces-and-tabs.md index 904e2fcfd83b..4f76b26179ca 100644 --- a/docs/rules/no-mixed-spaces-and-tabs.md +++ b/docs/rules/no-mixed-spaces-and-tabs.md @@ -62,4 +62,4 @@ function main() { ## Further Reading -* [Smart Tabs](http://www.emacswiki.org/emacs/SmartTabs) +* [Smart Tabs](https://www.emacswiki.org/emacs/SmartTabs) diff --git a/docs/rules/no-multi-spaces.md b/docs/rules/no-multi-spaces.md index fde45a706bfe..07cd0e9011ae 100644 --- a/docs/rules/no-multi-spaces.md +++ b/docs/rules/no-multi-spaces.md @@ -54,11 +54,55 @@ a ? b: c ## Options -To avoid contradictions if some other rules require multiple spaces, this rule has an option to ignore certain node types in the abstract syntax tree (AST) of JavaScript code. +This rule's configuration consists of an object with the following properties: + +* `"ignoreEOLComments": true` (defaults to `false`) ignores multiple spaces before comments that occur at the end of lines +* `"exceptions": { "Property": true }` (`"Property"` is the only node specified by default) specifies nodes to ignore + +### ignoreEOLComments + +Examples of **incorrect** code for this rule with the `{ "ignoreEOLComments": false }` (default) option: + +```js +/*eslint no-multi-spaces: ["error", { ignoreEOLComments: false }]*/ + +var x = 5; // comment +var x = 5; /* multiline + * comment + */ +``` + +Examples of **correct** code for this rule with the `{ "ignoreEOLComments": false }` (default) option: + +```js +/*eslint no-multi-spaces: ["error", { ignoreEOLComments: false }]*/ + +var x = 5; // comment +var x = 5; /* multiline + * comment + */ +``` + +Examples of **correct** code for this rule with the `{ "ignoreEOLComments": true }` option: + +```js +/*eslint no-multi-spaces: ["error", { ignoreEOLComments: true }]*/ + +var x = 5; // comment +var x = 5; // comment +var x = 5; /* multiline + * comment + */ +var x = 5; /* multiline + * comment + */ +``` ### exceptions -The `exceptions` object expects property names to be AST node types as defined by [ESTree](https://github.com/estree/estree). The easiest way to determine the node types for `exceptions` is to use the [online demo](http://eslint.org/parser). +To avoid contradictions with other rules that require multiple spaces, this rule has an `exceptions` option to ignore certain nodes. + +This option is an object that expects property names to be AST node types as defined by [ESTree](https://github.com/estree/estree). The easiest way to determine the node types for `exceptions` is to use the [online demo](https://eslint.org/parser). Only the `Property` node type is ignored by default, because for the [key-spacing](key-spacing.md) rule some alignment options require multiple spaces in properties of object literals. @@ -122,6 +166,6 @@ If you don't want to check and disallow multiple spaces, then you should turn th * [space-infix-ops](space-infix-ops.md) * [space-in-brackets](space-in-brackets.md) (deprecated) * [space-in-parens](space-in-parens.md) -* [space-after-keywords](space-after-keywords) -* [space-unary-ops](space-unary-ops) -* [space-return-throw-case](space-return-throw-case) +* [space-after-keywords](space-after-keywords.md) +* [space-unary-ops](space-unary-ops.md) +* [space-return-throw-case](space-return-throw-case.md) diff --git a/docs/rules/no-new-symbol.md b/docs/rules/no-new-symbol.md index 932926f281eb..66167cf6b2bd 100644 --- a/docs/rules/no-new-symbol.md +++ b/docs/rules/no-new-symbol.md @@ -45,4 +45,4 @@ This rule should not be used in ES3/5 environments. ## Further Reading -* [Symbol Objects specification](http://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects) +* [Symbol Objects specification](https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects) diff --git a/docs/rules/no-obj-calls.md b/docs/rules/no-obj-calls.md index 9500913a21a3..9484eb736884 100644 --- a/docs/rules/no-obj-calls.md +++ b/docs/rules/no-obj-calls.md @@ -2,11 +2,11 @@ ECMAScript provides several global objects that are intended to be used as-is. Some of these objects look as if they could be constructors due their capitalization (such as `Math` and `JSON`) but will throw an error if you try to execute them as functions. -The [ECMAScript 5 specification](http://es5.github.io/#x15.8) makes it clear that both `Math` and `JSON` cannot be invoked: +The [ECMAScript 5 specification](https://es5.github.io/#x15.8) makes it clear that both `Math` and `JSON` cannot be invoked: > The Math object does not have a `[[Call]]` internal property; it is not possible to invoke the Math object as a function. -And the [ECMAScript 2015 specification](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect-object) makes it clear that `Reflect` cannot be invoked: +And the [ECMAScript 2015 specification](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect-object) makes it clear that `Reflect` cannot be invoked: > The Reflect object also does not have a `[[Call]]` internal method; it is not possible to invoke the Reflect object as a function. @@ -38,4 +38,4 @@ var value = Reflect.get({ x: 1, y: 2 }, "x"); ## Further Reading -* [The Math Object](http://es5.github.io/#x15.8) +* [The Math Object](https://es5.github.io/#x15.8) diff --git a/docs/rules/no-param-reassign.md b/docs/rules/no-param-reassign.md index 770c481e4b64..51c81a8c0935 100644 --- a/docs/rules/no-param-reassign.md +++ b/docs/rules/no-param-reassign.md @@ -99,4 +99,4 @@ If you want to allow assignment to function parameters, then you can safely disa ## Further Reading -* [JavaScript: Don’t Reassign Your Function Arguments](http://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments/) +* [JavaScript: Don’t Reassign Your Function Arguments](https://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments/) diff --git a/docs/rules/no-process-env.md b/docs/rules/no-process-env.md index bca841b58fb4..e4a235676b05 100644 --- a/docs/rules/no-process-env.md +++ b/docs/rules/no-process-env.md @@ -35,5 +35,5 @@ If prefer to use `process.env` throughout your project to retrieve values from e ## Further Reading -* [How to store Node.js deployment settings/configuration files? - Stack Overflow](http://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files) -* [Storing Node.js application config data - Ben Hall's blog](http://blog.benhall.me.uk/2012/02/storing-application-config-data-in/) +* [How to store Node.js deployment settings/configuration files? - Stack Overflow](https://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files) +* [Storing Node.js application config data - Ben Hall's blog](https://blog.benhall.me.uk/2012/02/storing-application-config-data-in/) diff --git a/docs/rules/no-redeclare.md b/docs/rules/no-redeclare.md index 8e07c058b3b4..d0d71e5dee54 100644 --- a/docs/rules/no-redeclare.md +++ b/docs/rules/no-redeclare.md @@ -32,6 +32,8 @@ If set to `true`, this rule also checks redeclaration of built-in globals, such ### builtinGlobals +The `"builtinGlobals"` option will check for redeclaration of built-in globals in global scope. + Examples of **incorrect** code for the `{ "builtinGlobals": true }` option: ```js @@ -50,3 +52,9 @@ var top = 0; ``` The `browser` environment has many built-in global variables (for example, `top`). Some of built-in global variables cannot be redeclared. + +Note that when using the `node` or `commonjs` environments (or `ecmaFeatures.globalReturn`, if using the default parser), the top scope of a program is not actually the global scope, but rather a "module" scope. When this is the case, declaring a variable named after a builtin global is not a redeclaration, but rather a shadowing of the global variable. In that case, the [`no-shadow`](no-shadow.md) rule with the `"builtinGlobals"` option should be used. + +## Related Rules + +* [no-shadow](no-shadow.md) diff --git a/docs/rules/no-reserved-keys.md b/docs/rules/no-reserved-keys.md index 0ddb435750be..f1f8e41d27e1 100644 --- a/docs/rules/no-reserved-keys.md +++ b/docs/rules/no-reserved-keys.md @@ -50,4 +50,4 @@ If your code is only going to be executed in an ECMAScript 5 or higher environme ## Further Reading -* [Reserved words as property names](http://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names) +* [Reserved words as property names](https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names) diff --git a/docs/rules/no-restricted-globals.md b/docs/rules/no-restricted-globals.md index 751f5d66de65..e3d612412e1a 100644 --- a/docs/rules/no-restricted-globals.md +++ b/docs/rules/no-restricted-globals.md @@ -13,7 +13,35 @@ This rule allows you to specify global variable names that you don't want to use ## Options -This rule takes a list of strings which are the global variable names. +This rule takes a list of strings, where each string is a global to be restricted: + +```json +{ + "rules": { + "no-restricted-globals": ["error", "event", "fdescribe"] + } +} +``` + +Alternatively, the rule also accepts objects, where the global name and an optional custom message are specified: + +```json +{ + "rules": { + "no-restricted-globals": [ + "error", + { + "name": "event", + "message": "Use local parameter instead." + }, + { + "name": "fdescribe", + "message": "Do not commit fdescribe. Use describe instead." + } + ] + } +} +``` Examples of **incorrect** code for sample `"event", "fdescribe"` global variable names: @@ -45,6 +73,17 @@ import event from "event-module"; var event = 1; ``` +Examples of **incorrect** code for a sample `"event"` global variable name, along with a custom error message: + +```js +/*global event*/ +/* eslint no-restricted-globals: ["error", { name: "error", message: "Use local parameter instead." }] */ + +function onClick() { + console.log(event); // Unexpected global variable 'event'. Use local parameter instead. +} +``` + ## Related Rules * [no-restricted-properties](no-restricted-properties.md) diff --git a/docs/rules/no-restricted-imports.md b/docs/rules/no-restricted-imports.md index 856b8f2cce44..66b8095a02b3 100644 --- a/docs/rules/no-restricted-imports.md +++ b/docs/rules/no-restricted-imports.md @@ -35,6 +35,40 @@ When using the object form, you can also specify an array of gitignore-style pat }] ``` +You may also specify a custom message for any paths you want to restrict as follows: + +```json +"no-restricted-imports": ["error", [{ + "name": "import-foo", + "message": "Please use import-bar instead." +}]] +``` + +or like this: + +```json +"no-restricted-imports": ["error", { + "paths": [{ + "name": "import-foo", + "message": "Please use import-bar instead." + }] +}] +``` + +or like this if you need to restrict only certain imports from a module: + +```json +"no-restricted-imports": ["error", { + "paths": [{ + "name": "import-foo", + "importNames": ["Bar"], + "message": "Please use Bar from /import-bar/baz/ instead." + }] +}] +``` + +The custom message will be appended to the default error message. Please note that you may not specify custom error messages for restricted patterns as a particular import may match more than one pattern. + To restrict the use of all Node.js core imports (via https://github.com/nodejs/node/tree/master/lib): ```json @@ -65,6 +99,36 @@ import cluster from 'cluster'; import pick from 'lodash/pick'; ``` +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["default"], + message: "Please use the default import from '/bar/baz/' instead." +}]}]*/ + +import DisallowedObject from "foo"; +``` + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["DisallowedObject"], + message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +}]}]*/ + +import { DisallowedObject as AllowedObject } from "foo"; +``` + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["DisallowedObject"], + message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +}]}]*/ + +import * as Foo from "foo"; +``` + Examples of **correct** code for this rule: ```js @@ -80,6 +144,22 @@ import crypto from 'crypto'; import eslint from 'eslint'; ``` +```js +/*eslint no-restricted-imports: ["error", { paths: [{ name: "foo", importNames: ["DisallowedObject"] }] }]*/ + +import DisallowedObject from "foo" +``` + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["DisallowedObject"], + message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +}]}]*/ + +import { AllowedObject as DisallowedObject } from "foo"; +``` + ## When Not To Use It Don't use this rule or don't include a module in the list for this rule if you want to be able to import a module in your project without an ESLint error or warning. diff --git a/docs/rules/no-restricted-modules.md b/docs/rules/no-restricted-modules.md index e2f0dd2bf810..aa6c3e9822d6 100644 --- a/docs/rules/no-restricted-modules.md +++ b/docs/rules/no-restricted-modules.md @@ -1,22 +1,62 @@ # Disallow Node.js modules (no-restricted-modules) -Disallowing usage of specific Node.js modules can be useful if you want to control the available methods, a developer can -use, to implement a feature. +A module in Node.js is a simple or complex functionality organized in a JavaScript file which can be reused throughout the Node.js +application. The keyword `require` is used in Node.js/CommonJS to import modules into an application. This way you can have dynamic loading where the loaded module name isn't predefined /static, or where you conditionally load a module only if it's "truly required". -This way you can block usage of the `fs` module if you want to disallow file system access. -Blocking the `os` module can be useful if you don't want to allow any operating system specific code. +Why would you want to restrict a module? + +Disallowing usage of specific Node.js modules can be useful if you want to limit the available methods a developer can use. For example, you can block usage of the `fs` module if you want to disallow file system access. ## Rule Details -This rule allows you to specify modules that you don't want to use in your application. +This rule allows you to specify modules that you don’t want to use in your application. ## Options The rule takes one or more strings as options: the names of restricted modules. -It can also take an object with lists of "paths" and gitignore-style "patterns" strings. +```json +"no-restricted-modules": ["error", "foo-module", "bar-module"] +``` + +It can also take an object with lists of `paths` and gitignore-style `patterns` strings. -For example, to restrict the use of all Node.js core modules (via https://github.com/nodejs/node/tree/master/lib): +```json +"no-restricted-modules": ["error", { "paths": ["foo-module", "bar-module"] }] +``` + +```json +"no-restricted-modules": ["error", { + "paths": ["foo-module", "bar-module"], + "patterns": ["foo-module/private/*", "bar-module/*","!baz-module/good"] +}] +``` + +You may also specify a custom message for any paths you want to restrict as follows: + +```json +"no-restricted-modules": ["error", [{ + "name": "foo-module", + "message": "Please use bar-module instead." + }] +] +``` + +or like this: + +```json +"no-restricted-modules": ["error",{ +"paths":[{ + "name": "foo-module", + "message": "Please use bar-module instead." + }] +}] +``` + +The custom message will be appended to the default error message. Please note that you may not specify custom error messages for restricted patterns as a particular module may match more than one pattern. + + +To restrict the use of all Node.js core modules (via https://github.com/nodejs/node/tree/master/lib): ```json { @@ -26,7 +66,9 @@ For example, to restrict the use of all Node.js core modules (via https://github } ``` -Examples of **incorrect** code for this rule with sample `"fs", "cluster"` restricted modules: +## Examples + +Examples of **incorrect** code for this rule with sample `"fs", "cluster","loadash"` restricted modules: ```js /*eslint no-restricted-modules: ["error", "fs", "cluster"]*/ @@ -36,7 +78,7 @@ var cluster = require('cluster'); ``` ```js -/*eslint no-restricted-modules: ["error", { "paths": ["cluster"] }]*/ +/*eslint no-restricted-modules: ["error", {"paths": ["cluster"] }]*/ var cluster = require('cluster'); ``` @@ -44,10 +86,10 @@ var cluster = require('cluster'); ```js /*eslint no-restricted-modules: ["error", { "patterns": ["lodash/*"] }]*/ -var cluster = require('lodash/pick'); +var pick = require('lodash/pick'); ``` -Examples of **correct** code for this rule with sample `"fs", "cluster"` restricted modules: +Examples of **correct** code for this rule with sample `"fs", "cluster","loadash"` restricted modules: ```js /*eslint no-restricted-modules: ["error", "fs", "cluster"]*/ @@ -62,5 +104,5 @@ var crypto = require('crypto'); }]*/ var crypto = require('crypto'); -var eslint = require('lodash/pick'); +var pick = require('lodash/pick'); ``` diff --git a/docs/rules/no-restricted-syntax.md b/docs/rules/no-restricted-syntax.md index 8efbd780e339..37dbe8f016c6 100644 --- a/docs/rules/no-restricted-syntax.md +++ b/docs/rules/no-restricted-syntax.md @@ -2,7 +2,7 @@ JavaScript has a lot of language features, and not everyone likes all of them. As a result, some projects choose to disallow the use of certain language features altogether. For instance, you might decide to disallow the use of `try-catch` or `class`, or you might decide to disallow the use of the `in` operator. -Rather than creating separate rules for every language feature you want to turn off, this rule allows you to configure the syntax elements you want to restrict use of. These elements are represented by their [ESTree](https://github.com/estree/estree) node types. For example, a function declaration is represented by `FunctionDeclaration` and the `with` statement is represented by `WithStatement`. You may find the full list of AST node names you can use [on GitHub](https://github.com/eslint/espree/blob/master/lib/ast-node-types.js) and use the [online parser](http://eslint.org/parser/) to see what type of nodes your code consists of. +Rather than creating separate rules for every language feature you want to turn off, this rule allows you to configure the syntax elements you want to restrict use of. These elements are represented by their [ESTree](https://github.com/estree/estree) node types. For example, a function declaration is represented by `FunctionDeclaration` and the `with` statement is represented by `WithStatement`. You may find the full list of AST node names you can use [on GitHub](https://github.com/eslint/espree/blob/master/lib/ast-node-types.js) and use the [online parser](https://eslint.org/parser/) to see what type of nodes your code consists of. You can also specify [AST selectors](../developer-guide/selectors) to restrict, allowing much more precise control over syntax patterns. diff --git a/docs/rules/no-return-await.md b/docs/rules/no-return-await.md index 59dbc5a3b45a..6c82d0bc1bf7 100644 --- a/docs/rules/no-return-await.md +++ b/docs/rules/no-return-await.md @@ -30,8 +30,16 @@ async function foo() { const x = await bar(); return x; } + +async function foo() { + try { + return await bar(); + } catch (error) {} +} ``` +In the last example the `await` is necessary to be able to catch errors thrown from `bar()`. + ## When Not To Use It If you want to use `await` to denote a value that is a thenable, even when it is not necessary; or if you do not want the performance benefit of avoiding `return await`, you can turn off this rule. diff --git a/docs/rules/no-script-url.md b/docs/rules/no-script-url.md index 61ca9b741e73..475959dacb16 100644 --- a/docs/rules/no-script-url.md +++ b/docs/rules/no-script-url.md @@ -18,4 +18,4 @@ location.href = "javascript:void(0)"; ## Further Reading -* [What is the matter with script-targeted URLs?](http://stackoverflow.com/questions/13497971/what-is-the-matter-with-script-targeted-urls) +* [What is the matter with script-targeted URLs?](https://stackoverflow.com/questions/13497971/what-is-the-matter-with-script-targeted-urls) diff --git a/docs/rules/no-sequences.md b/docs/rules/no-sequences.md index 1d05e82554dd..f1f4afea0ac9 100644 --- a/docs/rules/no-sequences.md +++ b/docs/rules/no-sequences.md @@ -66,3 +66,12 @@ while ((val = foo(), val < 42)); ## When Not To Use It Disable this rule if sequence expressions with the comma operator are acceptable. +Another case is where you might want to report all usages of the comma operator, even if they are wrapped in parentheses or in a for loop. You can achieve this using rule `no-restricted-syntax`: + +```js +{ + "rules": { + "no-restricted-syntax": ["error", "SequenceExpression"] + } +} +``` diff --git a/docs/rules/no-shadow-restricted-names.md b/docs/rules/no-shadow-restricted-names.md index 1d7c0720a814..0624a82aa145 100644 --- a/docs/rules/no-shadow-restricted-names.md +++ b/docs/rules/no-shadow-restricted-names.md @@ -36,8 +36,8 @@ function f(a, b){} ## Further Reading -* [Annotated ES5 - §15.1.1](http://es5.github.io/#x15.1.1) -* [Annotated ES5 - Annex C](http://es5.github.io/#C) +* [Annotated ES5 - §15.1.1](https://es5.github.io/#x15.1.1) +* [Annotated ES5 - Annex C](https://es5.github.io/#C) ## Related Rules diff --git a/docs/rules/no-shadow.md b/docs/rules/no-shadow.md index c406478eea08..3bf9783a942c 100644 --- a/docs/rules/no-shadow.md +++ b/docs/rules/no-shadow.md @@ -166,7 +166,7 @@ foo(function (err, result) { ## Further Reading -* [Variable Shadowing](http://en.wikipedia.org/wiki/Variable_shadowing) +* [Variable Shadowing](https://en.wikipedia.org/wiki/Variable_shadowing) ## Related Rules diff --git a/docs/rules/no-sparse-arrays.md b/docs/rules/no-sparse-arrays.md index 932b55fdd171..826cfd87f786 100644 --- a/docs/rules/no-sparse-arrays.md +++ b/docs/rules/no-sparse-arrays.md @@ -47,4 +47,4 @@ If you want to use sparse arrays, then it is safe to disable this rule. ## Further Reading -* [Inconsistent array literals](http://www.nczonline.net/blog/2007/09/09/inconsistent-array-literals/) +* [Inconsistent array literals](https://www.nczonline.net/blog/2007/09/09/inconsistent-array-literals/) diff --git a/docs/rules/no-sync.md b/docs/rules/no-sync.md index b55ee424c66b..50ab515eef5d 100644 --- a/docs/rules/no-sync.md +++ b/docs/rules/no-sync.md @@ -6,17 +6,23 @@ In Node.js, most I/O is done through asynchronous methods. However, there are of This rule is aimed at preventing synchronous methods from being called in Node.js. It looks specifically for the method suffix "`Sync`" (as is the convention with Node.js operations). -Examples of **incorrect** code for this rule: +## Options + +This rule has an optional object option `{ allowAtRootLevel: }`, which determines whether synchronous methods should be allowed at the top level of a file, outside of any functions. This option defaults to `false`. + +Examples of **incorrect** code for this rule with the default `{ allowAtRootLevel: false }` option: ```js /*eslint no-sync: "error"*/ fs.existsSync(somePath); -var contents = fs.readFileSync(somePath).toString(); +function foo() { + var contents = fs.readFileSync(somePath).toString(); +} ``` -Examples of **correct** code for this rule: +Examples of **correct** code for this rule with the default `{ allowAtRootLevel: false }` option: ```js /*eslint no-sync: "error"*/ @@ -28,6 +34,26 @@ async(function() { }); ``` +Examples of **incorrect** code for this rule with the `{ allowAtRootLevel: true }` option + +```js +/*eslint no-sync: ["error", { allowAtRootLevel: true }]*/ + +function foo() { + var contents = fs.readFileSync(somePath).toString(); +} + +var bar = baz => fs.readFileSync(qux); +``` + +Examples of **correct** code for this rule with the `{ allowAtRootLevel: true }` option + +```js +/*eslint no-sync: ["error", { allowAtRootLevel: true }]*/ + +fs.readFileSync(somePath).toString(); +``` + ## When Not To Use It -If you want to allow synchronous operations in your script. +If you want to allow synchronous operations in your script, do not enable this rule. diff --git a/docs/rules/no-trailing-spaces.md b/docs/rules/no-trailing-spaces.md index 46f61fa6082d..050e93c3953c 100644 --- a/docs/rules/no-trailing-spaces.md +++ b/docs/rules/no-trailing-spaces.md @@ -31,6 +31,8 @@ This rule has an object option: * `"skipBlankLines": false` (default) disallows trailing whitespace on empty lines * `"skipBlankLines": true` allows trailing whitespace on empty lines +* `"ignoreComments": false` (default) disallows trailing whitespace in comment blocks +* `"ignoreComments": true` allows trailing whitespace in comment blocks ### skipBlankLines @@ -43,3 +45,19 @@ var foo = 0; var baz = 5; //••••• ``` + +### ignoreComments + +Examples of **correct** code for this rule with the `{ "ignoreComments": true }` option: + +```js +/*eslint no-trailing-spaces: ["error", { "ignoreComments": true }]*/ + +//foo• +//••••• +/** + *•baz + *•• + *•bar + */ +``` \ No newline at end of file diff --git a/docs/rules/no-undef.md b/docs/rules/no-undef.md index ac7c231ae514..49a149fde777 100644 --- a/docs/rules/no-undef.md +++ b/docs/rules/no-undef.md @@ -4,7 +4,7 @@ This rule can help you locate potential ReferenceErrors resulting from misspelli ## Rule Details -Any reference to an undeclared variable causes a warning, unless the variable is explicitly mentioned in a `/*global ...*/` comment. +Any reference to an undeclared variable causes a warning, unless the variable is explicitly mentioned in a `/*global ...*/` comment, or specified in the [`globals` key in the configuration file](https://eslint.org/docs/user-guide/configuring#specifying-globals). A common use case for these is if you intentionally use globals that are defined elsewhere (e.g. in a script sourced from HTML). Examples of **incorrect** code for this rule: @@ -75,7 +75,7 @@ if(typeof a === "string"){} ## Environments -For convenience, ESLint provides shortcuts that pre-define global variables exposed by popular libraries and runtime environments. This rule supports these environments, as listed in [Specifying Environments](http://eslint.org/docs/user-guide/configuring#specifying-environments). A few examples are given below. +For convenience, ESLint provides shortcuts that pre-define global variables exposed by popular libraries and runtime environments. This rule supports these environments, as listed in [Specifying Environments](../user-guide/configuring.md#specifying-environments). A few examples are given below. ### browser @@ -90,7 +90,7 @@ setTimeout(function() { }); ``` -### node +### Node.js Examples of **correct** code for this rule with `node` environment: @@ -110,4 +110,4 @@ If explicit declaration of global variables is not to your taste. ## Compatibility -This rule provides compatibility with treatment of global variables in [JSHint](http://www.jshint.com) and [JSLint](http://www.jslint.com). +This rule provides compatibility with treatment of global variables in [JSHint](http://jshint.com/) and [JSLint](http://www.jslint.com). diff --git a/docs/rules/no-undefined.md b/docs/rules/no-undefined.md index 19458c6ee41b..0e183f266d53 100644 --- a/docs/rules/no-undefined.md +++ b/docs/rules/no-undefined.md @@ -1,6 +1,6 @@ # Disallow Use of `undefined` Variable (no-undefined) -The `undefined` variable is unique in JavaScript because it is actually a property of the global object. As such, in ECMAScript 3 it was possible to overwrite the value of `undefined`. While ECMAScript 5 disallows overwriting `undefined`, it's still possible to shadow `undefined`, such as: +The `undefined` variable in JavaScript is actually a property of the global object. As such, in ECMAScript 3 it was possible to overwrite the value of `undefined`. While ECMAScript 5 disallows overwriting `undefined`, it's still possible to shadow `undefined`, such as: ```js function doSomething(data) { @@ -14,24 +14,15 @@ function doSomething(data) { } ``` -This represents a problem for `undefined` that doesn't exist for `null`, which is a keyword and primitive value that can neither be overwritten nor shadowed. +Because `undefined` can be overwritten or shadowed, reading `undefined` can give an unexpected value. (This is not the case for `null`, which is a keyword that always produces the same value.) To guard against this, you can avoid all uses of `undefined`, which is what some style guides recommend and what this rule enforces. Those style guides then also recommend: -All uninitialized variables automatically get the value of `undefined`: - -```js -var foo; - -console.log(foo === undefined); // true (assuming no shadowing) -``` - -For this reason, it's not necessary to explicitly initialize a variable to `undefined`. - -Taking all of this into account, some style guides forbid the use of `undefined`, recommending instead: - -* Variables that should be `undefined` are simply left uninitialized. +* Variables that should be `undefined` are simply left uninitialized. (All uninitialized variables automatically get the value of `undefined` in JavaScript.) * Checking if a value is `undefined` should be done with `typeof`. * Using the `void` operator to generate the value of `undefined` if necessary. +As an alternative, you can use the [no-global-assign](no-global-assign.md) and [no-shadow-restricted-names](no-shadow-restricted-names.md) rules to prevent `undefined` from being shadowed or assigned a different value. This ensures that `undefined` will always hold its original, expected value. + + ## Rule Details This rule aims to eliminate the use of `undefined`, and as such, generates a warning whenever it is used. @@ -77,10 +68,12 @@ If you want to allow the use of `undefined` in your code, then you can safely tu ## Further Reading * [undefined - JavaScript \| MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined) -* [Understanding JavaScript’s ‘undefined’ \| JavaScript, JavaScript...](http://javascriptweblog.wordpress.com/2010/08/16/understanding-undefined-and-preventing-referenceerrors/) +* [Understanding JavaScript’s ‘undefined’ \| JavaScript, JavaScript...](https://javascriptweblog.wordpress.com/2010/08/16/understanding-undefined-and-preventing-referenceerrors/) * [ECMA262 edition 5.1 §15.1.1.3: undefined](https://es5.github.io/#x15.1.1.3) ## Related Rules * [no-undef-init](no-undef-init.md) * [no-void](no-void.md) +* [no-shadow-restricted-names](no-shadow-restricted-names.md) +* [no-global-assign](no-global-assign.md) diff --git a/docs/rules/no-underscore-dangle.md b/docs/rules/no-underscore-dangle.md index d09c6d6ade41..7383815b3fd1 100644 --- a/docs/rules/no-underscore-dangle.md +++ b/docs/rules/no-underscore-dangle.md @@ -42,6 +42,7 @@ This rule has an object option: * `"allow"` allows specified identifiers to have dangling underscores * `"allowAfterThis": false` (default) disallows dangling underscores in members of the `this` object * `"allowAfterSuper": false` (default) disallows dangling underscores in members of the `super` object +* `"enforceInMethodNames": false` (default) allows dangling underscores in method names ### allow @@ -76,6 +77,30 @@ var a = super.foo_; super._bar(); ``` +### enforceInMethodNames + +Examples of incorrect code for this rule with the `{ "enforceInMethodNames": true }` option: + +```js +/*eslint no-underscore-dangle: ["error", { "enforceInMethodNames": true }]*/ + +class Foo { + _bar() {} +} + +class Foo { + bar_() {} +} + +const o = { + _bar() {} +}; + +const o = { + bar_() = {} +}; +``` + ## When Not To Use It If you want to allow dangling underscores in identifiers, then you can safely turn this rule off. diff --git a/docs/rules/no-unexpected-multiline.md b/docs/rules/no-unexpected-multiline.md index 9fa9ca5feb95..a328f83b2805 100644 --- a/docs/rules/no-unexpected-multiline.md +++ b/docs/rules/no-unexpected-multiline.md @@ -32,6 +32,9 @@ let x = function() {} let x = function() {} x `hello` + +let x = foo +/regex/g.test(bar) ``` Examples of **correct** code for this rule: diff --git a/docs/rules/no-unused-vars.md b/docs/rules/no-unused-vars.md index 56e61ec2cefb..d7502ed5cdf6 100644 --- a/docs/rules/no-unused-vars.md +++ b/docs/rules/no-unused-vars.md @@ -207,7 +207,7 @@ Examples of **correct** code for the `{ "args": "none" }` option: ### ignoreRestSiblings -The `ignoreRestSiblings` option is a boolean (default: `false`). Using a [Rest Property](https://github.com/sebmarkbage/ecmascript-rest-spread) it is possible to "omit" properties from an object, but by default the sibling properties are marked as "unused". With this option enabled the rest property's siblings are ignored. +The `ignoreRestSiblings` option is a boolean (default: `false`). Using a [Rest Property](https://github.com/tc39/proposal-object-rest-spread) it is possible to "omit" properties from an object, but by default the sibling properties are marked as "unused". With this option enabled the rest property's siblings are ignored. Examples of **correct** code for the `{ "ignoreRestSiblings": true }` option: diff --git a/docs/rules/no-use-before-define.md b/docs/rules/no-use-before-define.md index 4964d8d09187..f053eba3a8eb 100644 --- a/docs/rules/no-use-before-define.md +++ b/docs/rules/no-use-before-define.md @@ -25,7 +25,6 @@ function g() { } var b = 1; -// With blockBindings: true { alert(c); let c = 1; @@ -50,9 +49,8 @@ function g() { return b; } -// With blockBindings: true { - let C; + let c; c++; } ``` diff --git a/docs/rules/no-useless-escape.md b/docs/rules/no-useless-escape.md index 2bb0c1cc3a62..6088c5cb010b 100644 --- a/docs/rules/no-useless-escape.md +++ b/docs/rules/no-useless-escape.md @@ -41,8 +41,8 @@ Examples of **correct** code for this rule: "\371"; "xs\u2111"; `\``; -`\${${foo}\}`; -`$\{${foo}\}`; +`\${${foo}}`; +`$\{${foo}}`; /\\/g; /\t/g; /\w\$\*\^\./; diff --git a/docs/rules/no-void.md b/docs/rules/no-void.md index 5ff455c0ead7..856c934c6e16 100644 --- a/docs/rules/no-void.md +++ b/docs/rules/no-void.md @@ -65,7 +65,7 @@ If you intentionally use the `void` operator then you can disable this rule. ## Further Reading * [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void) -* [Bad Parts: Appendix B - JavaScript: The Good Parts by Douglas Crockford](http://oreilly.com/javascript/excerpts/javascript-good-parts/bad-parts.html) +* [Bad Parts: Appendix B - JavaScript: The Good Parts by Douglas Crockford](https://oreilly.com/javascript/excerpts/javascript-good-parts/bad-parts.html) ## Related Rules diff --git a/docs/rules/no-with.md b/docs/rules/no-with.md index c2551e2b08cd..979c58b0ae91 100644 --- a/docs/rules/no-with.md +++ b/docs/rules/no-with.md @@ -33,4 +33,4 @@ If you intentionally use `with` statements then you can disable this rule. ## Further Reading -* [with Statement Considered Harmful](http://www.yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/) +* [with Statement Considered Harmful](https://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/) diff --git a/docs/rules/object-curly-newline.md b/docs/rules/object-curly-newline.md index a548f4eaca95..61c0e6862d9e 100644 --- a/docs/rules/object-curly-newline.md +++ b/docs/rules/object-curly-newline.md @@ -16,21 +16,26 @@ This rule has either a string option: Or an object option: * `"multiline": true` (default) requires line breaks if there are line breaks inside properties or between properties -* `"minProperties"` requires line breaks if the number of properties is more than the given integer +* `"minProperties"` requires line breaks if the number of properties is at least the given integer. By default, an error will also be reported if an object contains linebreaks and has fewer properties than the given integer. However, the second behavior is disabled if the `consistent` option is set to `true` +* `"consistent": true` requires that either both curly braces, or neither, directly enclose newlines. Note that enabling this option will also change the behavior of the `minProperties` option. (See `minProperties` above for more information) -You can specify different options for object literals and destructuring assignments: +You can specify different options for object literals, destructuring assignments, and named imports and exports: ```json { "object-curly-newline": ["error", { "ObjectExpression": "always", - "ObjectPattern": { "multiline": true } + "ObjectPattern": { "multiline": true }, + "ImportDeclaration": "never", + "ExportDeclaration": { "multiline": true, "minProperties": 3 } }] } ``` * `"ObjectExpression"` configuration for object literals * `"ObjectPattern"` configuration for object patterns of destructuring assignments +* `"ImportDeclaration"` configuration for named imports +* `"ExportDeclaration"` configuration for named exports ### always @@ -312,6 +317,86 @@ let {k = function() { }} = obj; ``` +### consistent + +Examples of **incorrect** code for this rule with the `{ "consistent": true }` option: + +```js +/*eslint object-curly-newline: ["error", { "consistent": true }]*/ +/*eslint-env es6*/ + +let a = {foo: 1 +}; +let b = { + foo: 1}; +let c = {foo: 1, bar: 2 +}; +let d = { + foo: 1, bar: 2}; +let e = {foo: 1, + bar: 2}; +let f = {foo: function() { + dosomething(); +}}; + +let {g +} = obj; +let { + h} = obj; +let {i, j +} = obj; +let { + k, l} = obj; +let {m, + n} = obj; +let {o = function() { + dosomething(); +}} = obj; +``` + +Examples of **correct** code for this rule with the `{ "consistent": true }` option: + +```js +/*eslint object-curly-newline: ["error", { "consistent": true }]*/ +/*eslint-env es6*/ + +let a = {}; +let b = {foo: 1}; +let c = { + foo: 1 +}; +let d = { + foo: 1, bar: 2 +}; +let e = { + foo: 1, + bar: 2 +}; +let f = {foo: function() {dosomething();}}; +let g = { + foo: function() { + dosomething(); + } +}; + +let {} = obj; +let {h} = obj; +let {i, j} = obj; +let { + k, l +} = obj; +let { + m, + n +} = obj; +let {o = function() {dosomething();}} = obj; +let { + p = function() { + dosomething(); + } +} = obj; +``` + ### ObjectExpression and ObjectPattern Examples of **incorrect** code for this rule with the `{ "ObjectExpression": "always", "ObjectPattern": "never" }` options: @@ -382,6 +467,48 @@ let {k = function() { }} = obj; ``` +### ImportDeclaration and ExportDeclaration + +Examples of **incorrect** code for this rule with the `{ "ImportDeclaration": "always", "ExportDeclaration": "never" }` options: + +```js +/*eslint object-curly-newline: ["error", { "ImportDeclaration": "always", "ExportDeclaration": "never" }]*/ +/*eslint-env es6*/ + +import {foo, bar} from 'foo-bar'; +import {foo as f, bar} from 'foo-bar'; +import {foo, + bar} from 'foo-bar'; + +export { + foo, + bar +}; +export { + foo as f, + bar +} from 'foo-bar'; +``` + +Examples of **correct** code for this rule with the `{ "ImportDeclaration": "always", "ExportDeclaration": "never" }` options: + +```js +/*eslint object-curly-newline: ["error", { "ImportDeclaration": "always", "ExportDeclaration": "never" }]*/ +/*eslint-env es6*/ + +import { + foo, + bar +} from 'foo-bar'; +import { + foo as f, + bar +} from 'foo-bar'; + +export { foo, bar } from 'foo-bar'; +export { foo as f, bar } from 'foo-bar'; +``` + ## Compatibility * **JSCS**: [requirePaddingNewLinesInObjects](http://jscs.info/rule/requirePaddingNewLinesInObjects) and [disallowPaddingNewLinesInObjects](http://jscs.info/rule/disallowPaddingNewLinesInObjects) diff --git a/docs/rules/object-property-newline.md b/docs/rules/object-property-newline.md index 6104c2b9a530..15d8ee0a0953 100644 --- a/docs/rules/object-property-newline.md +++ b/docs/rules/object-property-newline.md @@ -1,11 +1,63 @@ # enforce placing object properties on separate lines (object-property-newline) -While formatting preferences are very personal, a number of style guides require that object properties be placed on separate lines for better readability. +This rule permits you to restrict the locations of property specifications in object literals. You may prohibit any part of any property specification from appearing on the same line as any part of any other property specification. You may make this prohibition absolute, or, by invoking an object option, you may allow an exception, permitting an object literal to have all parts of all of its property specifications on a single line. -Another argument in favor of this style is that it improves the readability of diffs when a property is changed: +## Rule Details + +### Motivations + +This rule makes it possible to ensure, as some style guides require, that property specifications appear on separate lines for better readability. For example, you can prohibit all of these: + +```js +const newObject = {a: 1, b: [2, {a: 3, b: 4}]}; +const newObject = { + a: 1, b: [2, {a: 3, b: 4}] +}; +const newObject = { + a: 1, + b: [2, {a: 3, b: 4}] +}; +const newObject = { + a: 1, + b: [ + 2, + {a: 3, b: 4} + ] +}; + +``` + +Instead of those, you can comply with the rule by writing + +```js +const newObject = { + a: 1, + b: [2, { + a: 3, + b: 4 + }] +}; +``` + +or + +```js +const newObject = { + a: 1, + b: [ + 2, + { + a: 3, + b: 4 + } + ] +}; +``` + +Another benefit of this rule is specificity of diffs when a property is changed: ```diff -// More readable +// More specific var obj = { foo: "foo", - bar: "bar", @@ -15,75 +67,204 @@ Another argument in favor of this style is that it improves the readability of d ``` ```diff -// Less readable +// Less specific -var obj = { foo: "foo", bar: "bar", baz: "baz" }; +var obj = { foo: "foo", bar: "bazz", baz: "baz" }; ``` -## Rule Details +### Optional Exception + +The rule offers one object option, `allowAllPropertiesOnSameLine` (a deprecated synonym is `allowMultiplePropertiesPerLine`). If you set it to `true`, object literals such as the first two above, with all property specifications on the same line, will be permitted, but one like + +```js +const newObject = { + a: 'a.m.', b: 'p.m.', + c: 'daylight saving time' +}; + +``` + +will be prohibited, because two properties, but not all properties, appear on the same line. + +### Notations + +This rule applies equally to all property specifications, regardless of notation, including: + +- `a: 1` (ES5) +- `a` (ES2015 shorthand property) +- ``[`prop${a}`]`` (ES2015 computed property name) + +Thus, the rule (without the object option) prohibits both of these: + +```js +const newObject = { + a: 1, [ + process.argv[4] + ]: '01' +}; +const newObject = { + a: 1, [process.argv[4]]: '01' +}; +``` + +(This behavior differs from that of the JSCS rule cited below, which does not treat the leading `[` of a computed property name as part of that property specification. The JSCS rule prohibits the second of these formats but permits the first.) -This rule aims to maintain consistency of newlines between object properties. +### Multiline Properties -Examples of **incorrect** code for this rule: +The rule prohibits the colocation on any line of at least 1 character of one property specification with at least 1 character of any other property specification. For example, the rule prohibits + +```js +const newObject = {a: [ + 'Officiële website van de Europese Unie', + 'Официален уебсайт на Европейския съюз' +], b: 2}; +``` + +because 1 character of the specification of `a` (i.e. the trailing `]` of its value) is on the same line as the specification of `b`. + +The optional exception does not excuse this case, because the entire collection of property specifications spans 4 lines, not 1. + +### Inter-property Delimiters + +The comma and any whitespace that delimit property specifications are not considered parts of them. Therefore, the rule permits both of these formats: + +```js +const newFunction = multiplier => ({ + a: 2 * multiplier, + b: 4 * multiplier, + c: 8 * multiplier +}); +const newFunction = multiplier => ({ + a: 2 * multiplier + , b: 4 * multiplier + , c: 8 * multiplier +}); +``` + +(This behavior differs from that of the JSCS rule cited below, which permits the first but prohibits the second format.) + +### --fix + +If this rule is invoked with the command-line `--fix` option, object literals that violate the rule are generally modified to comply with it. The modification in each case is to move a property specification to the next line whenever there is part or all of a previous property specification on the same line. For example, + +```js +const newObject = { + a: 'a.m.', b: 'p.m.', + c: 'daylight saving time' +}; +``` + +is converted to + +```js +const newObject = { + a: 'a.m.', +b: 'p.m.', + c: 'daylight saving time' +}; +``` + +The modification does not depend on whether the object option is set to `true`. In other words, ESLint never collects all the property specifications onto a single line, even when the object option would permit that. + +ESLint does not correct a violation of this rule if a comment immediately precedes the second or subsequent property specification on a line, since ESLint cannot determine which line to put the comment onto. + +As illustrated above, the `--fix` option, applied to this rule, does not comply with other rules, such as `indent`, but, if those other rules are also in effect, the option applies them, too. + +## Examples + +Examples of **incorrect** code for this rule, with no object option or with `allowAllPropertiesOnSameLine` set to `false`: ```js /*eslint object-property-newline: "error"*/ -var obj = { foo: "foo", bar: "bar", baz: "baz" }; +const obj0 = { foo: "foo", bar: "bar", baz: "baz" }; -var obj2 = { +const obj1 = { foo: "foo", bar: "bar", baz: "baz" }; -var obj3 = { +const obj2 = { foo: "foo", bar: "bar", baz: "baz" }; + +const obj3 = { + [process.argv[3] ? "foo" : "bar"]: 0, baz: [ + 1, + 2, + 4, + 8 + ] +}; + +const a = "antidisestablishmentarianistically"; +const b = "yugoslavyalılaştırabildiklerimizdenmişsiniz"; +const obj4 = {a, b}; + +const domain = process.argv[4]; +const obj5 = { + foo: "foo", [ + domain.includes(":") ? "complexdomain" : "simpledomain" +]: true}; ``` -Examples of **correct** code for this rule: +Examples of **correct** code for this rule, with no object option or with `allowAllPropertiesOnSameLine` set to `false`: ```js /*eslint object-property-newline: "error"*/ -var obj = { +const obj1 = { foo: "foo", bar: "bar", baz: "baz" }; -``` - -## Options - -This rule has an object option: -* `"allowMultiplePropertiesPerLine"`: `true` allows all keys and values to be on the same line +const obj2 = { + foo: "foo" + , bar: "bar" + , baz: "baz" +}; -### allowMultiplePropertiesPerLine +const user = process.argv[2]; +const obj3 = { + user, + [process.argv[3] ? "foo" : "bar"]: 0, + baz: [ + 1, + 2, + 4, + 8 + ] +}; +``` -Examples of additional **correct** code for this rule with the `{ "allowMultiplePropertiesPerLine": true }` option: +Examples of additional **correct** code for this rule with the `{ "allowAllPropertiesOnSameLine": true }` option: ```js -/*eslint object-property-newline: ["error", { "allowMultiplePropertiesPerLine": true }]*/ +/*eslint object-property-newline: ["error", { "allowAllPropertiesOnSameLine": true }]*/ -var obj = { foo: "foo", bar: "bar", baz: "baz" }; +const obj = { foo: "foo", bar: "bar", baz: "baz" }; -var obj2 = { +const obj2 = { foo: "foo", bar: "bar", baz: "baz" }; +const user = process.argv[2]; +const obj3 = { + user, [process.argv[3] ? "foo" : "bar"]: 0, baz: [1, 2, 4, 8] +}; ``` ## When Not To Use It -You can turn this rule off if you are not concerned with the consistency of newlines between object properties. +You can turn this rule off if you want to decide, case-by-case, whether to place property specifications on separate lines. ## Compatibility -* **JSCS**: [requireObjectKeysOnNewLine](http://jscs.info/rule/requireObjectKeysOnNewLine) +- **JSCS**: This rule provides partial compatibility with [requireObjectKeysOnNewLine](http://jscs.info/rule/requireObjectKeysOnNewLine). ## Related Rules -* [brace-style](brace-style.md) -* [comma-dangle](comma-dangle.md) -* [key-spacing](key-spacing.md) -* [object-curly-spacing](object-curly-spacing.md) +- [brace-style](brace-style.md) +- [comma-dangle](comma-dangle.md) +- [key-spacing](key-spacing.md) +- [object-curly-spacing](object-curly-spacing.md) diff --git a/docs/rules/object-shorthand.md b/docs/rules/object-shorthand.md index 2cf3df64e3d7..1f2f80db9db5 100644 --- a/docs/rules/object-shorthand.md +++ b/docs/rules/object-shorthand.md @@ -1,6 +1,6 @@ # Require Object Literal Shorthand Syntax (object-shorthand) -EcmaScript 6 provides a concise form for defining object literal methods and properties. This +ECMAScript 6 provides a concise form for defining object literal methods and properties. This syntax can make defining complex object literals much cleaner. Here are a few common examples using the ES5 syntax: @@ -90,8 +90,8 @@ The rule takes an option which specifies when it should be applied. It can be se * `"methods"` ensures the method shorthand is used (also applies to generators). * `"properties"` ensures the property shorthand is used (where the key and variable name match). * `"never"` ensures that no property or method shorthand is used in any object literal. -* `"consistent"` ensures that either all shorthand or all longform will be used in an object literal. -* `"consistent-as-needed"` ensures that either all shorthand or all longform will be used in an object literal, but ensures all shorthand whenever possible. +* `"consistent"` ensures that either all shorthand or all long-form will be used in an object literal. +* `"consistent-as-needed"` ensures that either all shorthand or all long-form will be used in an object literal, but ensures all shorthand whenever possible. You can set the option in configuration like this: @@ -103,7 +103,7 @@ You can set the option in configuration like this: Additionally, the rule takes an optional object configuration: -* `"avoidQuotes": true` indicates that longform syntax is preferred whenever the object key is a string literal (default: `false`). Note that this option can only be enabled when the string option is set to `"always"`, `"methods"`, or `"properties"`. +* `"avoidQuotes": true` indicates that long-form syntax is preferred whenever the object key is a string literal (default: `false`). Note that this option can only be enabled when the string option is set to `"always"`, `"methods"`, or `"properties"`. * `"ignoreConstructors": true` can be used to prevent the rule from reporting errors for constructor functions. (By default, the rule treats constructors the same way as other functions.) Note that this option can only be enabled when the string option is set to `"always"` or `"methods"`. * `"avoidExplicitReturnArrows": true` indicates that methods are preferred over explicit-return arrow functions for function properties. (By default, the rule allows either of these.) Note that this option can only be enabled when the string option is set to `"always"` or `"methods"`. diff --git a/docs/rules/one-var.md b/docs/rules/one-var.md index 4ccf86f692ad..e7e440333f24 100644 --- a/docs/rules/one-var.md +++ b/docs/rules/one-var.md @@ -45,6 +45,7 @@ Object option: * `"let": "never"` requires multiple `let` declarations per block * `"const": "always"` requires one `const` declaration per block * `"const": "never"` requires multiple `const` declarations per block +* `"separateRequires": true` enforces `requires` to be separate from declarations Alternate object option: @@ -252,6 +253,28 @@ function foo() { } ``` +Examples of **incorrect** code for this rule with the `{ separateRequires: true }` option: + +```js +/*eslint one-var: ["error", { separateRequires: true, var: "always" }]*/ +/*eslint-env node*/ + +var foo = require("foo"), + bar = "bar"; +``` + +Examples of **correct** code for this rule with the `{ separateRequires: true }` option: + +```js +/*eslint one-var: ["error", { separateRequires: true, var: "always" }]*/ +/*eslint-env node*/ + +var foo = require("foo"); +var bar = "bar"; + +var foo = require("foo"), + bar = require("bar"); +``` ### initialized and uninitialized @@ -318,4 +341,5 @@ function foo() { ## Compatibility * **JSHint**: This rule maps to the `onevar` JSHint rule, but allows `let` and `const` to be configured separately. -* **JSCS**: This rule roughly maps to [disallowMultipleVarDecl](http://jscs.info/rule/disallowMultipleVarDecl) +* **JSCS**: This rule roughly maps to [disallowMultipleVarDecl](http://jscs.info/rule/disallowMultipleVarDecl). +* **JSCS**: This rule option `separateRequires` roughly maps to [requireMultipleVarDecl](http://jscs.info/rule/requireMultipleVarDecl). diff --git a/docs/rules/padded-blocks.md b/docs/rules/padded-blocks.md index 81d3ec46103b..3393d090ee2f 100644 --- a/docs/rules/padded-blocks.md +++ b/docs/rules/padded-blocks.md @@ -24,8 +24,8 @@ This rule has one option, which can be a string option or an object option. String option: -* `"always"` (default) requires empty lines at the beginning and ending of block statements (except `switch` statements and classes) -* `"never"` disallows empty lines at the beginning and ending of block statements (except `switch` statements and classes) +* `"always"` (default) requires empty lines at the beginning and ending of block statements and classes +* `"never"` disallows empty lines at the beginning and ending of block statements and classes Object option: @@ -354,3 +354,8 @@ if (a) { ## When Not To Use It You can turn this rule off if you are not concerned with the consistency of padding within blocks. + +## Related Rules + +* [lines-between-class-members](lines-between-class-members.md) +* [padding-line-between-statements](padding-line-between-statements.md) diff --git a/docs/rules/padding-line-between-statements.md b/docs/rules/padding-line-between-statements.md new file mode 100644 index 000000000000..38b7fa637761 --- /dev/null +++ b/docs/rules/padding-line-between-statements.md @@ -0,0 +1,236 @@ +# Require or disallow padding lines between statements (padding-line-between-statements) + +This rule requires or disallows blank lines between the given 2 kinds of statements. +Properly blank lines help developers to understand the code. + +For example, the following configuration requires a blank line between a variable declaration and a `return` statement. + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: "var", next: "return" } +]*/ + +function foo() { + var a = 1; + + return a; +} +``` + +## Rule Details + +This rule does nothing if no configuration. + +A configuration is an object which has 3 properties; `blankLine`, `prev` and `next`. For example, `{ blankLine: "always", prev: "var", next: "return" }` means "it requires one or more blank lines between a variable declaration and a `return` statement." +You can supply any number of configurations. If a statement pair matches multiple configurations, the last matched configuration will be used. + +```json +{ + "padding-line-between-statements": [ + "error", + { "blankLine": LINEBREAK_TYPE, "prev": STATEMENT_TYPE, "next": STATEMENT_TYPE }, + { "blankLine": LINEBREAK_TYPE, "prev": STATEMENT_TYPE, "next": STATEMENT_TYPE }, + { "blankLine": LINEBREAK_TYPE, "prev": STATEMENT_TYPE, "next": STATEMENT_TYPE }, + { "blankLine": LINEBREAK_TYPE, "prev": STATEMENT_TYPE, "next": STATEMENT_TYPE }, + ... + ] +} +``` + +- `LINEBREAK_TYPE` is one of the following. + - `"any"` just ignores the statement pair. + - `"never"` disallows blank lines. + - `"always"` requires one or more blank lines. Note it does not count lines that comments exist as blank lines. + +- `STATEMENT_TYPE` is one of the following, or an array of the following. + - `"*"` is wildcard. This matches any statements. + - `"block"` is lonely blocks. + - `"block-like"` is block like statements. This matches statements that the last token is the closing brace of blocks; e.g. `{ }`, `if (a) { }`, and `while (a) { }`. + - `"break"` is `break` statements. + - `"case"` is `case` labels. + - `"cjs-export"` is `export` statements of CommonJS; e.g. `module.exports = 0`, `module.exports.foo = 1`, and `exports.foo = 2`. This is the special cases of assignment. + - `"cjs-import"` is `import` statements of CommonJS; e.g. `const foo = require("foo")`. This is the special cases of variable declarations. + - `"class"` is `class` declarations. + - `"const"` is `const` variable declarations. + - `"continue"` is `continue` statements. + - `"debugger"` is `debugger` statements. + - `"default"` is `default` labels. + - `"directive"` is directive prologues. This matches directives; e.g. `"use strict"`. + - `"do"` is `do-while` statements. This matches all statements that the first token is `do` keyword. + - `"empty"` is empty statements. + - `"export"` is `export` declarations. + - `"expression"` is expression statements. + - `"for"` is `for` loop families. This matches all statements that the first token is `for` keyword. + - `"function"` is function declarations. + - `"if"` is `if` statements. + - `"import"` is `import` declarations. + - `"let"` is `let` variable declarations. + - `"multiline-block-like"` is block like statements. This is the same as `block-like` type, but only if the block is multiline. + - `"multiline-expression"` is expression statements. This is the same as `expression` type, but only if the statement is multiline. + - `"return"` is `return` statements. + - `"switch"` is `switch` statements. + - `"throw"` is `throw` statements. + - `"try"` is `try` statements. + - `"var"` is `var` variable declarations. + - `"while"` is `while` loop statements. + - `"with"` is `with` statements. + +## Examples + +This configuration would require blank lines before all `return` statements, like the [newline-before-return] rule. + +Examples of **incorrect** code for the `[{ blankLine: "always", prev: "*", next: "return" }]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: "*", next: "return" } +]*/ + +function foo() { + bar(); + return; +} +``` + +Examples of **correct** code for the `[{ blankLine: "always", prev: "*", next: "return" }]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: "*", next: "return" } +]*/ + +function foo() { + bar(); + + return; +} + +function foo() { + return; +} +``` + +---- + +This configuration would require blank lines after every sequence of variable declarations, like the [newline-after-var] rule. + +Examples of **incorrect** code for the `[{ blankLine: "always", prev: ["const", "let", "var"], next: "*"}, { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]}]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: ["const", "let", "var"], next: "*"}, + { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]} +]*/ + +function foo() { + var a = 0; + bar(); +} + +function foo() { + let a = 0; + bar(); +} + +function foo() { + const a = 0; + bar(); +} +``` + +Examples of **correct** code for the `[{ blankLine: "always", prev: ["const", "let", "var"], next: "*"}, { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]}]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: ["const", "let", "var"], next: "*"}, + { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]} +]*/ + +function foo() { + var a = 0; + var b = 0; + + bar(); +} + +function foo() { + let a = 0; + const b = 0; + + bar(); +} + +function foo() { + const a = 0; + const b = 0; + + bar(); +} +``` + +---- + +This configuration would require blank lines after all directive prologues, like the [lines-around-directive] rule. + +Examples of **incorrect** code for the `[{ blankLine: "always", prev: "directive", next: "*" }, { blankLine: "any", prev: "directive", next: "directive" }]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: "directive", next: "*" }, + { blankLine: "any", prev: "directive", next: "directive" } +]*/ + +"use strict"; +foo(); +``` + +Examples of **correct** code for the `[{ blankLine: "always", prev: "directive", next: "*" }, { blankLine: "any", prev: "directive", next: "directive" }]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: "directive", next: "*" }, + { blankLine: "any", prev: "directive", next: "directive" } +]*/ + +"use strict"; +"use asm"; + +foo(); +``` + +## Compatibility + +- **JSCS:** [requirePaddingNewLineAfterVariableDeclaration] +- **JSCS:** [requirePaddingNewLinesAfterBlocks] +- **JSCS:** [disallowPaddingNewLinesAfterBlocks] +- **JSCS:** [requirePaddingNewLinesAfterUseStrict] +- **JSCS:** [disallowPaddingNewLinesAfterUseStrict] +- **JSCS:** [requirePaddingNewLinesBeforeExport] +- **JSCS:** [disallowPaddingNewLinesBeforeExport] +- **JSCS:** [requirePaddingNewlinesBeforeKeywords] +- **JSCS:** [disallowPaddingNewlinesBeforeKeywords] + +## When Not To Use It + +If you don't want to notify warnings about linebreaks, then it's safe to disable this rule. + + +[lines-around-directive]: https://eslint.org/docs/rules/lines-around-directive +[newline-after-var]: https://eslint.org/docs/rules/newline-after-var +[newline-before-return]: https://eslint.org/docs/rules/newline-before-return +[requirePaddingNewLineAfterVariableDeclaration]: http://jscs.info/rule/requirePaddingNewLineAfterVariableDeclaration +[requirePaddingNewLinesAfterBlocks]: http://jscs.info/rule/requirePaddingNewLinesAfterBlocks +[disallowPaddingNewLinesAfterBlocks]: http://jscs.info/rule/disallowPaddingNewLinesAfterBlocks +[requirePaddingNewLinesAfterUseStrict]: http://jscs.info/rule/requirePaddingNewLinesAfterUseStrict +[disallowPaddingNewLinesAfterUseStrict]: http://jscs.info/rule/disallowPaddingNewLinesAfterUseStrict +[requirePaddingNewLinesBeforeExport]: http://jscs.info/rule/requirePaddingNewLinesBeforeExport +[disallowPaddingNewLinesBeforeExport]: http://jscs.info/rule/disallowPaddingNewLinesBeforeExport +[requirePaddingNewlinesBeforeKeywords]: http://jscs.info/rule/requirePaddingNewlinesBeforeKeywords +[disallowPaddingNewlinesBeforeKeywords]: http://jscs.info/rule/disallowPaddingNewlinesBeforeKeywords diff --git a/docs/rules/prefer-arrow-callback.md b/docs/rules/prefer-arrow-callback.md index 4942295a5c1d..c9132122ea58 100644 --- a/docs/rules/prefer-arrow-callback.md +++ b/docs/rules/prefer-arrow-callback.md @@ -1,77 +1,100 @@ -# Suggest using arrow functions as callbacks. (prefer-arrow-callback) +# Require using arrow functions for callbacks (prefer-arrow-callback) -Arrow functions are suited to callbacks, because: +Arrow functions can be an attractive alternative to function expressions for callbacks or function arguments. -- `this` keywords in arrow functions bind to the upper scope's. -- The notation of the arrow function is shorter than function expression's. +For example, arrow functions are automatically bound to their surrounding scope/context. This provides an alternative to the pre-ES6 standard of explicitly binding function expressions to achieve similar behavior. + +Additionally, arrow functions are: + +- less verbose, and easier to reason about. + +- bound lexically regardless of where or when they are invoked. ## Rule Details -This rule is aimed to flag usage of function expressions in an argument list. +This rule locates function expressions used as callbacks or function arguments. An error will be produced for any that could be replaced by an arrow function without changing the result. -The following patterns are considered problems: +The following examples **will** be flagged: ```js -/*eslint prefer-arrow-callback: "error"*/ +/* eslint prefer-arrow-callback: "error" */ -foo(function(a) { return a; }); -foo(function() { return this.a; }.bind(this)); +foo(function(a) { return a; }); // ERROR +// prefer: foo(a => a) + +foo(function() { return this.a; }.bind(this)); // ERROR +// prefer: foo(() => this.a) ``` -The following patterns are not considered problems: +Instances where an arrow function would not produce identical results will be ignored. + +The following examples **will not** be flagged: ```js -/*eslint prefer-arrow-callback: "error"*/ -/*eslint-env es6*/ +/* eslint prefer-arrow-callback: "error" */ +/* eslint-env es6 */ + +// arrow function callback +foo(a => a); // OK -foo(a => a); -foo(function*() { yield; }); +// generator as callback +foo(function*() { yield; }); // OK -// this is not a callback. -var foo = function foo(a) { return a; }; +// function expression not used as callback or function argument +var foo = function foo(a) { return a; }; // OK -// using `this` without `.bind(this)`. -foo(function() { return this.a; }); +// unbound function expression callback +foo(function() { return this.a; }); // OK -// recursively. -foo(function bar(n) { return n && n + bar(n - 1); }); +// recursive named function callback +foo(function bar(n) { return n && n + bar(n - 1); }); // OK ``` ## Options -This rule takes one optional argument, an object which is an options object. +Access further control over this rule's behavior via an options object. + +Default: `{ allowNamedFunctions: false, allowUnboundThis: true }` ### allowNamedFunctions -This is a `boolean` option and it is `false` by default. When set to `true`, the rule doesn't warn on named functions used as callbacks. +By default `{ "allowNamedFunctions": false }`, this `boolean` option prohibits using named functions as callbacks or function arguments. + +Changing this value to `true` will reverse this option's behavior by allowing use of named functions without restriction. -Examples of **correct** code for the `{ "allowNamedFunctions": true }` option: +`{ "allowNamedFunctions": true }` **will not** flag the following example: ```js -/*eslint prefer-arrow-callback: ["error", { "allowNamedFunctions": true }]*/ +/* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */ foo(function bar() {}); ``` ### allowUnboundThis -This is a `boolean` option and it is `true` by default. When set to `false`, this option allows the use of `this` without restriction and checks for dynamically assigned `this` values such as when using `Array.prototype.map` with a `context` argument. Normally, the rule will flag the use of `this` whenever a function does not use `bind()` to specify the value of `this` constantly. +By default `{ "allowUnboundThis": true }`, this `boolean` option allows function expressions containing `this` to be used as callbacks, as long as the function in question has not been explicitly bound. + +When set to `false` this option prohibits the use of function expressions as callbacks or function arguments entirely, without exception. -Examples of **incorrect** code for the `{ "allowUnboundThis": false }` option: +`{ "allowUnboundThis": false }` **will** flag the following examples: ```js -/*eslint prefer-arrow-callback: ["error", { "allowUnboundThis": false }]*/ -/*eslint-env es6*/ +/* eslint prefer-arrow-callback: [ "error", { "allowUnboundThis": false } ] */ +/* eslint-env es6 */ foo(function() { this.a; }); foo(function() { (() => this); }); -someArray.map(function (itm) { return this.doSomething(itm); }, someObject); +someArray.map(function(itm) { return this.doSomething(itm); }, someObject); ``` ## When Not To Use It -This rule should not be used in ES3/5 environments. +- In environments that have not yet adopted ES6 language features (ES3/5). + +- In ES6+ environments that allow the use of function expressions when describing callbacks or function arguments. + +## Further Reading -In ES2015 (ES6) or later, if you don't want to be notified about function expressions in an argument list, you can safely disable this rule. +- [More on ES6 arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) diff --git a/docs/rules/prefer-destructuring.md b/docs/rules/prefer-destructuring.md index 2925225d3521..8525dc2d0ef1 100644 --- a/docs/rules/prefer-destructuring.md +++ b/docs/rules/prefer-destructuring.md @@ -4,6 +4,18 @@ With JavaScript ES6, a new syntax was added for creating variables from an array ## Rule Details +### Options + +This rule takes two sets of configuration objects. The first object parameter determines what types of destructuring the rule applies to. + +The two properties, `array` and `object`, can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true. + +Alternatively, you can use separate configurations for different assignment types. It accepts 2 other keys instead of `array` and `object`. + +One key is `VariableDeclarator` and the other is `AssignmentExpression`, which can be used to control the destructuring requirement for each of those types independently. Each property accepts an object that accepts two properties, `array` and `object`, which can be used to control the destructuring requirement for each of `array` and `object` independently for variable declarations and assignment expressions. By default, `array` and `object` are set to true for both `VariableDeclarator` and `AssignmentExpression`. + +The rule has a second object with a single key, `enforceForRenamedProperties`, which determines whether the `object` destructuring applies to renamed variables. + Examples of **incorrect** code for this rule: ```javascript @@ -24,16 +36,12 @@ var foo = array[someIndex]; // With `object` enabled var { foo } = object; -var foo = object.bar; -``` - -### Options - -This rule takes two sets of configuration objects; the first controls the types that the rule is applied to, and the second controls the way those objects are evaluated. -The first has two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are `true`. +var foo = object.bar; -The second has a single property, `enforceForRenamedProperties`, that controls whether or not the `object` destructuring rules are applied in cases where the variable requires the property being access to be renamed. +let foo; +({ foo } = object); +``` Examples of **incorrect** code when `enforceForRenamedProperties` is enabled: @@ -47,7 +55,7 @@ Examples of **correct** code when `enforceForRenamedProperties` is enabled: var { bar: foo } = object; ``` -An example configuration, with the defaults filled in, looks like this: +An example configuration, with the defaults `array` and `object` filled in, looks like this: ```json { @@ -62,6 +70,77 @@ An example configuration, with the defaults filled in, looks like this: } ``` +The two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true. + +For example, the following configuration enforces only object destructuring, but not array destructuring: + +```json +{ + "rules": { + "prefer-destructuring": ["error", {"object": true, "array": false}] + } +} +``` + +An example configuration, with the defaults `VariableDeclarator` and `AssignmentExpression` filled in, looks like this: + +```json +{ + "rules": { + "prefer-destructuring": ["error", { + "VariableDeclarator": { + "array": false, + "object": true + }, + "AssignmentExpression": { + "array": true, + "object": true + } + }, { + "enforceForRenamedProperties": false + }] + } +} +``` + +The two properties, `VariableDeclarator` and `AssignmentExpression`, which can be used to turn on or off the destructuring requirement for `array` and `object`. By default, all values are true. + +For example, the following configuration enforces object destructuring in variable declarations and enforces array destructuring in assignment expressions. + +```json +{ + "rules": { + "prefer-destructuring": ["error", { + "VariableDeclarator": { + "array": false, + "object": true + }, + "AssignmentExpression": { + "array": true, + "object": false + } + }, { + "enforceForRenamedProperties": false + }] + } +} + +``` + +Examples of **correct** code when object destructuring in `VariableDeclarator` is enforced: + +```javascript +/* eslint prefer-destructuring: ["error", {VariableDeclarator: {object: true}}] */ +var {bar: foo} = object; +``` + +Examples of **correct** code when array destructuring in `AssignmentExpression` is enforced: + +```javascript +/* eslint prefer-destructuring: ["error", {AssignmentExpression: {array: true}}] */ +[bar] = array; +``` + ## When Not To Use It If you want to be able to access array indices or object properties directly, you can either configure the rule to your tastes or disable the rule entirely. @@ -74,10 +153,18 @@ var foo = array[100]; Then the `array` part of this rule is not recommended, as destructuring does not match this use case very well. +Or for non-iterable 'array-like' objects: + +```javascript +var $ = require('jquery'); +var foo = $('body')[0]; +var [bar] = $('body'); // fails with a TypeError +``` + ## Further Reading If you want to learn more about destructuring, check out the links below: - [Destructuring Assignment (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) -- [Destructuring and parameter handling in ECMAScript 6 (2ality blog)](http://www.2ality.com/2015/01/es6-destructuring.html) +- [Destructuring and parameter handling in ECMAScript 6 (2ality blog)](http://2ality.com/2015/01/es6-destructuring.html) diff --git a/docs/rules/prefer-numeric-literals.md b/docs/rules/prefer-numeric-literals.md index f4c70b6d3c41..965fd529b72d 100644 --- a/docs/rules/prefer-numeric-literals.md +++ b/docs/rules/prefer-numeric-literals.md @@ -1,6 +1,6 @@ -# disallow `parseInt()` in favor of binary, octal, and hexadecimal literals (prefer-numeric-literals) +# disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals (prefer-numeric-literals) -The `parseInt()` function can be used to turn binary, octal, and hexadecimal strings into integers. As binary, octal, and hexadecimal literals are supported in ES6, this rule encourages use of those numeric literals instead of `parseInt()`. +The `parseInt()` and `Number.parseInt()` functions can be used to turn binary, octal, and hexadecimal strings into integers. As binary, octal, and hexadecimal literals are supported in ES6, this rule encourages use of those numeric literals instead of `parseInt()` or `Number.parseInt()`. ```js 0b111110111 === 503; @@ -9,7 +9,7 @@ The `parseInt()` function can be used to turn binary, octal, and hexadecimal str ## Rule Details -This rule disallows `parseInt()` if it is called with two arguments: a string and a radix option of 2 (binary), 8 (octal), or 16 (hexadecimal). +This rule disallows calls to `parseInt()` or `Number.parseInt()` if called with two arguments: a string; and a radix option of 2 (binary), 8 (octal), or 16 (hexadecimal). Examples of **incorrect** code for this rule: @@ -18,7 +18,10 @@ Examples of **incorrect** code for this rule: parseInt("111110111", 2) === 503; parseInt("767", 8) === 503; -parseInt("1F7", 16) === 255; +parseInt("1F7", 16) === 503; +Number.parseInt("111110111", 2) === 503; +Number.parseInt("767", 8) === 503; +Number.parseInt("1F7", 16) === 503; ``` Examples of **correct** code for this rule: @@ -29,6 +32,8 @@ Examples of **correct** code for this rule: parseInt(1); parseInt(1, 3); +Number.parseInt(1); +Number.parseInt(1, 3); 0b111110111 === 503; 0o767 === 503; @@ -38,11 +43,13 @@ a[parseInt](1,2); parseInt(foo); parseInt(foo, 2); +Number.parseInt(foo); +Number.parseInt(foo, 2); ``` ## When Not To Use It -If you want to allow use of `parseInt()` for binary, octal, or hexadecimal integers. If you are not using ES6 (because binary and octal literals are not supported in ES5 and below). +If you want to allow use of `parseInt()` or `Number.parseInt()` for binary, octal, or hexadecimal integers, or if you are not using ES6 (because binary and octal literals are not supported in ES5 and below), you may wish to disable this rule. ## Compatibility diff --git a/docs/rules/prefer-promise-reject-errors.md b/docs/rules/prefer-promise-reject-errors.md index af0f9bd876ce..ac0a227d1e62 100644 --- a/docs/rules/prefer-promise-reject-errors.md +++ b/docs/rules/prefer-promise-reject-errors.md @@ -65,9 +65,9 @@ new Promise(function(resolve, reject) { ## Known Limitations -Due to the limits of static analysis, this rule cannot guarantee that you will only reject Promises with `Error` objects. While the rule will report cases where it can guarantee that the rejection reason is clearly not an `Error`, it will not report cases where there is uncertainty about whether a given reason is an `Error`. For more information on this caveat, see the [similar limitations](http://eslint.org/docs/rules/no-throw-literal#known-limitations) in the `no-throw-literal` rule. +Due to the limits of static analysis, this rule cannot guarantee that you will only reject Promises with `Error` objects. While the rule will report cases where it can guarantee that the rejection reason is clearly not an `Error`, it will not report cases where there is uncertainty about whether a given reason is an `Error`. For more information on this caveat, see the [similar limitations](no-throw-literal.md#known-limitations) in the `no-throw-literal` rule. -To avoid conflicts between rules, this rule does not report non-error values used in `throw` statements in async functions, even though these lead to Promise rejections. To lint for these cases, use the [`no-throw-literal`](http://eslint.org/docs/rules/no-throw-literal) rule. +To avoid conflicts between rules, this rule does not report non-error values used in `throw` statements in async functions, even though these lead to Promise rejections. To lint for these cases, use the [`no-throw-literal`](https://eslint.org/docs/rules/no-throw-literal) rule. ## When Not To Use It @@ -75,5 +75,5 @@ If you're using custom non-error values as Promise rejection reasons, you can tu ## Further Reading -* [`no-throw-literal`](http://eslint.org/docs/rules/no-throw-literal) +* [`no-throw-literal`](https://eslint.org/docs/rules/no-throw-literal) * [Warning: a promise was rejected with a non-error](http://bluebirdjs.com/docs/warning-explanations.html#warning-a-promise-was-rejected-with-a-non-error) diff --git a/docs/rules/prefer-reflect.md b/docs/rules/prefer-reflect.md index 92e956a350c5..55bc1bbbe778 100644 --- a/docs/rules/prefer-reflect.md +++ b/docs/rules/prefer-reflect.md @@ -4,12 +4,12 @@ This rule was **deprecated** in ESLint v3.9.0 and will not be replaced. The orig The ES6 Reflect API comes with a handful of methods which somewhat deprecate methods on old constructors: -* [`Reflect.apply`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.apply) effectively deprecates [`Function.prototype.apply`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-function.prototype.apply) and [`Function.prototype.call`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-function.prototype.call) -* [`Reflect.deleteProperty`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.deleteproperty) effectively deprecates the [`delete` keyword](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-delete-operator-runtime-semantics-evaluation) -* [`Reflect.getOwnPropertyDescriptor`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.getownpropertydescriptor) effectively deprecates [`Object.getOwnPropertyDescriptor`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.getownpropertydescriptor) -* [`Reflect.getPrototypeOf`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.getprototypeof) effectively deprecates [`Object.getPrototypeOf`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.getprototypeof) -* [`Reflect.setPrototypeOf`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.setprototypeof) effectively deprecates [`Object.setPrototypeOf`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.setprototypeof) -* [`Reflect.preventExtensions`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.preventextensions) effectively deprecates [`Object.preventExtensions`](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.preventextensions) +* [`Reflect.apply`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.apply) effectively deprecates [`Function.prototype.apply`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-function.prototype.apply) and [`Function.prototype.call`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-function.prototype.call) +* [`Reflect.deleteProperty`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.deleteproperty) effectively deprecates the [`delete` keyword](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-delete-operator-runtime-semantics-evaluation) +* [`Reflect.getOwnPropertyDescriptor`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.getownpropertydescriptor) effectively deprecates [`Object.getOwnPropertyDescriptor`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.getownpropertydescriptor) +* [`Reflect.getPrototypeOf`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.getprototypeof) effectively deprecates [`Object.getPrototypeOf`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.getprototypeof) +* [`Reflect.setPrototypeOf`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.setprototypeof) effectively deprecates [`Object.setPrototypeOf`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.setprototypeof) +* [`Reflect.preventExtensions`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.preventextensions) effectively deprecates [`Object.preventExtensions`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.preventextensions) The prefer-reflect rule will flag usage of any older method, suggesting to instead use the newer Reflect version. diff --git a/docs/rules/quote-props.md b/docs/rules/quote-props.md index d30608ae7f92..623c30e7e6ea 100644 --- a/docs/rules/quote-props.md +++ b/docs/rules/quote-props.md @@ -265,5 +265,5 @@ If you don't care if property names are consistently wrapped in quotes or not, a ## Further Reading -* [Reserved words as property names](http://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names) +* [Reserved words as property names](https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names) * [Unquoted property names / object keys in JavaScript](https://mathiasbynens.be/notes/javascript-properties) diff --git a/docs/rules/quotes.md b/docs/rules/quotes.md index a1f8de41f64d..02c748c4da8c 100644 --- a/docs/rules/quotes.md +++ b/docs/rules/quotes.md @@ -44,6 +44,7 @@ Examples of **incorrect** code for this rule with the default `"double"` option: var single = 'single'; var unescaped = 'a string containing "double" quotes'; +var backtick = `back\ntick`; // you can use \n in single or double quoted strings ``` Examples of **correct** code for this rule with the default `"double"` option: @@ -53,7 +54,8 @@ Examples of **correct** code for this rule with the default `"double"` option: /*eslint-env es6*/ var double = "double"; -var backtick = `back\ntick`; // backticks are allowed due to newline +var backtick = `back +tick`; // backticks are allowed due to newline var backtick = tag`backtick`; // backticks are allowed due to tag ``` diff --git a/docs/rules/radix.md b/docs/rules/radix.md index 7a6e0c6764aa..64466ab6843c 100644 --- a/docs/rules/radix.md +++ b/docs/rules/radix.md @@ -90,4 +90,4 @@ If you don't want to enforce either presence or omission of the `10` radix value ## Further Reading -* [parseInt and radix](http://davidwalsh.name/parseint-radix) +* [parseInt and radix](https://davidwalsh.name/parseint-radix) diff --git a/docs/rules/require-jsdoc.md b/docs/rules/require-jsdoc.md index 8fa31d87d08a..b9deb7daa69a 100644 --- a/docs/rules/require-jsdoc.md +++ b/docs/rules/require-jsdoc.md @@ -24,6 +24,7 @@ This rule requires JSDoc comments for specified nodes. Supported nodes: * `"ClassDeclaration"` * `"MethodDefinition"` * `"ArrowFunctionExpression"` +* `"FunctionExpression"` ## Options @@ -40,7 +41,8 @@ Default option settings are: "FunctionDeclaration": true, "MethodDefinition": false, "ClassDeclaration": false, - "ArrowFunctionExpression": false + "ArrowFunctionExpression": false, + "FunctionExpression": false } }] } @@ -48,14 +50,16 @@ Default option settings are: ### require -Examples of **incorrect** code for this rule with the `{ "require": { "FunctionDeclaration": true, "MethodDefinition": true, "ClassDeclaration": true, "ArrowFunctionExpression": true } }` option: +Examples of **incorrect** code for this rule with the `{ "require": { "FunctionDeclaration": true, "MethodDefinition": true, "ClassDeclaration": true, "ArrowFunctionExpression": true, "FunctionExpression": true } }` option: ```js /*eslint "require-jsdoc": ["error", { "require": { "FunctionDeclaration": true, "MethodDefinition": true, - "ClassDeclaration": true + "ClassDeclaration": true, + "ArrowFunctionExpression": true, + "FunctionExpression": true } }]*/ @@ -65,21 +69,39 @@ function foo() { var foo = () => { return 10; -} +}; -class Test{ - getDate(){} +class Foo { + bar() { + return 10; + } } + +var foo = function() { + return 10; +}; + +var foo = { + bar: function() { + return 10; + }, + + baz() { + return 10; + } +}; ``` -Examples of **correct** code for this rule with the `{ "require": { "FunctionDeclaration": true, "MethodDefinition": true, "ClassDeclaration": true, "ArrowFunctionExpression": true } }` option: +Examples of **correct** code for this rule with the `{ "require": { "FunctionDeclaration": true, "MethodDefinition": true, "ClassDeclaration": true, "ArrowFunctionExpression": true, "FunctionExpression": true } }` option: ```js /*eslint "require-jsdoc": ["error", { "require": { "FunctionDeclaration": true, "MethodDefinition": true, - "ClassDeclaration": true + "ClassDeclaration": true, + "ArrowFunctionExpression": true, + "FunctionExpression": true } }]*/ @@ -119,15 +141,40 @@ array.filter(function(item) { }); /** - * It returns 10 + * A class that can return the number 10 */ -class Test{ +class Foo { /** - * returns the date + * It returns 10 */ - getDate(){} + bar() { + return 10; + } } +/** + * It returns 10 + */ +var foo = function() { + return 10; +}; + +var foo = { + /** + * It returns 10 + */ + bar: function() { + return 10; + }, + + /** + * It returns 10 + */ + baz() { + return 10; + } +}; + setTimeout(() => {}, 10); // since it's an anonymous arrow function ``` diff --git a/docs/rules/rest-spread-spacing.md b/docs/rules/rest-spread-spacing.md index 6ff21277037c..27cc626c1b76 100644 --- a/docs/rules/rest-spread-spacing.md +++ b/docs/rules/rest-spread-spacing.md @@ -54,7 +54,7 @@ This rule aims to enforce consistent spacing between rest and spread operators a } ``` -Please read the user guide's section on [configuring parser options](http://eslint.org/docs/user-guide/configuring#specifying-parser-options) to learn more. +Please read the user guide's section on [configuring parser options](/docs/user-guide/configuring#specifying-parser-options) to learn more. ## Options @@ -140,4 +140,4 @@ You can safely disable this rule if you do not care about enforcing consistent s ## Further Reading -* [Object Rest/Spread Properties for ECMAScript](https://github.com/sebmarkbage/ecmascript-rest-spread) +* [Object Rest/Spread Properties for ECMAScript](https://github.com/tc39/proposal-object-rest-spread) diff --git a/docs/rules/semi-style.md b/docs/rules/semi-style.md new file mode 100644 index 000000000000..f8aa8fcde30c --- /dev/null +++ b/docs/rules/semi-style.md @@ -0,0 +1,96 @@ +# Enforce location of semicolons (semi-style) + +Generally, semicolons are at the end of lines. However, in semicolon-less style, semicolons are at the beginning of lines. This rule enforces that semicolons are at the configured location. + +## Rule Details + +This rule reports line terminators around semicolons. + +This rule has an option. + +```json +{ + "semi-style": ["error", "last"], +} +``` + +- `"last"` (Default) ... enforces that semicolons are at the end of statements. +- `"first"` ... enforces that semicolons are at the beginning of statements. Semicolons of `for` loop heads (`for(a;b;c){}`) should be at the end of lines even if you use this option. + +Examples of **incorrect** code for this rule with `"last"` option: + +```js +/*eslint semi-style: ["error", "last"]*/ + +foo() +;[1, 2, 3].forEach(bar) + +for ( + var i = 0 + ; i < 10 + ; ++i +) { + foo() +} +``` + +Examples of **correct** code for this rule with `"last"` option: + +```js +/*eslint semi-style: ["error", "last"]*/ + +foo(); +[1, 2, 3].forEach(bar) + +for ( + var i = 0; + i < 10; + ++i +) { + foo() +} +``` + +Examples of **incorrect** code for this rule with `"first"` option: + +```js +/*eslint semi-style: ["error", "first"]*/ + +foo(); +[1, 2, 3].forEach(bar) + +for ( + var i = 0 + ; i < 10 + ; ++i +) { + foo() +} +``` + +Examples of **correct** code for this rule with `"first"` option: + +```js +/*eslint semi-style: ["error", "first"]*/ + +foo() +;[1, 2, 3].forEach(bar) + +for ( + var i = 0; + i < 10; + ++i +) { + foo() +} +``` + +## When Not To Use It + +If you don't want to notify the location of semicolons, then it's safe to disable this rule. + +## Related rules + +- [no-extra-semi](./no-extra-semi.md) +- [semi](./semi.md) +- [semi-spacing](./semi-spacing.md) diff --git a/docs/rules/semi.md b/docs/rules/semi.md index 042edefe3f20..c49b08faade3 100644 --- a/docs/rules/semi.md +++ b/docs/rules/semi.md @@ -66,10 +66,16 @@ String option: * `"always"` (default) requires semicolons at the end of statements * `"never"` disallows semicolons as the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) -Object option: +Object option (when `"always"`): * `"omitLastInOneLineBlock": true` ignores the last semicolon in a block in which its braces (and therefore the content of the block) are in the same line +Object option (when `"never"`): + +* `"beforeStatementContinuationChars": "any"` (default) ignores semicolons (or lacking semicolon) at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. +* `"beforeStatementContinuationChars": "always"` requires semicolons at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. +* `"beforeStatementContinuationChars": "never"` disallows semicolons as the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`. + ### always Examples of **incorrect** code for this rule with the default `"always"` option: @@ -123,6 +129,16 @@ object.method = function() { var name = "ESLint" +;(function() { + // ... +})() + +import a from "a" +(function() { + // ... +})() + +import b from "b" ;(function() { // ... })() @@ -140,6 +156,30 @@ if (foo) { bar() } if (foo) { bar(); baz() } ``` +#### beforeStatementContinuationChars + +Examples of additional **incorrect** code for this rule with the `"never", { "beforeStatementContinuationChars": "always" }` options: + +```js +/*eslint semi: ["error", "never", { "beforeStatementContinuationChars": "always"}] */ +import a from "a" + +(function() { + // ... +})() +``` + +Examples of additional **incorrect** code for this rule with the `"never", { "beforeStatementContinuationChars": "never" }` options: + +```js +/*eslint semi: ["error", "never", { "beforeStatementContinuationChars": "never"}] */ +import a from "a" + +;(function() { + // ... +})() +``` + ## When Not To Use It If you do not want to enforce semicolon usage (or omission) in any particular way, then you can turn this rule off. diff --git a/docs/rules/sort-imports.md b/docs/rules/sort-imports.md index c1b0195468f8..d375279384ea 100644 --- a/docs/rules/sort-imports.md +++ b/docs/rules/sort-imports.md @@ -62,8 +62,8 @@ Examples of **correct** code for this rule when using default options: ```js /*eslint sort-imports: "error"*/ import 'module-without-export.js'; -import * as foo from 'foo.js'; import * as bar from 'bar.js'; +import * as foo from 'foo.js'; import {alpha, beta} from 'alpha.js'; import {delta, gamma} from 'delta.js'; import a from 'baz.js'; @@ -76,7 +76,7 @@ import c from 'baz.js'; /*eslint sort-imports: "error"*/ import 'foo.js' -import * from 'bar.js'; +import * as bar from 'bar.js'; import {a, b} from 'baz.js'; import c from 'qux.js'; @@ -165,7 +165,7 @@ There are four different styles and the default member syntax sort order is: * `multiple` - import multiple members. * `single` - import single member. -All four options must be specified in the array, but you can customise their order. +All four options must be specified in the array, but you can customize their order. Examples of **incorrect** code for this rule with the default `{ "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] }` option: @@ -186,13 +186,12 @@ import * as b from 'bar.js'; Examples of **correct** code for this rule with the `{ "memberSyntaxSortOrder": ['all', 'single', 'multiple', 'none'] }` option: -``` +```js /*eslint sort-imports: ["error", { "memberSyntaxSortOrder": ['all', 'single', 'multiple', 'none'] }]*/ import * as foo from 'foo.js'; import z from 'zoo.js'; import {a, b} from 'foo.js'; - ``` Default is `["none", "all", "multiple", "single"]`. @@ -203,5 +202,5 @@ This rule is a formatting preference and not following it won't negatively affec ## Related Rules -* [sort-keys](http://eslint.org/docs/rules/sort-keys) -* [sort-vars](http://eslint.org/docs/rules/sort-vars) +* [sort-keys](sort-keys.md) +* [sort-vars](sort-vars.md) diff --git a/docs/rules/sort-keys.md b/docs/rules/sort-keys.md index 0bac3ede2d7c..43d3dc522eba 100644 --- a/docs/rules/sort-keys.md +++ b/docs/rules/sort-keys.md @@ -70,7 +70,23 @@ The 1st option is `"asc"` or `"desc"`. The 2nd option is an object which has 2 properties. * `caseSensitive` - if `true`, enforce properties to be in case-sensitive order. Default is `true`. -* `natural` - if `true`, enforce properties to be in natural order. Default is `false`. +* `natural` - if `true`, enforce properties to be in natural order. Default is `false`. Natural Order compares strings containing combination of letters and numbers in the way a human being would sort. It basically sorts numerically, instead of sorting alphabetically. So the number 10 comes after the number 3 in Natural Sorting. + +Example for a list: + +With `natural` as true, the ordering would be +1 +3 +6 +8 +10 + +With `natural` as false, the ordering would be +1 +10 +3 +6 +8 ### desc @@ -154,8 +170,8 @@ If you don't want to notify about properties' order, then it's safe to disable t ## Related Rules -* [sort-imports](http://eslint.org/docs/rules/sort-imports) -* [sort-vars](http://eslint.org/docs/rules/sort-vars) +* [sort-imports](sort-imports.md) +* [sort-vars](sort-vars.md) ## Compatibility diff --git a/docs/rules/sort-vars.md b/docs/rules/sort-vars.md index 01b4d4e440fd..8a201f358bfe 100644 --- a/docs/rules/sort-vars.md +++ b/docs/rules/sort-vars.md @@ -74,5 +74,5 @@ This rule is a formatting preference and not following it won't negatively affec ## Related Rules -* [sort-keys](http://eslint.org/docs/rules/sort-keys) -* [sort-imports](http://eslint.org/docs/rules/sort-imports) +* [sort-keys](sort-keys.md) +* [sort-imports](sort-imports.md) diff --git a/docs/rules/space-before-function-paren.md b/docs/rules/space-before-function-paren.md index f5332106005d..c9d0c39857da 100644 --- a/docs/rules/space-before-function-paren.md +++ b/docs/rules/space-before-function-paren.md @@ -33,7 +33,7 @@ This rule has a string option or an object option: "space-before-function-paren": ["error", { "anonymous": "always", "named": "always", - "asyncArrow": "ignore" + "asyncArrow": "always" }], } ``` @@ -44,13 +44,11 @@ This rule has a string option or an object option: The string option does not check async arrow function expressions for backward compatibility. You can also use a separate option for each type of function. -Each of the following options can be set to `"always"`, `"never"`, or `"ignore"`. -Default is `"always"` basically. +Each of the following options can be set to `"always"`, `"never"`, or `"ignore"`. The default is `"always"`. * `anonymous` is for anonymous function expressions (e.g. `function () {}`). * `named` is for named function expressions (e.g. `function foo () {}`). * `asyncArrow` is for async arrow function expressions (e.g. `async () => {}`). - `asyncArrow` is set to `"ignore"` by default for backwards compatibility. ### "always" @@ -83,6 +81,8 @@ var foo = { // ... } }; + +var foo = async() => 1 ``` Examples of **correct** code for this rule with the default `"always"` option: @@ -115,9 +115,7 @@ var foo = { } }; -// async arrow function expressions are ignored by default. var foo = async () => 1 -var foo = async() => 1 ``` ### "never" @@ -151,6 +149,8 @@ var foo = { // ... } }; + +var foo = async () => 1 ``` Examples of **correct** code for this rule with the `"never"` option: @@ -183,8 +183,6 @@ var foo = { } }; -// async arrow function expressions are ignored by default. -var foo = async () => 1 var foo = async() => 1 ``` diff --git a/docs/rules/space-before-keywords.md b/docs/rules/space-before-keywords.md index 9a0dd61c1353..8adf593a6c7e 100644 --- a/docs/rules/space-before-keywords.md +++ b/docs/rules/space-before-keywords.md @@ -28,7 +28,7 @@ must be preceded by at least one space. If `"never"` then no spaces will be allo the keywords `else`, `while` (do...while), `finally` and `catch`. The default value is `"always"`. This rule will allow keywords to be preceded by an opening curly brace (`{`). If you wish to alter -this behaviour, consider using the [block-spacing](block-spacing.md) rule. +this behavior, consider using the [block-spacing](block-spacing.md) rule. Examples of **incorrect** code for this rule with the default `"always"` option: diff --git a/docs/rules/space-unary-ops.md b/docs/rules/space-unary-ops.md index 8853d9239ad0..d04bd4a4561c 100644 --- a/docs/rules/space-unary-ops.md +++ b/docs/rules/space-unary-ops.md @@ -59,7 +59,7 @@ This rule has three options: In this case, spacing will be disallowed after a `new` operator and required before/after a `++` operator. -Examples of **incorrect** code for this rule with the `{"words": true, "nonwords": false}` option: +Examples of **incorrect** code for this rule with the default `{"words": true, "nonwords": false}` option: ```js /*eslint space-unary-ops: "error"*/ @@ -90,6 +90,14 @@ function *foo() { } ``` +```js +/*eslint space-unary-ops: "error"*/ + +async function foo() { + await(bar); +} +``` + Examples of **correct** code for this rule with the `{"words": true, "nonwords": false}` option: ```js @@ -125,3 +133,11 @@ function *foo() { yield (0) } ``` + +```js +/*eslint space-unary-ops: "error"*/ + +async function foo() { + await (bar); +} +``` diff --git a/docs/rules/strict.md b/docs/rules/strict.md index 03e2feea4c07..9df292b6e0de 100644 --- a/docs/rules/strict.md +++ b/docs/rules/strict.md @@ -42,13 +42,15 @@ In **ECMAScript** modules, which always have strict mode semantics, the directiv This rule requires or disallows strict mode directives. -This rule disallows strict mode directives, no matter which option is specified, if ESLint configuration specifies either of the following as [parser options](../user-guide/configuring#specifying-parser-options): +This rule disallows strict mode directives, no matter which option is specified, if ESLint configuration specifies either of the following as [parser options](/docs/user-guide/configuring.md#specifying-parser-options): * `"sourceType": "module"` that is, files are **ECMAScript** modules * `"impliedStrict": true` property in the `ecmaFeatures` object This rule disallows strict mode directives, no matter which option is specified, in functions with non-simple parameter lists (for example, parameter lists with default parameter values) because that is a syntax error in **ECMAScript 2016** and later. See the examples of the [function](#function) option. +The `--fix` option on the command line does not insert new `"use strict"` statements, but only removes unneeded statements. + ## Options This rule has a string option: @@ -64,10 +66,10 @@ This rule has a string option: The `"safe"` option corresponds to the `"global"` option if ESLint considers a file to be a **Node.js** or **CommonJS** module because the configuration specifies either of the following: -* `node` or `commonjs` [environments](../user-guide/configuring#specifying-environments) -* `"globalReturn": true` property in the `ecmaFeatures` object of [parser options](../user-guide/configuring#specifying-parser-options) +* `node` or `commonjs` [environments](/docs/user-guide/configuring.md#specifying-environments) +* `"globalReturn": true` property in the `ecmaFeatures` object of [parser options](/docs/user-guide/configuring.md#specifying-parser-options) -Otherwise the `"safe"` option corresponds to the `"function"` option. +Otherwise the `"safe"` option corresponds to the `"function"` option. Note that if `"globalReturn": false` is explicitly specified in the configuration, the `"safe"` option will correspond to the `"function"` option regardless of the specified environment. ### global @@ -267,4 +269,4 @@ function foo() { ## When Not To Use It -In a codebase that has both strict and non-strict code, either turn this rule off, or [selectively disable it](http://eslint.org/docs/user-guide/configuring) where necessary. For example, functions referencing `arguments.callee` are invalid in strict mode. A [full list of strict mode differences](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode/Transitioning_to_strict_mode#Differences_from_non-strict_to_strict) is available on MDN. +In a codebase that has both strict and non-strict code, either turn this rule off, or [selectively disable it](/docs/user-guide/configuring.md) where necessary. For example, functions referencing `arguments.callee` are invalid in strict mode. A [full list of strict mode differences](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode/Transitioning_to_strict_mode#Differences_from_non-strict_to_strict) is available on MDN. diff --git a/docs/rules/switch-colon-spacing.md b/docs/rules/switch-colon-spacing.md new file mode 100644 index 000000000000..baf081e4cb47 --- /dev/null +++ b/docs/rules/switch-colon-spacing.md @@ -0,0 +1,80 @@ +# Enforce spacing around colons of switch statements (switch-colon-spacing) + +Spacing around colons improves readability of `case`/`default` clauses. + +## Rule Details + +This rule controls spacing around colons of `case` and `default` clauses in `switch` statements. +This rule does the check only if the consecutive tokens exist on the same line. + +This rule has 2 options that are boolean value. + +```json +{ + "switch-colon-spacing": ["error", {"after": true, "before": false}] +} +``` + +- `"after": true` (Default) ... requires one or more spaces after colons. +- `"after": false` ... disallows spaces after colons. +- `"before": true` ... requires one or more spaces before colons. +- `"before": false` (Default) ... disallows before colons. + + +Examples of **incorrect** code for this rule: + +```js +/*eslint switch-colon-spacing: "error"*/ + +switch (a) { + case 0 :break; + default :foo(); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint switch-colon-spacing: "error"*/ + +switch (a) { + case 0: foo(); break; + case 1: + bar(); + break; + default: + baz(); + break; +} +``` + +Examples of **incorrect** code for this rule with `{"after": false, "before": true}` option: + +```js +/*eslint switch-colon-spacing: ["error", {"after": false, "before": true}]*/ + +switch (a) { + case 0: break; + default: foo(); +} +``` + +Examples of **correct** code for this rule with `{"after": false, "before": true}` option: + +```js +/*eslint switch-colon-spacing: ["error", {"after": false, "before": true}]*/ + +switch (a) { + case 0 :foo(); break; + case 1 : + bar(); + break; + default : + baz(); + break; +} +``` + +## When Not To Use It + +If you don't want to notify spacing around colons of switch statements, then it's safe to disable this rule. diff --git a/docs/rules/symbol-description.md b/docs/rules/symbol-description.md index bf2adc64d894..eb15d94ff472 100644 --- a/docs/rules/symbol-description.md +++ b/docs/rules/symbol-description.md @@ -58,4 +58,4 @@ In addition, this rule can be safely turned off if you don't want to enforce pre ## Further Reading -* [Symbol Objects specification: Symbol description](http://www.ecma-international.org/ecma-262/6.0/#sec-symbol-description) +* [Symbol Objects specification: Symbol description](https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-description) diff --git a/docs/rules/valid-typeof.md b/docs/rules/valid-typeof.md index 2ce02b4906b2..60539eecbce2 100644 --- a/docs/rules/valid-typeof.md +++ b/docs/rules/valid-typeof.md @@ -20,7 +20,7 @@ Examples of **incorrect** code for this rule: typeof foo === "strnig" typeof foo == "undefimed" typeof bar != "nunber" -typeof bar !== "fucntion" +typeof bar !== "function" ``` Examples of **correct** code for this rule: diff --git a/docs/rules/vars-on-top.md b/docs/rules/vars-on-top.md index 4f6766f8590f..78a8bee86b6c 100644 --- a/docs/rules/vars-on-top.md +++ b/docs/rules/vars-on-top.md @@ -2,7 +2,7 @@ The `vars-on-top` rule generates warnings when variable declarations are not used serially at the top of a function scope or the top of a program. By default variable declarations are always moved (“hoisted”) invisibly to the top of their containing scope by the JavaScript interpreter. -This rule forces the programmer to represent that behaviour by manually moving the variable declaration to the top of its containing scope. +This rule forces the programmer to represent that behavior by manually moving the variable declaration to the top of its containing scope. ## Rule Details diff --git a/docs/rules/yoda.md b/docs/rules/yoda.md index 98e5c70362bf..af052b96e40b 100644 --- a/docs/rules/yoda.md +++ b/docs/rules/yoda.md @@ -148,5 +148,5 @@ if (-1 < str.indexOf(substr)) { ## Further Reading -* [Yoda Conditions](http://en.wikipedia.org/wiki/Yoda_conditions) +* [Yoda Conditions](https://en.wikipedia.org/wiki/Yoda_conditions) * [Yoda Notation and Safe Switching](http://thomas.tuerke.net/on/design/?with=1249091668#msg1146181680) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index 50d33e149e0d..36e059a48c05 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -2,7 +2,7 @@ This guide is intended for those who wish to use ESLint as an end-user. If you're looking for how to extend ESLint or work with the ESLint source code, please see the [Developer Guide](../developer-guide). -## [Getting Started](getting-started) +## [Getting Started](getting-started.md) Want to skip ahead and just start using ESLint? This section gives a high-level overview of installation, setup, and configuration options. @@ -10,22 +10,27 @@ Want to skip ahead and just start using ESLint? This section gives a high-level ESLint has a lot of rules that you can configure to fine-tune it to your project. This section is an exhaustive list of every rule and link to each rule's documentation. -## [Configuring](configuring) +## [Configuring](configuring.md) Once you've got ESLint running, you'll probably want to adjust the configuration to better suit your project. This section explains all the different ways you can configure ESLint. -## [Command Line Interface](command-line-interface) +## [Command Line Interface](command-line-interface.md) There are a lot of command line flags for ESLint and this section explains what they do. -## [Integrations](integrations) +## [Integrations](integrations.md) Wondering if ESLint will work with your favorite editor or build system? This section has a list of all known integrations (submitted by their authors). -## [Rule Deprecation](rule-deprecation) +## [Rule Deprecation](rule-deprecation.md) The ESLint team is committed to making upgrading as easy and painless as possible. This section outlines the guidelines the team has set in place for the deprecation of rules in future releases. -## [Migrating to 1.0.0](migrating-to-1.0.0) +## Migrating -If you were using a version of ESLint prior to v1.0.0, this section helps you with the transition. +If you were using a prior version of ESLint, you can get help with the transition by reading: + +- [migrating-to-1.0.0](migrating-to-1.0.0.md) +- [migrating-to-2.0.0](migrating-to-2.0.0.md) +- [migrating-to-3.0.0](migrating-to-3.0.0.md) +- [migrating-to-4.0.0](migrating-to-4.0.0.md) diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md index 8e8b6206e62a..39a297c79c71 100644 --- a/docs/user-guide/command-line-interface.md +++ b/docs/user-guide/command-line-interface.md @@ -30,50 +30,56 @@ The command line utility has several options. You can view the options by runnin eslint [options] file.js [file.js] [dir] Basic configuration: - -c, --config path::String Use configuration from this file or shareable config - --no-eslintrc Disable use of configuration from .eslintrc - --env [String] Specify environments - --ext [String] Specify JavaScript file extensions - default: .js - --global [String] Define global variables - --parser String Specify the parser to be used - --parser-options Object Specify parser options - -Caching: - --cache Only check changed files - default: false - --cache-file path::String Path to the cache file. Deprecated: use --cache-location - default: .eslintcache - --cache-location path::String Path to the cache file or directory + --no-eslintrc Disable use of configuration from .eslintrc.* + -c, --config path::String Use this configuration, overriding .eslintrc.* config options if present + --env [String] Specify environments + --ext [String] Specify JavaScript file extensions - default: .js + --global [String] Define global variables + --parser String Specify the parser to be used + --parser-options Object Specify parser options Specifying rules and plugins: - --rulesdir [path::String] Use additional rules from this directory - --plugin [String] Specify plugins - --rule Object Specify rules + --rulesdir [path::String] Use additional rules from this directory + --plugin [String] Specify plugins + --rule Object Specify rules + +Fixing problems: + --fix Automatically fix problems + --fix-dry-run Automatically fix problems without saving the changes to the file system Ignoring files: - --ignore-path path::String Specify path of ignore file - --no-ignore Disable use of ignore files and patterns - --ignore-pattern [String] Pattern of files to ignore (in addition to those in .eslintignore) + --ignore-path path::String Specify path of ignore file + --no-ignore Disable use of ignore files and patterns + --ignore-pattern [String] Pattern of files to ignore (in addition to those in .eslintignore) Using stdin: - --stdin Lint code provided on - default: false - --stdin-filename String Specify filename to process STDIN as + --stdin Lint code provided on - default: false + --stdin-filename String Specify filename to process STDIN as Handling warnings: - --quiet Report errors only - default: false - --max-warnings Int Number of warnings to trigger nonzero exit code - default: -1 + --quiet Report errors only - default: false + --max-warnings Int Number of warnings to trigger nonzero exit code - default: -1 Output: -o, --output-file path::String Specify file to write report to - -f, --format String Use a specific output format - default: stylish - --color, --no-color Force enabling/disabling of color + -f, --format String Use a specific output format - default: stylish + --color, --no-color Force enabling/disabling of color + +Inline configuration comments: + --no-inline-config Prevent comments from changing config or rules + --report-unused-disable-directives Adds reported errors for unused eslint-disable directives + +Caching: + --cache Only check changed files - default: false + --cache-file path::String Path to the cache file. Deprecated: use --cache-location - default: .eslintcache + --cache-location path::String Path to the cache file or directory Miscellaneous: - --init Run config initialization wizard - default: false - --fix Automatically fix problems - --debug Output debugging information - -h, --help Show help - -v, --version Output the version number - --no-inline-config Prevent comments from changing config or rules - --print-config path::String Print the configuration for the given file + --init Run config initialization wizard - default: false + --debug Output debugging information + -h, --help Show help + -v, --version Output the version number + --print-config path::String Print the configuration for the given file ``` Options that accept array values can be specified by repeating the option or with a comma-delimited list (other than `--ignore-pattern` which does not allow the second style). @@ -86,6 +92,14 @@ Example: ### Basic configuration +#### `--no-eslintrc` + +Disables use of configuration from `.eslintrc.*` and `package.json` files. + +Example: + + eslint --no-eslintrc file.js + #### `-c`, `--config` This option allows you to specify an additional configuration file for ESLint (see [Configuring ESLint](configuring) for more). @@ -104,17 +118,11 @@ Example: This example directly uses the sharable config `eslint-config-myconfig`. -#### `--no-eslintrc` - -Disables use of configuration from `.eslintrc` and `package.json` files. - -Example: - - eslint --no-eslintrc file.js +If `.eslintrc.*` and/or `package.json` files are also used for configuration (i.e., `--no-eslintrc` was not specified), the configurations will be merged. Options from this configuration file have precedence over the options from `.eslintrc.*` and `package.json` files. #### `--env` -This option enables specific environments. Details about the global variables defined by each environment are available on the [configuration](configuring) documentation. This option only enables environments; it does not disable environments set in other configuration files. To specify multiple environments, separate them using commas, or use the option multiple times. +This option enables specific environments. Details about the global variables defined by each environment are available on the [configuration](configuring.md) documentation. This option only enables environments; it does not disable environments set in other configuration files. To specify multiple environments, separate them using commas, or use the option multiple times. Examples: @@ -152,11 +160,11 @@ Examples: #### `--parser` -This option allows you to specify a parser to be used by eslint. By default, `espree` will be used. +This option allows you to specify a parser to be used by ESLint. By default, `espree` will be used. #### `--parser-options` -This option allows you to specify parser options to be used by eslint. Note that the available parser options are determined by the parser being used. +This option allows you to specify parser options to be used by ESLint. Note that the available parser options are determined by the parser being used. Examples: @@ -308,18 +316,18 @@ When specified, the given format is output into the provided file name. This option specifies the output format for the console. Possible formats are: -* [checkstyle](formatters/#checkstyle) -* [codeframe](formatters/#codeframe) -* [compact](formatters/#compact) -* [html](formatters/#html) -* [jslint-xml](formatters/#jslint-xml) -* [json](formatters/#json) -* [junit](formatters/#junit) -* [stylish](formatters/#stylish) (the default) -* [table](formatters/#table) -* [tap](formatters/#tap) -* [unix](formatters/#unix) -* [visualstudio](formatters/#visualstudio) +* [checkstyle](formatters.md/#checkstyle) +* [codeframe](formatters.md/#codeframe) +* [compact](formatters.md/#compact) +* [html](formatters.md/#html) +* [jslint-xml](formatters.md/#jslint-xml) +* [json](formatters.md/#json) +* [junit](formatters.md/#junit) +* [stylish](formatters.md/#stylish) (the default) +* [table](formatters.md/#table) +* [tap](formatters.md/#tap) +* [unix](formatters.md/#unix) +* [visualstudio](formatters.md/#visualstudio) Example: @@ -331,6 +339,17 @@ Example: eslint -f ./customformat.js file.js +An npm-installed formatter is resolved with or without `eslint-formatter-` prefix. + +Example: + + npm install eslint-formatter-pretty + + eslint -f pretty file.js + + // equivalent: + eslint -f eslint-formatter-pretty file.js + When specified, the given format is output to the console. If you'd like to save that output into a file, you can do so on the command line like so: eslint -f compact file.js > results.txt @@ -359,7 +378,21 @@ The resulting configuration file will be created in the current directory. This option instructs ESLint to try to fix as many issues as possible. The fixes are made to the actual files themselves and only the remaining unfixed issues are output. Not all problems are fixable using this option, and the option does not work in these situations: 1. This option throws an error when code is piped to ESLint. -1. This option has no effect on code that uses processors. +1. This option has no effect on code that uses a processor, unless the processor opts into allowing autofixes. + +If you want to fix code from `stdin` or otherwise want to get the fixes without actually writing them to the file, use the [`--fix-dry-run`](#--fix-dry-run) option. + +#### `--fix-dry-run` + +This option has the same effect as `--fix` with one difference: the fixes are not saved to the file system. This makes it possible to fix code from `stdin` (when used with the `--stdin` flag). + +Because the default formatter does not output the fixed code, you'll have to use another one (e.g. `json`) to get the fixes. Here's an example of this pattern: + +``` +getSomeText | eslint --stdin --fix-dry-run --format=json +``` + +This flag can be useful for integrations (e.g. editor plugins) which need to autofix text from the command line without saving it to the filesystem. #### `--debug` @@ -391,6 +424,16 @@ Example: eslint --no-inline-config file.js +#### `--report-unused-disable-directives` + +This option causes ESLint to report directive comments like `// eslint-disable-line` when no errors would have been reported on that line anyway. This can be useful to prevent future errors from unexpectedly being suppressed, by cleaning up old `eslint-disable` comments which are no longer applicable. + +**Warning**: When using this option, it is possible that new errors will start being reported whenever ESLint or custom rules are upgraded. For example, suppose a rule has a bug that causes it to report a false positive, and an `eslint-disable` comment is added to suppress the incorrect report. If the bug is then fixed in a patch release of ESLint, the `eslint-disable` comment will become unused since ESLint is no longer generating an incorrect report. This will result in a new reported error for the unused directive if the `report-unused-disable-directives` option is used. + +Example: + + eslint --report-unused-disable-directives file.js + #### `--print-config` This option outputs the configuration to be used for the file passed. When present, no linting is performed and only config-related options are valid. @@ -406,4 +449,4 @@ ESLint supports `.eslintignore` files to exclude files from the linting process node_modules/* **/vendor/*.js -A more detailed breakdown of supported patterns and directories ESLint ignores by default can be found in [Configuring ESLint](http://eslint.org/docs/user-guide/configuring#ignoring-files-and-directories). +A more detailed breakdown of supported patterns and directories ESLint ignores by default can be found in [Configuring ESLint](configuring.md#ignoring-files-and-directories). diff --git a/docs/user-guide/configuring.md b/docs/user-guide/configuring.md index d425d74674dc..49d6f8149a11 100644 --- a/docs/user-guide/configuring.md +++ b/docs/user-guide/configuring.md @@ -3,7 +3,9 @@ ESLint is designed to be completely configurable, meaning you can turn off every rule and run only with basic syntax validation, or mix and match the bundled rules and your custom rules to make ESLint perfect for your project. There are two primary ways to configure ESLint: 1. **Configuration Comments** - use JavaScript comments to embed configuration information directly into a file. -1. **Configuration Files** - use a JavaScript, JSON or YAML file to specify configuration information for an entire directory and all of its subdirectories. This can be in the form of an [.eslintrc.*](#configuration-file-formats) file or an `eslintConfig` field in a [`package.json`](https://docs.npmjs.com/files/package.json) file, both of which ESLint will look for and read automatically, or you can specify a configuration file on the [command line](command-line-interface). +1. **Configuration Files** - use a JavaScript, JSON or YAML file to specify configuration information for an entire directory (other than your home directory) and all of its subdirectories. This can be in the form of an [`.eslintrc.*`](#configuration-file-formats) file or an `eslintConfig` field in a [`package.json`](https://docs.npmjs.com/files/package.json) file, both of which ESLint will look for and read automatically, or you can specify a configuration file on the [command line](command-line-interface). + + If you have a configuration file in your home directory (generally `~/`), ESLint uses it **only** if ESLint cannot find any other configuration file. There are several pieces of information that can be configured: @@ -18,16 +20,18 @@ All of these options give you fine-grained control over how ESLint treats your c ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions as well as JSX by using parser options. Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) if you are using React and want React semantics. - +By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as +`Set`). +For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env": +{ "es6": true } }` (this setting enables ES6 syntax automatically). Parser options are set in your `.eslintrc.*` file by using the `parserOptions` property. The available options are: -* `ecmaVersion` - set to 3, 5 (default), 6, 7, or 8 to specify the version of ECMAScript syntax you want to use. You can also set to 2015 (same as 6), 2016 (same as 7), or 2017 (same as 8) to use the year-based naming. +* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, or 9 to specify the version of ECMAScript syntax you want to use. You can also set to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), or 2018 (same as 9) to use the year-based naming. * `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules. * `ecmaFeatures` - an object indicating which additional language features you'd like to use: * `globalReturn` - allow `return` statements in the global scope * `impliedStrict` - enable global [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) (if `ecmaVersion` is 5 or greater) - * `jsx` - enable [JSX](http://facebook.github.io/jsx/) - * `experimentalObjectRestSpread` - enable support for the experimental [object rest/spread properties](https://github.com/sebmarkbage/ecmascript-rest-spread) (**IMPORTANT:** This is an experimental feature that may change significantly in the future. It's recommended that you do *not* write rules relying on this functionality unless you are willing to incur maintenance cost when it changes.) + * `jsx` - enable [JSX](https://facebook.github.io/jsx/) Here's an example `.eslintrc.json` file: @@ -48,6 +52,10 @@ Here's an example `.eslintrc.json` file: Setting parser options helps ESLint determine what is a parsing error. All language options are `false` by default. +### Deprecated + +* `ecmaFeatures.experimentalObjectRestSpread` - enable support for the experimental [object rest/spread properties](https://github.com/tc39/proposal-object-rest-spread). This syntax has been supported in `ecmaVersion: 2018`. This option will be removed in the future. + ## Specifying Parser By default, ESLint uses [Espree](https://github.com/eslint/espree) as its parser. You can optionally specify that a different parser should be used in your configuration file so long as the parser meets the following requirements: @@ -71,8 +79,9 @@ To indicate the npm module to use as your parser, specify it using the `parser` The following parsers are compatible with ESLint: -* [Esprima](https://npmjs.com/package/esprima) -* [Babel-ESLint](https://npmjs.com/package/babel-eslint) - A wrapper around the [Babel](http://babeljs.io) parser that makes it compatible with ESLint. +* [Esprima](https://www.npmjs.com/package/esprima) +* [Babel-ESLint](https://www.npmjs.com/package/babel-eslint) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint. +* [typescript-eslint-parser(Experimental)](https://www.npmjs.com/package/typescript-eslint-parser) - A parser that converts TypeScript into an ESTree-compatible form so it can be used in ESLint. The goal is to allow TypeScript files to be parsed by ESLint (though not necessarily pass all ESLint rules). Note when using a custom parser, the `parserOptions` configuration property is still required for ESLint to work properly with features not in ECMAScript 5 by default. Parsers are all passed `parserOptions` and may or may not use them to determine which features to enable. @@ -83,7 +92,7 @@ An environment defines global variables that are predefined. The available envir * `browser` - browser global variables. * `node` - Node.js global variables and Node.js scoping. * `commonjs` - CommonJS global variables and CommonJS scoping (use this for browser-only code that uses Browserify/WebPack). -* `shared-node-browser` - Globals common to both Node and Browser. +* `shared-node-browser` - Globals common to both Node.js and Browser. * `es6` - enable all ECMAScript 6 features except for modules (this automatically sets the `ecmaVersion` parser option to 6). * `worker` - web workers global variables. * `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/wiki/AMD) spec. @@ -253,7 +262,7 @@ And in YAML: - eslint-plugin-plugin2 ``` -**Note:** A globally-installed instance of ESLint can only use globally-installed ESLint plugins. A locally-installed ESLint can make use of both locally- and globally- installed ESLint plugins. +**Note:** Due to the behavior of Node's `require` function, a globally-installed instance of ESLint can only use globally-installed ESLint plugins, and locally-installed version can only use *locally-installed* plugins. Mixing local and global plugins is not supported. ## Configuring Rules @@ -388,13 +397,18 @@ You can also disable or enable specific rules for an entire file: alert('foo'); ``` -To disable all rules on a specific line, use a line comment in one of the following formats: +To disable all rules on a specific line, use a line or block comment in one of the following formats: ```js alert('foo'); // eslint-disable-line // eslint-disable-next-line alert('foo'); + +/* eslint-disable-next-line */ +alert('foo'); + +alert('foo'); /* eslint-disable-line */ ``` To disable a specific rule on a specific line: @@ -404,6 +418,11 @@ alert('foo'); // eslint-disable-line no-alert // eslint-disable-next-line no-alert alert('foo'); + +alert('foo'); /* eslint-disable-line no-alert */ + +/* eslint-disable-next-line no-alert */ +alert('foo'); ``` To disable multiple rules on a specific line: @@ -413,12 +432,18 @@ alert('foo'); // eslint-disable-line no-alert, quotes, semi // eslint-disable-next-line no-alert, quotes, semi alert('foo'); + +alert('foo'); /* eslint-disable-line no-alert, quotes, semi */ + +/* eslint-disable-next-line no-alert, quotes, semi */ +alert('foo'); ``` All of the above methods also work for plugin rules. For example, to disable `eslint-plugin-example`'s `rule-name` rule, combine the plugin's name (`example`) and the rule's name (`rule-name`) into `example/rule-name`: ```js foo(); // eslint-disable-line example/rule-name +foo(); /* eslint-disable-line example/rule-name */ ``` **Note:** Comments that disable warnings for a portion of a file tell ESLint not to report rule violations for the disabled code. ESLint still parses the entire file, however, so disabled code still needs to be syntactically valid JavaScript. @@ -447,11 +472,15 @@ And in YAML: ## Using Configuration Files -There are two ways to use configuration files. The first is to save the file wherever you would like and pass its location to the CLI using the `-c` option, such as: +There are two ways to use configuration files. + +The first way to use configuration files is via `.eslintrc.*` and `package.json` files. ESLint will automatically look for them in the directory of the file to be linted, and in successive parent directories all the way up to the root directory of the filesystem (unless `root: true` is specified). This option is useful when you want different configurations for different parts of a project or when you want others to be able to use ESLint directly without needing to remember to pass in the configuration file. + +The second is to save the file wherever you would like and pass its location to the CLI using the `-c` option, such as: eslint -c myconfig.json myfiletotest.js -The second way to use configuration files is via `.eslintrc.*` and `package.json` files. ESLint will automatically look for them in the directory of the file to be linted, and in successive parent directories all the way up to the root directory of the filesystem. This option is useful when you want different configurations for different parts of a project or when you want others to be able to use ESLint directly without needing to remember to pass in the configuration file. +If you are using one configuration file and want ESLint to ignore any `.eslintrc.*` files, make sure to use `--no-eslintrc` along with the `-c` flag. In each case, the settings in the configuration file override default settings. @@ -522,7 +551,7 @@ And in YAML: root: true ``` -For example, consider `projectA` which has `"root": true` set in the `.eslintrc` file in the main project directory. In this case, while linting `main.js`, the configurations within `lib/`will be used, but the `.eslintrc` file in `projectA/` will not. +For example, consider `projectA` which has `"root": true` set in the `.eslintrc` file in the `lib/` directory. In this case, while linting `main.js`, the configurations within `lib/` will be used, but the `.eslintrc` file in `projectA/` will not. ```text home @@ -542,15 +571,15 @@ The complete configuration hierarchy, from highest precedence to lowest preceden 1. `/*global*/` 1. `/*eslint*/` 1. `/*eslint-env*/` -2. Command line options: +1. Command line options (or CLIEngine equivalents): 1. `--global` 1. `--rule` 1. `--env` 1. `-c`, `--config` -3. Project-level configuration: +1. Project-level configuration: 1. `.eslintrc.*` or `package.json` file in same directory as linted file 1. Continue searching for `.eslintrc` and `package.json` files in ancestor directories (parent has highest precedence, then grandparent, etc.), up to and including the root directory or until a config with `"root": true` is found. - 1. In the absence of any configuration from (1) thru (3), fall back to a personal default configuration in `~/.eslintrc`. +1. In the absence of any configuration from (1) thru (3), fall back to a personal default configuration in `~/.eslintrc`. ## Extending Configuration Files @@ -709,6 +738,61 @@ module.exports = { } ``` +## Configuration Based on Glob Patterns + +Sometimes a more fine-controlled configuration is necessary, for example if the configuration for files within the same directory has to be different. Therefore you can provide configurations under the `overrides` key that will only apply to files that match specific glob patterns, using the same format you would pass on the command line (e.g., `app/**/*.test.js`). + +### How it works + +* Glob pattern overrides can only be configured within config files (`.eslintrc.*` or `package.json`). +* The patterns are applied against the file path relative to the directory of the config file. For example, if your config file has the path `/Users/john/workspace/any-project/.eslintrc.js` and the file you want to lint has the path `/Users/john/workspace/any-project/lib/util.js`, then the pattern provided in `.eslintrc.js` will be executed against the relative path `lib/util.js`. +* Glob pattern overrides have higher precedence than the regular configuration in the same config file. Multiple overrides within the same config are applied in order. That is, the last override block in a config file always has the highest precedence. +* A glob specific configuration works almost the same as any other ESLint config. Override blocks can contain any configuration options that are valid in a regular config, with the exception of `extends`, `overrides`, and `root`. +* Multiple glob patterns can be provided within a single override block. A file must match at least one of the supplied patterns for the configuration to apply. +* Override blocks can also specify patterns to exclude from matches. If a file matches any of the excluded patterns, the configuration won't apply. + +### Relative glob patterns + +``` +project-root +├── app +│ ├── lib +│ │ ├── foo.js +│ │ ├── fooSpec.js +│ ├── components +│ │ ├── bar.js +│ │ ├── barSpec.js +│ ├── .eslintrc.json +├── server +│ ├── server.js +│ ├── serverSpec.js +├── .eslintrc.json +``` + +The config in `app/.eslintrc.json` defines the glob pattern `**/*Spec.js`. This pattern is relative to the base directory of `app/.eslintrc.json`. So, this pattern would match `app/lib/fooSpec.js` and `app/components/barSpec.js` but **NOT** `server/serverSpec.js`. If you defined the same pattern in the `.eslintrc.json` file within in the `project-root` folder, it would match all three of the `*Spec` files. + +### Example configuration + +In your `.eslintrc.json`: + +```json +{ + "rules": { + "quotes": [ 2, "double" ] + }, + + "overrides": [ + { + "files": [ "bin/*.js", "lib/*.js" ], + "excludedFiles": "*.test.js", + "rules": { + "quotes": [ 2, "single" ] + } + } + ] +} +``` + ## Comments in Configuration Files Both the JSON and YAML configuration file formats support comments (`package.json` files should not include them). You can use JavaScript-style comments or YAML-style comments in either type of file and ESLint will safely ignore them. This allows your configuration files to be more human-friendly. For example: @@ -744,12 +828,12 @@ Globs are matched using [node-ignore](https://github.com/kaelzhang/node-ignore), * Lines beginning with `#` are treated as comments and do not affect ignore patterns. * Paths are relative to `.eslintignore` location or the current working directory. This also influences paths passed via `--ignore-pattern`. -* Ignore patterns behave according to the `.gitignore` [specification](http://git-scm.com/docs/gitignore) +* Ignore patterns behave according to the `.gitignore` [specification](https://git-scm.com/docs/gitignore) * Lines preceded by `!` are negated patterns that re-include a pattern that was ignored by an earlier pattern. In addition to any patterns in a `.eslintignore` file, ESLint always ignores files in `/node_modules/*` and `/bower_components/*`. -For example, placing the following `.eslintignore` file in the current working directory will ignore all of `node_modules`, `bower_components`, any files with the extensions `.ts.js` or `.coffee.js` extension that might have been transpiled, and anything in the `build/` directory except `build/index.js`: +For example, placing the following `.eslintignore` file in the current working directory will ignore all of `node_modules`, `bower_components` and anything in the `build/` directory except `build/index.js`: ```text # /node_modules/* and /bower_components/* ignored by default @@ -771,6 +855,22 @@ You can also use your `.gitignore` file: Any file that follows the standard ignore file format can be used. Keep in mind that specifying `--ignore-path` means that any existing `.eslintignore` file will not be used. Note that globbing rules in `.eslintignore` follow those of `.gitignore`. +### Using eslintIgnore in package.json + +If an `.eslintignore` file is not found and an alternate file is not specified, ESLint will look in package.json for an `eslintIgnore` key to check for files to ignore. + + { + "name": "mypackage", + "version": "0.0.1", + "eslintConfig": { + "env": { + "browser": true, + "node": true + } + }, + "eslintIgnore": ["hello.js", "world.js"] + } + ### Ignored File Warnings When you pass directories to ESLint, files and directories are silently ignored. If you pass a specific file to ESLint, then you will see a warning indicating that the file was skipped. For example, suppose you have an `.eslintignore` file that looks like this: diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index c6682b722a97..22dcd497fdab 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -13,6 +13,8 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J ## Installation and Usage +Prerequisites: [Node.js](https://nodejs.org/en/) (>=4.x), npm version 2+. + There are two ways to install ESLint: globally and locally. ### Local Installation and Usage @@ -29,7 +31,7 @@ You should then setup a configuration file: $ ./node_modules/.bin/eslint --init ``` -After that, you can run ESLint on any file or directory like this: +After that, you can run ESLint in your project's root directory like this: ``` $ ./node_modules/.bin/eslint yourfile.js @@ -63,7 +65,7 @@ Any plugins or shareable configs that you use must also be installed globally to ## Configuration -**Note:** If you are coming from a version before 1.0.0 please see the [migration guide](http://eslint.org/docs/user-guide/migrating-to-1.0.0). +**Note:** If you are coming from a version before 1.0.0 please see the [migration guide](migrating-to-1.0.0.md). After running `eslint --init`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this: @@ -76,13 +78,13 @@ After running `eslint --init`, you'll have a `.eslintrc` file in your directory. } ``` -The names `"semi"` and `"quotes"` are the names of [rules](http://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values: +The names `"semi"` and `"quotes"` are the names of [rules](/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values: * `"off"` or `0` - turn the rule off * `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) * `"error"` or `2` - turn the rule on as an error (exit code will be 1) -The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](http://eslint.org/docs/user-guide/configuring)). +The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](configuring.md)). Your `.eslintrc` configuration file will also include the line: @@ -90,14 +92,14 @@ Your `.eslintrc` configuration file will also include the line: "extends": "eslint:recommended" ``` -Because of this line, all of the rules marked "(recommended)" on the [rules page](http://eslint.org/docs/rules) will be turned on. Alternatively, you can use configurations that others have created by searching for "eslint-config" on [npmjs.com](https://www.npmjs.com/search?q=eslint-config). ESLint will not lint your code unless you extend from a shared configuration or explicitly turn rules on in your configuration. +Because of this line, all of the rules marked "(recommended)" on the [rules page](/docs/rules) will be turned on. Alternatively, you can use configurations that others have created by searching for "eslint-config" on [npmjs.com](https://www.npmjs.com/search?q=eslint-config). ESLint will not lint your code unless you extend from a shared configuration or explicitly turn rules on in your configuration. --- ## Next Steps -* Learn about [advanced configuration](http://eslint.org/docs/user-guide/configuring) of ESLint. -* Get familiar with the [command line options](/docs/user-guide/command-line-interface). -* Explore [ESLint integrations](http://eslint.org/docs/user-guide/integrations) into other tools like editors, build systems, and more. -* Can't find just the right rule? Make your own [custom rule](http://eslint.org/docs/developer-guide/working-with-rules). -* Make ESLint even better by [contributing](http://eslint.org/docs/developer-guide/contributing). +* Learn about [advanced configuration](configuring.md) of ESLint. +* Get familiar with the [command line options](command-line-interface.md). +* Explore [ESLint integrations](integrations.md) into other tools like editors, build systems, and more. +* Can't find just the right rule? Make your own [custom rule](/docs/developer-guide/working-with-rules.md). +* Make ESLint even better by [contributing](/docs/developer-guide/contributing/). diff --git a/docs/user-guide/integrations.md b/docs/user-guide/integrations.md index eda146464a21..1179fa9608ae 100644 --- a/docs/user-guide/integrations.md +++ b/docs/user-guide/integrations.md @@ -5,24 +5,24 @@ * Sublime Text 3: * [SublimeLinter-eslint](https://github.com/roadhump/SublimeLinter-eslint) * [Build Next](https://github.com/albertosantini/sublimetext-buildnext) -* [Vim](https://github.com/scrooloose/syntastic/tree/master/syntax_checkers/javascript) +* [Vim](https://github.com/vim-syntastic/syntastic/tree/master/syntax_checkers/javascript) * Emacs: [Flycheck](http://www.flycheck.org/) supports ESLint with the [javascript-eslint](http://www.flycheck.org/en/latest/languages.html#javascript) checker. -* Eclipse Orion: ESLint is the [default linter](http://dev.eclipse.org/mhonarc/lists/orion-dev/msg02718.html) +* Eclipse Orion: ESLint is the [default linter](https://dev.eclipse.org/mhonarc/lists/orion-dev/msg02718.html) * Eclipse IDE with [Tern ESLint linter](https://github.com/angelozerr/tern.java/wiki/Tern-Linter-ESLint) * [TextMate 2](https://github.com/natesilva/javascript-eslint.tmbundle) * Atom: [linter-eslint](https://atom.io/packages/linter-eslint) -* [IntelliJ IDEA, RubyMine, WebStorm, PhpStorm, PyCharm, AppCode, Android Studio, 0xDBE](http://plugins.jetbrains.com/plugin/7494) +* [IntelliJ IDEA, RubyMine, WebStorm, PhpStorm, PyCharm, AppCode, Android Studio, 0xDBE](https://plugins.jetbrains.com/plugin/7494-eslint) * [Visual Studio Code](https://code.visualstudio.com) with the [ESLint Extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) -## Build Systems +## Build tools -* Grunt: [grunt-eslint](https://npmjs.org/package/grunt-eslint) -* Gulp: [gulp-eslint](https://npmjs.org/package/gulp-eslint) -* Mimosa: [mimosa-eslint](https://npmjs.org/package/mimosa-eslint) -* Broccoli: [broccoli-eslint](https://www.npmjs.org/package/broccoli-eslint) +* Grunt: [grunt-eslint](https://www.npmjs.com/package/grunt-eslint) +* Gulp: [gulp-eslint](https://www.npmjs.com/package/gulp-eslint) +* Mimosa: [mimosa-eslint](https://www.npmjs.com/package/mimosa-eslint) +* Broccoli: [broccoli-eslint](https://www.npmjs.com/package/broccoli-eslint) * Browserify: [eslintify](https://www.npmjs.com/package/eslintify) -* Webpack: [eslint-loader](https://www.npmjs.org/package/eslint-loader) -* Rollup: [rollup-plugin-eslint](https://www.npmjs.org/package/rollup-plugin-eslint) +* Webpack: [eslint-loader](https://www.npmjs.com/package/eslint-loader) +* Rollup: [rollup-plugin-eslint](https://www.npmjs.com/package/rollup-plugin-eslint) * Ember-cli: [ember-cli-eslint](https://www.npmjs.com/package/ember-cli-eslint) * Sails.js: [sails-hook-lint](https://www.npmjs.com/package/sails-hook-lint), [sails-eslint](https://www.npmjs.com/package/sails-eslint) * Start: [start-eslint](https://www.npmjs.com/package/start-eslint) @@ -30,13 +30,13 @@ ## Command Line Tools -* [Eslint Watch](https://www.npmjs.com/package/eslint-watch) +* [ESLint Watch](https://www.npmjs.com/package/eslint-watch) * [Code Climate CLI](https://github.com/codeclimate/codeclimate) * [ESLint Nibble](https://github.com/IanVS/eslint-nibble) ## Source Control -* [Git Precommit Hook](https://coderwall.com/p/zq8jlq) +* [Git Precommit Hook](https://coderwall.com/p/zq8jlq/eslint-pre-commit-hook) * [Git pre-commit hook that only lints staged changes](https://gist.github.com/dahjelle/8ddedf0aebd488208a9a7c829f19b9e8) * [overcommit Git hook manager](https://github.com/brigade/overcommit) diff --git a/docs/user-guide/migrating-from-jscs.md b/docs/user-guide/migrating-from-jscs.md index 12a68237c3cc..c5ac5f235233 100644 --- a/docs/user-guide/migrating-from-jscs.md +++ b/docs/user-guide/migrating-from-jscs.md @@ -1,13 +1,13 @@ # Migrating from JSCS -In April 2016, we [announced](http://eslint.org/blog/2016/04/welcoming-jscs-to-eslint) that the JSCS project was shutting down and the JSCS team would be joining the ESLint team. This guide is intended to help those who are using JSCS to migrate their settings and projects to use ESLint. We've tried to automate as much of the conversion as possible, but there are some manual changes that are needed. +In April 2016, we [announced](https://eslint.org/blog/2016/04/welcoming-jscs-to-eslint) that the JSCS project was shutting down and the JSCS team would be joining the ESLint team. This guide is intended to help those who are using JSCS to migrate their settings and projects to use ESLint. We've tried to automate as much of the conversion as possible, but there are some manual changes that are needed. ## Terminology Before beginning the process of migrating to ESLint, it's helpful to understand some of the terminology that ESLint uses and how it relates to terminology that JSCS uses. * **Configuration File** - In JSCS, the configuration file is `.jscsrc`, `.jscsrc.json`, `.jscsrc.yaml`, or `.jscsrs.js`. In ESLint, the configuration file can be `.eslintrc.json`, `.eslintrc.yml`, `.eslintrc.yaml`, or `.eslintrc.js` (there is also a deprecated `.eslintrc` file format). -* **Presets** - In JSCS, there were numerous predefined configurations shipped directly within JSCS. ESLint ships with just one predefined configuration (`eslint:recommended`) that has no style rules enabled. However, ESLint does support [shareable configs](http://eslint.org/docs/developer-guide/shareable-configs). Shareable configs are configurations that are published on their own to npm and there are shareable configs available for almost all of the JSCS presets (see the "Converting Presets" section below). Additionally, the "preset" option in a configuration file is the equivalent of the ESLint "extends" option. +* **Presets** - In JSCS, there were numerous predefined configurations shipped directly within JSCS. ESLint ships with just one predefined configuration (`eslint:recommended`) that has no style rules enabled. However, ESLint does support [shareable configs](https://eslint.org/docs/developer-guide/shareable-configs). Shareable configs are configurations that are published on their own to npm and there are shareable configs available for almost all of the JSCS presets (see the "Converting Presets" section below). Additionally, the "preset" option in a configuration file is the equivalent of the ESLint "extends" option. ## Convert Configuration Files Using Polyjuice @@ -61,8 +61,8 @@ There are shareable configs available for most JSCS presets. The equivalent shar | `jquery` | [`eslint-config-jquery`](https://github.com/jquery/eslint-config-jquery) | | `mdcs` | [`eslint-config-mdcs`](https://github.com/zz85/mrdoobapproves) | | `node-style-guide` | [`eslint-config-node-style-guide`](https://github.com/pdehaan/eslint-config-node-style-guide) | -| `wikimedia` | [`eslint-config-wikimedia`](https://github.com/markelog/eslint-config-wikimedia) | -| `wordpress` | [`eslint-config-wordpress`](https://github.com/ntwb/eslint-config-wordpress) | +| `wikimedia` | [`eslint-config-wikimedia`](https://github.com/wikimedia/eslint-config-wikimedia) | +| `wordpress` | [`eslint-config-wordpress`](https://github.com/WordPress-Coding-Standards/eslint-config-wordpress) | As an example, suppose that you are using the `airbnb` preset, so your `.jscsrc` file looks like this: @@ -100,7 +100,7 @@ Both JSCS and ESLint use comments inside of files to disable rules around certai | Enable one rule | `// jscs:enable ruleName` or `/* jscs:enable ruleName */` | `/* eslint-enable rule-name */` | | Disable multiple rules | `// jscs:disable ruleName1, ruleName2` or `/* jscs:disable ruleName1, ruleName2 */` | `/* eslint-disable rule-name1, rule-name2 */` | | Enable multiple rules | `// jscs:enable ruleName1, ruleName2` or `/* jscs:enable ruleName1, ruleName2 */` | `/* eslint-enable rule-name1, rule-name2 */` | -| Disable one rule on single line | `// jscs:ignore ruleName` | `/* eslint-disable-line rule-name */` | +| Disable one rule on single line | `// jscs:ignore ruleName` | `// eslint-disable-line rule-name` | ## Command Line Options diff --git a/docs/user-guide/migrating-to-1.0.0.md b/docs/user-guide/migrating-to-1.0.0.md index 47fdf2a823ec..637e3059e6aa 100644 --- a/docs/user-guide/migrating-to-1.0.0.md +++ b/docs/user-guide/migrating-to-1.0.0.md @@ -20,62 +20,62 @@ This setting mimics some of the default behavior from 0.x, but not all. If you d The `"eslint:recommended"` configuration contains many of the same default rule settings from 0.x, but not all. These rules are no longer on by default, so you should review your settings to ensure they are still as you expect: -* [no-alert](http://eslint.org/docs/rules/no-alert) -* [no-array-constructor](http://eslint.org/docs/rules/no-array-constructor) -* [no-caller](http://eslint.org/docs/rules/no-caller) -* [no-catch-shadow](http://eslint.org/docs/rules/no-catch-shadow) -* [no-empty-label](http://eslint.org/docs/rules/no-empty-label) -* [no-eval](http://eslint.org/docs/rules/no-eval) -* [no-extend-native](http://eslint.org/docs/rules/no-extend-native) -* [no-extra-bind](http://eslint.org/docs/rules/no-extra-bind) -* [no-extra-strict](http://eslint.org/docs/rules/no-extra-strict) -* [no-implied-eval](http://eslint.org/docs/rules/no-implied-eval) -* [no-iterator](http://eslint.org/docs/rules/no-iterator) -* [no-label-var](http://eslint.org/docs/rules/no-label-var) -* [no-labels](http://eslint.org/docs/rules/no-labels) -* [no-lone-blocks](http://eslint.org/docs/rules/no-lone-blocks) -* [no-loop-func](http://eslint.org/docs/rules/no-loop-func) -* [no-multi-spaces](http://eslint.org/docs/rules/no-multi-spaces) -* [no-multi-str](http://eslint.org/docs/rules/no-multi-str) -* [no-native-reassign](http://eslint.org/docs/rules/no-native-reassign) -* [no-new](http://eslint.org/docs/rules/no-new) -* [no-new-func](http://eslint.org/docs/rules/no-new-func) -* [no-new-object](http://eslint.org/docs/rules/no-new-object) -* [no-new-wrappers](http://eslint.org/docs/rules/no-new-wrappers) -* [no-octal-escape](http://eslint.org/docs/rules/no-octal-escape) -* [no-process-exit](http://eslint.org/docs/rules/no-process-exit) -* [no-proto](http://eslint.org/docs/rules/no-proto) -* [no-return-assign](http://eslint.org/docs/rules/no-return-assign) -* [no-script-url](http://eslint.org/docs/rules/no-script-url) -* [no-sequences](http://eslint.org/docs/rules/no-sequences) -* [no-shadow](http://eslint.org/docs/rules/no-shadow) -* [no-shadow-restricted-names](http://eslint.org/docs/rules/no-shadow-restricted-names) -* [no-spaced-func](http://eslint.org/docs/rules/no-spaced-func) -* [no-trailing-spaces](http://eslint.org/docs/rules/no-trailing-spaces) -* [no-undef-init](http://eslint.org/docs/rules/no-undef-init) -* [no-underscore-dangle](http://eslint.org/docs/rules/no-underscore-dangle) -* [no-unused-expressions](http://eslint.org/docs/rules/no-unused-expressions) -* [no-use-before-define](http://eslint.org/docs/rules/no-use-before-define) -* [no-with](http://eslint.org/docs/rules/no-with) -* [no-wrap-func](http://eslint.org/docs/rules/no-wrap-func) -* [camelcase](http://eslint.org/docs/rules/camelcase) -* [comma-spacing](http://eslint.org/docs/rules/comma-spacing) -* [consistent-return](http://eslint.org/docs/rules/consistent-return) -* [curly](http://eslint.org/docs/rules/curly) -* [dot-notation](http://eslint.org/docs/rules/dot-notation) -* [eol-last](http://eslint.org/docs/rules/eol-last) -* [eqeqeq](http://eslint.org/docs/rules/eqeqeq) -* [key-spacing](http://eslint.org/docs/rules/key-spacing) -* [new-cap](http://eslint.org/docs/rules/new-cap) -* [new-parens](http://eslint.org/docs/rules/new-parens) -* [quotes](http://eslint.org/docs/rules/quotes) -* [semi](http://eslint.org/docs/rules/semi) -* [semi-spacing](http://eslint.org/docs/rules/semi-spacing) -* [space-infix-ops](http://eslint.org/docs/rules/space-infix-ops) -* [space-return-throw-case](http://eslint.org/docs/rules/space-return-throw-case) -* [space-unary-ops](http://eslint.org/docs/rules/space-unary-ops) -* [strict](http://eslint.org/docs/rules/strict) -* [yoda](http://eslint.org/docs/rules/yoda) +* [no-alert](https://eslint.org/docs/rules/no-alert) +* [no-array-constructor](https://eslint.org/docs/rules/no-array-constructor) +* [no-caller](https://eslint.org/docs/rules/no-caller) +* [no-catch-shadow](https://eslint.org/docs/rules/no-catch-shadow) +* [no-empty-label](https://eslint.org/docs/rules/no-empty-label) +* [no-eval](https://eslint.org/docs/rules/no-eval) +* [no-extend-native](https://eslint.org/docs/rules/no-extend-native) +* [no-extra-bind](https://eslint.org/docs/rules/no-extra-bind) +* [no-extra-strict](https://eslint.org/docs/rules/no-extra-strict) +* [no-implied-eval](https://eslint.org/docs/rules/no-implied-eval) +* [no-iterator](https://eslint.org/docs/rules/no-iterator) +* [no-label-var](https://eslint.org/docs/rules/no-label-var) +* [no-labels](https://eslint.org/docs/rules/no-labels) +* [no-lone-blocks](https://eslint.org/docs/rules/no-lone-blocks) +* [no-loop-func](https://eslint.org/docs/rules/no-loop-func) +* [no-multi-spaces](https://eslint.org/docs/rules/no-multi-spaces) +* [no-multi-str](https://eslint.org/docs/rules/no-multi-str) +* [no-native-reassign](https://eslint.org/docs/rules/no-native-reassign) +* [no-new](https://eslint.org/docs/rules/no-new) +* [no-new-func](https://eslint.org/docs/rules/no-new-func) +* [no-new-object](https://eslint.org/docs/rules/no-new-object) +* [no-new-wrappers](https://eslint.org/docs/rules/no-new-wrappers) +* [no-octal-escape](https://eslint.org/docs/rules/no-octal-escape) +* [no-process-exit](https://eslint.org/docs/rules/no-process-exit) +* [no-proto](https://eslint.org/docs/rules/no-proto) +* [no-return-assign](https://eslint.org/docs/rules/no-return-assign) +* [no-script-url](https://eslint.org/docs/rules/no-script-url) +* [no-sequences](https://eslint.org/docs/rules/no-sequences) +* [no-shadow](https://eslint.org/docs/rules/no-shadow) +* [no-shadow-restricted-names](https://eslint.org/docs/rules/no-shadow-restricted-names) +* [no-spaced-func](https://eslint.org/docs/rules/no-spaced-func) +* [no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces) +* [no-undef-init](https://eslint.org/docs/rules/no-undef-init) +* [no-underscore-dangle](https://eslint.org/docs/rules/no-underscore-dangle) +* [no-unused-expressions](https://eslint.org/docs/rules/no-unused-expressions) +* [no-use-before-define](https://eslint.org/docs/rules/no-use-before-define) +* [no-with](https://eslint.org/docs/rules/no-with) +* [no-wrap-func](https://eslint.org/docs/rules/no-wrap-func) +* [camelcase](https://eslint.org/docs/rules/camelcase) +* [comma-spacing](https://eslint.org/docs/rules/comma-spacing) +* [consistent-return](https://eslint.org/docs/rules/consistent-return) +* [curly](https://eslint.org/docs/rules/curly) +* [dot-notation](https://eslint.org/docs/rules/dot-notation) +* [eol-last](https://eslint.org/docs/rules/eol-last) +* [eqeqeq](https://eslint.org/docs/rules/eqeqeq) +* [key-spacing](https://eslint.org/docs/rules/key-spacing) +* [new-cap](https://eslint.org/docs/rules/new-cap) +* [new-parens](https://eslint.org/docs/rules/new-parens) +* [quotes](https://eslint.org/docs/rules/quotes) +* [semi](https://eslint.org/docs/rules/semi) +* [semi-spacing](https://eslint.org/docs/rules/semi-spacing) +* [space-infix-ops](https://eslint.org/docs/rules/space-infix-ops) +* [space-return-throw-case](https://eslint.org/docs/rules/space-return-throw-case) +* [space-unary-ops](https://eslint.org/docs/rules/space-unary-ops) +* [strict](https://eslint.org/docs/rules/strict) +* [yoda](https://eslint.org/docs/rules/yoda) See also: the [full diff](https://github.com/eslint/eslint/commit/e3e9dbd9876daf4bdeb4e15f8a76a9d5e6e03e39#diff-b01a5cfd9361ca9280a460fd6bb8edbbL1) where the defaults were changed. @@ -148,19 +148,19 @@ Here's a configuration file with the closest equivalent of the old defaults: Over the past several releases, we have been deprecating rules and introducing new rules to take their place. The following is a list of the removed rules and their replacements: -* [generator-star](http://eslint.org/docs/rules/generator-star) is replaced by [generator-star-spacing](http://eslint.org/docs/rules/generator-star-spacing) -* [global-strict](http://eslint.org/docs/rules/global-strict) is replaced by [strict](http://eslint.org/docs/rules/strict) -* [no-comma-dangle](http://eslint.org/docs/rules/no-comma-dangle) is replaced by [comma-dangle](http://eslint.org/docs/rules/comma-dangle) -* [no-empty-class](http://eslint.org/docs/rules/no-empty-class) is replaced by [no-empty-character-class](http://eslint.org/docs/rules/no-empty-character-class) -* [no-extra-strict](http://eslint.org/docs/rules/no-extra-strict) is replaced by [strict](http://eslint.org/docs/rules/strict) -* [no-reserved-keys](http://eslint.org/docs/rules/no-reserved-keys) is replaced by [quote-props](http://eslint.org/docs/rules/quote-props) -* [no-space-before-semi](http://eslint.org/docs/rules/no-space-before-semi) is replaced by [semi-spacing](http://eslint.org/docs/rules/semi-spacing) -* [no-wrap-func](http://eslint.org/docs/rules/no-wrap-func) is replaced by [no-extra-parens](http://eslint.org/docs/rules/no-extra-parens) -* [space-after-function-name](http://eslint.org/docs/rules/space-after-function-name) is replaced by [space-before-function-paren](http://eslint.org/docs/rules/space-before-function-paren) -* [space-before-function-parentheses](http://eslint.org/docs/rules/space-before-function-parentheses) is replaced by [space-before-function-paren](http://eslint.org/docs/rules/space-before-function-paren) -* [space-in-brackets](http://eslint.org/docs/rules/space-in-brackets) is replaced by[object-curly-spacing](http://eslint.org/docs/rules/object-curly-spacing) and [array-bracket-spacing](http://eslint.org/docs/rules/array-bracket-spacing) -* [space-unary-word-ops](http://eslint.org/docs/rules/space-unary-word-ops) is replaced by [space-unary-ops](http://eslint.org/docs/rules/space-unary-ops) -* [spaced-line-comment](http://eslint.org/docs/rules/spaced-line-comment) is replaced by [spaced-comment](http://eslint.org/docs/rules/spaced-comment) +* [generator-star](https://eslint.org/docs/rules/generator-star) is replaced by [generator-star-spacing](https://eslint.org/docs/rules/generator-star-spacing) +* [global-strict](https://eslint.org/docs/rules/global-strict) is replaced by [strict](https://eslint.org/docs/rules/strict) +* [no-comma-dangle](https://eslint.org/docs/rules/no-comma-dangle) is replaced by [comma-dangle](https://eslint.org/docs/rules/comma-dangle) +* [no-empty-class](https://eslint.org/docs/rules/no-empty-class) is replaced by [no-empty-character-class](https://eslint.org/docs/rules/no-empty-character-class) +* [no-extra-strict](https://eslint.org/docs/rules/no-extra-strict) is replaced by [strict](https://eslint.org/docs/rules/strict) +* [no-reserved-keys](https://eslint.org/docs/rules/no-reserved-keys) is replaced by [quote-props](https://eslint.org/docs/rules/quote-props) +* [no-space-before-semi](https://eslint.org/docs/rules/no-space-before-semi) is replaced by [semi-spacing](https://eslint.org/docs/rules/semi-spacing) +* [no-wrap-func](https://eslint.org/docs/rules/no-wrap-func) is replaced by [no-extra-parens](https://eslint.org/docs/rules/no-extra-parens) +* [space-after-function-name](https://eslint.org/docs/rules/space-after-function-name) is replaced by [space-before-function-paren](https://eslint.org/docs/rules/space-before-function-paren) +* [space-before-function-parentheses](https://eslint.org/docs/rules/space-before-function-parentheses) is replaced by [space-before-function-paren](https://eslint.org/docs/rules/space-before-function-paren) +* [space-in-brackets](https://eslint.org/docs/rules/space-in-brackets) is replaced by[object-curly-spacing](https://eslint.org/docs/rules/object-curly-spacing) and [array-bracket-spacing](https://eslint.org/docs/rules/array-bracket-spacing) +* [space-unary-word-ops](https://eslint.org/docs/rules/space-unary-word-ops) is replaced by [space-unary-ops](https://eslint.org/docs/rules/space-unary-ops) +* [spaced-line-comment](https://eslint.org/docs/rules/spaced-line-comment) is replaced by [spaced-comment](https://eslint.org/docs/rules/spaced-comment) **To address:** You'll need to update your rule configurations to use the new rules. ESLint v1.0.0 will also warn you when you're using a rule that has been removed and will suggest the replacement rules. Hopefully, this will result in few surprises during the upgrade process. diff --git a/docs/user-guide/migrating-to-2.0.0.md b/docs/user-guide/migrating-to-2.0.0.md index 2f4ccda667be..95a3a1774e9c 100644 --- a/docs/user-guide/migrating-to-2.0.0.md +++ b/docs/user-guide/migrating-to-2.0.0.md @@ -51,11 +51,11 @@ module.exports = { The following rules have been deprecated with new rules created to take their place. The following is a list of the removed rules and their replacements: -* [no-arrow-condition](http://eslint.org/docs/rules/no-arrow-condition) is replaced by a combination of [no-confusing-arrow](http://eslint.org/docs/rules/no-confusing-arrow) and [no-constant-condition](http://eslint.org/docs/rules/no-constant-condition). Turn on both of these rules to get the same functionality as `no-arrow-condition`. -* [no-empty-label](http://eslint.org/docs/rules/no-empty-label) is replaced by [no-labels](http://eslint.org/docs/rules/no-labels) with `{"allowLoop": true, "allowSwitch": true}` option. -* [space-after-keywords](http://eslint.org/docs/rules/space-after-keywords) is replaced by [keyword-spacing](http://eslint.org/docs/rules/keyword-spacing). -* [space-before-keywords](http://eslint.org/docs/rules/space-before-keywords) is replaced by [keyword-spacing](http://eslint.org/docs/rules/keyword-spacing). -* [space-return-throw-case](http://eslint.org/docs/rules/space-return-throw-case) is replaced by [keyword-spacing](http://eslint.org/docs/rules/keyword-spacing). +* [no-arrow-condition](https://eslint.org/docs/rules/no-arrow-condition) is replaced by a combination of [no-confusing-arrow](https://eslint.org/docs/rules/no-confusing-arrow) and [no-constant-condition](https://eslint.org/docs/rules/no-constant-condition). Turn on both of these rules to get the same functionality as `no-arrow-condition`. +* [no-empty-label](https://eslint.org/docs/rules/no-empty-label) is replaced by [no-labels](https://eslint.org/docs/rules/no-labels) with `{"allowLoop": true, "allowSwitch": true}` option. +* [space-after-keywords](https://eslint.org/docs/rules/space-after-keywords) is replaced by [keyword-spacing](https://eslint.org/docs/rules/keyword-spacing). +* [space-before-keywords](https://eslint.org/docs/rules/space-before-keywords) is replaced by [keyword-spacing](https://eslint.org/docs/rules/keyword-spacing). +* [space-return-throw-case](https://eslint.org/docs/rules/space-return-throw-case) is replaced by [keyword-spacing](https://eslint.org/docs/rules/keyword-spacing). **To address:** You'll need to update your rule configurations to use the new rules. ESLint v2.0.0 will also warn you when you're using a rule that has been removed and will suggest the replacement rules. Hopefully, this will result in few surprises during the upgrade process. @@ -207,17 +207,17 @@ If you're not using `ecmaFeatures` in your configuration or your custom/plugin r In 2.0.0, the following 11 rules were added to `"eslint:recommended"`. -* [constructor-super](http://eslint.org/docs/rules/constructor-super) -* [no-case-declarations](http://eslint.org/docs/rules/no-case-declarations) -* [no-class-assign](http://eslint.org/docs/rules/no-class-assign) -* [no-const-assign](http://eslint.org/docs/rules/no-const-assign) -* [no-dupe-class-members](http://eslint.org/docs/rules/no-dupe-class-members) -* [no-empty-pattern](http://eslint.org/docs/rules/no-empty-pattern) -* [no-new-symbol](http://eslint.org/docs/rules/no-new-symbol) -* [no-self-assign](http://eslint.org/docs/rules/no-self-assign) -* [no-this-before-super](http://eslint.org/docs/rules/no-this-before-super) -* [no-unexpected-multiline](http://eslint.org/docs/rules/no-unexpected-multiline) -* [no-unused-labels](http://eslint.org/docs/rules/no-unused-labels) +* [constructor-super](https://eslint.org/docs/rules/constructor-super) +* [no-case-declarations](https://eslint.org/docs/rules/no-case-declarations) +* [no-class-assign](https://eslint.org/docs/rules/no-class-assign) +* [no-const-assign](https://eslint.org/docs/rules/no-const-assign) +* [no-dupe-class-members](https://eslint.org/docs/rules/no-dupe-class-members) +* [no-empty-pattern](https://eslint.org/docs/rules/no-empty-pattern) +* [no-new-symbol](https://eslint.org/docs/rules/no-new-symbol) +* [no-self-assign](https://eslint.org/docs/rules/no-self-assign) +* [no-this-before-super](https://eslint.org/docs/rules/no-this-before-super) +* [no-unexpected-multiline](https://eslint.org/docs/rules/no-unexpected-multiline) +* [no-unused-labels](https://eslint.org/docs/rules/no-unused-labels) **To address:** If you don't want to be notified by those rules, you can simply disable those rules. @@ -285,7 +285,7 @@ if (variable) { } ``` -Further Reading: http://estools.github.io/escope/ +Further Reading: https://estools.github.io/escope/ ## Default Changes When Using `eslint:recommended` diff --git a/docs/user-guide/migrating-to-3.0.0.md b/docs/user-guide/migrating-to-3.0.0.md index 51052771207c..af19c769a56b 100644 --- a/docs/user-guide/migrating-to-3.0.0.md +++ b/docs/user-guide/migrating-to-3.0.0.md @@ -4,7 +4,7 @@ ESLint v3.0.0 is the third major version release. We have made several breaking ## Dropping Support for Node.js < 4 -With ESLint v3.0.0, we are dropping support for Node.js versions prior to 4. Node.js 0.10 and 0.12 are in [maintenance mode](https://github.com/nodejs/LTS) and Node.js 4 is the current LTS version. If you are using an older version of Node.js, we recommend upgrading to at least Node.js 4 as soon as possible. If you are unable to upgrade to Node.js 4 or higher, then we recommend continuing to use ESLint v2.x until you are ready to upgrade Node.js. +With ESLint v3.0.0, we are dropping support for Node.js versions prior to 4. Node.js 0.10 and 0.12 are in [maintenance mode](https://github.com/nodejs/Release) and Node.js 4 is the current LTS version. If you are using an older version of Node.js, we recommend upgrading to at least Node.js 4 as soon as possible. If you are unable to upgrade to Node.js 4 or higher, then we recommend continuing to use ESLint v2.x until you are ready to upgrade Node.js. **Important:** We will not be updating the ESLint v2.x versions going forward. All bug fixes and enhancements will land in ESLint v3.x. @@ -35,17 +35,17 @@ To create a new configuration, use `eslint --init`. In 3.0.0, the following rules were added to `"eslint:recommended"`: -* [`no-unsafe-finally`](http://eslint.org/docs/rules/no-unsafe-finally) helps catch `finally` clauses that may not behave as you think. -* [`no-native-reassign`](http://eslint.org/docs/rules/no-native-reassign) was previously part of `no-undef`, but was split out because it didn't make sense as part of another rule. The `no-native-reassign` rule warns whenever you try to overwrite a read-only global variable. -* [`require-yield`](http://eslint.org/docs/rules/require-yield) helps to identify generator functions that do not have the `yield` keyword. +* [`no-unsafe-finally`](https://eslint.org/docs/rules/no-unsafe-finally) helps catch `finally` clauses that may not behave as you think. +* [`no-native-reassign`](https://eslint.org/docs/rules/no-native-reassign) was previously part of `no-undef`, but was split out because it didn't make sense as part of another rule. The `no-native-reassign` rule warns whenever you try to overwrite a read-only global variable. +* [`require-yield`](https://eslint.org/docs/rules/require-yield) helps to identify generator functions that do not have the `yield` keyword. The following rules were removed from `"eslint:recommended"`: -* [`comma-dangle`](http://eslint.org/docs/rules/comma-dangle) used to be recommended because Internet Explorer 8 and earlier threw a syntax error when it found a dangling comma on object literal properties. However, [Internet Explorer 8 was end-of-lifed](https://www.microsoft.com/en-us/WindowsForBusiness/End-of-IE-support) in January 2016 and all other active browsers allow dangling commas. As such, we consider dangling commas to now be a stylistic issue instead of a possible error. +* [`comma-dangle`](https://eslint.org/docs/rules/comma-dangle) used to be recommended because Internet Explorer 8 and earlier threw a syntax error when it found a dangling comma on object literal properties. However, [Internet Explorer 8 was end-of-lifed](https://www.microsoft.com/en-us/WindowsForBusiness/End-of-IE-support) in January 2016 and all other active browsers allow dangling commas. As such, we consider dangling commas to now be a stylistic issue instead of a possible error. The following rules were modified: -* [`complexity`](http://eslint.org/docs/rules/complexity) used to have a hardcoded default of 11 in `eslint:recommended` that would be used if you turned the rule on without specifying a maximum. The default is now 20. The rule actually always had a default of 20, but `eslint:recommended` was overriding it by mistake. +* [`complexity`](https://eslint.org/docs/rules/complexity) used to have a hardcoded default of 11 in `eslint:recommended` that would be used if you turned the rule on without specifying a maximum. The default is now 20. The rule actually always had a default of 20, but `eslint:recommended` was overriding it by mistake. **To address:** If you want to mimic how `eslint:recommended` worked in v2.x, you can use the following: diff --git a/docs/user-guide/migrating-to-4.0.0.md b/docs/user-guide/migrating-to-4.0.0.md new file mode 100644 index 000000000000..de1d3781b8d7 --- /dev/null +++ b/docs/user-guide/migrating-to-4.0.0.md @@ -0,0 +1,226 @@ +# Migrating to v4.0.0 + +ESLint v4.0.0 is the fourth major version release. We have made several breaking changes in this release; however, we expect that most of the changes will only affect a very small percentage of users. This guide is intended to walk you through the changes. + +The lists below are ordered roughly by the number of users each change is expected to affect, where the first items are expected to affect the most users. + +### Breaking changes for users + +1. [New rules have been added to `eslint:recommended`](#eslint-recommended-changes) +1. [The `indent` rule is more strict](#indent-rewrite) +1. [Unrecognized properties in config files now cause a fatal error](#config-validation) +1. [.eslintignore patterns are now resolved from the location of the file](#eslintignore-patterns) +1. [The `padded-blocks` rule is more strict by default](#padded-blocks-defaults) +1. [The `space-before-function-paren` rule is more strict by default](#space-before-function-paren-defaults) +1. [The `no-multi-spaces` rule is more strict by default](#no-multi-spaces-eol-comments) +1. [References to scoped plugins in config files are now required to include the scope](#scoped-plugin-resolution) + +### Breaking changes for plugin/custom rule developers + +1. [`RuleTester` now validates properties of test cases](#rule-tester-validation) +1. [AST nodes no longer have comment properties](#comment-attachment) +1. [`LineComment` and `BlockComment` events will no longer be emitted during AST traversal](#event-comments) +1. [Shebangs are now returned from comment APIs](#shebangs) + +### Breaking changes for integration developers + +1. [The `global` property in the `linter.verify()` API is no longer supported](#global-property) +1. [More report messages now have full location ranges](#report-locations) +1. [Some exposed APIs are now ES2015 classes](#exposed-es2015-classes) + +--- + +##
`eslint:recommended` changes + +Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config: + +* [`no-compare-neg-zero`](/docs/rules/no-compare-neg-zero) disallows comparisons to `-0` +* [`no-useless-escape`](/docs/rules/no-useless-escape) disallows uselessly-escaped characters in strings and regular expressions + +**To address:** To mimic the `eslint:recommended` behavior from 3.x, you can disable these rules in a config file: + +```json +{ + "extends": "eslint:recommended", + + "rules": { + "no-compare-neg-zero": "off", + "no-useless-escape": "off" + } +} +``` + +## The `indent` rule is more strict + +Previously, the [`indent`](/docs/rules/indent) rule was fairly lenient about checking indentation; there were many code patterns where indentation was not validated by the rule. This caused confusion for users, because they were accidentally writing code with incorrect indentation, and they expected ESLint to catch the issues. + +In 4.0.0, the `indent` rule has been rewritten. The new version of the rule will report some indentation errors that the old version of the rule did not catch. Additionally, the indentation of `MemberExpression` nodes, function parameters, and function arguments will now be checked by default (it was previously ignored by default for backwards compatibility). + +To make the upgrade process easier, we've introduced the [`indent-legacy`](/docs/rules/indent-legacy) rule as a snapshot of the `indent` rule from 3.x. If you run into issues from the `indent` rule when you upgrade, you should be able to use the `indent-legacy` rule to replicate the 3.x behavior. However, the `indent-legacy` rule is deprecated and will not receive bugfixes or improvements in the future, so you should eventually switch back to the `indent` rule. + +**To address:** We recommend upgrading without changing your `indent` configuration, and fixing any new indentation errors that appear in your codebase. However, if you want to mimic how the `indent` rule worked in 3.x, you can update your configuration: + +```js +{ + rules: { + indent: "off", + "indent-legacy": "error" // replace this with your previous `indent` configuration + } +} +``` + +## Unrecognized properties in config files now cause a fatal error + +When creating a config, users sometimes make typos or misunderstand how the config is supposed to be structured. Previously, ESLint did not validate the properties of a config file, so a typo in a config could be very tedious to debug. Starting in 4.0.0, ESLint will raise an error if a property in a config file is unrecognized or has the wrong type. + +**To address:** If you see a config validation error after upgrading, verify that your config doesn't contain any typos. If you are using an unrecognized property, you should be able to remove it from your config to restore the previous behavior. + +## .eslintignore patterns are now resolved from the location of the file + +Due to a bug, glob patterns in an `.eslintignore` file were previously resolved from the current working directory of the process, rather than the location of the `.eslintignore` file. Starting in 4.0, patterns in an `.eslintignore` file will be resolved from the `.eslintignore` file's location. + +**To address:** If you use an `.eslintignore` file and you frequently run ESLint from somewhere other than the project root, it's possible that the patterns will be matched differently. You should update the patterns in the `.eslintignore` file to ensure they are relative to the file, not to the working directory. + +## The `padded-blocks` rule is more strict by default + +By default, the [`padded-blocks`](/docs/rules/padded-blocks) rule will now enforce padding in class bodies and switch statements. Previously, the rule would ignore these cases unless the user opted into enforcing them. + +**To address:** If this change results in more linting errors in your codebase, you should fix them or reconfigure the rule. + +## The `space-before-function-paren` rule is more strict by default + +By default, the [`space-before-function-paren`](/docs/rules/space-before-function-paren) rule will now enforce spacing for async arrow functions. Previously, the rule would ignore these cases unless the user opted into enforcing them. + +**To address:** To mimic the default config from 3.x, you can use: + +```json +{ + "rules": { + "space-before-function-paren": ["error", { + "anonymous": "always", + "named": "always", + "asyncArrow": "ignore" + }] + } +} +``` + +## The `no-multi-spaces` rule is more strict by default + +By default, the [`no-multi-spaces`](/docs/rules/no-multi-spaces) rule will now disallow multiple spaces before comments at the end of a line. Previously, the rule did not check this case. + +**To address:** To mimic the default config from 3.x, you can use: + +```json +{ + "rules": { + "no-multi-spaces": ["error", {"ignoreEOLComments": true}] + } +} +``` + +## References to scoped plugins in config files are now required to include the scope + +In 3.x, there was a bug where references to scoped NPM packages as plugins in config files could omit the scope. For example, in 3.x the following config was legal: + +```json +{ + "plugins": [ + "@my-organization/foo" + ], + "rules": { + "foo/some-rule": "error" + } +} +``` + +In other words, it was possible to reference a rule from a scoped plugin (such as `foo/some-rule`) without explicitly stating the `@my-organization` scope. This was a bug because it could lead to ambiguous rule references if there was also an unscoped plugin called `eslint-plugin-foo` loaded at the same time. + +To avoid this ambiguity, in 4.0 references to scoped plugins must include the scope. The config from above should be fixed to: + +```json +{ + "plugins": [ + "@my-organization/foo" + ], + "rules": { + "@my-organization/foo/some-rule": "error" + } +} +``` + +**To address:** If you reference a scoped NPM package as a plugin in a config file, be sure to include the scope wherever you reference it. + +--- + +## `RuleTester` now validates properties of test cases + +Starting in 4.0, the `RuleTester` utility will validate properties of test case objects, and an error will be thrown if an unknown property is encountered. This change was added because we found that it was relatively common for developers to make typos in rule tests, often invalidating the assertions that the test cases were trying to make. + +**To address:** If your tests for custom rules have extra properties, you should remove those properties. + +## AST Nodes no longer have comment properties + +Prior to 4.0, ESLint required parsers to implement comment attachment, a process where AST nodes would gain additional properties corresponding to their leading and trailing comments in the source file. This made it difficult for users to develop custom parsers, because they would have to replicate the confusing comment attachment semantics required by ESLint. + +In 4.0, we have moved away from the concept of comment attachment and have moved all comment handling logic into ESLint itself. This should make it easier to develop custom parsers, but it also means that AST nodes will no longer have `leadingComments` and `trailingComments` properties. Conceptually, rule authors can now think of comments in the context of tokens rather than AST nodes. + +**To address:** If you have a custom rule that depends on the `leadingComments` or `trailingComments` properties of an AST node, you can now use `sourceCode.getCommentsBefore()` and `sourceCode.getCommentsAfter()` instead, respectively. + +Additionally, the `sourceCode` object now also has `sourceCode.getCommentsInside()` (which returns all the comments inside a node), `sourceCode.getAllComments()` (which returns all the comments in the file), and allows comments to be accessed through various other token iterator methods (such as `getTokenBefore()` and `getTokenAfter()`) with the `{ includeComments: true }` option. + +For rule authors concerned about supporting ESLint v3.0 in addition to v4.0, the now deprecated `sourceCode.getComments()` is still available and will work for both versions. + +Finally, please note that the following `SourceCode` methods have been deprecated and will be removed in a future version of ESLint: + +* `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` +* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option +* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option + +## `LineComment` and `BlockComment` events will no longer be emitted during AST traversal + +Starting in 4.0, `LineComment` and `BlockComments` events will not be emitted during AST traversal. There are two reasons for this: + +* This behavior was relying on comment attachment happening at the parser level, which does not happen anymore, to ensure that all comments would be accounted for +* Thinking of comments in the context of tokens is more predictable and easier to reason about than thinking about comment tokens in the context of AST nodes + +**To address:** Instead of relying on `LineComment` and `BlockComment`, rules can now use `sourceCode.getAllComments()` to get all comments in a file. To check all comments of a specific type, rules can use the following pattern: + +``` +sourceCode.getAllComments().filter(comment => comment.type === "Line"); +sourceCode.getAllComments().filter(comment => comment.type === "Block"); +``` + +## Shebangs are now returned from comment APIs + +Prior to 4.0, shebang comments in a source file would not appear in the output of `sourceCode.getAllComments()` or `sourceCode.getComments()`, but they would appear in the output of `sourceCode.getTokenOrCommentBefore` as line comments. This inconsistency led to some confusion for rule developers. + +In 4.0, shebang comments are treated as comment tokens of type `Shebang` and will be returned by any `SourceCode` method that returns comments. The goal of this change is to make working with shebang comments more consistent with how other tokens are handled. + +**To address:** If you have a custom rule that performs operations on comments, some additional logic might be required to ensure that shebang comments are correctly handled or filtered out: + +``` +sourceCode.getAllComments().filter(comment => comment.type !== "Shebang"); +``` + +--- + +## The `global` property in the `linter.verify()` API is no longer supported + +Previously, the `linter.verify()` API accepted a `global` config option, which was a synonym for the documented `globals` property. The `global` option was never documented or officially supported, and did not work in config files. It has been removed in 4.0. + +**To address:** If you were using the `global` property, please use the `globals` property instead, which does the same thing. + +## More report messages now have full location ranges + +Starting in 3.1.0, rules have been able to specify the *end* location of a reported problem, in addition to the start location, by explicitly specifying an end location in the `report` call. This is useful for tools like editor integrations, which can use the range to precisely display where a reported problem occurs. Starting in 4.0, if a *node* is reported rather than a location, the end location of the range will automatically be inferred from the end location of the node. As a result, many more reported problems will have end locations. + +This is not expected to cause breakage. However, it will likely result in larger report locations than before. For example, if a rule reports the root node of the AST, the reported problem's range will be the entire program. In some integrations, this could result in a poor user experience (e.g. if the entire program is highlighted to indicate an error). + +**To address:** If you have an integration that deals with the ranges of reported problems, make sure you handle large report ranges in a user-friendly way. + +## Some exposed APIs are now ES2015 classes + +The `CLIEngine`, `SourceCode`, and `RuleTester` modules from ESLint's Node.js API are now ES2015 classes. This will not break any documented behavior, but it does have some observable effects (for example, the methods on `CLIEngine.prototype` are now non-enumerable). + +**To address:** If you rely on enumerating the methods of ESLint's Node.js APIs, use a function that can also access non-enumerable properties such as `Object.getOwnPropertyNames`. diff --git a/docs/user-guide/rule-deprecation.md b/docs/user-guide/rule-deprecation.md index 762c4c3bcb39..e306916532d9 100644 --- a/docs/user-guide/rule-deprecation.md +++ b/docs/user-guide/rule-deprecation.md @@ -2,13 +2,12 @@ Balancing the trade-offs of improving a tool and the frustration these changes can cause is a difficult task. One key area in which this affects our users is in the removal of rules. -The ESLint team is committed to making upgrading as easy and painless as possible. To that end, the team has agreed upon some guidelines for deprecating rules in the future. The goal of these guidelines is to allow for improvements and changes to be made without breaking existing configurations. +The ESLint team is committed to making upgrading as easy and painless as possible. To that end, the team has agreed upon the following set of guidelines for deprecating rules in the future. The goal of these guidelines is to allow for improvements and changes to be made without breaking existing configurations. -Until May 1, 2017, the team has committed to not remove any rules in any releases of ESLint; however, the team will deprecate rules as needed. When a rule is deprecated, it means that: +* Rules will never be removed from ESLint. +* Rules will be deprecated as needed, and marked as such in all documentation. +* After a rule has been deprecated, the team will no longer do any work on it. This includes bug fixes, enhancements, and updates to the rule's documentation. Issues and pull requests related to deprecated rule will not be accepted and will be closed. -* The rule will be marked as deprecated in all documentation. -* The team will no longer do any work on the rule. This includes bug fixes, enhancements, and updates to the rule's documentation. Issues and pull requests related to the deprecated rule will not be accepted and will be closed automatically. - -After May 1, 2017, the team will revisit all deprecated rules and evaluate whether they should actually be removed or not. If the rule is removed at this time, users will have to adjust their configuration accordingly. +Since deprecated rules will never be removed, you can continue to use them indefinitely if they are working for you. However, keep in mind that deprecated rules will effectively be unmaintained. We hope that by following these guidelines we will be able to continue improving and working to make ESLint the best tool it can be while causing as little disruption to our users as possible during the process. diff --git a/karma.conf.js b/karma.conf.js index 594a5e03bd0a..0ffab8bc5208 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -18,7 +18,7 @@ module.exports = function(config) { "node_modules/chai/chai.js", "node_modules/sinon/pkg/sinon.js", "build/eslint.js", - "tests/lib/eslint.js" + "tests/lib/linter.js" ], @@ -30,7 +30,7 @@ module.exports = function(config) { // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - "tests/lib/eslint.js": ["babel"] + "tests/lib/linter.js": ["babel"] }, babelPreprocessor: { options: { diff --git a/lib/api.js b/lib/api.js index 664e9a5b40ee..0a0832a47645 100644 --- a/lib/api.js +++ b/lib/api.js @@ -5,8 +5,11 @@ "use strict"; +const Linter = require("./linter"); + module.exports = { - linter: require("./eslint"), + linter: new Linter(), + Linter, CLIEngine: require("./cli-engine"), RuleTester: require("./testers/rule-tester"), SourceCode: require("./util/source-code") diff --git a/lib/ast-utils.js b/lib/ast-utils.js index 0f2f3d6af539..a186bdee54df 100644 --- a/lib/ast-utils.js +++ b/lib/ast-utils.js @@ -10,6 +10,7 @@ //------------------------------------------------------------------------------ const esutils = require("esutils"); +const espree = require("espree"); //------------------------------------------------------------------------------ // Helpers @@ -27,6 +28,7 @@ const thisTagPattern = /^[\s*]*@this/m; const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/; const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]); const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/; +const SHEBANG_MATCHER = /^#!([^\r\n]+)/; // A set of node types that can contain a list of statements const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]); @@ -243,7 +245,7 @@ function hasJSDocThisTag(node, sourceCode) { // because callbacks don't have its JSDoc comment. // e.g. // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); }); - return sourceCode.getComments(node).leading.some(comment => thisTagPattern.test(comment.value)); + return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value)); } /** @@ -404,6 +406,31 @@ function createGlobalLinebreakMatcher() { return new RegExp(LINEBREAK_MATCHER.source, "g"); } +/** + * Checks whether or not the tokens of two given nodes are same. + * @param {ASTNode} left - A node 1 to compare. + * @param {ASTNode} right - A node 2 to compare. + * @param {SourceCode} sourceCode - The ESLint source code object. + * @returns {boolean} the source code for the given node. + */ +function equalTokens(left, right, sourceCode) { + const tokensL = sourceCode.getTokens(left); + const tokensR = sourceCode.getTokens(right); + + if (tokensL.length !== tokensR.length) { + return false; + } + for (let i = 0; i < tokensL.length; ++i) { + if (tokensL[i].type !== tokensR[i].type || + tokensL[i].value !== tokensR[i].value + ) { + return false; + } + } + + return true; +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -412,6 +439,7 @@ module.exports = { COMMENTS_IGNORE_PATTERN, LINEBREAKS, LINEBREAK_MATCHER, + SHEBANG_MATCHER, STATEMENT_LIST_PARENTS, /** @@ -435,6 +463,7 @@ module.exports = { isArrayFromMethod, isParenthesised, createGlobalLinebreakMatcher, + equalTokens, isArrowToken, isClosingBraceToken, @@ -524,7 +553,7 @@ module.exports = { /** * Returns whether the provided node is an ESLint directive comment or not - * @param {LineComment|BlockComment} node The node to be checked + * @param {Line|Block} node The comment token to be checked * @returns {boolean} `true` if the node is an ESLint directive comment */ isDirectiveComment(node) { @@ -556,9 +585,9 @@ module.exports = { /** * Finds the variable by a given name in a given scope and its upper scopes. * - * @param {escope.Scope} initScope - A scope to start find. + * @param {eslint-scope.Scope} initScope - A scope to start find. * @param {string} name - A variable name to find. - * @returns {escope.Variable|null} A found variable or `null`. + * @returns {eslint-scope.Variable|null} A found variable or `null`. */ getVariableByName(initScope, name) { let scope = initScope; @@ -617,12 +646,17 @@ module.exports = { node = parent; break; - // If the upper function is IIFE, checks the destination of the return value. - // e.g. - // obj.foo = (function() { - // // setup... - // return function foo() { ... }; - // })(); + /* + * If the upper function is IIFE, checks the destination of the return value. + * e.g. + * obj.foo = (function() { + * // setup... + * return function foo() { ... }; + * })(); + * obj.foo = (() => + * function foo() { ... } + * )(); + */ case "ReturnStatement": { const func = getUpperFunction(parent); @@ -632,41 +666,52 @@ module.exports = { node = func.parent; break; } + case "ArrowFunctionExpression": + if (node !== parent.body || !isCallee(parent)) { + return true; + } + node = parent.parent; + break; - // e.g. - // var obj = { foo() { ... } }; - // var obj = { foo: function() { ... } }; - // class A { constructor() { ... } } - // class A { foo() { ... } } - // class A { get foo() { ... } } - // class A { set foo() { ... } } - // class A { static foo() { ... } } + /* + * e.g. + * var obj = { foo() { ... } }; + * var obj = { foo: function() { ... } }; + * class A { constructor() { ... } } + * class A { foo() { ... } } + * class A { get foo() { ... } } + * class A { set foo() { ... } } + * class A { static foo() { ... } } + */ case "Property": case "MethodDefinition": return parent.value !== node; - // e.g. - // obj.foo = function foo() { ... }; - // Foo = function() { ... }; - // [obj.foo = function foo() { ... }] = a; - // [Foo = function() { ... }] = a; + /* + * e.g. + * obj.foo = function foo() { ... }; + * Foo = function() { ... }; + * [obj.foo = function foo() { ... }] = a; + * [Foo = function() { ... }] = a; + */ case "AssignmentExpression": case "AssignmentPattern": - if (parent.right === node) { - if (parent.left.type === "MemberExpression") { - return false; - } - if (isAnonymous && - parent.left.type === "Identifier" && - startsWithUpperCase(parent.left.name) - ) { - return false; - } + if (parent.left.type === "MemberExpression") { + return false; + } + if ( + isAnonymous && + parent.left.type === "Identifier" && + startsWithUpperCase(parent.left.name) + ) { + return false; } return true; - // e.g. - // var Foo = function() { ... }; + /* + * e.g. + * var Foo = function() { ... }; + */ case "VariableDeclarator": return !( isAnonymous && @@ -675,10 +720,12 @@ module.exports = { startsWithUpperCase(parent.id.name) ); - // e.g. - // var foo = function foo() { ... }.bind(obj); - // (function foo() { ... }).call(obj); - // (function foo() { ... }).apply(obj, []); + /* + * e.g. + * var foo = function foo() { ... }.bind(obj); + * (function foo() { ... }).call(obj); + * (function foo() { ... }).apply(obj, []); + */ case "MemberExpression": return ( parent.object !== node || @@ -689,10 +736,12 @@ module.exports = { isNullOrUndefined(parent.parent.arguments[0]) ); - // e.g. - // Reflect.apply(function() {}, obj, []); - // Array.from([], function() {}, obj); - // list.forEach(function() {}, obj); + /* + * e.g. + * Reflect.apply(function() {}, obj, []); + * Array.from([], function() {}, obj); + * list.forEach(function() {}, obj); + */ case "CallExpression": if (isReflectApply(parent.callee)) { return ( @@ -806,19 +855,14 @@ module.exports = { return 17; case "CallExpression": - - // IIFE is allowed to have parens in any position (#655) - if (node.callee.type === "FunctionExpression") { - return -1; - } return 18; case "NewExpression": return 19; - // no default + default: + return 20; } - return 20; }, /** @@ -924,8 +968,10 @@ module.exports = { node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || - // Do not check arrow functions with implicit return. - // `() => "use strict";` returns the string `"use strict"`. + /* + * Do not check arrow functions with implicit return. + * `() => "use strict";` returns the string `"use strict"`. + */ (node.type === "ArrowFunctionExpression" && node.body.type === "BlockStatement") ) { const statements = node.type === "Program" ? node.body : node.body.body; @@ -948,7 +994,7 @@ module.exports = { /** * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added - after the node will be parsed as a decimal point, rather than a property-access dot. + * after the node will be parsed as a decimal point, rather than a property-access dot. * @param {ASTNode} node - The node to check. * @returns {boolean} `true` if this node is a decimal integer. * @example @@ -1035,7 +1081,8 @@ module.exports = { } else if (parent.type === "Property" || parent.type === "MethodDefinition") { if (parent.kind === "constructor") { return "constructor"; - } else if (parent.kind === "get") { + } + if (parent.kind === "get") { tokens.push("getter"); } else if (parent.kind === "set") { tokens.push("setter"); @@ -1176,12 +1223,12 @@ module.exports = { }, /** - * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses - * surrounding the node. - * @param {SourceCode} sourceCode The source code object - * @param {ASTNode} node An expression node - * @returns {string} The text representing the node, with all surrounding parentheses included - */ + * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses + * surrounding the node. + * @param {SourceCode} sourceCode The source code object + * @param {ASTNode} node An expression node + * @returns {string} The text representing the node, with all surrounding parentheses included + */ getParenthesisedText(sourceCode, node) { let leftToken = sourceCode.getFirstToken(node); let rightToken = sourceCode.getLastToken(node); @@ -1252,5 +1299,52 @@ module.exports = { * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020 */ return node.type === "Literal" && node.value === null && !node.regex; + }, + + /** + * Determines whether two tokens can safely be placed next to each other without merging into a single token + * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used. + * @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used. + * @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed + * next to each other, behavior is undefined (although it should return `true` in most cases). + */ + canTokensBeAdjacent(leftValue, rightValue) { + let leftToken; + + if (typeof leftValue === "string") { + const leftTokens = espree.tokenize(leftValue, { ecmaVersion: 2015 }); + + leftToken = leftTokens[leftTokens.length - 1]; + } else { + leftToken = leftValue; + } + + const rightToken = typeof rightValue === "string" ? espree.tokenize(rightValue, { ecmaVersion: 2015 })[0] : rightValue; + + if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") { + if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") { + const PLUS_TOKENS = new Set(["+", "++"]); + const MINUS_TOKENS = new Set(["-", "--"]); + + return !( + PLUS_TOKENS.has(leftToken.value) && PLUS_TOKENS.has(rightToken.value) || + MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value) + ); + } + return true; + } + + if ( + leftToken.type === "String" || rightToken.type === "String" || + leftToken.type === "Template" || rightToken.type === "Template" + ) { + return true; + } + + if (leftToken.type !== "Numeric" && rightToken.type === "Numeric" && rightToken.value.startsWith(".")) { + return true; + } + + return false; } }; diff --git a/lib/cli-engine.js b/lib/cli-engine.js index de875a4d3529..0c1afcbcebd1 100644 --- a/lib/cli-engine.js +++ b/lib/cli-engine.js @@ -17,23 +17,23 @@ const fs = require("fs"), path = require("path"), - rules = require("./rules"), - eslint = require("./eslint"), - defaultOptions = require("../conf/cli-options"), + defaultOptions = require("../conf/default-cli-options"), + Linter = require("./linter"), IgnoredPaths = require("./ignored-paths"), Config = require("./config"), - Plugins = require("./config/plugins"), fileEntryCache = require("file-entry-cache"), globUtil = require("./util/glob-util"), - SourceCodeFixer = require("./util/source-code-fixer"), validator = require("./config/config-validator"), - stringify = require("json-stable-stringify"), + stringify = require("json-stable-stringify-without-jsonify"), hash = require("./util/hash"), - + ModuleResolver = require("./util/module-resolver"), + naming = require("./util/naming"), pkg = require("../package.json"); const debug = require("debug")("eslint:cli-engine"); +const resolver = new ModuleResolver(); + //------------------------------------------------------------------------------ // Typedefs //------------------------------------------------------------------------------ @@ -49,7 +49,7 @@ const debug = require("debug")("eslint:cli-engine"); * @property {string} cwd The value to use for the current working directory. * @property {string[]} envs An array of environments to load. * @property {string[]} extensions An array of file extensions to check. - * @property {boolean} fix Execute in autofix mode. + * @property {boolean|Function} fix Execute in autofix mode. If a function, should return a boolean. * @property {string[]} globals An array of global variables to declare. * @property {boolean} ignore False disables use of .eslintignore. * @property {string} ignorePath The ignore file to use instead of .eslintignore. @@ -60,6 +60,7 @@ const debug = require("debug")("eslint:cli-engine"); * @property {string[]} plugins An array of plugins to load. * @property {Object} rules An object of rules to use. * @property {string[]} rulePaths An array of directories to load custom rules from. + * @property {boolean} reportUnusedDisableDirectives `true` adds reports for unused eslint-disable directives */ /** @@ -73,8 +74,10 @@ const debug = require("debug")("eslint:cli-engine"); * @typedef {Object} LintResult * @property {string} filePath The path to the file that was linted. * @property {LintMessage[]} messages All of the messages for the result. - * @property {number} errorCount Number or errors for the result. - * @property {number} warningCount Number or warnings for the result. + * @property {number} errorCount Number of errors for the result. + * @property {number} warningCount Number of warnings for the result. + * @property {number} fixableErrorCount Number of fixable errors for the result. + * @property {number} fixableWarningCount Number of fixable warnings for the result. * @property {string=} [source] The source code of the file that was linted. * @property {string=} [output] The source code of the file that was linted, with as many fixes applied as possible. */ @@ -93,13 +96,21 @@ function calculateStatsPerFile(messages) { return messages.reduce((stat, message) => { if (message.fatal || message.severity === 2) { stat.errorCount++; + if (message.fix) { + stat.fixableErrorCount++; + } } else { stat.warningCount++; + if (message.fix) { + stat.fixableWarningCount++; + } } return stat; }, { errorCount: 0, - warningCount: 0 + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 }); } @@ -113,106 +124,33 @@ function calculateStatsPerRun(results) { return results.reduce((stat, result) => { stat.errorCount += result.errorCount; stat.warningCount += result.warningCount; + stat.fixableErrorCount += result.fixableErrorCount; + stat.fixableWarningCount += result.fixableWarningCount; return stat; }, { errorCount: 0, - warningCount: 0 + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 }); } -/** - * 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) { - const MAX_PASSES = 10; - let messages = [], - fixedResult, - fixed = false, - passNumber = 0; - - /** - * This loop continues until one of the following is true: - * - * 1. No more fixes have been applied. - * 2. 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++; - - 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); - - // stop if there are any syntax errors. - // 'fixedResult.output' is a empty string. - if (messages.length === 1 && messages[0].fatal) { - break; - } - - // 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 && - passNumber < MAX_PASSES - ); - - - /* - * If the last result had fixes, we need to lint again to be 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. * @param {Object} configHelper The configuration options for ESLint. * @param {string} filename An optional string representing the texts filename. - * @param {boolean} fix Indicates if fixes should be processed. + * @param {boolean|Function} fix Indicates if fixes should be processed. * @param {boolean} allowInlineConfig Allow/ignore comments that change config. + * @param {boolean} reportUnusedDisableDirectives Allow/ignore comments that change config. + * @param {Linter} linter Linter context * @returns {LintResult} The results for linting on this text. * @private */ -function processText(text, configHelper, filename, fix, allowInlineConfig) { - - // clear all existing settings for a new file - eslint.reset(); - +function processText(text, configHelper, filename, fix, allowInlineConfig, reportUnusedDisableDirectives, linter) { let filePath, - messages, fileExtension, - processor, - fixedResult; + processor; if (filename) { filePath = path.resolve(filename); @@ -224,10 +162,10 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) { const config = configHelper.getConfig(filePath); if (config.plugins) { - Plugins.loadAll(config.plugins); + configHelper.plugins.loadAll(config.plugins); } - const loadedPlugins = Plugins.getAll(); + const loadedPlugins = configHelper.plugins.getAll(); for (const plugin in loadedPlugins) { if (loadedPlugins[plugin].processors && Object.keys(loadedPlugins[plugin].processors).indexOf(fileExtension) >= 0) { @@ -236,48 +174,29 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) { } } - if (processor) { - debug("Using processor"); - const parsedBlocks = processor.preprocess(text, filename); - const unprocessedMessages = []; + const autofixingEnabled = typeof fix !== "undefined" && (!processor || processor.supportsAutofix); - parsedBlocks.forEach(block => { - unprocessedMessages.push(eslint.verify(block, config, { - filename, - allowInlineConfig - })); - }); - - // TODO(nzakas): Figure out how fixes might work for processors - - messages = processor.postprocess(unprocessedMessages, filename); - - } else { - - if (fix) { - fixedResult = multipassFix(text, config, { - filename, - allowInlineConfig - }); - messages = fixedResult.messages; - } else { - messages = eslint.verify(text, config, { - filename, - allowInlineConfig - }); - } - } + const fixedResult = linter.verifyAndFix(text, config, { + filename, + allowInlineConfig, + reportUnusedDisableDirectives, + fix: !!autofixingEnabled && fix, + preprocess: processor && (rawText => processor.preprocess(rawText, filename)), + postprocess: processor && (problemLists => processor.postprocess(problemLists, filename)) + }); - const stats = calculateStatsPerFile(messages); + const stats = calculateStatsPerFile(fixedResult.messages); const result = { filePath: filename, - messages, + messages: fixedResult.messages, errorCount: stats.errorCount, - warningCount: stats.warningCount + warningCount: stats.warningCount, + fixableErrorCount: stats.fixableErrorCount, + fixableWarningCount: stats.fixableWarningCount }; - if (fixedResult && fixedResult.fixed) { + if (fixedResult.fixed) { result.output = fixedResult.output; } @@ -294,13 +213,22 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) { * @param {string} filename The filename of the file being checked. * @param {Object} configHelper The configuration options for ESLint. * @param {Object} options The CLIEngine options object. + * @param {Linter} linter Linter context * @returns {LintResult} The results for linting on this file. * @private */ -function processFile(filename, configHelper, options) { +function processFile(filename, configHelper, options, linter) { const text = fs.readFileSync(path.resolve(filename), "utf8"), - result = processText(text, configHelper, filename, options.fix, options.allowInlineConfig); + result = processText( + text, + configHelper, + filename, + options.fix, + options.allowInlineConfig, + options.reportUnusedDisableDirectives, + linter + ); return result; @@ -316,8 +244,8 @@ function processFile(filename, configHelper, options) { function createIgnoreResult(filePath, baseDir) { let message; const isHidden = /^\./.test(path.basename(filePath)); - const isInNodeModules = baseDir && /^node_modules/.test(path.relative(baseDir, filePath)); - const isInBowerComponents = baseDir && /^bower_components/.test(path.relative(baseDir, filePath)); + const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules"); + const isInBowerComponents = baseDir && path.relative(baseDir, filePath).startsWith("bower_components"); if (isHidden) { message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; @@ -339,7 +267,9 @@ function createIgnoreResult(filePath, baseDir) { } ], errorCount: 0, - warningCount: 1 + warningCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0 }; } @@ -375,7 +305,7 @@ function getCacheFile(cacheFile, cwd) { cacheFile = path.normalize(cacheFile); const resolvedCacheFile = path.resolve(cwd, cacheFile); - const looksLikeADirectory = cacheFile[cacheFile.length - 1 ] === path.sep; + const looksLikeADirectory = cacheFile[cacheFile.length - 1] === path.sep; /** * return the name for the cache file in case the provided parameter is a directory @@ -428,145 +358,120 @@ function getCacheFile(cacheFile, cwd) { return resolvedCacheFile; } +const configHashCache = new WeakMap(); + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ -/** - * Creates a new instance of the core CLI engine. - * @param {CLIEngineOptions} options The options for this instance. - * @constructor - */ -function CLIEngine(options) { - - options = Object.assign( - Object.create(null), - defaultOptions, - { cwd: process.cwd() }, - options - ); - - /** - * Stored options for this instance - * @type {Object} - */ - this.options = options; - - const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd); +class CLIEngine { /** - * Cache used to avoid operating on files that haven't changed since the - * last successful execution (e.g., file passed linting with no errors and - * no warnings). - * @type {Object} + * Creates a new instance of the core CLI engine. + * @param {CLIEngineOptions} options The options for this instance. + * @constructor */ - this._fileCache = fileEntryCache.create(cacheFile); - - // load in additional rules - if (this.options.rulePaths) { - const cwd = this.options.cwd; - - this.options.rulePaths.forEach(rulesdir => { - debug(`Loading rules from ${rulesdir}`); - rules.load(rulesdir, cwd); - }); - } - - Object.keys(this.options.rules || {}).forEach(name => { - validator.validateRuleOptions(name, this.options.rules[name], "CLI"); - }); -} - -/** - * Returns the formatter representing the given format or null if no formatter - * with the given name can be found. - * @param {string} [format] The name of the format to load or the path to a - * custom formatter. - * @returns {Function} The formatter function or null if not found. - */ -CLIEngine.getFormatter = function(format) { + constructor(options) { - let formatterPath; + options = Object.assign( + Object.create(null), + defaultOptions, + { cwd: process.cwd() }, + options + ); - // default is stylish - format = format || "stylish"; - - // only strings are valid formatters - if (typeof format === "string") { + /** + * Stored options for this instance + * @type {Object} + */ + this.options = options; + this.linter = new Linter(); - // replace \ with / for Windows compatibility - format = format.replace(/\\/g, "/"); + if (options.cache) { + const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd); + + /** + * Cache used to avoid operating on files that haven't changed since the + * last successful execution (e.g., file passed linting with no errors and + * no warnings). + * @type {Object} + */ + this._fileCache = fileEntryCache.create(cacheFile); + } - // if there's a slash, then it's a file - if (format.indexOf("/") > -1) { - const cwd = this.options ? this.options.cwd : process.cwd(); + // load in additional rules + if (this.options.rulePaths) { + const cwd = this.options.cwd; - formatterPath = path.resolve(cwd, format); - } else { - formatterPath = `./formatters/${format}`; + this.options.rulePaths.forEach(rulesdir => { + debug(`Loading rules from ${rulesdir}`); + this.linter.rules.load(rulesdir, cwd); + }); } - try { - return require(formatterPath); - } catch (ex) { - ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; - throw ex; + if (this.options.rules && Object.keys(this.options.rules).length) { + const loadedRules = this.linter.getRules(); + + Object.keys(this.options.rules).forEach(name => { + validator.validateRuleOptions(loadedRules.get(name), name, this.options.rules[name], "CLI"); + }); } - } else { - return null; + this.config = new Config(this.options, this.linter); } -}; -/** - * Returns results that only contains errors. - * @param {LintResult[]} results The results to filter. - * @returns {LintResult[]} The filtered results. - */ -CLIEngine.getErrorResults = function(results) { - const filtered = []; - - results.forEach(result => { - const filteredMessages = result.messages.filter(isErrorMessage); - - if (filteredMessages.length > 0) { - filtered.push( - Object.assign(result, { - messages: filteredMessages, - errorCount: filteredMessages.length, - warningCount: 0 - }) - ); - } - }); + getRules() { + return this.linter.getRules(); + } - return filtered; -}; + /** + * Returns results that only contains errors. + * @param {LintResult[]} results The results to filter. + * @returns {LintResult[]} The filtered results. + */ + static getErrorResults(results) { + const filtered = []; + + results.forEach(result => { + const filteredMessages = result.messages.filter(isErrorMessage); + + if (filteredMessages.length > 0) { + filtered.push( + Object.assign(result, { + messages: filteredMessages, + errorCount: filteredMessages.length, + warningCount: 0, + fixableErrorCount: result.fixableErrorCount, + fixableWarningCount: 0 + }) + ); + } + }); -/** - * Outputs fixes from the given results to files. - * @param {Object} report The report object created by CLIEngine. - * @returns {void} - */ -CLIEngine.outputFixes = function(report) { - report.results.filter(result => result.hasOwnProperty("output")).forEach(result => { - fs.writeFileSync(result.filePath, result.output); - }); -}; + return filtered; + } -CLIEngine.prototype = { + /** + * Outputs fixes from the given results to files. + * @param {Object} report The report object created by CLIEngine. + * @returns {void} + */ + static outputFixes(report) { + report.results.filter(result => result.hasOwnProperty("output")).forEach(result => { + fs.writeFileSync(result.filePath, result.output); + }); + } - constructor: CLIEngine, /** - * Add a plugin by passing it's configuration + * Add a plugin by passing its configuration * @param {string} name Name of the plugin. * @param {Object} pluginobject Plugin configuration object. * @returns {void} */ addPlugin(name, pluginobject) { - Plugins.define(name, pluginobject); - }, + this.config.plugins.define(name, pluginobject); + } /** * Resolves the patterns passed into executeOnFiles() into glob-based patterns @@ -576,7 +481,7 @@ CLIEngine.prototype = { */ resolveFileGlobPatterns(patterns) { return globUtil.resolveFileGlobPatterns(patterns, this.options); - }, + } /** * Executes the current configuration on an array of file and directory names. @@ -584,11 +489,14 @@ CLIEngine.prototype = { * @returns {Object} The results for all files that were linted. */ executeOnFiles(patterns) { - const results = [], - options = this.options, + const options = this.options, fileCache = this._fileCache, - configHelper = new Config(options); - let prevConfig; // the previous configuration used + configHelper = this.config; + const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd); + + if (!options.cache && fs.existsSync(cacheFile)) { + fs.unlinkSync(cacheFile); + } /** * Calculates the hash of the config file used to validate a given file @@ -598,41 +506,18 @@ CLIEngine.prototype = { function hashOfConfigFor(filename) { const config = configHelper.getConfig(filename); - if (!prevConfig) { - prevConfig = {}; - } - - // reuse the previously hashed config if the config hasn't changed - if (prevConfig.config !== config) { - - /* - * config changed so we need to calculate the hash of the config - * and the hash of the plugins being used - */ - prevConfig.config = config; - - const eslintVersion = pkg.version; - - prevConfig.hash = hash(`${eslintVersion}_${stringify(config)}`); + if (!configHashCache.has(config)) { + configHashCache.set(config, hash(`${pkg.version}_${stringify(config)}`)); } - return prevConfig.hash; + return configHashCache.get(config); } - /** - * Executes the linter on a file defined by the `filename`. Skips - * unsupported file extensions and any files that are already linted. - * @param {string} filename The resolved filename of the file to be linted - * @param {boolean} warnIgnored always warn when a file is ignored - * @returns {void} - */ - function executeOnFile(filename, warnIgnored) { - let hashOfConfig, - descriptor; - - if (warnIgnored) { - results.push(createIgnoreResult(filename, options.cwd)); - return; + const startTime = Date.now(); + const fileList = globUtil.listFilesToProcess(this.resolveFileGlobPatterns(patterns), options); + const results = fileList.map(fileInfo => { + if (fileInfo.ignored) { + return createIgnoreResult(fileInfo.filename, options.cwd); } if (options.cache) { @@ -642,15 +527,12 @@ CLIEngine.prototype = { * with the metadata and the flag that determines if * the file has changed */ - descriptor = fileCache.getFileDescriptor(filename); - const meta = descriptor.meta || {}; - - hashOfConfig = hashOfConfigFor(filename); - - const changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig; + const descriptor = fileCache.getFileDescriptor(fileInfo.filename); + const hashOfConfig = hashOfConfigFor(fileInfo.filename); + const changed = descriptor.changed || descriptor.meta.hashOfConfig !== hashOfConfig; if (!changed) { - debug(`Skipping file since hasn't changed: ${filename}`); + debug(`Skipping file since hasn't changed: ${fileInfo.filename}`); /* * Add the the cached results (always will be 0 error and @@ -658,74 +540,55 @@ CLIEngine.prototype = { * failed, in order to guarantee that next execution will * process those files as well. */ - results.push(descriptor.meta.results); - - // move to the next file - return; + return descriptor.meta.results; } - } else { - fileCache.destroy(); } - debug(`Processing ${filename}`); + debug(`Processing ${fileInfo.filename}`); - const res = processFile(filename, configHelper, options); - - if (options.cache) { + return processFile(fileInfo.filename, configHelper, options, this.linter); + }); - /* - * if a file contains errors or warnings we don't want to - * store the file in the cache so we can guarantee that - * next execution will also operate on this file - */ - if (res.errorCount > 0 || res.warningCount > 0) { - debug(`File has problems, skipping it: ${filename}`); + if (options.cache) { + results.forEach(result => { + if (result.messages.length) { - // remove the entry from the cache - fileCache.removeEntry(filename); + /* + * if a file contains errors or warnings we don't want to + * store the file in the cache so we can guarantee that + * next execution will also operate on this file + */ + fileCache.removeEntry(result.filePath); } else { /* * since the file passed we store the result here - * TODO: check this as we might not need to store the - * successful runs as it will always should be 0 errors and - * 0 warnings. + * TODO: it might not be necessary to store the results list in the cache, + * since it should always be 0 errors/warnings */ - descriptor.meta.hashOfConfig = hashOfConfig; - descriptor.meta.results = res; - } - } + const descriptor = fileCache.getFileDescriptor(result.filePath); - results.push(res); - } - - const startTime = Date.now(); - - - - patterns = this.resolveFileGlobPatterns(patterns); - const fileList = globUtil.listFilesToProcess(patterns, options); - - fileList.forEach(fileInfo => { - executeOnFile(fileInfo.filename, fileInfo.ignored); - }); - - const stats = calculateStatsPerRun(results); - - if (options.cache) { + descriptor.meta.hashOfConfig = hashOfConfigFor(result.filePath); + descriptor.meta.results = result; + } + }); // persist the cache to disk fileCache.reconcile(); } + const stats = calculateStatsPerRun(results); + debug(`Linting complete in: ${Date.now() - startTime}ms`); return { results, errorCount: stats.errorCount, - warningCount: stats.warningCount + warningCount: stats.warningCount, + fixableErrorCount: stats.fixableErrorCount, + fixableWarningCount: stats.fixableWarningCount }; - }, + } /** * Executes the current configuration on text. @@ -738,7 +601,7 @@ CLIEngine.prototype = { const results = [], options = this.options, - configHelper = new Config(options), + configHelper = this.config, ignoredPaths = new IgnoredPaths(options); // resolve filename based on options.cwd (for reporting, ignoredPaths also resolves) @@ -751,7 +614,17 @@ CLIEngine.prototype = { results.push(createIgnoreResult(filename, options.cwd)); } } else { - results.push(processText(text, configHelper, filename, options.fix, options.allowInlineConfig)); + results.push( + processText( + text, + configHelper, + filename, + options.fix, + options.allowInlineConfig, + options.reportUnusedDisableDirectives, + this.linter + ) + ); } const stats = calculateStatsPerRun(results); @@ -759,9 +632,11 @@ CLIEngine.prototype = { return { results, errorCount: stats.errorCount, - warningCount: stats.warningCount + warningCount: stats.warningCount, + fixableErrorCount: stats.fixableErrorCount, + fixableWarningCount: stats.fixableWarningCount }; - }, + } /** * Returns a configuration object for the given file based on the CLI options. @@ -771,10 +646,10 @@ CLIEngine.prototype = { * @returns {Object} A configuration object for the file. */ getConfigForFile(filePath) { - const configHelper = new Config(this.options); + const configHelper = this.config; return configHelper.getConfig(filePath); - }, + } /** * Checks if a given path is ignored by ESLint. @@ -786,12 +661,59 @@ CLIEngine.prototype = { const ignoredPaths = new IgnoredPaths(this.options); return ignoredPaths.contains(resolvedPath); - }, + } - getFormatter: CLIEngine.getFormatter + /** + * Returns the formatter representing the given format or null if no formatter + * with the given name can be found. + * @param {string} [format] The name of the format to load or the path to a + * custom formatter. + * @returns {Function} The formatter function or null if not found. + */ + getFormatter(format) { + + + // default is stylish + format = format || "stylish"; + + // only strings are valid formatters + if (typeof format === "string") { + + // replace \ with / for Windows compatibility + format = format.replace(/\\/g, "/"); + + const cwd = this.options ? this.options.cwd : process.cwd(); + const namespace = naming.getNamespaceFromTerm(format); + + let formatterPath; + + // if there's a slash, then it's a file + if (!namespace && format.indexOf("/") > -1) { + formatterPath = path.resolve(cwd, format); + } else { + try { + const npmFormat = naming.normalizePackageName(format, "eslint-formatter"); + + formatterPath = resolver.resolve(npmFormat, `${cwd}/node_modules`); + } catch (e) { + formatterPath = `./formatters/${format}`; + } + } + + try { + return require(formatterPath); + } catch (ex) { + ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; + throw ex; + } -}; + } else { + return null; + } + } +} CLIEngine.version = pkg.version; +CLIEngine.getFormatter = CLIEngine.prototype.getFormatter; module.exports = CLIEngine; diff --git a/lib/cli.js b/lib/cli.js index 640bd81baba6..6a5482bf9adf 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -17,7 +17,6 @@ const fs = require("fs"), path = require("path"), - shell = require("shelljs"), options = require("./options"), CLIEngine = require("./cli-engine"), mkdirp = require("mkdirp"), @@ -29,6 +28,17 @@ const debug = require("debug")("eslint:cli"); // Helpers //------------------------------------------------------------------------------ +/** + * Predicate function for whether or not to apply fixes in quiet mode. + * If a message is a warning, do not apply a fix. + * @param {LintResult} lintResult The lint result. + * @returns {boolean} True if the lint message is an error (and thus should be + * autofixed), false otherwise. + */ +function quietFixPredicate(lintResult) { + return lintResult.severity === 2; +} + /** * Translates the CLI options into the options expected by the CLIEngine. * @param {Object} cliOptions The CLI options to translate. @@ -53,8 +63,9 @@ function translateOptions(cliOptions) { cache: cliOptions.cache, cacheFile: cliOptions.cacheFile, cacheLocation: cliOptions.cacheLocation, - fix: cliOptions.fix, - allowInlineConfig: cliOptions.inlineConfig + fix: (cliOptions.fix || cliOptions.fixDryRun) && (cliOptions.quiet ? quietFixPredicate : true), + allowInlineConfig: cliOptions.inlineConfig, + reportUnusedDisableDirectives: cliOptions.reportUnusedDisableDirectives }; } @@ -83,7 +94,7 @@ function printResults(engine, results, format, outputFile) { if (outputFile) { const filePath = path.resolve(process.cwd(), outputFile); - if (shell.test("-d", filePath)) { + if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) { log.error("Cannot write to output file path, it is a directory: %s", outputFile); return false; } @@ -133,6 +144,8 @@ const cli = { const files = currentOptions._; + const useStdin = typeof text === "string"; + if (currentOptions.version) { // version from package.json log.info(`v${require("../package.json").version}`); @@ -141,7 +154,8 @@ const cli = { if (files.length) { log.error("The --print-config option must be used with exactly one file name."); return 1; - } else if (text) { + } + if (useStdin) { log.error("The --print-config option is not available for piped-in code."); return 1; } @@ -152,23 +166,27 @@ const cli = { log.info(JSON.stringify(fileConfig, null, " ")); return 0; - } else if (currentOptions.help || (!files.length && !text)) { + } else if (currentOptions.help || (!files.length && !useStdin)) { log.info(options.generateHelp()); } else { - debug(`Running on ${text ? "text" : "files"}`); + debug(`Running on ${useStdin ? "text" : "files"}`); + + if (currentOptions.fix && currentOptions.fixDryRun) { + log.error("The --fix option and the --fix-dry-run option cannot be used together."); + return 1; + } - // disable --fix for piped-in code until we know how to do it correctly - if (text && currentOptions.fix) { - log.error("The --fix option is not available for piped-in code."); + if (useStdin && currentOptions.fix) { + log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead."); return 1; } const engine = new CLIEngine(translateOptions(currentOptions)); - const report = text ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files); + const report = useStdin ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files); if (currentOptions.fix) { debug("Fix mode enabled - applying fixes"); diff --git a/lib/code-path-analysis/code-path-analyzer.js b/lib/code-path-analysis/code-path-analyzer.js index 539b5e18b3cf..1a4f7870baaa 100644 --- a/lib/code-path-analysis/code-path-analyzer.js +++ b/lib/code-path-analysis/code-path-analyzer.js @@ -154,7 +154,8 @@ function forwardCurrentToHead(analyzer, node) { analyzer.emitter.emit( "onCodePathSegmentEnd", currentSegment, - node); + node + ); } } } @@ -175,7 +176,8 @@ function forwardCurrentToHead(analyzer, node) { analyzer.emitter.emit( "onCodePathSegmentStart", headSegment, - node); + node + ); } } } @@ -202,7 +204,8 @@ function leaveFromCurrentSegment(analyzer, node) { analyzer.emitter.emit( "onCodePathSegmentEnd", currentSegment, - node); + node + ); } } @@ -369,7 +372,8 @@ function processCodePathToEnter(analyzer, node) { case "SwitchStatement": state.pushSwitchContext( node.cases.some(isCaseNode), - astUtils.getLabel(node)); + astUtils.getLabel(node) + ); break; case "TryStatement": @@ -594,8 +598,10 @@ class CodePathAnalyzer { preprocess(this, node); } - // Updates the code path. - // And emits onCodePathStart/onCodePathSegmentStart events. + /* + * Updates the code path. + * And emits onCodePathStart/onCodePathSegmentStart events. + */ processCodePathToEnter(this, node); // Emits node events. @@ -614,8 +620,10 @@ class CodePathAnalyzer { leaveNode(node) { this.currentNode = node; - // Updates the code path. - // And emits onCodePathStart/onCodePathSegmentStart events. + /* + * Updates the code path. + * And emits onCodePathStart/onCodePathSegmentStart events. + */ processCodePathToExit(this, node); // Emits node events. diff --git a/lib/code-path-analysis/code-path-segment.js b/lib/code-path-analysis/code-path-segment.js index db1eba4560c2..8145f9280162 100644 --- a/lib/code-path-analysis/code-path-segment.js +++ b/lib/code-path-analysis/code-path-segment.js @@ -15,43 +15,6 @@ const debug = require("./debug-helpers"); // Helpers //------------------------------------------------------------------------------ -/** - * Replaces unused segments with the previous segments of each unused segment. - * - * @param {CodePathSegment[]} segments - An array of segments to replace. - * @returns {CodePathSegment[]} The replaced array. - */ -function flattenUnusedSegments(segments) { - const done = Object.create(null); - const retv = []; - - for (let i = 0; i < segments.length; ++i) { - const segment = segments[i]; - - // Ignores duplicated. - if (done[segment.id]) { - continue; - } - - // Use previous segments if unused. - if (!segment.internal.used) { - for (let j = 0; j < segment.allPrevSegments.length; ++j) { - const prevSegment = segment.allPrevSegments[j]; - - if (!done[prevSegment.id]) { - done[prevSegment.id] = true; - retv.push(prevSegment); - } - } - } else { - done[segment.id] = true; - retv.push(segment); - } - } - - return retv; -} - /** * Checks whether or not a given segment is reachable. * @@ -163,8 +126,9 @@ class CodePathSegment { static newNext(id, allPrevSegments) { return new CodePathSegment( id, - flattenUnusedSegments(allPrevSegments), - allPrevSegments.some(isReachable)); + CodePathSegment.flattenUnusedSegments(allPrevSegments), + allPrevSegments.some(isReachable) + ); } /** @@ -175,10 +139,12 @@ class CodePathSegment { * @returns {CodePathSegment} The created segment. */ static newUnreachable(id, allPrevSegments) { - const segment = new CodePathSegment(id, flattenUnusedSegments(allPrevSegments), false); + const segment = new CodePathSegment(id, CodePathSegment.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. + /* + * 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; @@ -237,6 +203,43 @@ class CodePathSegment { static markPrevSegmentAsLooped(segment, prevSegment) { segment.internal.loopedPrevSegments.push(prevSegment); } + + /** + * Replaces unused segments with the previous segments of each unused segment. + * + * @param {CodePathSegment[]} segments - An array of segments to replace. + * @returns {CodePathSegment[]} The replaced array. + */ + static flattenUnusedSegments(segments) { + const done = Object.create(null); + const retv = []; + + for (let i = 0; i < segments.length; ++i) { + const segment = segments[i]; + + // Ignores duplicated. + if (done[segment.id]) { + continue; + } + + // Use previous segments if unused. + if (!segment.internal.used) { + for (let j = 0; j < segment.allPrevSegments.length; ++j) { + const prevSegment = segment.allPrevSegments[j]; + + if (!done[prevSegment.id]) { + done[prevSegment.id] = true; + retv.push(prevSegment); + } + } + } else { + done[segment.id] = true; + retv.push(segment); + } + } + + return retv; + } } module.exports = CodePathSegment; diff --git a/lib/code-path-analysis/code-path-state.js b/lib/code-path-analysis/code-path-state.js index 3faff3ebb859..0c31e2072b00 100644 --- a/lib/code-path-analysis/code-path-state.js +++ b/lib/code-path-analysis/code-path-state.js @@ -169,6 +169,9 @@ function removeConnection(prevSegments, nextSegments) { * @returns {void} */ function makeLooped(state, fromSegments, toSegments) { + fromSegments = CodePathSegment.flattenUnusedSegments(fromSegments); + toSegments = CodePathSegment.flattenUnusedSegments(toSegments); + const end = Math.min(fromSegments.length, toSegments.length); for (let i = 0; i < end; ++i) { @@ -240,7 +243,7 @@ class CodePathState { this.breakContext = null; this.currentSegments = []; - this.initialSegment = this.forkContext.head[ 0 ]; + this.initialSegment = this.forkContext.head[0]; // returnedSegments and thrownSegments push elements into finalSegments also. const final = this.finalSegments = []; @@ -771,8 +774,10 @@ class CodePathState { // Sets the normal path as the next. this.forkContext.replaceHead(normalSegments); - // If both paths of the `try` block and the `catch` block are - // unreachable, the next path becomes unreachable as well. + /* + * If both paths of the `try` block and the `catch` block are + * unreachable, the next path becomes unreachable as well. + */ if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) { this.forkContext.makeUnreachable(); } @@ -843,21 +848,23 @@ class CodePathState { * This segment will leave at the end of this finally block. */ const segments = forkContext.makeNext(-1, -1); - let j; for (let i = 0; i < forkContext.count; ++i) { const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]]; - for (j = 0; j < returned.segmentsList.length; ++j) { + for (let j = 0; j < returned.segmentsList.length; ++j) { prevSegsOfLeavingSegment.push(returned.segmentsList[j][i]); } - for (j = 0; j < thrown.segmentsList.length; ++j) { + for (let j = 0; j < thrown.segmentsList.length; ++j) { prevSegsOfLeavingSegment.push(thrown.segmentsList[j][i]); } - segments.push(CodePathSegment.newNext( - this.idGenerator.next(), - prevSegsOfLeavingSegment)); + segments.push( + CodePathSegment.newNext( + this.idGenerator.next(), + prevSegsOfLeavingSegment + ) + ); } this.pushForkContext(true); @@ -982,21 +989,21 @@ class CodePathState { const forkContext = this.forkContext; const brokenForkContext = this.popBreakContext().brokenForkContext; - let choiceContext; // Creates a looped path. switch (context.type) { case "WhileStatement": case "ForStatement": - choiceContext = this.popChoiceContext(); + this.popChoiceContext(); makeLooped( this, forkContext.head, - context.continueDestSegments); + context.continueDestSegments + ); break; case "DoWhileStatement": { - choiceContext = this.popChoiceContext(); + const choiceContext = this.popChoiceContext(); if (!choiceContext.processed) { choiceContext.trueForkContext.add(forkContext.head); @@ -1013,7 +1020,8 @@ class CodePathState { makeLooped( this, segmentsList[i], - context.entrySegments); + context.entrySegments + ); } break; } @@ -1024,7 +1032,8 @@ class CodePathState { makeLooped( this, forkContext.head, - context.leftSegments); + context.leftSegments + ); break; /* istanbul ignore next */ @@ -1149,7 +1158,8 @@ class CodePathState { finalizeTestSegmentsOfFor( context, choiceContext, - forkContext.head); + forkContext.head + ); } else { context.endOfInitSegments = forkContext.head; } @@ -1180,13 +1190,15 @@ class CodePathState { makeLooped( this, context.endOfUpdateSegments, - context.testSegments); + context.testSegments + ); } } else if (context.testSegments) { finalizeTestSegmentsOfFor( context, choiceContext, - forkContext.head); + forkContext.head + ); } else { context.endOfInitSegments = forkContext.head; } diff --git a/lib/code-path-analysis/code-path.js b/lib/code-path-analysis/code-path.js index 6ef07b4a2d93..709a1111890d 100644 --- a/lib/code-path-analysis/code-path.js +++ b/lib/code-path-analysis/code-path.js @@ -51,7 +51,8 @@ class CodePath { Object.defineProperty( this, "internal", - { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) }); + { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) } + ); // Adds this into `childCodePaths` of `upper`. if (upper) { @@ -205,7 +206,7 @@ class CodePath { // Call the callback when the first time. if (!skippedSegment) { - callback.call(this, segment, controller); // eslint-disable-line callback-return + callback.call(this, segment, controller); if (segment === lastSegment) { controller.skip(); } diff --git a/lib/code-path-analysis/fork-context.js b/lib/code-path-analysis/fork-context.js index 7423c13199b3..4fae6bbb1e8a 100644 --- a/lib/code-path-analysis/fork-context.js +++ b/lib/code-path-analysis/fork-context.js @@ -254,7 +254,8 @@ class ForkContext { return new ForkContext( parentContext.idGenerator, parentContext, - (forkLeavingPath ? 2 : 1) * parentContext.count); + (forkLeavingPath ? 2 : 1) * parentContext.count + ); } } diff --git a/lib/config.js b/lib/config.js index 03fda87c973c..b66b9f41e0d8 100644 --- a/lib/config.js +++ b/lib/config.js @@ -10,13 +10,13 @@ //------------------------------------------------------------------------------ const path = require("path"), + os = require("os"), ConfigOps = require("./config/config-ops"), ConfigFile = require("./config/config-file"), + ConfigCache = require("./config/config-cache"), Plugins = require("./config/plugins"), FileFinder = require("./file-finder"), - userHome = require("user-home"), - isResolvable = require("is-resolvable"), - pathIsInside = require("path-is-inside"); + isResolvable = require("is-resolvable"); const debug = require("debug")("eslint:config"); @@ -24,156 +24,23 @@ const debug = require("debug")("eslint:config"); // Constants //------------------------------------------------------------------------------ -const PERSONAL_CONFIG_DIR = userHome || null; +const PERSONAL_CONFIG_DIR = os.homedir(); +const SUBCONFIG_SEP = ":"; //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** - * Check if item is an javascript object - * @param {*} item object to check for - * @returns {boolean} True if its an object - * @private - */ -function isObject(item) { - return typeof item === "object" && !Array.isArray(item) && item !== null; -} - -/** - * Load and parse a JSON config object from a file. - * @param {string|Object} configToLoad the path to the JSON config file or the config object itself. - * @returns {Object} the parsed config object (empty object if there was a parse error) - * @private - */ -function loadConfig(configToLoad) { - let config = {}, - filePath = ""; - - if (configToLoad) { - - if (isObject(configToLoad)) { - config = configToLoad; - - if (config.extends) { - config = ConfigFile.applyExtends(config, filePath); - } - } else { - filePath = configToLoad; - config = ConfigFile.load(filePath); - } - - } - - return config; -} - -/** - * Get personal config object from ~/.eslintrc. - * @returns {Object} the personal config object (null if there is no personal config) - * @private - */ -function getPersonalConfig() { - let config; - - if (PERSONAL_CONFIG_DIR) { - const filename = ConfigFile.getFilenameForDirectory(PERSONAL_CONFIG_DIR); - - if (filename) { - debug("Using personal config"); - config = loadConfig(filename); - } - } - - return config || null; -} - -/** - * Determine if rules were explicitly passed in as options. + * Determines if any rules were explicitly passed in as options. * @param {Object} options The options used to create our configuration. * @returns {boolean} True if rules were passed in as options, false otherwise. + * @private */ function hasRules(options) { return options.rules && Object.keys(options.rules).length > 0; } -/** - * Get a local config object. - * @param {Object} thisConfig A Config object. - * @param {string} directory The directory to start looking in for a local config file. - * @returns {Object} The local config object, or an empty object if there is no local config. - */ -function getLocalConfig(thisConfig, directory) { - const localConfigFiles = thisConfig.findLocalConfigFiles(directory), - numFiles = localConfigFiles.length, - projectConfigPath = ConfigFile.getFilenameForDirectory(thisConfig.options.cwd); - let found, - config = {}, - rootPath; - - for (let i = 0; i < numFiles; i++) { - - const localConfigFile = localConfigFiles[i]; - - // Don't consider the personal config file in the home directory, - // except if the home directory is the same as the current working directory - if (path.dirname(localConfigFile) === PERSONAL_CONFIG_DIR && localConfigFile !== projectConfigPath) { - continue; - } - - // If root flag is set, don't consider file if it is above root - if (rootPath && !pathIsInside(path.dirname(localConfigFile), rootPath)) { - continue; - } - - debug(`Loading ${localConfigFile}`); - const localConfig = loadConfig(localConfigFile); - - // Don't consider a local config file found if the config is null - if (!localConfig) { - continue; - } - - // Check for root flag - if (localConfig.root === true) { - rootPath = path.dirname(localConfigFile); - } - - found = true; - debug(`Using ${localConfigFile}`); - config = ConfigOps.merge(localConfig, config); - } - - if (!found && !thisConfig.useSpecificConfig) { - - /* - * - Is there a personal config in the user's home directory? If so, - * merge that with the passed-in config. - * - Otherwise, if no rules were manually passed in, throw and error. - * - Note: This function is not called if useEslintrc is false. - */ - const personalConfig = getPersonalConfig(); - - if (personalConfig) { - config = ConfigOps.merge(config, personalConfig); - } else if (!hasRules(thisConfig.options) && !thisConfig.options.baseConfig) { - - // No config file, no manual configuration, and no rules, so error. - const noConfigError = new Error("No ESLint configuration found."); - - noConfigError.messageTemplate = "no-config-found"; - noConfigError.messageData = { - directory, - filesExamined: localConfigFiles - }; - - throw noConfigError; - } - } - - return config; -} - //------------------------------------------------------------------------------ // API //------------------------------------------------------------------------------ @@ -184,24 +51,36 @@ function getLocalConfig(thisConfig, directory) { class Config { /** - * Config options * @param {Object} options Options to be passed in + * @param {Linter} linterContext Linter instance object */ - constructor(options) { + constructor(options, linterContext) { options = options || {}; + this.linterContext = linterContext; + this.plugins = new Plugins(linterContext.environments, linterContext.rules); + + this.options = options; this.ignore = options.ignore; this.ignorePath = options.ignorePath; - this.cache = {}; this.parser = options.parser; this.parserOptions = options.parserOptions || {}; - this.baseConfig = options.baseConfig ? loadConfig(options.baseConfig) : { rules: {} }; + this.configCache = new ConfigCache(); + + this.baseConfig = options.baseConfig + ? ConfigOps.merge({}, ConfigFile.loadObject(options.baseConfig, this)) + : { rules: {} }; + this.baseConfig.filePath = ""; + this.baseConfig.baseDirectory = this.options.cwd; + + this.configCache.setConfig(this.baseConfig.filePath, this.baseConfig); + this.configCache.setMergedVectorConfig(this.baseConfig.filePath, this.baseConfig); this.useEslintrc = (options.useEslintrc !== false); this.env = (options.envs || []).reduce((envs, name) => { - envs[ name ] = true; + envs[name] = true; return envs; }, {}); @@ -212,127 +91,275 @@ class Config { * If user declares "foo", convert to "foo:false". */ this.globals = (options.globals || []).reduce((globals, def) => { - const parts = def.split(":"); + const parts = def.split(SUBCONFIG_SEP); globals[parts[0]] = (parts.length > 1 && parts[1] === "true"); return globals; }, {}); - const useConfig = options.configFile; + this.loadSpecificConfig(options.configFile); - this.options = options; + // Empty values in configs don't merge properly + const cliConfigOptions = { + env: this.env, + rules: this.options.rules, + globals: this.globals, + parserOptions: this.parserOptions, + plugins: this.options.plugins + }; - if (useConfig) { - debug(`Using command line config ${useConfig}`); - if (isResolvable(useConfig) || isResolvable(`eslint-config-${useConfig}`) || useConfig.charAt(0) === "@") { - this.useSpecificConfig = loadConfig(useConfig); - } else { - this.useSpecificConfig = loadConfig(path.resolve(this.options.cwd, useConfig)); + this.cliConfig = {}; + Object.keys(cliConfigOptions).forEach(configKey => { + const value = cliConfigOptions[configKey]; + + if (value) { + this.cliConfig[configKey] = value; } - } + }); } /** - * Build a config object merging the base config (conf/eslint-recommended), - * the environments config (conf/environments.js) and eventually the user - * config. - * @param {string} filePath a file in whose directory we start looking for a local config - * @returns {Object} config object + * Loads the config options from a config specified on the command line. + * @param {string} [config] A shareable named config or path to a config file. + * @returns {void} */ - getConfig(filePath) { - const directory = filePath ? path.dirname(filePath) : this.options.cwd; - let config, - userConfig; + loadSpecificConfig(config) { + if (config) { + debug(`Using command line config ${config}`); + const isNamedConfig = + isResolvable(config) || + isResolvable(`eslint-config-${config}`) || + config.charAt(0) === "@"; + + if (!isNamedConfig) { + config = path.resolve(this.options.cwd, config); + } - debug(`Constructing config for ${filePath ? filePath : "text"}`); + this.specificConfig = ConfigFile.load(config, this); + } + } - config = this.cache[directory]; + /** + * Gets the personal config object from user's home directory. + * @returns {Object} the personal config object (null if there is no personal config) + * @private + */ + getPersonalConfig() { + if (typeof this.personalConfig === "undefined") { + let config; + const filename = ConfigFile.getFilenameForDirectory(PERSONAL_CONFIG_DIR); + + if (filename) { + debug("Using personal config"); + config = ConfigFile.load(filename, this); + } - if (config) { - debug("Using config from cache"); - return config; + this.personalConfig = config || null; } - // Step 1: Determine user-specified config from .eslintrc.* and package.json files + return this.personalConfig; + } + + /** + * Builds a hierarchy of config objects, including the base config, all local configs from the directory tree, + * and a config file specified on the command line, if applicable. + * @param {string} directory a file in whose directory we start looking for a local config + * @returns {Object[]} The config objects, in ascending order of precedence + * @private + */ + getConfigHierarchy(directory) { + debug(`Constructing config file hierarchy for ${directory}`); + + // Step 1: Always include baseConfig + let configs = [this.baseConfig]; + + // Step 2: Add user-specified config from .eslintrc.* and package.json files if (this.useEslintrc) { debug("Using .eslintrc and package.json files"); - userConfig = getLocalConfig(this, directory); + configs = configs.concat(this.getLocalConfigHierarchy(directory)); } else { debug("Not using .eslintrc or package.json files"); - userConfig = {}; } - // Step 2: Create a copy of the baseConfig - config = ConfigOps.merge({}, this.baseConfig); + // Step 3: Merge in command line config file + if (this.specificConfig) { + debug("Using command line config file"); + configs.push(this.specificConfig); + } - // Step 3: Merge in the user-specified configuration from .eslintrc and package.json - config = ConfigOps.merge(config, userConfig); + return configs; + } - // Step 4: Merge in command line config file - if (this.useSpecificConfig) { - debug("Merging command line config file"); + /** + * Gets a list of config objects extracted from local config files that apply to the current directory, in + * descending order, beginning with the config that is highest in the directory tree. + * @param {string} directory The directory to start looking in for local config files. + * @returns {Object[]} The shallow local config objects, in ascending order of precedence (closest to the current + * directory at the end), or an empty array if there are no local configs. + * @private + */ + getLocalConfigHierarchy(directory) { + const localConfigFiles = this.findLocalConfigFiles(directory), + projectConfigPath = ConfigFile.getFilenameForDirectory(this.options.cwd), + searched = [], + configs = []; - config = ConfigOps.merge(config, this.useSpecificConfig); - } + for (const localConfigFile of localConfigFiles) { + const localConfigDirectory = path.dirname(localConfigFile); + const localConfigHierarchyCache = this.configCache.getHierarchyLocalConfigs(localConfigDirectory); - // Step 5: Merge in command line environments - debug("Merging command line environment settings"); - config = ConfigOps.merge(config, { env: this.env }); + if (localConfigHierarchyCache) { + const localConfigHierarchy = localConfigHierarchyCache.concat(configs.reverse()); - // Step 6: Merge in command line rules - if (this.options.rules) { - debug("Merging command line rules"); - config = ConfigOps.merge(config, { rules: this.options.rules }); - } + this.configCache.setHierarchyLocalConfigs(searched, localConfigHierarchy); + return localConfigHierarchy; + } - // Step 7: Merge in command line globals - config = ConfigOps.merge(config, { globals: this.globals }); + /* + * Don't consider the personal config file in the home directory, + * except if the home directory is the same as the current working directory + */ + if (localConfigDirectory === PERSONAL_CONFIG_DIR && localConfigFile !== projectConfigPath) { + continue; + } - // Only override parser if it is passed explicitly through the command line or if it's not - // defined yet (because the final object will at least have the parser key) - if (this.parser || !config.parser) { - config = ConfigOps.merge(config, { - parser: this.parser - }); - } + debug(`Loading ${localConfigFile}`); + const localConfig = ConfigFile.load(localConfigFile, this); - if (this.parserOptions) { - config = ConfigOps.merge(config, { - parserOptions: this.parserOptions - }); - } + // Ignore empty config files + if (!localConfig) { + continue; + } + + debug(`Using ${localConfigFile}`); + configs.push(localConfig); + searched.push(localConfigDirectory); - // Step 8: Merge in command line plugins - if (this.options.plugins) { - debug("Merging command line plugins"); - Plugins.loadAll(this.options.plugins); - config = ConfigOps.merge(config, { plugins: this.options.plugins }); + // Stop traversing if a config is found with the root flag set + if (localConfig.root) { + break; + } } - // Step 9: Apply environments to the config if present - if (config.env) { - config = ConfigOps.applyEnvironments(config); + if (!configs.length && !this.specificConfig) { + + // Fall back on the personal config from ~/.eslintrc + debug("Using personal config file"); + const personalConfig = this.getPersonalConfig(); + + if (personalConfig) { + configs.push(personalConfig); + } else if (!hasRules(this.options) && !this.options.baseConfig) { + + // No config file, no manual configuration, and no rules, so error. + const noConfigError = new Error("No ESLint configuration found."); + + noConfigError.messageTemplate = "no-config-found"; + noConfigError.messageData = { + directory, + filesExamined: localConfigFiles + }; + + throw noConfigError; + } } - this.cache[directory] = config; + // Set the caches for the parent directories + this.configCache.setHierarchyLocalConfigs(searched, configs.reverse()); - return config; + return configs; + } + + /** + * Gets the vector of applicable configs and subconfigs from the hierarchy for a given file. A vector is an array of + * entries, each of which in an object specifying a config file path and an array of override indices corresponding + * to entries in the config file's overrides section whose glob patterns match the specified file path; e.g., the + * vector entry { configFile: '/home/john/app/.eslintrc', matchingOverrides: [0, 2] } would indicate that the main + * project .eslintrc file and its first and third override blocks apply to the current file. + * @param {string} filePath The file path for which to build the hierarchy and config vector. + * @returns {Array} config vector applicable to the specified path + * @private + */ + getConfigVector(filePath) { + const directory = filePath ? path.dirname(filePath) : this.options.cwd; + + return this.getConfigHierarchy(directory).map(config => { + const vectorEntry = { + filePath: config.filePath, + matchingOverrides: [] + }; + + if (config.overrides) { + const relativePath = path.relative(config.baseDirectory, filePath || directory); + + config.overrides.forEach((override, i) => { + if (ConfigOps.pathMatchesGlobs(relativePath, override.files, override.excludedFiles)) { + vectorEntry.matchingOverrides.push(i); + } + }); + } + + return vectorEntry; + }); } /** - * Find local config files from directory and parent directories. + * Finds local config files from the specified directory and its parent directories. * @param {string} directory The directory to start searching from. - * @returns {string[]} The paths of local config files found. + * @returns {GeneratorFunction} The paths of local config files found. */ findLocalConfigFiles(directory) { - if (!this.localConfigFinder) { this.localConfigFinder = new FileFinder(ConfigFile.CONFIG_FILES, this.options.cwd); } return this.localConfigFinder.findAllInDirectoryAndParents(directory); } + + /** + * Builds the authoritative config object for the specified file path by merging the hierarchy of config objects + * that apply to the current file, including the base config (conf/eslint-recommended), the user's personal config + * from their homedir, all local configs from the directory tree, any specific config file passed on the command + * line, any configuration overrides set directly on the command line, and finally the environment configs + * (conf/environments). + * @param {string} filePath a file in whose directory we start looking for a local config + * @returns {Object} config object + */ + getConfig(filePath) { + const vector = this.getConfigVector(filePath); + let config = this.configCache.getMergedConfig(vector); + + if (config) { + debug("Using config from cache"); + return config; + } + + // Step 1: Merge in the filesystem configurations (base, local, and personal) + config = ConfigOps.getConfigFromVector(vector, this.configCache); + + // Step 2: Merge in command line configurations + config = ConfigOps.merge(config, this.cliConfig); + + if (this.cliConfig.plugins) { + this.plugins.loadAll(this.cliConfig.plugins); + } + + /* + * Step 3: Override parser only if it is passed explicitly through the command line + * or if it's not defined yet (because the final object will at least have the parser key) + */ + if (this.parser || !config.parser) { + config = ConfigOps.merge(config, { parser: this.parser }); + } + + // Step 4: Apply environments to the config + config = ConfigOps.applyEnvironments(config, this.linterContext.environments); + + this.configCache.setMergedConfig(vector, config); + + return config; + } } module.exports = Config; diff --git a/lib/config/autoconfig.js b/lib/config/autoconfig.js index 4a50ce25cdd2..8536fdc55ad7 100644 --- a/lib/config/autoconfig.js +++ b/lib/config/autoconfig.js @@ -10,12 +10,13 @@ //------------------------------------------------------------------------------ const lodash = require("lodash"), - eslint = require("../eslint"), + Linter = require("../linter"), configRule = require("./config-rule"), ConfigOps = require("./config-ops"), recConfig = require("../../conf/eslint-recommended"); const debug = require("debug")("eslint:autoconfig"); +const linter = new Linter(); //------------------------------------------------------------------------------ // Data @@ -37,11 +38,11 @@ const MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only * @param {number} errorCount The number of errors encountered when linting with the config */ - /** - * This callback is used to measure execution status in a progress bar - * @callback progressCallback - * @param {number} The total number of times the callback will be called. - */ +/** + * This callback is used to measure execution status in a progress bar + * @callback progressCallback + * @param {number} The total number of times the callback will be called. + */ /** * Create registryItems for rules @@ -60,13 +61,13 @@ function makeRegistryItems(rulesConfig) { } /** -* Creates an object in which to store rule configs and error counts -* -* Unless a rulesConfig is provided at construction, the registry will not contain -* any rules, only methods. This will be useful for building up registries manually. -* -* Registry class -*/ + * Creates an object in which to store rule configs and error counts + * + * Unless a rulesConfig is provided at construction, the registry will not contain + * any rules, only methods. This will be useful for building up registries manually. + * + * Registry class + */ class Registry { /** @@ -268,10 +269,8 @@ class Registry { * @returns {Registry} New registry with errorCount populated */ lintSourceCode(sourceCodes, config, cb) { - let ruleSetIdx, - lintedRegistry; + let lintedRegistry = new Registry(); - lintedRegistry = new Registry(); lintedRegistry.rules = Object.assign({}, this.rules); const ruleSets = lintedRegistry.buildRuleSets(); @@ -286,19 +285,21 @@ class Registry { filenames.forEach(filename => { debug(`Linting file: ${filename}`); - ruleSetIdx = 0; + let ruleSetIdx = 0; ruleSets.forEach(ruleSet => { const lintConfig = Object.assign({}, config, { rules: ruleSet }); - const lintResults = eslint.verify(sourceCodes[filename], lintConfig); + const lintResults = linter.verify(sourceCodes[filename], lintConfig); lintResults.forEach(result => { - // 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) - // (https://github.com/eslint/eslint/issues/7860) + /* + * 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) + * (https://github.com/eslint/eslint/issues/7860) + */ if ( lintedRegistry.rules[result.ruleId] && lintedRegistry.rules[result.ruleId][ruleSetIdx] @@ -310,7 +311,7 @@ class Registry { ruleSetIdx += 1; if (cb) { - cb(totalFilesLinting); // eslint-disable-line callback-return + cb(totalFilesLinting); // eslint-disable-line callback-return } }); diff --git a/lib/config/config-cache.js b/lib/config/config-cache.js new file mode 100644 index 000000000000..07436a87c8fc --- /dev/null +++ b/lib/config/config-cache.js @@ -0,0 +1,130 @@ +/** + * @fileoverview Responsible for caching config files + * @author Sylvan Mably + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Get a string hash for a config vector + * @param {Array} vector config vector to hash + * @returns {string} hash of the vector values + * @private + */ +function hash(vector) { + return JSON.stringify(vector); +} + +//------------------------------------------------------------------------------ +// API +//------------------------------------------------------------------------------ + +/** + * Configuration caching class + */ +module.exports = class ConfigCache { + + constructor() { + this.configFullNameCache = new Map(); + this.localHierarchyCache = new Map(); + this.mergedVectorCache = new Map(); + this.mergedCache = new Map(); + } + + /** + * Gets a config object from the cache for the specified config file path. + * @param {string} configFullName the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'), + * or the absolute path to a config file. This should uniquely identify a config. + * @returns {Object|null} config object, if found in the cache, otherwise null + * @private + */ + getConfig(configFullName) { + return this.configFullNameCache.get(configFullName); + } + + /** + * Sets a config object in the cache for the specified config file path. + * @param {string} configFullName the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'), + * or the absolute path to a config file. This should uniquely identify a config. + * @param {Object} config the config object to add to the cache + * @returns {void} + * @private + */ + setConfig(configFullName, config) { + this.configFullNameCache.set(configFullName, config); + } + + /** + * Gets a list of hierarchy-local config objects that apply to the specified directory. + * @param {string} directory the path to the directory + * @returns {Object[]|null} a list of config objects, if found in the cache, otherwise null + * @private + */ + getHierarchyLocalConfigs(directory) { + return this.localHierarchyCache.get(directory); + } + + /** + * For each of the supplied parent directories, sets the list of config objects for that directory to the + * appropriate subset of the supplied parent config objects. + * @param {string[]} parentDirectories a list of parent directories to add to the config cache + * @param {Object[]} parentConfigs a list of config objects that apply to the lowest directory in parentDirectories + * @returns {void} + * @private + */ + setHierarchyLocalConfigs(parentDirectories, parentConfigs) { + parentDirectories.forEach((localConfigDirectory, i) => { + const directoryParentConfigs = parentConfigs.slice(0, parentConfigs.length - i); + + this.localHierarchyCache.set(localConfigDirectory, directoryParentConfigs); + }); + } + + /** + * Gets a merged config object corresponding to the supplied vector. + * @param {Array} vector the vector to find a merged config for + * @returns {Object|null} a merged config object, if found in the cache, otherwise null + * @private + */ + getMergedVectorConfig(vector) { + return this.mergedVectorCache.get(hash(vector)); + } + + /** + * Sets a merged config object in the cache for the supplied vector. + * @param {Array} vector the vector to save a merged config for + * @param {Object} config the merged config object to add to the cache + * @returns {void} + * @private + */ + setMergedVectorConfig(vector, config) { + this.mergedVectorCache.set(hash(vector), config); + } + + /** + * Gets a merged config object corresponding to the supplied vector, including configuration options from outside + * the vector. + * @param {Array} vector the vector to find a merged config for + * @returns {Object|null} a merged config object, if found in the cache, otherwise null + * @private + */ + getMergedConfig(vector) { + return this.mergedCache.get(hash(vector)); + } + + /** + * Sets a merged config object in the cache for the supplied vector, including configuration options from outside + * the vector. + * @param {Array} vector the vector to save a merged config for + * @param {Object} config the merged config object to add to the cache + * @returns {void} + * @private + */ + setMergedConfig(vector, config) { + this.mergedCache.set(hash(vector), config); + } +}; diff --git a/lib/config/config-file.js b/lib/config/config-file.js index 4e886b8af27b..c5ff073cfcb9 100644 --- a/lib/config/config-file.js +++ b/lib/config/config-file.js @@ -3,8 +3,6 @@ * @author Nicholas C. Zakas */ -/* eslint no-use-before-define: 0 */ - "use strict"; //------------------------------------------------------------------------------ @@ -13,17 +11,13 @@ const fs = require("fs"), path = require("path"), - shell = require("shelljs"), ConfigOps = require("./config-ops"), validator = require("./config-validator"), - Plugins = require("./plugins"), - pathUtil = require("../util/path-util"), ModuleResolver = require("../util/module-resolver"), + naming = require("../util/naming"), pathIsInside = require("path-is-inside"), - stripBom = require("strip-bom"), stripComments = require("strip-json-comments"), - stringify = require("json-stable-stringify"), - defaultOptions = require("../../conf/eslint-recommended"), + stringify = require("json-stable-stringify-without-jsonify"), requireUncached = require("require-uncached"); const debug = require("debug")("eslint:config-file"); @@ -35,7 +29,7 @@ const debug = require("debug")("eslint:config-file"); /** * Determines sort order for object keys for json-stable-stringify * - * see: https://github.com/substack/json-stable-stringify#cmp + * see: https://github.com/samn/json-stable-stringify#cmp * * @param {Object} a The first comparison object ({key: akey, value: avalue}) * @param {Object} b The second comparison object ({key: bkey, value: bvalue}) @@ -63,11 +57,11 @@ const resolver = new ModuleResolver(); /** * Convenience wrapper for synchronously reading file contents. * @param {string} filePath The filename to read. - * @returns {string} The file contents. + * @returns {string} The file contents, with the BOM removed. * @private */ function readFile(filePath) { - return stripBom(fs.readFileSync(filePath, "utf8")); + return fs.readFileSync(filePath, "utf8").replace(/^\ufeff/, ""); } /** @@ -368,18 +362,18 @@ function getLookupPath(configFilePath) { function getEslintCoreConfigPath(name) { if (name === "eslint:recommended") { - /* - * Add an explicit substitution for eslint:recommended to - * conf/eslint-recommended.js. - */ + /* + * Add an explicit substitution for eslint:recommended to + * conf/eslint-recommended.js. + */ return path.resolve(__dirname, "../../conf/eslint-recommended.js"); } if (name === "eslint:all") { - /* - * Add an explicit substitution for eslint:all to conf/eslint-all.js - */ + /* + * Add an explicit substitution for eslint:all to conf/eslint-all.js + */ return path.resolve(__dirname, "../../conf/eslint-all.js"); } @@ -389,6 +383,7 @@ function getEslintCoreConfigPath(name) { /** * Applies values from the "extends" field in a configuration file. * @param {Object} config The configuration information. + * @param {Config} configContext Plugin context for the config instance * @param {string} filePath The file path from which the configuration information * was loaded. * @param {string} [relativeTo] The path to resolve relative to. @@ -396,7 +391,7 @@ function getEslintCoreConfigPath(name) { * loaded and merged. * @private */ -function applyExtends(config, filePath, relativeTo) { +function applyExtends(config, configContext, filePath, relativeTo) { let configExtends = config.extends; // normalize into an array for easier handling @@ -421,7 +416,9 @@ function applyExtends(config, filePath, relativeTo) { ); } debug(`Loading ${parentPath}`); - return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue); + + // eslint-disable-next-line no-use-before-define + return ConfigOps.merge(load(parentPath, configContext, relativeTo), previousValue); } catch (e) { /* @@ -439,50 +436,6 @@ function applyExtends(config, filePath, relativeTo) { return config; } -/** - * Brings package name to correct format based on prefix - * @param {string} name The name of the package. - * @param {string} prefix Can be either "eslint-plugin" or "eslint-config - * @returns {string} Normalized name of the package - * @private - */ -function normalizePackageName(name, prefix) { - - /* - * On Windows, name can come in with Windows slashes instead of Unix slashes. - * Normalize to Unix first to avoid errors later on. - * https://github.com/eslint/eslint/issues/5644 - */ - if (name.indexOf("\\") > -1) { - name = pathUtil.convertPathToPosix(name); - } - - if (name.charAt(0) === "@") { - - /* - * it's a scoped package - * package name is "eslint-config", or just a username - */ - const scopedPackageShortcutRegex = new RegExp(`^(@[^/]+)(?:/(?:${prefix})?)?$`), - scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`); - - if (scopedPackageShortcutRegex.test(name)) { - name = name.replace(scopedPackageShortcutRegex, `$1/${prefix}`); - } else if (!scopedPackageNameRegex.test(name.split("/")[1])) { - - /* - * for scoped packages, insert the eslint-config after the first / unless - * the path is already @scope/eslint or @scope/eslint-config-xxx - */ - name = name.replace(/^@([^/]+)\/(.*)$/, `@$1/${prefix}-$2`); - } - } else if (name.indexOf(`${prefix}-`) !== 0) { - name = `${prefix}-${name}`; - } - - return name; -} - /** * Resolves a configuration file path into the fully-formed path, whether filename * or package name. @@ -491,46 +444,44 @@ function normalizePackageName(name, prefix) { * @returns {Object} An object containing 3 properties: * - 'filePath' (required) the resolved path that can be used directly to load the configuration. * - 'configName' the name of the configuration inside the plugin. - * - 'configFullName' the name of the configuration as used in the eslint config (e.g. 'plugin:node/recommended'). + * - 'configFullName' (required) the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'), + * or the absolute path to a config file. This should uniquely identify a config. * @private */ function resolve(filePath, relativeTo) { if (isFilePath(filePath)) { - return { filePath: path.resolve(relativeTo || "", filePath) }; + const fullPath = path.resolve(relativeTo || "", filePath); + + return { filePath: fullPath, configFullName: fullPath }; } let normalizedPackageName; if (filePath.startsWith("plugin:")) { const configFullName = filePath; - const pluginName = filePath.substr(7, filePath.lastIndexOf("/") - 7); - const configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1); + const pluginName = filePath.slice(7, filePath.lastIndexOf("/")); + const configName = filePath.slice(filePath.lastIndexOf("/") + 1); - normalizedPackageName = normalizePackageName(pluginName, "eslint-plugin"); + normalizedPackageName = naming.normalizePackageName(pluginName, "eslint-plugin"); debug(`Attempting to resolve ${normalizedPackageName}`); filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)); return { filePath, configName, configFullName }; } - normalizedPackageName = normalizePackageName(filePath, "eslint-config"); + normalizedPackageName = naming.normalizePackageName(filePath, "eslint-config"); debug(`Attempting to resolve ${normalizedPackageName}`); filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)); - return { filePath }; - + return { filePath, configFullName: filePath }; } /** * Loads a configuration file from the given file path. - * @param {string} filePath The filename or package name to load the configuration - * information from. - * @param {boolean} [applyEnvironments=false] Set to true to merge in environment settings. - * @param {string} [relativeTo] The path to resolve relative to. + * @param {Object} resolvedPath The value from calling resolve() on a filename or package name. + * @param {Config} configContext Plugins context * @returns {Object} The configuration information. - * @private */ -function load(filePath, applyEnvironments, relativeTo) { - const resolvedPath = resolve(filePath, relativeTo), - dirname = path.dirname(resolvedPath.filePath), +function loadFromDisk(resolvedPath, configContext) { + const dirname = path.dirname(resolvedPath.filePath), lookupPath = getLookupPath(dirname); let config = loadConfigFile(resolvedPath); @@ -538,12 +489,7 @@ function load(filePath, applyEnvironments, relativeTo) { // ensure plugins are properly loaded first if (config.plugins) { - Plugins.loadAll(config.plugins); - } - - // remove parser from config if it is the default parser - if (config.parser === defaultOptions.parser) { - config.parser = null; + configContext.plugins.loadAll(config.plugins); } // include full path of parser if present @@ -555,28 +501,64 @@ function load(filePath, applyEnvironments, relativeTo) { } } + const ruleMap = configContext.linterContext.getRules(); + // validate the configuration before continuing - validator.validate(config, filePath); + validator.validate(config, resolvedPath.configFullName, ruleMap.get.bind(ruleMap), configContext.linterContext.environments); /* * If an `extends` property is defined, it represents a configuration file to use as * a "parent". Load the referenced file and merge the configuration recursively. */ if (config.extends) { - config = applyExtends(config, filePath, dirname); + config = applyExtends(config, configContext, resolvedPath.filePath, dirname); } + } - if (config.env && applyEnvironments) { + return config; +} - // Merge in environment-specific globals and parserOptions. - config = ConfigOps.applyEnvironments(config); - } +/** + * Loads a config object, applying extends if present. + * @param {Object} configObject a config object to load + * @param {Config} configContext Context for the config instance + * @returns {Object} the config object with extends applied if present, or the passed config if not + * @private + */ +function loadObject(configObject, configContext) { + return configObject.extends ? applyExtends(configObject, configContext, "") : configObject; +} + +/** + * Loads a config object from the config cache based on its filename, falling back to the disk if the file is not yet + * cached. + * @param {string} filePath the path to the config file + * @param {Config} configContext Context for the config instance + * @param {string} [relativeTo] The path to resolve relative to. + * @returns {Object} the parsed config object (empty object if there was a parse error) + * @private + */ +function load(filePath, configContext, relativeTo) { + const resolvedPath = resolve(filePath, relativeTo); + const cachedConfig = configContext.configCache.getConfig(resolvedPath.configFullName); + + if (cachedConfig) { + return cachedConfig; + } + + const config = loadFromDisk(resolvedPath, configContext); + + if (config) { + config.filePath = resolvedPath.filePath; + config.baseDirectory = path.dirname(resolvedPath.filePath); + configContext.configCache.setConfig(resolvedPath.configFullName, config); } return config; } + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -586,10 +568,10 @@ module.exports = { getBaseDir, getLookupPath, load, + loadObject, resolve, write, applyExtends, - normalizePackageName, CONFIG_FILES, /** @@ -603,7 +585,7 @@ module.exports = { for (let i = 0, len = CONFIG_FILES.length; i < len; i++) { const filename = path.join(directory, CONFIG_FILES[i]); - if (shell.test("-f", filename)) { + if (fs.existsSync(filename) && fs.statSync(filename).isFile()) { return filename; } } diff --git a/lib/config/config-initializer.js b/lib/config/config-initializer.js index 0062a46504fd..e4865a008cac 100644 --- a/lib/config/config-initializer.js +++ b/lib/config/config-initializer.js @@ -12,10 +12,12 @@ const util = require("util"), inquirer = require("inquirer"), ProgressBar = require("progress"), + semver = require("semver"), autoconfig = require("./autoconfig.js"), ConfigFile = require("./config-file"), ConfigOps = require("./config-ops"), getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles, + ModuleResolver = require("../util/module-resolver"), npmUtil = require("../util/npm-util"), recConfig = require("../../conf/eslint-recommended"), log = require("../logging"); @@ -56,48 +58,78 @@ function writeFile(config, format) { } } +/** + * Get the peer dependencies of the given module. + * This adds the gotten value to cache at the first time, then reuses it. + * In a process, this function is called twice, but `npmUtil.fetchPeerDependencies` needs to access network which is relatively slow. + * @param {string} moduleName The module name to get. + * @returns {Object} The peer dependencies of the given module. + * This object is the object of `peerDependencies` field of `package.json`. + * Returns null if npm was not found. + */ +function getPeerDependencies(moduleName) { + let result = getPeerDependencies.cache.get(moduleName); + + if (!result) { + log.info(`Checking peerDependencies of ${moduleName}`); + + result = npmUtil.fetchPeerDependencies(moduleName); + getPeerDependencies.cache.set(moduleName, result); + } + + return result; +} +getPeerDependencies.cache = new Map(); + /** * Synchronously install necessary plugins, configs, parsers, etc. based on the config * @param {Object} config config object + * @param {boolean} [installESLint=true] If `false` is given, it does not install eslint. * @returns {void} */ -function installModules(config) { - let modules = []; +function installModules(config, installESLint) { + const modules = {}; // Create a list of modules which should be installed based on config if (config.plugins) { - modules = modules.concat(config.plugins.map(name => `eslint-plugin-${name}`)); + for (const plugin of config.plugins) { + modules[`eslint-plugin-${plugin}`] = "latest"; + } } if (config.extends && config.extends.indexOf("eslint:") === -1) { - modules.push(`eslint-config-${config.extends}`); + const moduleName = `eslint-config-${config.extends}`; + + modules[moduleName] = "latest"; + Object.assign( + modules, + getPeerDependencies(`${moduleName}@latest`) + ); } - // Determine which modules are already installed - if (modules.length === 0) { + // If no modules, do nothing. + if (Object.keys(modules).length === 0) { return; } - // Add eslint to list in case user does not have it installed locally - modules.unshift("eslint"); - - const installStatus = npmUtil.checkDevDeps(modules); - - // Install packages which aren't already installed - const modulesToInstall = Object.keys(installStatus).filter(module => { - const notInstalled = installStatus[module] === false; + if (installESLint === false) { + delete modules.eslint; + } else { + const installStatus = npmUtil.checkDevDeps(["eslint"]); - if (module === "eslint" && notInstalled) { + // Mark to show messages if it's new installation of eslint. + if (installStatus.eslint === false) { log.info("Local ESLint installation not found."); + modules.eslint = modules.eslint || "latest"; config.installedESLint = true; } + } - return notInstalled; - }); + // Install packages + const modulesToInstall = Object.keys(modules).map(name => `${name}@${modules[name]}`); - if (modulesToInstall.length > 0) { - log.info(`Installing ${modulesToInstall.join(", ")}`); - npmUtil.installSyncSaveDev(modulesToInstall); - } + log.info(`Installing ${modulesToInstall.join(", ")}`); + + npmUtil.installSyncSaveDev(modulesToInstall); } /** @@ -169,14 +201,18 @@ function configureRules(answers, config) { // Now that we know which rules to disable, strip out configs with errors registry = registry.stripFailingConfigs(); - // If there is only one config that results in no errors for a rule, we should use it. - // createConfig will only add rules that have one configuration in the registry. + /* + * If there is only one config that results in no errors for a rule, we should use it. + * createConfig will only add rules that have one configuration in the registry. + */ const singleConfigs = registry.createConfig().rules; - // The "sweet spot" for number of options in a config seems to be two (severity plus one option). - // Very often, a third option (usually an object) is available to address - // edge cases, exceptions, or unique situations. We will prefer to use a config with - // specificity of two. + /* + * The "sweet spot" for number of options in a config seems to be two (severity plus one option). + * Very often, a third option (usually an object) is available to address + * edge cases, exceptions, or unique situations. We will prefer to use a config with + * specificity of two. + */ const specTwoConfigs = registry.filterBySpecificity(2).createConfig().rules; // Maybe a specific combination using all three options works @@ -260,35 +296,98 @@ function processAnswers(answers) { /** * process user's style guide of choice and return an appropriate config object. * @param {string} guide name of the chosen style guide + * @param {boolean} [installESLint=true] If `false` is given, it does not install eslint. * @returns {Object} config object */ -function getConfigForStyleGuide(guide) { +function getConfigForStyleGuide(guide, installESLint) { const guides = { google: { extends: "google" }, - airbnb: { extends: "airbnb", plugins: ["react", "jsx-a11y", "import"] }, - "airbnb-base": { extends: "airbnb-base", plugins: ["import"] }, - standard: { extends: "standard", plugins: ["standard", "promise"] } + airbnb: { extends: "airbnb" }, + "airbnb-base": { extends: "airbnb-base" }, + standard: { extends: "standard" } }; if (!guides[guide]) { throw new Error("You referenced an unsupported guide."); } - installModules(guides[guide]); + installModules(guides[guide], installESLint); return guides[guide]; } +/** + * Get the version of the local ESLint. + * @returns {string|null} The version. If the local ESLint was not found, returns null. + */ +function getLocalESLintVersion() { + try { + const resolver = new ModuleResolver(); + const eslintPath = resolver.resolve("eslint", process.cwd()); + const eslint = require(eslintPath); + + return eslint.linter.version || null; + } catch (_err) { + return null; + } +} + +/** + * Get the shareable config name of the chosen style guide. + * @param {Object} answers The answers object. + * @returns {string} The shareable config name. + */ +function getStyleGuideName(answers) { + if (answers.styleguide === "airbnb" && !answers.airbnbReact) { + return "airbnb-base"; + } + return answers.styleguide; +} + +/** + * Check whether the local ESLint version conflicts with the required version of the chosen shareable config. + * @param {Object} answers The answers object. + * @returns {boolean} `true` if the local ESLint is found then it conflicts with the required version of the chosen shareable config. + */ +function hasESLintVersionConflict(answers) { + + // Get the local ESLint version. + const localESLintVersion = getLocalESLintVersion(); + + if (!localESLintVersion) { + return false; + } + + // Get the required range of ESLint version. + const configName = getStyleGuideName(answers); + const moduleName = `eslint-config-${configName}@latest`; + const peerDependencies = getPeerDependencies(moduleName) || {}; + const requiredESLintVersionRange = peerDependencies.eslint; + + if (!requiredESLintVersionRange) { + return false; + } + + answers.localESLintVersion = localESLintVersion; + answers.requiredESLintVersionRange = requiredESLintVersionRange; + + // Check the version. + if (semver.satisfies(localESLintVersion, requiredESLintVersionRange)) { + answers.installESLint = false; + return false; + } + + return true; +} + /* istanbul ignore next: no need to test inquirer*/ /** * Ask use a few questions on command prompt - * @param {Function} callback callback function when file has been written - * @returns {void} + * @returns {Promise} The promise with the result of the prompt */ -function promptUser(callback) { - let config; +function promptUser() { - inquirer.prompt([ + return inquirer.prompt([ { type: "list", name: "source", @@ -342,30 +441,46 @@ function promptUser(callback) { when(answers) { return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto"); } + }, + { + type: "confirm", + name: "installESLint", + message(answers) { + const verb = semver.ltr(answers.localESLintVersion, answers.requiredESLintVersionRange) + ? "upgrade" + : "downgrade"; + + return `The style guide "${answers.styleguide}" requires eslint@${answers.requiredESLintVersionRange}. You are currently using eslint@${answers.localESLintVersion}.\n Do you want to ${verb}?`; + }, + default: true, + when(answers) { + return answers.source === "guide" && answers.packageJsonExists && hasESLintVersionConflict(answers); + } } - ], earlyAnswers => { + ]).then(earlyAnswers => { // early exit if you are using a style guide if (earlyAnswers.source === "guide") { if (!earlyAnswers.packageJsonExists) { log.info("A package.json is necessary to install plugins such as style guides. Run `npm init` to create a package.json file and try again."); - return; + return void 0; + } + if (earlyAnswers.installESLint === false && !semver.satisfies(earlyAnswers.localESLintVersion, earlyAnswers.requiredESLintVersionRange)) { + log.info(`Note: it might not work since ESLint's version is mismatched with the ${earlyAnswers.styleguide} config.`); } if (earlyAnswers.styleguide === "airbnb" && !earlyAnswers.airbnbReact) { earlyAnswers.styleguide = "airbnb-base"; } - try { - config = getConfigForStyleGuide(earlyAnswers.styleguide); - writeFile(config, earlyAnswers.format); - } catch (err) { - callback(err); - return; - } - return; + + const config = getConfigForStyleGuide(earlyAnswers.styleguide, earlyAnswers.installESLint); + + writeFile(config, earlyAnswers.format); + + return void 0; } // continue with the questions otherwise... - inquirer.prompt([ + return inquirer.prompt([ { type: "confirm", name: "es6", @@ -412,25 +527,22 @@ function promptUser(callback) { return answers.jsx; } } - ], secondAnswers => { + ]).then(secondAnswers => { // early exit if you are using automatic style generation if (earlyAnswers.source === "auto") { - try { - const combinedAnswers = Object.assign({}, earlyAnswers, secondAnswers); - - config = processAnswers(combinedAnswers); - installModules(config); - writeFile(config, earlyAnswers.format); - } catch (err) { - callback(err); - return; - } - return; + const combinedAnswers = Object.assign({}, earlyAnswers, secondAnswers); + + const config = processAnswers(combinedAnswers); + + installModules(config); + writeFile(config, earlyAnswers.format); + + return void 0; } // continue with the style questions otherwise... - inquirer.prompt([ + return inquirer.prompt([ { type: "list", name: "indent", @@ -465,16 +577,13 @@ function promptUser(callback) { default: "JavaScript", choices: ["JavaScript", "YAML", "JSON"] } - ], answers => { - try { - const totalAnswers = Object.assign({}, earlyAnswers, secondAnswers, answers); - - config = processAnswers(totalAnswers); - installModules(config); - writeFile(config, answers.format); - } catch (err) { - callback(err); // eslint-disable-line callback-return - } + ]).then(answers => { + const totalAnswers = Object.assign({}, earlyAnswers, secondAnswers, answers); + + const config = processAnswers(totalAnswers); + + installModules(config); + writeFile(config, answers.format); }); }); }); @@ -486,9 +595,10 @@ function promptUser(callback) { const init = { getConfigForStyleGuide, + hasESLintVersionConflict, processAnswers, - /* istanbul ignore next */initializeConfig(callback) { - promptUser(callback); + /* istanbul ignore next */initializeConfig() { + return promptUser(); } }; diff --git a/lib/config/config-ops.js b/lib/config/config-ops.js index 52dea1a106df..2ce500a4f479 100644 --- a/lib/config/config-ops.js +++ b/lib/config/config-ops.js @@ -9,7 +9,8 @@ // Requirements //------------------------------------------------------------------------------ -const Environments = require("./environments"); +const minimatch = require("minimatch"), + path = require("path"); const debug = require("debug")("eslint:config-ops"); @@ -46,10 +47,11 @@ module.exports = { /** * Creates an environment config based on the specified environments. * @param {Object} env The environment settings. + * @param {Environments} envContext The environment context. * @returns {Object} A configuration object with the appropriate rules and globals * set. */ - createEnvironmentConfig(env) { + createEnvironmentConfig(env, envContext) { const envConfig = this.createEmptyConfig(); @@ -58,7 +60,7 @@ module.exports = { envConfig.env = env; Object.keys(env).filter(name => env[name]).forEach(name => { - const environment = Environments.get(name); + const environment = envContext.get(name); if (environment) { debug(`Creating config for environment ${name}`); @@ -80,12 +82,13 @@ module.exports = { * Given a config with environment settings, applies the globals and * ecmaFeatures to the configuration and returns the result. * @param {Object} config The configuration information. + * @param {Environments} envContent env context. * @returns {Object} The updated configuration information. */ - applyEnvironments(config) { + applyEnvironments(config, envContent) { if (config.env && typeof config.env === "object") { debug("Apply environment settings to config"); - return this.merge(this.createEnvironmentConfig(config.env), config); + return this.merge(this.createEnvironmentConfig(config.env, envContent), config); } return config; @@ -102,27 +105,27 @@ module.exports = { merge: function deepmerge(target, src, combine, isRule) { /* - The MIT License (MIT) - - Copyright (c) 2012 Nicholas Fisher - - 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. + * The MIT License (MIT) + * + * Copyright (c) 2012 Nicholas Fisher + * + * 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. */ /* @@ -174,8 +177,10 @@ module.exports = { }); } Object.keys(src).forEach(key => { - if (Array.isArray(src[key]) || Array.isArray(target[key])) { - dst[key] = deepmerge(target[key], src[key], key === "plugins", isRule); + if (key === "overrides") { + dst[key] = (target[key] || []).concat(src[key] || []); + } else if (Array.isArray(src[key]) || Array.isArray(target[key])) { + dst[key] = deepmerge(target[key], src[key], key === "plugins" || key === "extends", isRule); } else if (typeof src[key] !== "object" || !src[key] || key === "exported" || key === "astGlobals") { dst[key] = src[key]; } else { @@ -188,25 +193,25 @@ module.exports = { }, /** - * Converts new-style severity settings (off, warn, error) into old-style - * severity settings (0, 1, 2) for all rules. Assumption is that severity - * values have already been validated as correct. - * @param {Object} config The config object to normalize. - * @returns {void} + * Normalizes the severity value of a rule's configuration to a number + * @param {(number|string|[number, ...*]|[string, ...*])} ruleConfig A rule's configuration value, generally + * received from the user. A valid config value is either 0, 1, 2, the string "off" (treated the same as 0), + * the string "warn" (treated the same as 1), the string "error" (treated the same as 2), or an array + * whose first element is one of the above values. Strings are matched case-insensitively. + * @returns {(0|1|2)} The numeric severity value if the config value was valid, otherwise 0. */ - normalize(config) { + getRuleSeverity(ruleConfig) { + const severityValue = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig; - if (config.rules) { - Object.keys(config.rules).forEach(ruleId => { - const ruleConfig = config.rules[ruleId]; + if (severityValue === 0 || severityValue === 1 || severityValue === 2) { + return severityValue; + } - if (typeof ruleConfig === "string") { - config.rules[ruleId] = RULE_SEVERITY[ruleConfig.toLowerCase()] || 0; - } else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "string") { - ruleConfig[0] = RULE_SEVERITY[ruleConfig[0].toLowerCase()] || 0; - } - }); + if (typeof severityValue === "string") { + return RULE_SEVERITY[severityValue.toLowerCase()] || 0; } + + return 0; }, /** @@ -268,5 +273,111 @@ module.exports = { */ isEverySeverityValid(config) { return Object.keys(config).every(ruleId => this.isValidSeverity(config[ruleId])); + }, + + /** + * Merges all configurations in a given config vector. A vector is an array of objects, each containing a config + * file path and a list of subconfig indices that match the current file path. All config data is assumed to be + * cached. + * @param {Array} vector list of config files and their subconfig indices that match the current file path + * @param {Object} configCache the config cache + * @returns {Object} config object + */ + getConfigFromVector(vector, configCache) { + + const cachedConfig = configCache.getMergedVectorConfig(vector); + + if (cachedConfig) { + return cachedConfig; + } + + debug("Using config from partial cache"); + + const subvector = Array.from(vector); + let nearestCacheIndex = subvector.length - 1, + partialCachedConfig; + + while (nearestCacheIndex >= 0) { + partialCachedConfig = configCache.getMergedVectorConfig(subvector); + if (partialCachedConfig) { + break; + } + subvector.pop(); + nearestCacheIndex--; + } + + if (!partialCachedConfig) { + partialCachedConfig = {}; + } + + let finalConfig = partialCachedConfig; + + // Start from entry immediately following nearest cached config (first uncached entry) + for (let i = nearestCacheIndex + 1; i < vector.length; i++) { + finalConfig = this.mergeVectorEntry(finalConfig, vector[i], configCache); + configCache.setMergedVectorConfig(vector.slice(0, i + 1), finalConfig); + } + + return finalConfig; + }, + + /** + * Merges the config options from a single vector entry into the supplied config. + * @param {Object} config the base config to merge the vector entry's options into + * @param {Object} vectorEntry a single entry from a vector, consisting of a config file path and an array of + * matching override indices + * @param {Object} configCache the config cache + * @returns {Object} merged config object + */ + mergeVectorEntry(config, vectorEntry, configCache) { + const vectorEntryConfig = Object.assign({}, configCache.getConfig(vectorEntry.filePath)); + let mergedConfig = Object.assign({}, config), + overrides; + + if (vectorEntryConfig.overrides) { + overrides = vectorEntryConfig.overrides.filter( + (override, overrideIndex) => vectorEntry.matchingOverrides.indexOf(overrideIndex) !== -1 + ); + } else { + overrides = []; + } + + mergedConfig = this.merge(mergedConfig, vectorEntryConfig); + + delete mergedConfig.overrides; + + mergedConfig = overrides.reduce((lastConfig, override) => this.merge(lastConfig, override), mergedConfig); + + if (mergedConfig.filePath) { + delete mergedConfig.filePath; + delete mergedConfig.baseDirectory; + } else if (mergedConfig.files) { + delete mergedConfig.files; + } + + return mergedConfig; + }, + + /** + * Checks that the specified file path matches all of the supplied glob patterns. + * @param {string} filePath The file path to test patterns against + * @param {string|string[]} patterns One or more glob patterns, of which at least one should match the file path + * @param {string|string[]} [excludedPatterns] One or more glob patterns, of which none should match the file path + * @returns {boolean} True if all the supplied patterns match the file path, false otherwise + */ + pathMatchesGlobs(filePath, patterns, excludedPatterns) { + const patternList = [].concat(patterns); + const excludedPatternList = [].concat(excludedPatterns || []); + + patternList.concat(excludedPatternList).forEach(pattern => { + if (path.isAbsolute(pattern) || pattern.includes("..")) { + throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`); + } + }); + + const opts = { matchBase: true }; + + return patternList.some(pattern => minimatch(filePath, pattern, opts)) && + !excludedPatternList.some(excludedPattern => minimatch(filePath, excludedPattern, opts)); } }; diff --git a/lib/config/config-rule.js b/lib/config/config-rule.js index a8a073caa370..5fc38ac5d17a 100644 --- a/lib/config/config-rule.js +++ b/lib/config/config-rule.js @@ -9,9 +9,10 @@ // Requirements //------------------------------------------------------------------------------ -const rules = require("../rules"), +const Rules = require("../rules"), loadRules = require("../load-rules"); +const rules = new Rules(); //------------------------------------------------------------------------------ // Helpers @@ -168,16 +169,16 @@ function combinePropertyObjects(objArr1, objArr2) { return res; } - /** - * Creates a new instance of a rule configuration set - * - * A rule configuration set is an array of configurations that are valid for a - * given rule. For example, the configuration set for the "semi" rule could be: - * - * ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]] - * - * Rule configuration set class - */ +/** + * Creates a new instance of a rule configuration set + * + * A rule configuration set is an array of configurations that are valid for a + * given rule. For example, the configuration set for the "semi" rule could be: + * + * ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]] + * + * Rule configuration set class + */ class RuleConfigSet { /** @@ -186,19 +187,19 @@ class RuleConfigSet { constructor(configs) { /** - * Stored valid rule configurations for this instance - * @type {array} - */ + * Stored valid rule configurations for this instance + * @type {array} + */ this.ruleConfigs = configs || []; } /** - * Add a severity level to the front of all configs in the instance. - * This should only be called after all configs have been added to the instance. - * - * @param {number} [severity=2] The level of severity for the rule (0, 1, 2) - * @returns {void} - */ + * Add a severity level to the front of all configs in the instance. + * This should only be called after all configs have been added to the instance. + * + * @param {number} [severity=2] The level of severity for the rule (0, 1, 2) + * @returns {void} + */ addErrorSeverity(severity) { severity = severity || 2; @@ -212,19 +213,19 @@ class RuleConfigSet { } /** - * Add rule configs from an array of strings (schema enums) - * @param {string[]} enums Array of valid rule options (e.g. ["always", "never"]) - * @returns {void} - */ + * Add rule configs from an array of strings (schema enums) + * @param {string[]} enums Array of valid rule options (e.g. ["always", "never"]) + * @returns {void} + */ addEnums(enums) { this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, enums)); } /** - * Add rule configurations from a schema object - * @param {Object} obj Schema item with type === "object" - * @returns {boolean} true if at least one schema for the object could be generated, false otherwise - */ + * Add rule configurations from a schema object + * @param {Object} obj Schema item with type === "object" + * @returns {boolean} true if at least one schema for the object could be generated, false otherwise + */ addObject(obj) { const objectConfigSet = { objectConfigs: [], @@ -266,10 +267,10 @@ class RuleConfigSet { } /** -* Generate valid rule configurations based on a schema object -* @param {Object} schema A rule's schema object -* @returns {array[]} Valid rule configurations -*/ + * Generate valid rule configurations based on a schema object + * @param {Object} schema A rule's schema object + * @returns {array[]} Valid rule configurations + */ function generateConfigsFromSchema(schema) { const configSet = new RuleConfigSet(); @@ -295,9 +296,9 @@ function generateConfigsFromSchema(schema) { } /** -* Generate possible rule configurations for all of the core rules -* @returns {rulesConfig} Hash of rule names and arrays of possible configurations -*/ + * Generate possible rule configurations for all of the core rules + * @returns {rulesConfig} Hash of rule names and arrays of possible configurations + */ function createCoreRuleConfigs() { const ruleList = loadRules(); diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js index 36e0e9fddb32..1a5b3ef13e03 100644 --- a/lib/config/config-validator.js +++ b/lib/config/config-validator.js @@ -9,27 +9,25 @@ // Requirements //------------------------------------------------------------------------------ -const rules = require("../rules"), - Environments = require("./environments"), - schemaValidator = require("is-my-json-valid"), +const ajv = require("../util/ajv"), + lodash = require("lodash"), + configSchema = require("../../conf/config-schema.js"), util = require("util"); -const validators = { - rules: Object.create(null) -}; +const ruleValidators = new WeakMap(); //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ +let validateSchema; /** * Gets a complete options schema for a rule. - * @param {string} id The rule's unique name. + * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object * @returns {Object} JSON Schema for the rule's options. */ -function getRuleOptionsSchema(id) { - const rule = rules.get(id), - schema = rule && rule.schema || rule && rule.meta && rule.meta.schema; +function getRuleOptionsSchema(rule) { + const schema = rule.schema || rule.meta && rule.meta.schema; // Given a tuple of schemas, insert warning level at the beginning if (Array.isArray(schema)) { @@ -54,10 +52,10 @@ function getRuleOptionsSchema(id) { } /** -* Validates a rule's severity and returns the severity value. Throws an error if the severity is invalid. -* @param {options} options The given options for the rule. -* @returns {number|string} The rule's severity value -*/ + * Validates a rule's severity and returns the severity value. Throws an error if the severity is invalid. + * @param {options} options The given options for the rule. + * @returns {number|string} The rule's severity value + */ function validateRuleSeverity(options) { const severity = Array.isArray(options) ? options[0] : options; @@ -69,95 +67,177 @@ function validateRuleSeverity(options) { } /** -* Validates the non-severity options passed to a rule, based on its schema. -* @param {string} id The rule's unique name -* @param {array} localOptions The options for the rule, excluding severity -* @returns {void} -*/ -function validateRuleSchema(id, localOptions) { - const schema = getRuleOptionsSchema(id); - - if (!validators.rules[id] && schema) { - validators.rules[id] = schemaValidator(schema, { verbose: true }); + * Validates the non-severity options passed to a rule, based on its schema. + * @param {{create: Function}} rule The rule to validate + * @param {array} localOptions The options for the rule, excluding severity + * @returns {void} + */ +function validateRuleSchema(rule, localOptions) { + if (!ruleValidators.has(rule)) { + const schema = getRuleOptionsSchema(rule); + + if (schema) { + ruleValidators.set(rule, ajv.compile(schema)); + } } - const validateRule = validators.rules[id]; + const validateRule = ruleValidators.get(rule); if (validateRule) { validateRule(localOptions); if (validateRule.errors) { - throw new Error(validateRule.errors.map(error => `\tValue "${error.value}" ${error.message}.\n`).join("")); + throw new Error(validateRule.errors.map( + error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n` + ).join("")); } } } /** * Validates a rule's options against its schema. - * @param {string} id The rule's unique name. + * @param {{create: Function}|null} rule The rule that the config is being validated for + * @param {string} ruleId The rule's unique name. * @param {array|number} options The given options for the rule. - * @param {string} source The name of the configuration source. + * @param {string|null} source The name of the configuration source to report in any errors. If null or undefined, + * no source is prepended to the message. * @returns {void} */ -function validateRuleOptions(id, options, source) { +function validateRuleOptions(rule, ruleId, options, source) { + if (!rule) { + return; + } try { const severity = validateRuleSeverity(options); if (severity !== 0 && !(typeof severity === "string" && severity.toLowerCase() === "off")) { - validateRuleSchema(id, Array.isArray(options) ? options.slice(1) : []); + validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []); } } catch (err) { - throw new Error(`${source}:\n\tConfiguration for rule "${id}" is invalid:\n${err.message}`); + const enhancedMessage = `Configuration for rule "${ruleId}" is invalid:\n${err.message}`; + + if (typeof source === "string") { + throw new Error(`${source}:\n\t${enhancedMessage}`); + } else { + throw new Error(enhancedMessage); + } } } /** * Validates an environment object * @param {Object} environment The environment config object to validate. - * @param {string} source The location to report with any errors. + * @param {string} source The name of the configuration source to report in any errors. + * @param {Environments} envContext Env context * @returns {void} */ -function validateEnvironment(environment, source) { +function validateEnvironment(environment, source, envContext) { // not having an environment is ok if (!environment) { return; } - if (Array.isArray(environment)) { - throw new Error("Environment must not be an array"); - } + Object.keys(environment).forEach(env => { + if (!envContext.get(env)) { + const message = `${source}:\n\tEnvironment key "${env}" is unknown\n`; - if (typeof environment === "object") { - Object.keys(environment).forEach(env => { - if (!Environments.get(env)) { - const message = [ - source, ":\n", - "\tEnvironment key \"", env, "\" is unknown\n" - ]; - - throw new Error(message.join("")); - } - }); - } else { - throw new Error("Environment must be an object"); + throw new Error(message); + } + }); +} + +/** + * Validates a rules config object + * @param {Object} rulesConfig The rules config object to validate. + * @param {string} source The name of the configuration source to report in any errors. + * @param {function(string): {create: Function}} ruleMapper A mapper function from strings to loaded rules + * @returns {void} + */ +function validateRules(rulesConfig, source, ruleMapper) { + if (!rulesConfig) { + return; } + + Object.keys(rulesConfig).forEach(id => { + validateRuleOptions(ruleMapper(id), id, rulesConfig[id], source); + }); } /** - * Validates an entire config object. + * Formats an array of schema validation errors. + * @param {Array} errors An array of error messages to format. + * @returns {string} Formatted error message + */ +function formatErrors(errors) { + return errors.map(error => { + if (error.keyword === "additionalProperties") { + const formattedPropertyPath = error.dataPath.length ? `${error.dataPath.slice(1)}.${error.params.additionalProperty}` : error.params.additionalProperty; + + return `Unexpected top-level property "${formattedPropertyPath}"`; + } + if (error.keyword === "type") { + const formattedField = error.dataPath.slice(1); + const formattedExpectedType = Array.isArray(error.schema) ? error.schema.join("/") : error.schema; + const formattedValue = JSON.stringify(error.data); + + return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)`; + } + + const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath; + + return `"${field}" ${error.message}. Value: ${JSON.stringify(error.data)}`; + }).map(message => `\t- ${message}.\n`).join(""); +} + +/** + * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted + * for each unique file path, but repeated invocations with the same file path have no effect. + * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active. + * @param {string} source The name of the configuration source to report the warning for. + * @returns {void} + */ +const emitEcmaFeaturesWarning = lodash.memoize(source => { + + /* + * util.deprecate seems to be the only way to emit a warning in Node 4.x while respecting the --no-warnings flag. + * (In Node 6+, process.emitWarning could be used instead.) + */ + util.deprecate( + () => {}, + `[eslint] The 'ecmaFeatures' config file property is deprecated, and has no effect. (found in ${source})` + )(); +}); + +/** + * Validates the top level properties of the config object. * @param {Object} config The config object to validate. - * @param {string} source The location to report with any errors. + * @param {string} source The name of the configuration source to report in any errors. * @returns {void} */ -function validate(config, source) { +function validateConfigSchema(config, source) { + validateSchema = validateSchema || ajv.compile(configSchema); - if (typeof config.rules === "object") { - Object.keys(config.rules).forEach(id => { - validateRuleOptions(id, config.rules[id], source); - }); + if (!validateSchema(config)) { + throw new Error(`ESLint configuration in ${source} is invalid:\n${formatErrors(validateSchema.errors)}`); } - validateEnvironment(config.env, source); + if (Object.prototype.hasOwnProperty.call(config, "ecmaFeatures")) { + emitEcmaFeaturesWarning(source); + } +} + +/** + * Validates an entire config object. + * @param {Object} config The config object to validate. + * @param {string} source The name of the configuration source to report in any errors. + * @param {function(string): {create: Function}} ruleMapper A mapper function from rule IDs to defined rules + * @param {Environments} envContext The env context + * @returns {void} + */ +function validate(config, source, ruleMapper, envContext) { + validateConfigSchema(config, source); + validateRules(config.rules, source, ruleMapper); + validateEnvironment(config.env, source, envContext); } //------------------------------------------------------------------------------ diff --git a/lib/config/environments.js b/lib/config/environments.js index 5c34da9328a4..1ec9438af5de 100644 --- a/lib/config/environments.js +++ b/lib/config/environments.js @@ -11,32 +11,30 @@ const envs = require("../../conf/environments"); //------------------------------------------------------------------------------ -// Private +// Public Interface //------------------------------------------------------------------------------ -let environments = new Map(); +class Environments { -/** - * Loads the default environments. - * @returns {void} - * @private - */ -function load() { - Object.keys(envs).forEach(envName => { - environments.set(envName, envs[envName]); - }); -} - -// always load default environments upfront -load(); - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ + /** + * create env context + */ + constructor() { + this._environments = new Map(); -module.exports = { + this.load(); + } - load, + /** + * Loads the default environments. + * @returns {void} + * @private + */ + load() { + Object.keys(envs).forEach(envName => { + this._environments.set(envName, envs[envName]); + }); + } /** * Gets the environment with the given name. @@ -44,8 +42,19 @@ module.exports = { * @returns {Object?} The environment object or null if not found. */ get(name) { - return environments.get(name) || null; - }, + return this._environments.get(name) || null; + } + + /** + * Gets all the environment present + * @returns {Object} The environment object for each env name + */ + getAll() { + return Array.from(this._environments).reduce((coll, env) => { + coll[env[0]] = env[1]; + return coll; + }, {}); + } /** * Defines an environment. @@ -54,8 +63,8 @@ module.exports = { * @returns {void} */ define(name, env) { - environments.set(name, env); - }, + this._environments.set(name, env); + } /** * Imports all environments from a plugin. @@ -69,14 +78,7 @@ module.exports = { this.define(`${pluginName}/${envName}`, plugin.environments[envName]); }); } - }, - - /** - * Resets all environments. Only use for tests! - * @returns {void} - */ - testReset() { - environments = new Map(); - load(); } -}; +} + +module.exports = Environments; diff --git a/lib/config/plugins.js b/lib/config/plugins.js index e28a77929c0b..c509c48fe25c 100644 --- a/lib/config/plugins.js +++ b/lib/config/plugins.js @@ -8,56 +8,34 @@ // Requirements //------------------------------------------------------------------------------ -const Environments = require("./environments"), - Rules = require("../rules"); - const debug = require("debug")("eslint:plugins"); +const naming = require("../util/naming"); //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ -let plugins = Object.create(null); - -const PLUGIN_NAME_PREFIX = "eslint-plugin-", - NAMESPACE_REGEX = /^@.*\//i; - -/** - * Removes the prefix `eslint-plugin-` from a plugin name. - * @param {string} pluginName The name of the plugin which may have the prefix. - * @returns {string} The name of the plugin without prefix. - */ -function removePrefix(pluginName) { - return pluginName.indexOf(PLUGIN_NAME_PREFIX) === 0 ? pluginName.substring(PLUGIN_NAME_PREFIX.length) : pluginName; -} - -/** - * Gets the scope (namespace) of a plugin. - * @param {string} pluginName The name of the plugin which may have the prefix. - * @returns {string} The name of the plugins namepace if it has one. - */ -function getNamespace(pluginName) { - return pluginName.match(NAMESPACE_REGEX) ? pluginName.match(NAMESPACE_REGEX)[0] : ""; -} - -/** - * Removes the namespace from a plugin name. - * @param {string} pluginName The name of the plugin which may have the prefix. - * @returns {string} The name of the plugin without the namespace. - */ -function removeNamespace(pluginName) { - return pluginName.replace(NAMESPACE_REGEX, ""); -} +const PLUGIN_NAME_PREFIX = "eslint-plugin-"; //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ -module.exports = { +/** + * Plugin class + */ +class Plugins { - removePrefix, - getNamespace, - removeNamespace, + /** + * Creates the plugins context + * @param {Environments} envContext - env context + * @param {Rules} rulesContext - rules context + */ + constructor(envContext, rulesContext) { + this._plugins = Object.create(null); + this._environments = envContext; + this._rules = rulesContext; + } /** * Defines a plugin with a given name rather than loading from disk. @@ -66,22 +44,16 @@ module.exports = { * @returns {void} */ define(pluginName, plugin) { - const pluginNamespace = getNamespace(pluginName), - pluginNameWithoutNamespace = removeNamespace(pluginName), - pluginNameWithoutPrefix = removePrefix(pluginNameWithoutNamespace), + const pluginNamespace = naming.getNamespaceFromTerm(pluginName), + pluginNameWithoutNamespace = naming.removeNamespaceFromTerm(pluginName), + pluginNameWithoutPrefix = naming.removePrefixFromTerm(PLUGIN_NAME_PREFIX, pluginNameWithoutNamespace), shortName = pluginNamespace + pluginNameWithoutPrefix; // load up environments and rules - plugins[shortName] = plugin; - Environments.importPlugin(plugin, shortName); - Rules.importPlugin(plugin, shortName); - - // load up environments and rules for the name that '@scope/' was omitted - // 3 lines below will be removed by 4.0.0 - plugins[pluginNameWithoutPrefix] = plugin; - Environments.importPlugin(plugin, pluginNameWithoutPrefix); - Rules.importPlugin(plugin, pluginNameWithoutPrefix); - }, + this._plugins[shortName] = plugin; + this._environments.importPlugin(plugin, shortName); + this._rules.importPlugin(plugin, shortName); + } /** * Gets a plugin with the given name. @@ -89,16 +61,16 @@ module.exports = { * @returns {Object} The plugin or null if not loaded. */ get(pluginName) { - return plugins[pluginName] || null; - }, + return this._plugins[pluginName] || null; + } /** * Returns all plugins that are loaded. * @returns {Object} The plugins cache. */ getAll() { - return plugins; - }, + return this._plugins; + } /** * Loads a plugin with the given name. @@ -107,9 +79,9 @@ module.exports = { * @throws {Error} If the plugin cannot be loaded. */ load(pluginName) { - const pluginNamespace = getNamespace(pluginName), - pluginNameWithoutNamespace = removeNamespace(pluginName), - pluginNameWithoutPrefix = removePrefix(pluginNameWithoutNamespace), + const pluginNamespace = naming.getNamespaceFromTerm(pluginName), + pluginNameWithoutNamespace = naming.removeNamespaceFromTerm(pluginName), + pluginNameWithoutPrefix = naming.removePrefixFromTerm(PLUGIN_NAME_PREFIX, pluginNameWithoutNamespace), shortName = pluginNamespace + pluginNameWithoutPrefix, longName = pluginNamespace + PLUGIN_NAME_PREFIX + pluginNameWithoutPrefix; let plugin = null; @@ -124,7 +96,7 @@ module.exports = { throw whitespaceError; } - if (!plugins[shortName]) { + if (!this._plugins[shortName]) { try { plugin = require(longName); } catch (pluginLoadErr) { @@ -150,23 +122,29 @@ module.exports = { this.define(pluginName, plugin); } - }, + } /** * Loads all plugins from an array. * @param {string[]} pluginNames An array of plugins names. * @returns {void} * @throws {Error} If a plugin cannot be loaded. + * @throws {Error} If "plugins" in config is not an array */ loadAll(pluginNames) { - pluginNames.forEach(this.load, this); - }, - /** - * Resets plugin information. Use for tests only. - * @returns {void} - */ - testReset() { - plugins = Object.create(null); + // if "plugins" in config is not an array, throw an error so user can fix their config. + if (!Array.isArray(pluginNames)) { + const pluginNotArrayMessage = "ESLint configuration error: \"plugins\" value must be an array"; + + debug(`${pluginNotArrayMessage}: ${JSON.stringify(pluginNames)}`); + + throw new Error(pluginNotArrayMessage); + } + + // load each plugin by name + pluginNames.forEach(this.load, this); } -}; +} + +module.exports = Plugins; diff --git a/lib/eslint.js b/lib/eslint.js deleted file mode 100755 index a9066c4c8b68..000000000000 --- a/lib/eslint.js +++ /dev/null @@ -1,1234 +0,0 @@ -/** - * @fileoverview Main ESLint object. - * @author Nicholas C. Zakas - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("assert"), - EventEmitter = require("events").EventEmitter, - escope = require("escope"), - levn = require("levn"), - blankScriptAST = require("../conf/blank-script.json"), - DEFAULT_PARSER = require("../conf/eslint-recommended").parser, - replacements = require("../conf/replacements.json"), - CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"), - ConfigOps = require("./config/config-ops"), - validator = require("./config/config-validator"), - Environments = require("./config/environments"), - CommentEventGenerator = require("./util/comment-event-generator"), - NodeEventGenerator = require("./util/node-event-generator"), - SourceCode = require("./util/source-code"), - Traverser = require("./util/traverser"), - RuleContext = require("./rule-context"), - rules = require("./rules"), - timing = require("./timing"), - - pkg = require("../package.json"); - - -//------------------------------------------------------------------------------ -// Typedefs -//------------------------------------------------------------------------------ - -/** - * The result of a parsing operation from parseForESLint() - * @typedef {Object} CustomParseResult - * @property {ASTNode} ast The ESTree AST Program node. - * @property {Object} services An object containing additional services related - * to the parser. - */ - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Parses a list of "name:boolean_value" or/and "name" options divided by comma or - * whitespace. - * @param {string} string The string to parse. - * @param {Comment} comment The comment node which has the string. - * @returns {Object} Result map object of names and boolean values - */ -function parseBooleanConfig(string, comment) { - const items = {}; - - // Collapse whitespace around `:` and `,` to make parsing easier - string = string.replace(/\s*([:,])\s*/g, "$1"); - - string.split(/\s|,+/).forEach(name => { - if (!name) { - return; - } - const pos = name.indexOf(":"); - let value; - - if (pos !== -1) { - value = name.substring(pos + 1, name.length); - name = name.substring(0, pos); - } - - items[name] = { - value: (value === "true"), - comment - }; - - }); - return items; -} - -/** - * Parses a JSON-like config. - * @param {string} string The string to parse. - * @param {Object} location Start line and column of comments for potential error message. - * @param {Object[]} messages The messages queue for potential error message. - * @returns {Object} Result map object - */ -function parseJsonConfig(string, location, messages) { - let items = {}; - - // Parses a JSON-like comment by the same way as parsing CLI option. - try { - items = levn.parse("Object", string) || {}; - - // Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`. - // Also, commaless notations have invalid severity: - // "no-alert: 2 no-console: 2" --> {"no-alert": "2 no-console: 2"} - // Should ignore that case as well. - if (ConfigOps.isEverySeverityValid(items)) { - return items; - } - } catch (ex) { - - // ignore to parse the string by a fallback. - } - - // Optionator cannot parse commaless notations. - // But we are supporting that. So this is a fallback for that. - items = {}; - string = string.replace(/([a-zA-Z0-9\-/]+):/g, "\"$1\":").replace(/(]|[0-9])\s+(?=")/, "$1,"); - try { - items = JSON.parse(`{${string}}`); - } catch (ex) { - - messages.push({ - ruleId: null, - fatal: true, - severity: 2, - source: null, - message: `Failed to parse JSON from '${string}': ${ex.message}`, - line: location.start.line, - column: location.start.column + 1 - }); - - } - - return items; -} - -/** - * Parses a config of values separated by comma. - * @param {string} string The string to parse. - * @returns {Object} Result map of values and true values - */ -function parseListConfig(string) { - const items = {}; - - // Collapse whitespace around , - string = string.replace(/\s*,\s*/g, ","); - - string.split(/,+/).forEach(name => { - name = name.trim(); - if (!name) { - return; - } - items[name] = true; - }); - return items; -} - -/** - * Ensures that variables representing built-in properties of the Global Object, - * and any globals declared by special block comments, are present in the global - * scope. - * @param {ASTNode} program The top node of the AST. - * @param {Scope} globalScope The global scope. - * @param {Object} config The existing configuration data. - * @returns {void} - */ -function addDeclaredGlobals(program, globalScope, config) { - const declaredGlobals = {}, - exportedGlobals = {}, - explicitGlobals = {}, - builtin = Environments.get("builtin"); - - Object.assign(declaredGlobals, builtin); - - Object.keys(config.env).forEach(name => { - if (config.env[name]) { - const env = Environments.get(name), - environmentGlobals = env && env.globals; - - if (environmentGlobals) { - Object.assign(declaredGlobals, environmentGlobals); - } - } - }); - - Object.assign(exportedGlobals, config.exported); - Object.assign(declaredGlobals, config.globals); - Object.assign(explicitGlobals, config.astGlobals); - - Object.keys(declaredGlobals).forEach(name => { - let variable = globalScope.set.get(name); - - if (!variable) { - variable = new escope.Variable(name, globalScope); - variable.eslintExplicitGlobal = false; - globalScope.variables.push(variable); - globalScope.set.set(name, variable); - } - variable.writeable = declaredGlobals[name]; - }); - - Object.keys(explicitGlobals).forEach(name => { - let variable = globalScope.set.get(name); - - if (!variable) { - variable = new escope.Variable(name, globalScope); - variable.eslintExplicitGlobal = true; - variable.eslintExplicitGlobalComment = explicitGlobals[name].comment; - globalScope.variables.push(variable); - globalScope.set.set(name, variable); - } - variable.writeable = explicitGlobals[name].value; - }); - - // mark all exported variables as such - Object.keys(exportedGlobals).forEach(name => { - const variable = globalScope.set.get(name); - - if (variable) { - variable.eslintUsed = true; - } - }); - - /* - * "through" contains all references which definitions cannot be found. - * Since we augment the global scope using configuration, we need to update - * references and remove the ones that were added by configuration. - */ - globalScope.through = globalScope.through.filter(reference => { - const name = reference.identifier.name; - const variable = globalScope.set.get(name); - - if (variable) { - - /* - * Links the variable and the reference. - * And this reference is removed from `Scope#through`. - */ - reference.resolved = variable; - variable.references.push(reference); - - return false; - } - - return true; - }); -} - -/** - * Add data to reporting configuration to disable reporting for list of rules - * starting from start location - * @param {Object[]} reportingConfig Current reporting configuration - * @param {Object} start Position to start - * @param {string[]} rulesToDisable List of rules - * @returns {void} - */ -function disableReporting(reportingConfig, start, rulesToDisable) { - - if (rulesToDisable.length) { - rulesToDisable.forEach(rule => { - reportingConfig.push({ - start, - end: null, - rule - }); - }); - } else { - reportingConfig.push({ - start, - end: null, - rule: null - }); - } -} - -/** - * Add data to reporting configuration to enable reporting for list of rules - * starting from start location - * @param {Object[]} reportingConfig Current reporting configuration - * @param {Object} start Position to start - * @param {string[]} rulesToEnable List of rules - * @returns {void} - */ -function enableReporting(reportingConfig, start, rulesToEnable) { - let i; - - if (rulesToEnable.length) { - rulesToEnable.forEach(rule => { - for (i = reportingConfig.length - 1; i >= 0; i--) { - if (!reportingConfig[i].end && reportingConfig[i].rule === rule) { - reportingConfig[i].end = start; - break; - } - } - }); - } else { - - // find all previous disabled locations if they was started as list of rules - let prevStart; - - for (i = reportingConfig.length - 1; i >= 0; i--) { - if (prevStart && prevStart !== reportingConfig[i].start) { - break; - } - - if (!reportingConfig[i].end) { - reportingConfig[i].end = start; - prevStart = reportingConfig[i].start; - } - } - } -} - -/** - * Parses comments in file to extract file-specific config of rules, globals - * and environments and merges them with global config; also code blocks - * where reporting is disabled or enabled and merges them with reporting config. - * @param {string} filename The file being checked. - * @param {ASTNode} ast The top node of the AST. - * @param {Object} config The existing configuration data. - * @param {Object[]} reportingConfig The existing reporting configuration data. - * @param {Object[]} messages The messages queue. - * @returns {Object} Modified config object - */ -function modifyConfigsFromComments(filename, ast, config, reportingConfig, messages) { - - let commentConfig = { - exported: {}, - astGlobals: {}, - rules: {}, - env: {} - }; - const commentRules = {}; - - ast.comments.forEach(comment => { - - let value = comment.value.trim(); - const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value); - - if (match) { - value = value.substring(match.index + match[1].length); - - if (comment.type === "Block") { - switch (match[1]) { - case "exported": - Object.assign(commentConfig.exported, parseBooleanConfig(value, comment)); - break; - - case "globals": - case "global": - Object.assign(commentConfig.astGlobals, parseBooleanConfig(value, comment)); - break; - - case "eslint-env": - Object.assign(commentConfig.env, parseListConfig(value)); - break; - - case "eslint-disable": - disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value))); - break; - - case "eslint-enable": - enableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value))); - break; - - case "eslint": { - const items = parseJsonConfig(value, comment.loc, messages); - - Object.keys(items).forEach(name => { - const ruleValue = items[name]; - - validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`); - commentRules[name] = ruleValue; - }); - break; - } - - // no default - } - } else { // comment.type === "Line" - if (match[1] === "eslint-disable-line") { - disableReporting(reportingConfig, { line: comment.loc.start.line, column: 0 }, Object.keys(parseListConfig(value))); - enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value))); - } else if (match[1] === "eslint-disable-next-line") { - disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value))); - enableReporting(reportingConfig, { line: comment.loc.start.line + 2 }, Object.keys(parseListConfig(value))); - } - } - } - }); - - // apply environment configs - Object.keys(commentConfig.env).forEach(name => { - const env = Environments.get(name); - - if (env) { - commentConfig = ConfigOps.merge(commentConfig, env); - } - }); - Object.assign(commentConfig.rules, commentRules); - - return ConfigOps.merge(config, commentConfig); -} - -/** - * Check if message of rule with ruleId should be ignored in location - * @param {Object[]} reportingConfig Collection of ignore records - * @param {string} ruleId Id of rule - * @param {Object} location Location of message - * @returns {boolean} True if message should be ignored, false otherwise - */ -function isDisabledByReportingConfig(reportingConfig, ruleId, location) { - - for (let i = 0, c = reportingConfig.length; i < c; i++) { - - const ignore = reportingConfig[i]; - - if ((!ignore.rule || ignore.rule === ruleId) && - (location.line > ignore.start.line || (location.line === ignore.start.line && location.column >= ignore.start.column)) && - (!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column <= ignore.end.column)))) { - return true; - } - } - - return false; -} - -/** - * Normalize ECMAScript version from the initial config - * @param {number} ecmaVersion ECMAScript version from the initial config - * @param {boolean} isModule Whether the source type is module or not - * @returns {number} normalized ECMAScript version - */ -function normalizeEcmaVersion(ecmaVersion, isModule) { - - // Need at least ES6 for modules - if (isModule && (!ecmaVersion || ecmaVersion < 6)) { - ecmaVersion = 6; - } - - // Calculate ECMAScript edition number from official year version starting with - // ES2015, which corresponds with ES6 (or a difference of 2009). - if (ecmaVersion >= 2015) { - ecmaVersion -= 2009; - } - - return ecmaVersion; -} - -/** - * Process initial config to make it safe to extend by file comment config - * @param {Object} config Initial config - * @returns {Object} Processed config - */ -function prepareConfig(config) { - - config.globals = config.globals || config.global || {}; - delete config.global; - - const copiedRules = {}; - let parserOptions = {}; - - if (typeof config.rules === "object") { - Object.keys(config.rules).forEach(k => { - const rule = config.rules[k]; - - if (rule === null) { - throw new Error(`Invalid config for rule '${k}'.`); - } - if (Array.isArray(rule)) { - copiedRules[k] = rule.slice(); - } else { - copiedRules[k] = rule; - } - }); - } - - // merge in environment parserOptions - if (typeof config.env === "object") { - Object.keys(config.env).forEach(envName => { - const env = Environments.get(envName); - - if (config.env[envName] && env && env.parserOptions) { - parserOptions = ConfigOps.merge(parserOptions, env.parserOptions); - } - }); - } - - const preparedConfig = { - rules: copiedRules, - parser: config.parser || DEFAULT_PARSER, - globals: ConfigOps.merge({}, config.globals), - env: ConfigOps.merge({}, config.env || {}), - settings: ConfigOps.merge({}, config.settings || {}), - parserOptions: ConfigOps.merge(parserOptions, config.parserOptions || {}) - }; - const isModule = preparedConfig.parserOptions.sourceType === "module"; - - if (isModule) { - if (!preparedConfig.parserOptions.ecmaFeatures) { - preparedConfig.parserOptions.ecmaFeatures = {}; - } - - // can't have global return inside of modules - preparedConfig.parserOptions.ecmaFeatures.globalReturn = false; - } - - preparedConfig.parserOptions.ecmaVersion = normalizeEcmaVersion(preparedConfig.parserOptions.ecmaVersion, isModule); - - return preparedConfig; -} - -/** - * Provide a stub rule with a given message - * @param {string} message The message to be displayed for the rule - * @returns {Function} Stub rule function - */ -function createStubRule(message) { - - /** - * Creates a fake rule object - * @param {Object} context context object for each rule - * @returns {Object} collection of node to listen on - */ - function createRuleModule(context) { - return { - Program(node) { - context.report(node, message); - } - }; - } - - if (message) { - return createRuleModule; - } - throw new Error("No message passed to stub rule"); - -} - -/** - * Provide a rule replacement message - * @param {string} ruleId Name of the rule - * @returns {string} Message detailing rule replacement - */ -function getRuleReplacementMessage(ruleId) { - if (ruleId in replacements.rules) { - const newRules = replacements.rules[ruleId]; - - return `Rule '${ruleId}' was removed and replaced by: ${newRules.join(", ")}`; - } - - return null; -} - -const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g; - -/** - * Checks whether or not there is a comment which has "eslint-env *" in a given text. - * @param {string} text - A source code text to check. - * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment. - */ -function findEslintEnv(text) { - let match, retv; - - eslintEnvPattern.lastIndex = 0; - - while ((match = eslintEnvPattern.exec(text))) { - retv = Object.assign(retv || {}, parseListConfig(match[1])); - } - - return retv; -} - -/** - * Strips Unicode BOM from a given text. - * - * @param {string} text - A text to strip. - * @returns {string} The stripped text. - */ -function stripUnicodeBOM(text) { - - /* - * Check Unicode BOM. - * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF. - * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters - */ - if (text.charCodeAt(0) === 0xFEFF) { - return text.slice(1); - } - return text; -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -/** - * Object that is responsible for verifying JavaScript text - * @name eslint - */ -module.exports = (function() { - - const api = Object.create(new EventEmitter()); - let messages = [], - currentConfig = null, - currentScopes = null, - scopeManager = null, - currentFilename = null, - traverser = null, - reportingConfig = [], - sourceCode = null; - - /** - * Parses text into an AST. Moved out here because the try-catch prevents - * optimization of functions, so it's best to keep the try-catch as isolated - * as possible - * @param {string} text The text to parse. - * @param {Object} config The ESLint configuration object. - * @param {string} filePath The path to the file being parsed. - * @returns {ASTNode|CustomParseResult} The AST or parse result if successful, - * or null if not. - * @private - */ - function parse(text, config, filePath) { - - let parser, - parserOptions = { - loc: true, - range: true, - raw: true, - tokens: true, - comment: true, - attachComment: true, - filePath - }; - - try { - parser = require(config.parser); - } catch (ex) { - messages.push({ - ruleId: null, - fatal: true, - severity: 2, - source: null, - message: ex.message, - line: 0, - column: 0 - }); - - return null; - } - - // merge in any additional parser options - if (config.parserOptions) { - parserOptions = Object.assign({}, config.parserOptions, parserOptions); - } - - /* - * Check for parsing errors first. If there's a parsing error, nothing - * else can happen. However, a parsing error does not throw an error - * from this method - it's just considered a fatal error message, a - * problem that ESLint identified just like any other. - */ - try { - if (typeof parser.parseForESLint === "function") { - return parser.parseForESLint(text, parserOptions); - } - return parser.parse(text, parserOptions); - - } catch (ex) { - - // If the message includes a leading line number, strip it: - const message = ex.message.replace(/^line \d+:/i, "").trim(); - const source = (ex.lineNumber) ? SourceCode.splitLines(text)[ex.lineNumber - 1] : null; - - messages.push({ - ruleId: null, - fatal: true, - severity: 2, - source, - message: `Parsing error: ${message}`, - - line: ex.lineNumber, - column: ex.column - }); - - return null; - } - } - - /** - * Get the severity level of a rule (0 - none, 1 - warning, 2 - error) - * Returns 0 if the rule config is not valid (an Array or a number) - * @param {Array|number} ruleConfig rule configuration - * @returns {number} 0, 1, or 2, indicating rule severity - */ - function getRuleSeverity(ruleConfig) { - if (typeof ruleConfig === "number") { - return ruleConfig; - } else if (Array.isArray(ruleConfig)) { - return ruleConfig[0]; - } - return 0; - - } - - /** - * Get the options for a rule (not including severity), if any - * @param {Array|number} ruleConfig rule configuration - * @returns {Array} of rule options, empty Array if none - */ - function getRuleOptions(ruleConfig) { - if (Array.isArray(ruleConfig)) { - return ruleConfig.slice(1); - } - return []; - - } - - // set unlimited listeners (see https://github.com/eslint/eslint/issues/524) - api.setMaxListeners(0); - - /** - * Resets the internal state of the object. - * @returns {void} - */ - api.reset = function() { - this.removeAllListeners(); - messages = []; - currentConfig = null; - currentScopes = null; - scopeManager = null; - traverser = null; - reportingConfig = []; - sourceCode = null; - }; - - /** - * Configuration object for the `verify` API. A JS representation of the eslintrc files. - * @typedef {Object} ESLintConfig - * @property {Object} rules The rule configuration to verify against. - * @property {string} [parser] Parser to use when generatig the AST. - * @property {Object} [parserOptions] Options for the parsed used. - * @property {Object} [settings] Global settings passed to each rule. - * @property {Object} [env] The environment to verify in. - * @property {Object} [globals] Available globalsto the code. - */ - - /** - * Verifies the text against the rules specified by the second argument. - * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. - * @param {ESLintConfig} config An ESLintConfig instance to configure everything. - * @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked. - * If this is not set, the filename will default to '' in the rule context. If - * an object, then it has "filename", "saveState", and "allowInlineConfig" properties. - * @param {boolean} [saveState] Indicates if the state from the last run should be saved. - * Mostly useful for testing purposes. - * @param {boolean} [filenameOrOptions.allowInlineConfig] Allow/disallow inline comments' ability to change config once it is set. Defaults to true if not supplied. - * Useful if you want to validate JS without comments overriding rules. - * @returns {Object[]} The results as an array of messages or null if no messages. - */ - api.verify = function(textOrSourceCode, config, filenameOrOptions, saveState) { - const text = (typeof textOrSourceCode === "string") ? textOrSourceCode : null; - let ast, - parseResult, - shebang, - allowInlineConfig; - - // evaluate arguments - if (typeof filenameOrOptions === "object") { - currentFilename = filenameOrOptions.filename; - allowInlineConfig = filenameOrOptions.allowInlineConfig; - saveState = filenameOrOptions.saveState; - } else { - currentFilename = filenameOrOptions; - } - - if (!saveState) { - this.reset(); - } - - // search and apply "eslint-env *". - const envInFile = findEslintEnv(text || textOrSourceCode.text); - - config = Object.assign({}, config); - - if (envInFile) { - if (config.env) { - config.env = Object.assign({}, config.env, envInFile); - } else { - config.env = envInFile; - } - } - - // process initial config to make it safe to extend - config = prepareConfig(config); - - // only do this for text - if (text !== null) { - - // there's no input, just exit here - if (text.trim().length === 0) { - sourceCode = new SourceCode(text, blankScriptAST); - return messages; - } - - parseResult = parse( - stripUnicodeBOM(text).replace(/^#!([^\r\n]+)/, (match, captured) => { - shebang = captured; - return `//${captured}`; - }), - config, - currentFilename - ); - - // if this result is from a parseForESLint() method, normalize - if (parseResult && parseResult.ast) { - ast = parseResult.ast; - } else { - ast = parseResult; - parseResult = null; - } - - if (ast) { - sourceCode = new SourceCode(text, ast); - } - - } else { - sourceCode = textOrSourceCode; - ast = sourceCode.ast; - } - - // if espree failed to parse the file, there's no sense in setting up rules - if (ast) { - - // parse global comments and modify config - if (allowInlineConfig !== false) { - config = modifyConfigsFromComments(currentFilename, ast, config, reportingConfig, messages); - } - - // ensure that severities are normalized in the config - ConfigOps.normalize(config); - - // enable appropriate rules - Object.keys(config.rules).filter(key => getRuleSeverity(config.rules[key]) > 0).forEach(key => { - let ruleCreator; - - ruleCreator = rules.get(key); - - if (!ruleCreator) { - const replacementMsg = getRuleReplacementMessage(key); - - if (replacementMsg) { - ruleCreator = createStubRule(replacementMsg); - } else { - ruleCreator = createStubRule(`Definition for rule '${key}' was not found`); - } - rules.define(key, ruleCreator); - } - - const severity = getRuleSeverity(config.rules[key]); - const options = getRuleOptions(config.rules[key]); - - try { - const ruleContext = new RuleContext( - key, api, severity, options, - config.settings, config.parserOptions, config.parser, - ruleCreator.meta, - (parseResult && parseResult.services ? parseResult.services : {}) - ); - - const rule = ruleCreator.create ? ruleCreator.create(ruleContext) - : ruleCreator(ruleContext); - - // add all the selectors from the rule as listeners - Object.keys(rule).forEach(selector => { - api.on(selector, timing.enabled - ? timing.time(key, rule[selector]) - : rule[selector] - ); - }); - } catch (ex) { - ex.message = `Error while loading rule '${key}': ${ex.message}`; - throw ex; - } - }); - - // save config so rules can access as necessary - currentConfig = config; - traverser = new Traverser(); - - const ecmaFeatures = currentConfig.parserOptions.ecmaFeatures || {}; - const ecmaVersion = currentConfig.parserOptions.ecmaVersion || 5; - - // gather scope data that may be needed by the rules - scopeManager = escope.analyze(ast, { - ignoreEval: true, - nodejsScope: ecmaFeatures.globalReturn, - impliedStrict: ecmaFeatures.impliedStrict, - ecmaVersion, - sourceType: currentConfig.parserOptions.sourceType || "script", - fallback: Traverser.getKeys - }); - - currentScopes = scopeManager.scopes; - - // augment global scope with declared global variables - addDeclaredGlobals(ast, currentScopes[0], currentConfig); - - // remove shebang comments - if (shebang && ast.comments.length && ast.comments[0].value === shebang) { - ast.comments.splice(0, 1); - - if (ast.body.length && ast.body[0].leadingComments && ast.body[0].leadingComments[0].value === shebang) { - ast.body[0].leadingComments.splice(0, 1); - } - } - - let eventGenerator = new NodeEventGenerator(api); - - eventGenerator = new CodePathAnalyzer(eventGenerator); - eventGenerator = new CommentEventGenerator(eventGenerator, sourceCode); - - /* - * Each node has a type property. Whenever a particular type of - * node is found, an event is fired. This allows any listeners to - * automatically be informed that this type of node has been found - * and react accordingly. - */ - traverser.traverse(ast, { - enter(node, parent) { - node.parent = parent; - eventGenerator.enterNode(node); - }, - leave(node) { - eventGenerator.leaveNode(node); - } - }); - } - - // sort by line and column - messages.sort((a, b) => { - const lineDiff = a.line - b.line; - - if (lineDiff === 0) { - return a.column - b.column; - } - return lineDiff; - - }); - - return messages; - }; - - /** - * Reports a message from one of the rules. - * @param {string} ruleId The ID of the rule causing the message. - * @param {number} severity The severity level of the rule as configured. - * @param {ASTNode} node The AST node that the message relates to. - * @param {Object=} location An object containing the error line and column - * numbers. If location is not provided the node's start location will - * be used. - * @param {string} message The actual message. - * @param {Object} opts Optional template data which produces a formatted message - * with symbols being replaced by this object's values. - * @param {Object} fix A fix command description. - * @param {Object} meta Metadata of the rule - * @returns {void} - */ - api.report = function(ruleId, severity, node, location, message, opts, fix, meta) { - if (node) { - assert.strictEqual(typeof node, "object", "Node must be an object"); - } - - if (typeof location === "string") { - assert.ok(node, "Node must be provided when reporting error if location is not provided"); - - meta = fix; - fix = opts; - opts = message; - message = location; - location = node.loc.start; - } - - // Store end location. - const endLocation = location.end; - - location = location.start || location; - - if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) { - return; - } - - if (opts) { - message = message.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (fullMatch, term) => { - if (term in opts) { - return opts[term]; - } - - // Preserve old behavior: If parameter name not provided, don't replace it. - return fullMatch; - }); - } - - const problem = { - ruleId, - severity, - message, - line: location.line, - column: location.column + 1, // switch to 1-base instead of 0-base - nodeType: node && node.type, - source: sourceCode.lines[location.line - 1] || "" - }; - - // Define endLine and endColumn if exists. - if (endLocation) { - problem.endLine = endLocation.line; - problem.endColumn = endLocation.column + 1; // switch to 1-base instead of 0-base - } - - // ensure there's range and text properties, otherwise it's not a valid fix - if (fix && Array.isArray(fix.range) && (typeof fix.text === "string")) { - - // If rule uses fix, has metadata, but has no metadata.fixable, we should throw - if (meta && !meta.fixable) { - throw new Error("Fixable rules should export a `meta.fixable` property."); - } - - problem.fix = fix; - } - - messages.push(problem); - }; - - /** - * Gets the SourceCode object representing the parsed source. - * @returns {SourceCode} The SourceCode object. - */ - api.getSourceCode = function() { - return sourceCode; - }; - - // methods that exist on SourceCode object - const externalMethods = { - getSource: "getText", - getSourceLines: "getLines", - getAllComments: "getAllComments", - getNodeByRangeIndex: "getNodeByRangeIndex", - getComments: "getComments", - getJSDocComment: "getJSDocComment", - getFirstToken: "getFirstToken", - getFirstTokens: "getFirstTokens", - getLastToken: "getLastToken", - getLastTokens: "getLastTokens", - getTokenAfter: "getTokenAfter", - getTokenBefore: "getTokenBefore", - getTokenByRangeStart: "getTokenByRangeStart", - getTokens: "getTokens", - getTokensAfter: "getTokensAfter", - getTokensBefore: "getTokensBefore", - getTokensBetween: "getTokensBetween" - }; - - // copy over methods - Object.keys(externalMethods).forEach(methodName => { - const exMethodName = externalMethods[methodName]; - - // All functions expected to have less arguments than 5. - api[methodName] = function(a, b, c, d, e) { - if (sourceCode) { - return sourceCode[exMethodName](a, b, c, d, e); - } - return null; - }; - }); - - /** - * Gets nodes that are ancestors of current node. - * @returns {ASTNode[]} Array of objects representing ancestors. - */ - api.getAncestors = function() { - return traverser.parents(); - }; - - /** - * Gets the scope for the current node. - * @returns {Object} An object representing the current node's scope. - */ - api.getScope = function() { - const parents = traverser.parents(); - - // Don't do this for Program nodes - they have no parents - if (parents.length) { - - // if current node introduces a scope, add it to the list - const current = traverser.current(); - - if (currentConfig.parserOptions.ecmaVersion >= 6) { - if (["BlockStatement", "SwitchStatement", "CatchClause", "FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) { - parents.push(current); - } - } else { - if (["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) { - parents.push(current); - } - } - - // Ascend the current node's parents - for (let i = parents.length - 1; i >= 0; --i) { - - // Get the innermost scope - const scope = scopeManager.acquire(parents[i], true); - - if (scope) { - if (scope.type === "function-expression-name") { - return scope.childScopes[0]; - } - return scope; - - } - - } - - } - - return currentScopes[0]; - }; - - /** - * Record that a particular variable has been used in code - * @param {string} name The name of the variable to mark as used - * @returns {boolean} True if the variable was found and marked as used, - * false if not. - */ - api.markVariableAsUsed = function(name) { - const hasGlobalReturn = currentConfig.parserOptions.ecmaFeatures && currentConfig.parserOptions.ecmaFeatures.globalReturn, - specialScope = hasGlobalReturn || currentConfig.parserOptions.sourceType === "module"; - let scope = this.getScope(), - i, - len; - - // Special Node.js scope means we need to start one level deeper - if (scope.type === "global" && specialScope) { - scope = scope.childScopes[0]; - } - - do { - const variables = scope.variables; - - for (i = 0, len = variables.length; i < len; i++) { - if (variables[i].name === name) { - variables[i].eslintUsed = true; - return true; - } - } - } while ((scope = scope.upper)); - - return false; - }; - - /** - * Gets the filename for the currently parsed source. - * @returns {string} The filename associated with the source being parsed. - * Defaults to "" if no filename info is present. - */ - api.getFilename = function() { - if (typeof currentFilename === "string") { - return currentFilename; - } - return ""; - - }; - - /** - * Defines a new linting rule. - * @param {string} ruleId A unique rule identifier - * @param {Function} ruleModule Function from context to object mapping AST node types to event handlers - * @returns {void} - */ - const defineRule = api.defineRule = function(ruleId, ruleModule) { - rules.define(ruleId, ruleModule); - }; - - /** - * Defines many new linting rules. - * @param {Object} rulesToDefine map from unique rule identifier to rule - * @returns {void} - */ - api.defineRules = function(rulesToDefine) { - Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => { - defineRule(ruleId, rulesToDefine[ruleId]); - }); - }; - - /** - * Gets the default eslint configuration. - * @returns {Object} Object mapping rule IDs to their default configurations - */ - api.defaults = function() { - return require("../conf/eslint-recommended"); - }; - - /** - * Gets an object with all loaded rules. - * @returns {Map} All loaded rules - */ - api.getRules = function() { - return rules.getAllLoadedRules(); - }; - - api.version = pkg.version; - - /** - * Gets variables that are declared by a specified node. - * - * The variables are its `defs[].node` or `defs[].parent` is same as the specified node. - * Specifically, below: - * - * - `VariableDeclaration` - variables of its all declarators. - * - `VariableDeclarator` - variables. - * - `FunctionDeclaration`/`FunctionExpression` - its function name and parameters. - * - `ArrowFunctionExpression` - its parameters. - * - `ClassDeclaration`/`ClassExpression` - its class name. - * - `CatchClause` - variables of its exception. - * - `ImportDeclaration` - variables of its all specifiers. - * - `ImportSpecifier`/`ImportDefaultSpecifier`/`ImportNamespaceSpecifier` - a variable. - * - others - always an empty array. - * - * @param {ASTNode} node A node to get. - * @returns {escope.Variable[]} Variables that are declared by the node. - */ - api.getDeclaredVariables = function(node) { - return (scopeManager && scopeManager.getDeclaredVariables(node)) || []; - }; - - return api; - -}()); diff --git a/lib/file-finder.js b/lib/file-finder.js index acb886c9d1e2..3458bbf52a41 100644 --- a/lib/file-finder.js +++ b/lib/file-finder.js @@ -25,6 +25,7 @@ const fs = require("fs"), */ function getDirectoryEntries(directory) { try { + return fs.readdirSync(directory); } catch (ex) { return []; @@ -79,9 +80,9 @@ class FileFinder { * Searches for all the file names in this.fileNames. * Is currently used by lib/config.js to find .eslintrc and package.json files. * @param {string} directory The directory to start the search from. - * @returns {string[]} The file paths found. + * @returns {GeneratorFunction} to iterate the file paths found */ - findAllInDirectoryAndParents(directory) { + *findAllInDirectoryAndParents(directory) { const cache = this.cache; if (directory) { @@ -91,7 +92,8 @@ class FileFinder { } if (cache.hasOwnProperty(directory)) { - return cache[directory]; + yield* cache[directory]; + return; // to avoid doing the normal loop afterwards } const dirs = []; @@ -114,19 +116,21 @@ class FileFinder { for (let j = 0; j < searched; j++) { cache[dirs[j]].push(filePath); } - + yield filePath; break; } } } + const child = directory; // Assign parent directory to directory. directory = path.dirname(directory); if (directory === child) { - return cache[dirs[0]]; + return; } + } while (!cache.hasOwnProperty(directory)); // Add what has been cached previously to the cache of each directory searched. @@ -134,7 +138,7 @@ class FileFinder { dirs.push.apply(cache[dirs[i]], cache[directory]); } - return cache[dirs[0]]; + yield* cache[dirs[0]]; } } diff --git a/lib/formatters/codeframe.js b/lib/formatters/codeframe.js index 1191ccb8efaa..0b97a0d81803 100644 --- a/lib/formatters/codeframe.js +++ b/lib/formatters/codeframe.js @@ -47,7 +47,7 @@ function formatFilePath(filePath, line, column) { */ function formatMessage(message, parentResult) { const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning"); - const msg = `${chalk.bold(message.message.replace(/\.$/, ""))}`; + const msg = `${chalk.bold(message.message.replace(/([^ ])\.$/, "$1"))}`; const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`); const filePath = formatFilePath(parentResult.filePath, message.line, message.column); const sourceCode = parentResult.output ? parentResult.output : parentResult.source; @@ -74,11 +74,14 @@ function formatMessage(message, parentResult) { * Gets the formatted output summary for a given number of errors and warnings. * @param {number} errors The number of errors. * @param {number} warnings The number of warnings. + * @param {number} fixableErrors The number of fixable errors. + * @param {number} fixableWarnings The number of fixable warnings. * @returns {string} The formatted output summary. */ -function formatSummary(errors, warnings) { +function formatSummary(errors, warnings, fixableErrors, fixableWarnings) { const summaryColor = errors > 0 ? "red" : "yellow"; const summary = []; + const fixablesSummary = []; if (errors > 0) { summary.push(`${errors} ${pluralize("error", errors)}`); @@ -88,7 +91,21 @@ function formatSummary(errors, warnings) { summary.push(`${warnings} ${pluralize("warning", warnings)}`); } - return chalk[summaryColor].bold(`${summary.join(" and ")} found.`); + if (fixableErrors > 0) { + fixablesSummary.push(`${fixableErrors} ${pluralize("error", fixableErrors)}`); + } + + if (fixableWarnings > 0) { + fixablesSummary.push(`${fixableWarnings} ${pluralize("warning", fixableWarnings)}`); + } + + let output = chalk[summaryColor].bold(`${summary.join(" and ")} found.`); + + if (fixableErrors || fixableWarnings) { + output += chalk[summaryColor].bold(`\n${fixablesSummary.join(" and ")} potentially fixable with the \`--fix\` option.`); + } + + return output; } //------------------------------------------------------------------------------ @@ -98,6 +115,9 @@ function formatSummary(errors, warnings) { module.exports = function(results) { let errors = 0; let warnings = 0; + let fixableErrors = 0; + let fixableWarnings = 0; + const resultsWithMessages = results.filter(result => result.messages.length > 0); let output = resultsWithMessages.reduce((resultsOutput, result) => { @@ -105,12 +125,14 @@ module.exports = function(results) { errors += result.errorCount; warnings += result.warningCount; + fixableErrors += result.fixableErrorCount; + fixableWarnings += result.fixableWarningCount; return resultsOutput.concat(messages); }, []).join("\n"); output += "\n"; - output += formatSummary(errors, warnings); + output += formatSummary(errors, warnings, fixableErrors, fixableWarnings); return (errors + warnings) > 0 ? output : ""; }; diff --git a/lib/formatters/html-template-message.html b/lib/formatters/html-template-message.html index 068317274849..66f49ff49d4f 100644 --- a/lib/formatters/html-template-message.html +++ b/lib/formatters/html-template-message.html @@ -3,6 +3,6 @@ <%= severityName %> <%- message %> - <%= ruleId %> + <%= ruleId %> diff --git a/lib/formatters/html-template-page.html b/lib/formatters/html-template-page.html index 39e15562e97e..4016576fa067 100644 --- a/lib/formatters/html-template-page.html +++ b/lib/formatters/html-template-page.html @@ -1,5 +1,7 @@ + + ESLint Report