diff --git a/.nycrc b/.c8rc similarity index 100% rename from .nycrc rename to .c8rc diff --git a/.eslintignore b/.eslintignore index 905f2a39ddf..ffc34e9a954 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,8 @@ /build/** /coverage/** -/docs/** -!/docs/.eleventy.js +/docs/* +!/docs/*.js +!/docs/tools/ /jsdoc/** /templates/** /tests/bench/** diff --git a/.eslintrc.js b/.eslintrc.js index d4e2587afa6..cd47323f3e9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,20 @@ +/* + * IMPORTANT! + * + * Any changes made to this file must also be made to eslint.config.js. + * + * Internally, ESLint is using the eslint.config.js file to lint itself. + * This file is needed too, because: + * + * 1. There are tests that expect .eslintrc.js to be present to actually run. + * 2. ESLint VS Code extension expects eslintrc config files to be + * present to work correctly. + * + * Once we no longer need to support both eslintrc and flat config, we will + * remove this file. + */ + + "use strict"; const path = require("path"); @@ -66,9 +83,10 @@ module.exports = { }, overrides: [ { - files: ["tools/*.js"], + files: ["tools/*.js", "docs/tools/*.js"], rules: { - "no-console": "off" + "no-console": "off", + "n/no-process-exit": "off" } }, { @@ -84,7 +102,7 @@ module.exports = { "eslint-plugin/prefer-placeholders": "error", "eslint-plugin/prefer-replace-text": "error", "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], - "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow)" }], + "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow) .+[^. ]$" }], "internal-rules/no-invalid-meta": "error" } }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..5496e79293f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +/docs/ @eslint/website-team @eslint/eslint-team +* @eslint/eslint-team \ No newline at end of file diff --git a/.github/CODEOWNERS.md b/.github/CODEOWNERS.md deleted file mode 100644 index b032d30bb8e..00000000000 --- a/.github/CODEOWNERS.md +++ /dev/null @@ -1,17 +0,0 @@ -# Config-related files - -lib/conf/config-schema.js @nzakas -lib/cli-engine/config-array/* @nzakas -lib/cli-engine/config-array-factory.js @nzakas -lib/cli-engine/cascading-config-array-factory.js @nzakas -lib/shared/config-* @nzakas -lib/shared/naming.js @nzakas -lib/shared/relative-module-resolver.js @nzakas - -tests/lib/conf/config-schema.js @nzakas -tests/lib/cli-engine/config-array/* @nzakas -tests/lib/cli-engine/config-array-factory.js @nzakas -tests/lib/cli-engine/cascading-config-array-factory.js @nzakas -tests/lib/shared/config-* @nzakas -tests/lib/shared/naming.js @nzakas -tests/lib/shared/relative-module-resolver.js @nzakas diff --git a/.github/ISSUE_TEMPLATE/change.yml b/.github/ISSUE_TEMPLATE/change.yml index cc2066dd3d3..c7a21127eba 100644 --- a/.github/ISSUE_TEMPLATE/change.yml +++ b/.github/ISSUE_TEMPLATE/change.yml @@ -3,7 +3,6 @@ description: "Request a change that is not a bug fix, rule change, or new rule" title: "Change Request: (fill in)" labels: - enhancement - - triage - core body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index af8c43451d4..1e20844c21f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,3 +3,9 @@ contact_links: - name: 🗣 Ask a Question, Discuss url: https://github.com/eslint/eslint/discussions about: Get help using ESLint + - name: 📝 Help with VS Code ESLint + url: https://github.com/microsoft/vscode-eslint/issues/ + about: Bugs and feature requests for the VS Code ESLint plugin + - name: Discord Server + url: https://eslint.org/chat + about: Talk with the team diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8c4bf8eb1b7..35d24100191 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -31,7 +31,7 @@ diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5ace4600a1f..4c39a334be4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,5 @@ updates: directory: "/" schedule: interval: "weekly" + commit-message: + prefix: "ci" diff --git a/.github/workflows/add-to-triage.yml b/.github/workflows/add-to-triage.yml new file mode 100644 index 00000000000..f2e652bd1f6 --- /dev/null +++ b/.github/workflows/add-to-triage.yml @@ -0,0 +1,18 @@ +name: Add to Triage + +on: + issues: + types: + - opened + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v0.4.1 + with: + project-url: https://github.com/orgs/eslint/projects/3 + github-token: ${{ secrets.PROJECT_BOT_TOKEN }} + labeled: "triage:no" + label-operator: NOT diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9401ec54924..5f8b968a550 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: '16.x' + node-version: 'lts/*' - name: Install Packages run: npm install - name: Lint Files @@ -28,20 +28,29 @@ jobs: - name: Install Docs Packages working-directory: docs run: npm install + - name: Stylelint Docs + working-directory: docs + run: npm run lint:scss - name: Lint Docs JS Files run: node Makefile lintDocsJS + - name: Build Docs Website + working-directory: docs + run: npm run build + - name: Validate internal links + working-directory: docs + run: npm run lint:links test_on_node: name: Test strategy: matrix: os: [ubuntu-latest] - node: [18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] + node: [19.x, 18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] include: - os: windows-latest - node: "16.x" + node: "lts/*" - os: macOS-latest - node: "16.x" + node: "lts/*" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 8680ebdadc4..9e7355d34b9 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,13 +18,15 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v5 + - uses: actions/stale@v8 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-stale: 60 + days-before-issue-stale: 30 + days-before-pr-stale: 10 days-before-close: 7 stale-issue-message: 'Oops! It looks like we lost track of this issue. What do we want to do here? This issue will auto-close in 7 days without an update.' - stale-pr-message: 'Oops! It looks like we lost track of this pull request. What do we want to do here? This pull request will auto-close in 7 days without an update.' + close-issue-message: 'This issue was auto-closed due to inactivity. While we wish we could keep responding to every issue, we unfortunately don''t have the bandwidth and need to focus on high-value issues.' + stale-pr-message: 'Hi everyone, it looks like we lost track of this pull request. Please review and see what the next steps are. This pull request will auto-close in 7 days without an update.' + close-pr-message: 'This pull request was auto-closed due to inactivity. While we wish we could keep working on every request, we unfortunately don''t have the bandwidth to continue here and need to focus on other things. You can resubmit this pull request if you would like to continue working on it.' exempt-all-assignees: true exempt-issue-labels: accepted - exempt-pr-labels: accepted diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml new file mode 100644 index 00000000000..e6399920b51 --- /dev/null +++ b/.github/workflows/update-readme.yml @@ -0,0 +1,33 @@ +name: Data Fetch + +on: + schedule: + - cron: "0 8 * * *" # Every day at 1am PDT + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v3 + with: + token: ${{ secrets.WORKFLOW_PUSH_BOT_TOKEN }} + + - name: Set up Node.js + uses: actions/setup-node@v3 + + - name: Install npm packages + run: npm install + + - name: Update README with latest team and sponsor data + run: npm run build:readme + + - name: Setup Git + run: | + git config user.name "GitHub Actions Bot" + git config user.email "" + + - name: Save updated files + run: | + chmod +x ./tools/commit-readme.sh + ./tools/commit-readme.sh diff --git a/.npmrc b/.npmrc index c1ca392feaa..8783d627dc5 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ package-lock = false +install-links = false diff --git a/CHANGELOG.md b/CHANGELOG.md index 962e4e200e2..1ee070df357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,316 @@ +v8.37.0 - March 28, 2023 + +* [`c67f299`](https://github.com/eslint/eslint/commit/c67f2992a743de4765bb6f11c12622e3651324b9) chore: upgrade @eslint/js@8.37.0 (#17033) (Milos Djermanovic) +* [`ee9ddbd`](https://github.com/eslint/eslint/commit/ee9ddbd63e262aed0052853760866c7a054af561) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`dddb475`](https://github.com/eslint/eslint/commit/dddb47528816cd7e2e737bfde108ed4d62e6a219) chore: upgrade @eslint/eslintrc@2.0.2 (#17032) (Milos Djermanovic) +* [`522431e`](https://github.com/eslint/eslint/commit/522431e5206bac2fcb41c0d6dc98a84929203bee) chore: upgrade espree@9.5.1 (#17031) (Milos Djermanovic) +* [`f5f9a88`](https://github.com/eslint/eslint/commit/f5f9a88c79b32222c0331a9bac1c02571d953b69) chore: upgrade eslint-visitor-keys@3.4.0 (#17030) (Milos Djermanovic) +* [`75339df`](https://github.com/eslint/eslint/commit/75339df99418df4d7e05a77e42ed7e22eabcc9e0) docs: fix typos and missing info in id-match docs (#17029) (Ed Lucas) +* [`b6ab8b2`](https://github.com/eslint/eslint/commit/b6ab8b2a2ca8807baca121407f5bfb0a0a839427) feat: `require-unicode-regexp` add suggestions (#17007) (Josh Goldberg) +* [`4dd8d52`](https://github.com/eslint/eslint/commit/4dd8d524e0fc9e8e2019df13f8b968021600e85c) ci: bump actions/stale from 7 to 8 (#17026) (dependabot[bot]) +* [`619f3fd`](https://github.com/eslint/eslint/commit/619f3fd17324c7b71bf17e02047d0c6dc7e5109e) fix: correctly handle `null` default config in `RuleTester` (#17023) (Brad Zacher) +* [`ec2d830`](https://github.com/eslint/eslint/commit/ec2d8307850dd039e118c001416606e1e0342bc8) docs: Fix typos in the `semi` rule docs (#17012) (Andrii Lundiak) +* [`e39f28d`](https://github.com/eslint/eslint/commit/e39f28d8578a00f4da8d4ddad559547950128a0d) docs: add back to top button (#16979) (Tanuj Kanti) +* [`ad9dd6a`](https://github.com/eslint/eslint/commit/ad9dd6a933fd098a0d99c6a9aa059850535c23ee) chore: remove duplicate scss, (#17005) (Strek) +* [`10022b1`](https://github.com/eslint/eslint/commit/10022b1f4bda1ad89193512ecf18c2ee61db8202) feat: Copy getScope() to SourceCode (#17004) (Nicholas C. Zakas) +* [`1665c02`](https://github.com/eslint/eslint/commit/1665c029acb92bf8812267f1647ad1a7054cbcb4) feat: Use plugin metadata for flat config serialization (#16992) (Nicholas C. Zakas) +* [`b3634f6`](https://github.com/eslint/eslint/commit/b3634f695ddab6a82c0a9b1d8695e62b60d23366) feat: docs license (#17010) (Samuel Roldan) +* [`721c717`](https://github.com/eslint/eslint/commit/721c71782a7c11025689a1500e7690fb3794fcce) docs: Custom Processors cleanup and expansion (#16838) (Ben Perlmutter) +* [`1fbf118`](https://github.com/eslint/eslint/commit/1fbf1184fed57df02640aad4659afb54dc26a2e9) fix: `getFirstToken`/`getLastToken` on comment-only node (#16889) (Francesco Trotta) +* [`129e252`](https://github.com/eslint/eslint/commit/129e252132c7c476d7de17f40b54a333ddb2e6bb) fix: Fix typo in `logical-assignment-operators` rule description (#17000) (Francesco Trotta) +* [`892e6e5`](https://github.com/eslint/eslint/commit/892e6e58c5a07a549d3104de3b6b5879797dc97f) feat: languageOptions.parser must be an object. (#16985) (Nicholas C. Zakas) +* [`ada6a3e`](https://github.com/eslint/eslint/commit/ada6a3e6e3607523958f35e1260537630ec0e976) ci: unpin Node 19 (#16993) (Milos Djermanovic) +* [`c3da975`](https://github.com/eslint/eslint/commit/c3da975e69fde46f35338ce48528841a8dc1ffd2) chore: Remove triage label from template (#16990) (Nicholas C. Zakas) +* [`d049f97`](https://github.com/eslint/eslint/commit/d049f974103e530ef76ede25af701635caf1f405) docs: 'How ESLint is Maintained' page (#16961) (Ben Perlmutter) +* [`5251a92`](https://github.com/eslint/eslint/commit/5251a921866e8d3b380dfe8db8a6e6ab97773d5e) docs: Describe guard options for guard-for-in (#16986) (alope107) +* [`69bc0e2`](https://github.com/eslint/eslint/commit/69bc0e2f4412998f9384600a100d7882ea4dd3f3) ci: pin Node 19 to 19.7.0 (#16987) (Milos Djermanovic) +* [`6157d81`](https://github.com/eslint/eslint/commit/6157d813e19b80481a46f8cbdf9eae18a55e5619) docs: Add example to guard-for-in docs. (#16983) (alope107) +* [`fd47998`](https://github.com/eslint/eslint/commit/fd47998af6efadcdf5ba93e0bd1f4c02d97d22b3) docs: update `Array.prototype.toSorted` specification link (#16982) (Milos Djermanovic) +* [`3e1cf6b`](https://github.com/eslint/eslint/commit/3e1cf6bfc5ebc29314ddbe462d6cb580e9ab085c) docs: Copy edits on Maintain ESLint docs (#16939) (Ben Perlmutter) + +v8.36.0 - March 10, 2023 + +* [`602b111`](https://github.com/eslint/eslint/commit/602b11121910a97ab2bc4a95a46dd0ccd0a89309) chore: upgrade @eslint/js@8.36.0 (#16978) (Milos Djermanovic) +* [`43c2345`](https://github.com/eslint/eslint/commit/43c2345c27024aeab6127e6bbfd55c8b70bd317e) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`00afb84`](https://github.com/eslint/eslint/commit/00afb84e5039874c8745a45c953fceaf0c71c454) chore: upgrade @eslint/eslintrc@2.0.1 (#16977) (Milos Djermanovic) +* [`698c5aa`](https://github.com/eslint/eslint/commit/698c5aad50e628ff00281dbc786e42de79834035) chore: upgrade espree@9.5.0 (#16976) (Milos Djermanovic) +* [`b98fdd4`](https://github.com/eslint/eslint/commit/b98fdd413a3b07b262bfce6f704c1c1bb8582770) docs: Update README (GitHub Actions Bot) +* [`c89a485`](https://github.com/eslint/eslint/commit/c89a485c49450532ee3db74f2638429f1f37d0dd) feat: Add `checkJSDoc` option to multiline-comment-style (#16807) (Laurent Cozic) +* [`f5f5e11`](https://github.com/eslint/eslint/commit/f5f5e11bd5fd3daab9ccae41e270739c836c305e) feat: Serialize parsers/processors in flat config (#16944) (Nicholas C. Zakas) +* [`caf08ce`](https://github.com/eslint/eslint/commit/caf08ce0cc74917f7c0eec92d25fd784dc33ac4d) docs: fix estree link in custom formatters docs (#16967) (Milos Djermanovic) +* [`75acdd2`](https://github.com/eslint/eslint/commit/75acdd21c5ce7024252e9d41ed77d2f30587caac) chore: lint more js files in docs (#16964) (Milos Djermanovic) +* [`3398431`](https://github.com/eslint/eslint/commit/3398431574b903757bc78b08c8ed36b7b9fce8eb) docs: Custom Parsers cleanup/expansion (#16887) (Ben Perlmutter) +* [`19d3531`](https://github.com/eslint/eslint/commit/19d3531d9b54e1004318d28f9a6e18305c5bcc18) docs: Update README (GitHub Actions Bot) +* [`4799297`](https://github.com/eslint/eslint/commit/4799297ea582c81fd1e5623d32a7ddf7a7f3a126) feat: use @eslint-community dependencies (#16784) (Michaël De Boey) +* [`b09a512`](https://github.com/eslint/eslint/commit/b09a512107249a4eb19ef5a37b0bd672266eafdb) docs: detect and fix broken links (#16837) (Nitin Kumar) +* [`92c1943`](https://github.com/eslint/eslint/commit/92c1943ba73ea01e87086236e8736539b0eed558) fix: correctly iterate files matched by glob patterns (#16831) (Nitin Kumar) +* [`89d9844`](https://github.com/eslint/eslint/commit/89d9844b3151f09b5b21b6eeeda671009ec301e9) ci: bump actions/add-to-project from 0.4.0 to 0.4.1 (#16943) (dependabot[bot]) + +v8.35.0 - February 26, 2023 + +* [`cdcbe12`](https://github.com/eslint/eslint/commit/cdcbe127de20cbcc4e24131a808c13b1024e61a2) chore: upgrade @eslint/js@8.35.0 (#16935) (Brandon Mills) +* [`c954c34`](https://github.com/eslint/eslint/commit/c954c349c0c2f88919614efc95e1368c245582fd) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`5a517da`](https://github.com/eslint/eslint/commit/5a517da8e55f6de28e9c028c5627fa7d82945969) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`9f10926`](https://github.com/eslint/eslint/commit/9f10926d76be7cf675721b29bd5030e85cb4ab30) chore: upgrade @eslint/eslintrc@2.0.0 (#16928) (Milos Djermanovic) +* [`8e34a04`](https://github.com/eslint/eslint/commit/8e34a04e3a4395bce59bc6acadf84281abc11d18) feat: add `afterHashbangComment` option to `lines-around-comment` rule (#16920) (SUZUKI Sosuke) +* [`c8c0c71`](https://github.com/eslint/eslint/commit/c8c0c715a2964cc1859b99f9d4f542675094d1d5) feat: Move all and recommended configs into package. (#16844) (Nicholas C. Zakas) +* [`f9f195e`](https://github.com/eslint/eslint/commit/f9f195ef12deb114fb86763010a23ea0cb4c78d1) docs: Plugin docs cleanup & expansion (#16862) (Ben Perlmutter) +* [`df809fd`](https://github.com/eslint/eslint/commit/df809fdedc5fc92df4be8340e28baedbde605b4f) docs: Custom Formatters page cleanup/expansion (#16886) (Ben Perlmutter) +* [`0700d1b`](https://github.com/eslint/eslint/commit/0700d1b14659bf39b1a08f082c44c9084cf676a8) docs: Add PostCSS/Autoprefixer/CSSNano (#16502) (Nick Schonning) +* [`da728fa`](https://github.com/eslint/eslint/commit/da728fae6c4e5fdda74195e84d45d67ad5cafc45) ci: use LTS node version in workflows (#16907) (Nitin Kumar) +* [`7b9e9bf`](https://github.com/eslint/eslint/commit/7b9e9bf78bedb009fe2813308ede1f46502c3890) docs: support unicode anchors (#16782) (Percy Ma) +* [`5fbc0bf`](https://github.com/eslint/eslint/commit/5fbc0bffdd9f84feb43296eb502d1e484fb323f2) docs: Update README (GitHub Actions Bot) +* [`c57b4f3`](https://github.com/eslint/eslint/commit/c57b4f3dc6383e452120381204ee4a7c874225a0) perf: upgrade to esquery@1.4.2 (#16901) (Milos Djermanovic) +* [`9698bc5`](https://github.com/eslint/eslint/commit/9698bc5cdec1bbee567a6a489da82e87fe65d019) fix: pin esquery v1.4.0 (fixes #16896) (#16897) (唯然) +* [`67865a0`](https://github.com/eslint/eslint/commit/67865a064cc1a4e320030299edc1cfdd1f9ac3b8) docs: Remove mention of mailing list (#16869) (Amaresh S M) +* [`43af24a`](https://github.com/eslint/eslint/commit/43af24a88b939a62880c37d1332b02f677d82f16) docs: Add explanation of when to use 'warn' severity (#16882) (Nicholas C. Zakas) +* [`71f6f0d`](https://github.com/eslint/eslint/commit/71f6f0dcd574320ee71c3eb1f313841899bdf260) feat: report more cases with `??` in no-constant-binary-expression (#16826) (Daiki Nishikawa) +* [`ed2999b`](https://github.com/eslint/eslint/commit/ed2999b38b4d61f5c278301738e294012d5d3c9e) docs: Shareable configs page edits and expansion (#16824) (Ben Perlmutter) +* [`2780635`](https://github.com/eslint/eslint/commit/27806358b5e1c4d37b63b1c61595e86ff03b5b42) docs: fix typos (#16884) (Lioness100) +* [`5bdaae2`](https://github.com/eslint/eslint/commit/5bdaae205c3a0089ea338b382df59e21d5b06436) docs: Ways to Extend ESLint page (#16861) (Ben Perlmutter) +* [`9122f07`](https://github.com/eslint/eslint/commit/9122f0764031dc36970df715bc5e16973890e18d) chore: Update stale bot settings (#16870) (Nicholas C. Zakas) + +v8.34.0 - February 10, 2023 + +* [`f0a9883`](https://github.com/eslint/eslint/commit/f0a988384ea1a262150e70d83abd8a5e50c46fa7) docs: split rules documentation (#16797) (Ben Perlmutter) +* [`923f61d`](https://github.com/eslint/eslint/commit/923f61d8fc82d83b912c6ba95abb5a509c4d7b52) fix: false positive with assignment in `no-extra-parens` (#16872) (Francesco Trotta) +* [`9dbe06d`](https://github.com/eslint/eslint/commit/9dbe06d0ad875e6d5964497e2975e8d789e763d0) chore: add `type` property to array-element-newline schema (#16877) (MHO) +* [`a061527`](https://github.com/eslint/eslint/commit/a061527a0332f0edf559acfc2902a327cae098d9) chore: Remove unused functions (#16868) (Nicholas C. Zakas) +* [`67aa37b`](https://github.com/eslint/eslint/commit/67aa37b583f059226b9c959672400f04ed6a56b5) docs: fix typo in command-line-interface.md (#16871) (Kevin Rouchut) +* [`337f7ed`](https://github.com/eslint/eslint/commit/337f7ed96131d873be7ae6b010739476d0ad15e9) docs: fix width of language input (#16849) (Tanuj Kanti) +* [`9b2fcf7`](https://github.com/eslint/eslint/commit/9b2fcf7e928fc92ac6d43617bdee1bda250b7491) feat: `array-callback-return` supports `Array.prototype.toSorted` (#16845) (SUZUKI Sosuke) +* [`71349a1`](https://github.com/eslint/eslint/commit/71349a1f709baa361bd656a7ce4a7d35d857a9a8) docs: Configure a Parser page (#16803) (Ben Perlmutter) +* [`de7e925`](https://github.com/eslint/eslint/commit/de7e925d03764f3681269b30bb60b92ee463c10f) docs: remove extra line numbers in example (#16848) (jonz94) +* [`ad38d77`](https://github.com/eslint/eslint/commit/ad38d77102d6fe30cfa92c831174f178bb35c88b) docs: Update README (GitHub Actions Bot) + +v8.33.0 - January 28, 2023 + +* [`17f4be2`](https://github.com/eslint/eslint/commit/17f4be2b66deb81f4e9ffb3d6bdfb79f3fcf85a2) docs: Fix examples in no-multiple-empty-lines rule (#16835) (jonz94) +* [`9c7cfe3`](https://github.com/eslint/eslint/commit/9c7cfe33c4a39cf2c23529afe02030ea7f8acf70) docs: 'Source Code' content in 'Set up Development Environment' page (#16780) (Ben Perlmutter) +* [`ede5c64`](https://github.com/eslint/eslint/commit/ede5c6475469a905da4f559ab55f0ee73168a9d7) docs: Custom processors page (#16802) (Ben Perlmutter) +* [`2620614`](https://github.com/eslint/eslint/commit/2620614f525de13f2e3ab0a7cd92abe89dae4897) docs: Code of Conduct page (#16781) (Ben Perlmutter) +* [`50a8efd`](https://github.com/eslint/eslint/commit/50a8efd957c70c9978a8ed25744a24193b00e078) docs: report a sec vulnerability page (#16808) (Ben Perlmutter) +* [`2cc7954`](https://github.com/eslint/eslint/commit/2cc7954cdb1fed44e8a5d3c9b3ea1deceadb5e00) feat: add `restrictDefaultExports` option to no-restricted-exports rule (#16785) (Nitin Kumar) +* [`ed60afd`](https://github.com/eslint/eslint/commit/ed60afd4450e769a975447178299446f4439d926) docs: Update page titles, section landing pages, and side TOC (#16760) (Ben Perlmutter) +* [`333c712`](https://github.com/eslint/eslint/commit/333c71243537966930e9ab8178bc98c37949b5f2) docs: add background to code-path-diagrams for dark-mode (#16822) (Tanuj Kanti) +* [`f5f7b9b`](https://github.com/eslint/eslint/commit/f5f7b9b8b512f5c6a5b4a1037f81bb3f5a7311e0) docs: Update README (GitHub Actions Bot) +* [`2aa4f5f`](https://github.com/eslint/eslint/commit/2aa4f5fb2fdb1c4a1734093c225e5c6251b0ee0f) docs: no-constant-condition: Add multi-comparison example (#16776) (Sebastian Simon) +* [`40287db`](https://github.com/eslint/eslint/commit/40287dbe7407934a69805f02ece07491778c3694) docs: Remove Google Group icon (#16779) (Nicholas C. Zakas) +* [`ea10ca5`](https://github.com/eslint/eslint/commit/ea10ca5b7b5bd8f6e6daf030ece9a3a82f10994c) docs: 'a .eslint' -> 'an .eslint' for consistency (#16809) (Ben Perlmutter) +* [`3be0748`](https://github.com/eslint/eslint/commit/3be07488ee7b6a9591d169be9648fbd36b32105e) docs: add example for nodejs lintText api (#16789) (Siva K) +* [`ce4f5ff`](https://github.com/eslint/eslint/commit/ce4f5ff30590df053a539c8e8e2597838e038a36) docs: Replace removed related rules with a valid rule (#16800) (Ville Saalo) + +v8.32.0 - January 14, 2023 + +* [`17b65ad`](https://github.com/eslint/eslint/commit/17b65ad10d653bb05077f21d8b1f79bee96e38d8) docs: IA Update page URL move (#16665) (Ben Perlmutter) +* [`b4f8329`](https://github.com/eslint/eslint/commit/b4f8329164d7b293a1557e05b987d2a685fe1d30) fix: ignore directives for no-fallthrough (#16757) (gfyoung) +* [`5981296`](https://github.com/eslint/eslint/commit/5981296d5c7c86228ad766009901191fdd87d5a4) docs: fix theme switcher button (#16752) (Sam Chen) +* [`6669413`](https://github.com/eslint/eslint/commit/66694136b67277c050bd27f60050779687a88c9f) docs: deploy prerelease docs under the `/docs/next/` path (#16541) (Nitin Kumar) +* [`2952d6e`](https://github.com/eslint/eslint/commit/2952d6ed95811ce0971b6855d66fb7a9767a7b72) chore: sync templates/*.md files with issue templates (#16758) (gfyoung) +* [`78ecfe0`](https://github.com/eslint/eslint/commit/78ecfe0e52c0e5780fefc8dc9a98864e48de6637) docs: use inline code for rule options name (#16768) (Percy Ma) +* [`3e34418`](https://github.com/eslint/eslint/commit/3e34418b31664decfb2337de798feafbf985b66c) chore: Add new issues to triage project (#16740) (Nicholas C. Zakas) +* [`fc2ea59`](https://github.com/eslint/eslint/commit/fc2ea598aee97beb6d768866da1ee4f63775f0c9) docs: Update README (GitHub Actions Bot) +* [`fc20f24`](https://github.com/eslint/eslint/commit/fc20f242a2ac073b5af6d5fca67e07a175f36c3b) feat: add suggestions for redundant wrapping in prefer-regex-literals (#16658) (YeonJuan) +* [`762a872`](https://github.com/eslint/eslint/commit/762a8727fb3b5619cff900826053b643ca5f1162) docs: Update README (GitHub Actions Bot) + +v8.31.0 - December 31, 2022 + +* [`65d4e24`](https://github.com/eslint/eslint/commit/65d4e24c36367cd63f0eba7371820e0e81dae7aa) chore: Upgrade @eslint/eslintrc@1.4.1 (#16729) (Brandon Mills) +* [`35439f1`](https://github.com/eslint/eslint/commit/35439f1572e1a8888f7feb6c5e51a15b5582495d) fix: correct syntax error in `prefer-arrow-callback` autofix (#16722) (Francesco Trotta) +* [`87b2470`](https://github.com/eslint/eslint/commit/87b247058ed520061fe1a146b7f0e7072a94990d) fix: new instance of FlatESLint should load latest config file version (#16608) (Milos Djermanovic) +* [`8d93081`](https://github.com/eslint/eslint/commit/8d93081a717f6e8b8cb60c3075cc1d7e4e655e6b) chore: fix CI failure (#16721) (Sam Chen) +* [`4339dc4`](https://github.com/eslint/eslint/commit/4339dc462d78888fe2e10acdfacd6f57245ce6ae) docs: Update README (GitHub Actions Bot) +* [`8f17247`](https://github.com/eslint/eslint/commit/8f17247a93240ff8a08980d8e06352e4ff4e8fe3) chore: Set up automatic updating of README (#16717) (Nicholas C. Zakas) +* [`4e4049c`](https://github.com/eslint/eslint/commit/4e4049c5fa355b2091afc8948690fcd7b1c1e6df) docs: optimize code block structure (#16669) (Sam Chen) +* [`54a7ade`](https://github.com/eslint/eslint/commit/54a7ade5d8e6f59554afeb9202ba6143f8afdf57) docs: do not escape code blocks of formatters examples (#16719) (Sam Chen) +* [`52c7c73`](https://github.com/eslint/eslint/commit/52c7c73c052e1ec2528c6b4af78181bc30cf8cdd) feat: check assignment patterns in no-underscore-dangle (#16693) (Milos Djermanovic) +* [`e5ecfef`](https://github.com/eslint/eslint/commit/e5ecfefa1c952195a3a8371f5953cc655d844079) docs: Add function call example for no-undefined (#16712) (Elliot Huffman) +* [`a3262f0`](https://github.com/eslint/eslint/commit/a3262f0a6305d2a721fac137a60c62c019b26aa4) docs: Add mastodon link (#16638) (Amaresh S M) +* [`4cd87cb`](https://github.com/eslint/eslint/commit/4cd87cb3c52412277577ba00c4fbb1aec36acc8c) ci: bump actions/stale from 6 to 7 (#16713) (dependabot[bot]) +* [`a14ccf9`](https://github.com/eslint/eslint/commit/a14ccf91af1122e419710f58ef494980fc4894b3) docs: clarify files property (#16709) (Sam Chen) +* [`3b29eb1`](https://github.com/eslint/eslint/commit/3b29eb14e00182614c986d8498b483a9917976e7) docs: fix npm link (#16710) (Abdullah Osama) +* [`fd20c75`](https://github.com/eslint/eslint/commit/fd20c75b1059c54d598c0abaf63e7d7a80f04f32) chore: sort package.json scripts in alphabetical order (#16705) (Darius Dzien) +* [`a638673`](https://github.com/eslint/eslint/commit/a638673ee6e94344c46d12dfc988adeb3783f817) docs: fix search bar focus on `Esc` (#16700) (Shanmughapriyan S) +* [`f62b722`](https://github.com/eslint/eslint/commit/f62b722251858a5dfb157591910edbaaeb4a966f) docs: country flag missing in windows (#16698) (Shanmughapriyan S) +* [`4d27ec6`](https://github.com/eslint/eslint/commit/4d27ec6019847afabeebf592dddc014e9220057c) docs: display zh-hans in the docs language switcher (#16686) (Percy Ma) +* [`8bda20e`](https://github.com/eslint/eslint/commit/8bda20e8276c6ba17d31842fcdd63ba65476fbbd) docs: remove manually maintained anchors (#16685) (Percy Ma) +* [`b401cde`](https://github.com/eslint/eslint/commit/b401cde47d44746ff91b8feced3fb3a4e32c0e12) feat: add options to check destructuring in no-underscore-dangle (#16006) (Morten Kaltoft) +* [`b68440f`](https://github.com/eslint/eslint/commit/b68440ff2b8322fc00373792701169205c94ed94) docs: User Guide Getting Started expansion (#16596) (Ben Perlmutter) +* [`30d0daf`](https://github.com/eslint/eslint/commit/30d0daf55e85a412995f6d69f47cab3fb591f2c3) feat: group properties with values in parentheses in `key-spacing` (#16677) (Francesco Trotta) +* [`10a5c78`](https://github.com/eslint/eslint/commit/10a5c7839370219c79f44d4206cbd7c28a72bad5) chore: update ignore patterns in `eslint.config.js` (#16678) (Milos Djermanovic) + +v8.30.0 - December 16, 2022 + +* [`f2c4737`](https://github.com/eslint/eslint/commit/f2c47372420f050ad8f2300271345de1c1232635) chore: upgrade @eslint/eslintrc@1.4.0 (#16675) (Milos Djermanovic) +* [`1a327aa`](https://github.com/eslint/eslint/commit/1a327aae57f1b68c96b27cc1bd57f8198d5a3a7c) fix: Ensure flat config unignores work consistently like eslintrc (#16579) (Nicholas C. Zakas) +* [`075ef2c`](https://github.com/eslint/eslint/commit/075ef2cf315e75b51b671c40ce9a97c66b2e4b50) feat: add suggestion for no-return-await (#16637) (Daniel Bartholomae) +* [`ba74253`](https://github.com/eslint/eslint/commit/ba74253e8bd63e9e163bbee0540031be77e39253) chore: standardize npm script names per #14827 (#16315) (Patrick McElhaney) +* [`6a8cd94`](https://github.com/eslint/eslint/commit/6a8cd94ed08983c70ca7d72dc6e360770a743405) docs: Clarify Discord info in issue template config (#16663) (Nicholas C. Zakas) +* [`0d9af4c`](https://github.com/eslint/eslint/commit/0d9af4c5674809be993439c766dcd9d7f65fcec9) ci: fix npm v9 problem with `file:` (#16664) (Milos Djermanovic) +* [`7190d98`](https://github.com/eslint/eslint/commit/7190d98ff40023f24b0c6a98319ae8a82c99ff5b) feat: update globals (#16654) (Sébastien Règne) +* [`ad44344`](https://github.com/eslint/eslint/commit/ad44344ef6fdeac7217eb83bc54a230382c0da5e) docs: CLI documentation standardization (#16563) (Ben Perlmutter) +* [`90c9219`](https://github.com/eslint/eslint/commit/90c9219181e0aadcae7224602d2988186d457113) refactor: migrate off deprecated function-style rules in all tests (#16618) (Bryan Mishkin) +* [`9b8bb72`](https://github.com/eslint/eslint/commit/9b8bb72c49a453086954b06a5d7dd390731b1975) fix: autofix recursive functions in no-var (#16611) (Milos Djermanovic) +* [`293573e`](https://github.com/eslint/eslint/commit/293573eb530d161d2a5b01efd9d3de49dadea022) docs: fix broken line numbers (#16606) (Sam Chen) +* [`fa2c64b`](https://github.com/eslint/eslint/commit/fa2c64be10d5854fb586c20957737d7d2da1975a) docs: use relative links for internal links (#16631) (Percy Ma) +* [`75276c9`](https://github.com/eslint/eslint/commit/75276c9bc7c4bc013fc6bdf277353c979934d73b) docs: reorder options in no-unused-vars (#16625) (Milos Djermanovic) +* [`7276fe5`](https://github.com/eslint/eslint/commit/7276fe5776f03fb90e575ed63a9b1a6766993e42) docs: Fix anchor in URL (#16628) (Karl Horky) +* [`6bef135`](https://github.com/eslint/eslint/commit/6bef1350e692c818c55c6d2074c12506e98cdf4f) docs: don't apply layouts to html formatter example (#16591) (Tanuj Kanti) +* [`dfc7ec1`](https://github.com/eslint/eslint/commit/dfc7ec11b11b56daaa10e8e6d08c5cddfc8c2c59) docs: Formatters page updates (#16566) (Ben Perlmutter) +* [`8ba124c`](https://github.com/eslint/eslint/commit/8ba124cfd8aaf01d14ccbcb1654798624948fb0a) docs: update the `prefer-const` example (#16607) (Pavel) +* [`e6cb05a`](https://github.com/eslint/eslint/commit/e6cb05aa35bafb9e88f161ad1fa6b01942a7c13c) docs: fix css leaking (#16603) (Sam Chen) + +v8.29.0 - December 2, 2022 + +* [`0311d81`](https://github.com/eslint/eslint/commit/0311d81834d675b8ae7cc92a460b37115edc4018) docs: Configuring Plugins page intro, page tweaks, and rename (#16534) (Ben Perlmutter) +* [`57089b1`](https://github.com/eslint/eslint/commit/57089b1ede624452bc94404b6e60d01d48cfd468) docs: add a property assignment example for camelcase rule (#16605) (Milos Djermanovic) +* [`b6ab030`](https://github.com/eslint/eslint/commit/b6ab030897d2e8b314b33a6502346a4ac45bb8da) docs: add docs codeowners (#16601) (Strek) +* [`7628403`](https://github.com/eslint/eslint/commit/7628403a57d9d9b4e2cb2b36309170900f58832e) chore: add discord channel link (#16590) (Amaresh S M) +* [`49a07c5`](https://github.com/eslint/eslint/commit/49a07c52c5af7e98d161ff4acd44bbbe0aa6383b) feat: add `allowParensAfterCommentPattern` option to no-extra-parens (#16561) (Nitin Kumar) +* [`6380c87`](https://github.com/eslint/eslint/commit/6380c87c563be5dc78ce0ddd5c7409aaf71692bb) docs: fix sitemap and feed (#16592) (Milos Djermanovic) +* [`e6a865d`](https://github.com/eslint/eslint/commit/e6a865d70aed9e1c07be712e40c38da1a5dda849) feat: `prefer-named-capture-group` add suggestions (#16544) (Josh Goldberg) +* [`ade621d`](https://github.com/eslint/eslint/commit/ade621dd12fcd3b65644bb3468248cc040db756c) docs: perf debounce the search query (#16586) (Shanmughapriyan S) +* [`a91332b`](https://github.com/eslint/eslint/commit/a91332b8bd9adfa2aa8110071bdf73f56d400050) feat: In no-invalid-regexp validate flags also for non-literal patterns (#16583) (trosos) +* [`fbcf3ab`](https://github.com/eslint/eslint/commit/fbcf3abd54dd20aec3c695cacece56493633c97f) docs: fix searchbar clear button (#16585) (Shanmughapriyan S) +* [`f894035`](https://github.com/eslint/eslint/commit/f89403553b31d24f4fc841424cc7dcb8c3ef689f) docs: HTTPS link to yeoman.io (#16582) (Christian Oliff) +* [`de12b26`](https://github.com/eslint/eslint/commit/de12b266f2aa6f063d0af888b8f0de41d09ec33f) docs: Update configuration file pages (#16509) (Ben Perlmutter) +* [`f5808cb`](https://github.com/eslint/eslint/commit/f5808cb51529174a67b4938223f06435ad6d5118) chore: fix rule doc headers check (#16564) (Milos Djermanovic) +* [`1ae9f20`](https://github.com/eslint/eslint/commit/1ae9f2067442434c6ccc6b41703624b302d17c67) docs: update correct code examples for `no-extra-parens` rule (#16560) (Nitin Kumar) + +v8.28.0 - November 18, 2022 + +* [`34c05a7`](https://github.com/eslint/eslint/commit/34c05a779ada3142995392ae12978461900088df) docs: Language Options page intro and tweaks (#16511) (Ben Perlmutter) +* [`3e66387`](https://github.com/eslint/eslint/commit/3e663873c97773ab1ecdff54aaa122075d5bb389) docs: add intro and edit ignoring files page (#16510) (Ben Perlmutter) +* [`436f712`](https://github.com/eslint/eslint/commit/436f712843360f98b2bd63256bf0c4f77013b54c) docs: fix Header UI inconsistency (#16464) (Tanuj Kanti) +* [`f743816`](https://github.com/eslint/eslint/commit/f74381696703d8eed0e175d42f96904a3d1cb4cb) docs: switch to wrench emoji for auto-fixable rules (#16545) (Bryan Mishkin) +* [`bc0547e`](https://github.com/eslint/eslint/commit/bc0547eb149a1e04211826662d2d798fb331983d) docs: improve styles for versions and languages page (#16553) (Nitin Kumar) +* [`6070f58`](https://github.com/eslint/eslint/commit/6070f58d802d77c6c781c6bc1f554eef8b3d8f68) docs: clarify esquery issue workaround (#16556) (Milos Djermanovic) +* [`b48e4f8`](https://github.com/eslint/eslint/commit/b48e4f89c59bd1c5408e3db492a0e95a402820bd) docs: Command Line Interface intro and tweaks (#16535) (Ben Perlmutter) +* [`b92b30f`](https://github.com/eslint/eslint/commit/b92b30f93db64314827305b552cbb832c63fa949) docs: Add Rules page intro and content tweaks (#16523) (Ben Perlmutter) +* [`1769b42`](https://github.com/eslint/eslint/commit/1769b423392512db4adf1eff75896c1ac0c3606b) docs: Integrations page introduction (#16548) (Ben Perlmutter) +* [`63bce44`](https://github.com/eslint/eslint/commit/63bce44e7b6326e1e94fc7f6283df8de7bbac273) feat: add `ignoreClassFieldInitialValues` option to no-magic-numbers (#16539) (Milos Djermanovic) +* [`c50ae4f`](https://github.com/eslint/eslint/commit/c50ae4f840d1ee9dc7b80a46c887398c0ec0a67c) fix: Ensure that dot files are found with globs. (#16550) (Nicholas C. Zakas) +* [`a8d0a57`](https://github.com/eslint/eslint/commit/a8d0a57cbc29a917258df41d3254ecd29bcf61ab) docs: make table of contents sticky on desktop (#16506) (Sam Chen) +* [`9432b67`](https://github.com/eslint/eslint/commit/9432b67f76ddd7b8a73d37e8a041a9ff25822f0c) fix: throw error for first unmatched pattern (#16533) (Milos Djermanovic) +* [`8385ecd`](https://github.com/eslint/eslint/commit/8385ecdbbe342211e20aebe76fa7affe8ec04c33) feat: multiline properties in rule `key-spacing` with option `align` (#16532) (Francesco Trotta) +* [`a4e89db`](https://github.com/eslint/eslint/commit/a4e89dbe85589dab982885872dc206e090c27b3c) feat: `no-obj-calls` support `Intl` (#16543) (Sosuke Suzuki) +* [`a01315a`](https://github.com/eslint/eslint/commit/a01315a7d8f3a70468b7a644fde01d6983778c6b) docs: fix route of japanese translation site (#16542) (Tanuj Kanti) +* [`e94a4a9`](https://github.com/eslint/eslint/commit/e94a4a95ee301b0344d3292c37a0b29d8e18ab30) chore: Add tests to verify #16038 is fixed (#16538) (Nicholas C. Zakas) +* [`0515628`](https://github.com/eslint/eslint/commit/05156285396eba9ce3d3a0990a8c89d5bc229636) docs: use emoji instead of svg for deprecated rule (#16536) (Bryan Mishkin) +* [`e76c382`](https://github.com/eslint/eslint/commit/e76c3827727b48c16af8467c02c31160e5595d83) fix: allow `* 1` when followed by `/` in no-implicit-coercion (#16522) (Milos Djermanovic) +* [`68f1288`](https://github.com/eslint/eslint/commit/68f12882fbaeda8ffb26425d42d261346ff5af51) docs: set default layouts (#16484) (Percy Ma) +* [`e13f194`](https://github.com/eslint/eslint/commit/e13f194f89f591730aa955f7b62192c7e8296069) chore: stricter validation of `meta.docs.description` in core rules (#16529) (Milos Djermanovic) +* [`776827a`](https://github.com/eslint/eslint/commit/776827a1748da88a25e7903bd794f5439de922b5) docs: init config about specifying shared configs (#16483) (Percy Ma) +* [`72dbfbc`](https://github.com/eslint/eslint/commit/72dbfbc0c45d2b9d19b21c6a5a6b4ca71403ffbf) chore: use `pkg` parameter in `getNpmPackageVersion` (#16525) (webxmsj) +* [`5c39425`](https://github.com/eslint/eslint/commit/5c39425fc55ecc0b97bbd07ac22654c0eb4f789c) docs: fix broken link to plugins (#16520) (Ádám T. Nagy) +* [`c97c789`](https://github.com/eslint/eslint/commit/c97c7897686ac4dc2828537d6a017f3c99f7d905) docs: Add missing no-new-native-nonconstructor docs code fence (#16503) (Brandon Mills) + +v8.27.0 - November 6, 2022 + +* [`f14587c`](https://github.com/eslint/eslint/commit/f14587c42bb0fe6ec89529aede045a488083d6ee) feat: new `no-new-native-nonconstructor` rule (#16368) (Sosuke Suzuki) +* [`978799b`](https://github.com/eslint/eslint/commit/978799bd5c76fecf4ce8f17d89ad6c9f436c3228) feat: add new rule `no-empty-static-block` (#16325) (Sosuke Suzuki) +* [`ce93b42`](https://github.com/eslint/eslint/commit/ce93b429bf917640473dd7e26b49bba993c68ce4) docs: Stylelint property-no-unknown (#16497) (Nick Schonning) +* [`d2cecb4`](https://github.com/eslint/eslint/commit/d2cecb4ad2a6d33444cf0288a863c43acb3b468a) docs: Stylelint declaration-block-no-shorthand-property-overrides (#16498) (Nick Schonning) +* [`0a92805`](https://github.com/eslint/eslint/commit/0a92805d7713118866e519b0ff2a61c5d6238ad9) docs: stylelint color-hex-case (#16496) (Nick Schonning) +* [`c3ce521`](https://github.com/eslint/eslint/commit/c3ce5212f672d95dde3465d7d3c4bf99ff665f8b) fix: Ensure unmatched glob patterns throw an error (#16462) (Nicholas C. Zakas) +* [`74a5af4`](https://github.com/eslint/eslint/commit/74a5af487ac7296a46a8078e585f00df72b63d83) docs: fix stylelint error (#16491) (Milos Djermanovic) +* [`69216ee`](https://github.com/eslint/eslint/commit/69216ee69c7172e847b64e0e934b5121a34d0ea3) feat: no-empty suggest to add comment in empty BlockStatement (#16470) (Nitin Kumar) +* [`324db1a`](https://github.com/eslint/eslint/commit/324db1a11e43ba9d954dc522763faea19129ce6a) docs: explicit stylelint color related rules (#16465) (Nick Schonning) +* [`94dc4f1`](https://github.com/eslint/eslint/commit/94dc4f19ba49fe2358f8bcc2fc3555d222766755) docs: use Stylelint for HTML files (#16468) (Nick Schonning) +* [`cc6128d`](https://github.com/eslint/eslint/commit/cc6128db4f489c3ab80fff2f9dbeea313e72208d) docs: enable stylelint declaration-block-no-duplicate-properties (#16466) (Nick Schonning) +* [`d03a8bf`](https://github.com/eslint/eslint/commit/d03a8bf8978bd330aeb951f18cc92bf1ad24eeec) docs: Add heading to justification explanation (#16430) (Maritaria) +* [`886a038`](https://github.com/eslint/eslint/commit/886a0386897f96d2da95eba8c52bd893fcbf7e86) fix: handle files with unspecified path in `getRulesMetaForResults` (#16437) (Francesco Trotta) +* [`319f0a5`](https://github.com/eslint/eslint/commit/319f0a5491598825bbd528c6d1fc12771056a74c) feat: use `context.languageOptions.ecmaVersion` in core rules (#16458) (Milos Djermanovic) +* [`8a15968`](https://github.com/eslint/eslint/commit/8a159686f9d497262d573dd601855ce28362199b) docs: add Stylelint configuration and cleanup (#16379) (Nick Schonning) +* [`9b0a469`](https://github.com/eslint/eslint/commit/9b0a469d1e4650c1d9da26239357e715b11b2d97) docs: note commit messages don't support scope (#16435) (Andy Edwards) +* [`1581405`](https://github.com/eslint/eslint/commit/15814057fd69319b3744bdea5db2455f85d2e74f) docs: improve context.getScope() docs (#16417) (Ben Perlmutter) +* [`b797149`](https://github.com/eslint/eslint/commit/b7971496e9b44add405ca0360294f5c3be85b540) docs: update formatters template (#16454) (Milos Djermanovic) +* [`5ac4de9`](https://github.com/eslint/eslint/commit/5ac4de911f712cb3a5a16eb7a4063eee09dfc97c) docs: fix link to formatters on the Core Concepts page (#16455) (Vladislav) +* [`33313ef`](https://github.com/eslint/eslint/commit/33313ef56258a6a96b00a3e70025b94bd2f2fe9f) docs: core-concepts: fix link to semi rule (#16453) (coderaiser) + +v8.26.0 - October 21, 2022 + +* [`df77409`](https://github.com/eslint/eslint/commit/df7740967ffab2915974c7b310ac76ea2915ac2d) fix: use `baseConfig` constructor option in FlatESLint (#16432) (Milos Djermanovic) +* [`33668ee`](https://github.com/eslint/eslint/commit/33668ee9d22e1988ba03e07fb547738bdb21dc0e) fix: Ensure that glob patterns are matched correctly. (#16449) (Nicholas C. Zakas) +* [`651649b`](https://github.com/eslint/eslint/commit/651649b12797594a86c0d659d6a0d1cdbda6f57b) docs: Core concepts page (#16399) (Ben Perlmutter) +* [`4715787`](https://github.com/eslint/eslint/commit/4715787724a71494ba0bb0c5fe4639570bb6985b) feat: check `Object.create()` in getter-return (#16420) (Yuki Hirasawa) +* [`e917a9a`](https://github.com/eslint/eslint/commit/e917a9a2e555d398c64b985fc933d44a42c958f0) ci: add node v19 (#16443) (Koichi ITO) +* [`740b208`](https://github.com/eslint/eslint/commit/740b20826fadc5322ea5547c1ba41793944e571d) fix: ignore messages without a `ruleId` in `getRulesMetaForResults` (#16409) (Francesco Trotta) +* [`8f9759e`](https://github.com/eslint/eslint/commit/8f9759e2a94586357d85fac902e038fabdba79a7) fix: `--ignore-pattern` in flat config mode should be relative to `cwd` (#16425) (Milos Djermanovic) +* [`325ad37`](https://github.com/eslint/eslint/commit/325ad375a52d1c7b8b8fd23943350c91781366a2) fix: make `getRulesMetaForResults` return a plain object in trivial case (#16438) (Francesco Trotta) +* [`a2810bc`](https://github.com/eslint/eslint/commit/a2810bc485d9f1123a86b60702fcaa51e19d71a3) fix: Ensure that directories can be unignored. (#16436) (Nicholas C. Zakas) +* [`631cf72`](https://github.com/eslint/eslint/commit/631cf72e82f316a2cc08770e5c81b858637ab04a) docs: note --ignore-path not supported with flat config (#16434) (Andy Edwards) +* [`1692840`](https://github.com/eslint/eslint/commit/1692840a2f763737a4891419dc304db4ebedab5d) docs: fix syntax in examples for new config files (#16427) (Milos Djermanovic) +* [`28d1902`](https://github.com/eslint/eslint/commit/28d190264017dbaa29f2ab218f73b623143cd1af) feat: `no-implicit-globals` supports `exported` block comment (#16343) (Sosuke Suzuki) +* [`35916ad`](https://github.com/eslint/eslint/commit/35916ad9bfc07dab63361721df1bd7f21e43e094) fix: Ensure unignore and reignore work correctly in flat config. (#16422) (Nicholas C. Zakas) +* [`4b70b91`](https://github.com/eslint/eslint/commit/4b70b91a6e28669ab8e2a4ce2a6d9ed40be20fa7) chore: Add VS Code issues link (#16423) (Nicholas C. Zakas) +* [`e940be7`](https://github.com/eslint/eslint/commit/e940be7a83d0caea15b64c1e1c2785a6540e2641) feat: Use ESLINT_USE_FLAT_CONFIG environment variable for flat config (#16356) (Tomer Aberbach) +* [`d336cfc`](https://github.com/eslint/eslint/commit/d336cfc9145a72bf8730250ee1e331a135e6ee2c) docs: Document extending plugin with new config (#16394) (Ben Perlmutter) +* [`dd0c58f`](https://github.com/eslint/eslint/commit/dd0c58f0f34d24331ae55139af39cf2747125f5e) feat: Swap out Globby for custom globbing solution. (#16369) (Nicholas C. Zakas) +* [`232d291`](https://github.com/eslint/eslint/commit/232d2916ac5e44db55c2ffbd2f3b37ad70037b7b) chore: suppress a Node.js deprecation warning (#16398) (Koichi ITO) + +v8.25.0 - October 7, 2022 + +* [`1f78594`](https://github.com/eslint/eslint/commit/1f785944f61c97996445e48cb74fc300142e7310) chore: upgrade @eslint/eslintrc@1.3.3 (#16397) (Milos Djermanovic) +* [`173e820`](https://github.com/eslint/eslint/commit/173e82040895ad53b2d9940bfb3fb67a0478f00b) feat: Pass --max-warnings value to formatters (#16348) (Brandon Mills) +* [`8476a9b`](https://github.com/eslint/eslint/commit/8476a9b8b81164887cdf38a21d431b75ff2956b1) chore: Remove CODEOWNERS (#16375) (Nick Schonning) +* [`720ff75`](https://github.com/eslint/eslint/commit/720ff75beb9f4fdcf2a185fcb8020cf78483fdeb) chore: use "ci" for Dependabot commit message (#16377) (Nick Schonning) +* [`90c6028`](https://github.com/eslint/eslint/commit/90c602802b6e330b79c42f282e9a615c583e32d7) docs: Conflicting fixes (#16366) (Ben Perlmutter) +* [`5a3fe70`](https://github.com/eslint/eslint/commit/5a3fe70c5261acbf115fa5f47231cbc4ac62c1bc) docs: Add VS to integrations page (#16381) (Maria José Solano) +* [`6964cb1`](https://github.com/eslint/eslint/commit/6964cb1e0f073b236cb3288b9d8be495336bbf29) feat: remove support for ignore files in FlatESLint (#16355) (Milos Djermanovic) +* [`49bd1e5`](https://github.com/eslint/eslint/commit/49bd1e5669b34fd7e0f4a3cf42009866980d7e15) docs: remove unused link definitions (#16376) (Nick Schonning) +* [`42f5479`](https://github.com/eslint/eslint/commit/42f547948f284f1c67799f237dfeb86fc400c7c7) chore: bump actions/stale from 5 to 6 (#16350) (dependabot[bot]) +* [`3bd380d`](https://github.com/eslint/eslint/commit/3bd380d3ea7e88ade4905ec0b240c866ab79a69d) docs: typo cleanups for docs (#16374) (Nick Schonning) +* [`b3a0837`](https://github.com/eslint/eslint/commit/b3a08376cfb61275a7557d6d166b6116f36e5ac2) docs: remove duplicate words (#16378) (Nick Schonning) +* [`a682562`](https://github.com/eslint/eslint/commit/a682562458948f74a227be60a80e10e7a3753124) docs: add `BigInt` to `new-cap` docs (#16362) (Sosuke Suzuki) +* [`1cc4b3a`](https://github.com/eslint/eslint/commit/1cc4b3a8f82a7945dcd8c59550b6a906a0fabbb4) feat: `id-length` counts graphemes instead of code units (#16321) (Sosuke Suzuki) +* [`f6d57fb`](https://github.com/eslint/eslint/commit/f6d57fb657c2f4e8e0140ad057da34c935482972) docs: Update docs README (#16352) (Ben Perlmutter) +* [`e5e9e27`](https://github.com/eslint/eslint/commit/e5e9e271da58361bda16f7abc8f367ccc6f91510) chore: remove `jsdoc` dev dependency (#16344) (Milos Djermanovic) +* [`7214347`](https://github.com/eslint/eslint/commit/721434705bd569e33911e25d2688e33f10898d52) docs: fix logical-assignment-operators option typo (#16346) (Jonathan Wilsson) + +v8.24.0 - September 23, 2022 + +* [`131e646`](https://github.com/eslint/eslint/commit/131e646e227b9aca3937fe287343bf2c3df408af) chore: Upgrade @humanwhocodes/config-array for perf (#16339) (Nicholas C. Zakas) +* [`2c152ff`](https://github.com/eslint/eslint/commit/2c152ff0fb709b99e62c19ecd2c95689efacbe4c) docs: note false positive `Object.getOwnPropertyNames` in prefer-reflect (#16317) (AnnAngela) +* [`bf7bd88`](https://github.com/eslint/eslint/commit/bf7bd885a92046a6b6bcbcaaa1e78e9f2c4b482f) docs: fix warn severity description for new config files (#16324) (Nitin Kumar) +* [`504fe59`](https://github.com/eslint/eslint/commit/504fe59b0e0f4f5a2afb6a69aaed5cb4ca631012) perf: switch from object spread to `Object.assign` when merging globals (#16311) (Milos Djermanovic) +* [`1729f9e`](https://github.com/eslint/eslint/commit/1729f9ea4d7b2945b2b701d72027fd4aace954cf) feat: account for `sourceType: "commonjs"` in the strict rule (#16308) (Milos Djermanovic) +* [`b0d72c9`](https://github.com/eslint/eslint/commit/b0d72c96b2a9cde7a5798c2b08ec4e70683c6aca) feat: add rule logical-assignment-operators (#16102) (fnx) +* [`f02bcd9`](https://github.com/eslint/eslint/commit/f02bcd91bf89b6c167d5346a36677fdb854f0c05) feat: `array-callback-return` support `findLast` and `findLastIndex` (#16314) (Sosuke Suzuki) +* [`8cc0bbe`](https://github.com/eslint/eslint/commit/8cc0bbe440dc5e6af6ef02f00d0514a40ca07c24) docs: use more clean link syntax (#16309) (Percy Ma) +* [`6ba269e`](https://github.com/eslint/eslint/commit/6ba269ed673f965d081287b769c12beeb5f98887) docs: fix typo (#16288) (jjangga0214) + +v8.23.1 - September 12, 2022 + +* [`b719893`](https://github.com/eslint/eslint/commit/b71989388a921886caa4c6cb48729bbf60c46100) fix: Upgrade eslintrc to stop redefining plugins (#16297) (Brandon Mills) +* [`734b54e`](https://github.com/eslint/eslint/commit/734b54eb9c6c4839c0f99ebe18dc5695754aac1d) fix: improve autofix for the `prefer-const` rule (#16292) (Nitin Kumar) +* [`6a923ff`](https://github.com/eslint/eslint/commit/6a923ff9257a4f009cefed049ebb59a4b5acdab5) fix: Ensure that glob patterns are normalized (#16287) (Nicholas C. Zakas) +* [`38e8171`](https://github.com/eslint/eslint/commit/38e8171d9b170f400ac340368d044b2093114e94) perf: migrate rbTree to js-sdsl (#16267) (Zilong Yao) +* [`16cba3f`](https://github.com/eslint/eslint/commit/16cba3f31294a673721864267aa13ea35233326b) docs: fix mobile double tap issue (#16293) (Sam Chen) +* [`c6900f8`](https://github.com/eslint/eslint/commit/c6900f89a89f3de5d3c50c69a1bc62eac6eb76d7) fix: Ensure globbing doesn't include subdirectories (#16272) (Nicholas C. Zakas) +* [`e098b5f`](https://github.com/eslint/eslint/commit/e098b5f80472e80c70603306e77e14ea15f1a93b) docs: keyboard control to search results (#16222) (Shanmughapriyan S) +* [`1b5b2a7`](https://github.com/eslint/eslint/commit/1b5b2a7de504f2971a6a488d8a57442e73b56a51) docs: add Consolas font and prioritize resource loading (#16225) (Amaresh S M) +* [`1c388fb`](https://github.com/eslint/eslint/commit/1c388fb37739cc09dbd0b4aa59e9d45674280ad5) chore: switch nyc to c8 (#16263) (唯然) +* [`67db10c`](https://github.com/eslint/eslint/commit/67db10c51dbb871a201eab444f6a73fbc1e4fc75) chore: enable linting `.eleventy.js` again (#16274) (Milos Djermanovic) +* [`1ae8236`](https://github.com/eslint/eslint/commit/1ae8236a2e71c9dead20ba9da60d8cc9e317859a) docs: copy & use main package version in docs on release (#16252) (Jugal Thakkar) +* [`42bfbd7`](https://github.com/eslint/eslint/commit/42bfbd7b7b91106e5f279a05f40c20769e3cd29f) chore: fix `npm run perf` crashes (#16258) (唯然) +* [`279f0af`](https://github.com/eslint/eslint/commit/279f0afc14617c037da482919942beef87f56e45) docs: Improve id-denylist documentation (#16223) (Mert Ciflikli) + +v8.23.0 - August 26, 2022 + +* [`2e004ab`](https://github.com/eslint/eslint/commit/2e004ab990a4a5a4efc44974da005d2161490256) chore: upgrade @eslint/eslintrc@1.3.1 (#16249) (Milos Djermanovic) +* [`d35fbbe`](https://github.com/eslint/eslint/commit/d35fbbef895e8f4ac6eaf1756349230769a02b4d) chore: Upgrade to espree@9.4.0 (#16243) (Milos Djermanovic) +* [`3e5839e`](https://github.com/eslint/eslint/commit/3e5839ecae96aecfbc1ac9526e88e0105e671032) feat: Enable eslint.config.js lookup from CLI (#16235) (Nicholas C. Zakas) +* [`30b1a2d`](https://github.com/eslint/eslint/commit/30b1a2dac9060673101485841c4c7521675bf917) feat: add `allowEmptyCase` option to no-fallthrough rule (#15887) (Amaresh S M) +* [`ed26229`](https://github.com/eslint/eslint/commit/ed26229a19359b356f3a401698488c1707d4c029) test: add no-extra-parens tests with rest properties (#16236) (Milos Djermanovic) +* [`deaf69f`](https://github.com/eslint/eslint/commit/deaf69ffd8f9b97b4b8c29a244a79969ff14c80a) chore: fix off-by-one `min-width: 1023px` media queries (#15974) (Milos Djermanovic) +* [`63dec9f`](https://github.com/eslint/eslint/commit/63dec9fdee793be9bf2939e1bda0717b9cc6dcf8) refactor: simplify `parseListConfig` (#16241) (Milos Djermanovic) +* [`43f03aa`](https://github.com/eslint/eslint/commit/43f03aa96b632039b1d9cad097a70b227bb7d348) feat: no-warning-comments support comments with decoration (#16120) (Lachlan Hunt) +* [`b1918da`](https://github.com/eslint/eslint/commit/b1918da0f6cb8fe690c7377667616ec7cb57111e) docs: package.json conventions (#16206) (Patrick McElhaney) +* [`0e03c33`](https://github.com/eslint/eslint/commit/0e03c333a70bebd00307deead0befa519f983f44) docs: remove word immediately (#16217) (Strek) +* [`c6790db`](https://github.com/eslint/eslint/commit/c6790db6494e64a5261d74c0f3c4dc6139c59435) docs: add anchor link for "migrating from jscs" (#16207) (Percy Ma) +* [`7137344`](https://github.com/eslint/eslint/commit/71373442c42b356f34179dba18f860e1d79a780d) docs: auto-generation edit link (#16213) (Percy Ma) + v8.22.0 - August 13, 2022 * [`2b97607`](https://github.com/eslint/eslint/commit/2b97607675e1d0920a3abedd736e2ae00ed26d52) feat: Implement caching for FlatESLint (#16190) (Nicholas C. Zakas) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40163d86847..c3c3a2a2e02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,18 +10,18 @@ This project adheres to the [Open JS Foundation Code of Conduct](https://eslint. Before filing an issue, please be sure to read the guidelines for what you're reporting: -* [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) +* [Report Bugs](https://eslint.org/docs/latest/contribute/report-bugs) +* [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule) +* [Propose a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change) +* [Request a Change](https://eslint.org/docs/latest/contribute/request-change) To report a security vulnerability in ESLint, please use our [HackerOne program](https://hackerone.com/eslint). ## Contributing Code -In order to submit code or documentation to an ESLint project, you’ll be asked to sign our CLA when you send your first pull request. (Read more about the Open JS Foundation CLA process at .) Also, please read over the [Pull Request Guidelines](https://eslint.org/docs/developer-guide/contributing/pull-requests). +In order to submit code or documentation to an ESLint project, you’ll be asked to sign our CLA when you send your first pull request. (Read more about the Open JS Foundation CLA process at .) Also, please read over the [Pull Request Guidelines](https://eslint.org/docs/latest/contribute/pull-requests). ## Full Documentation Our full contribution guidelines can be found at: - + diff --git a/Makefile.js b/Makefile.js index a9c527df557..717cc785946 100644 --- a/Makefile.js +++ b/Makefile.js @@ -15,6 +15,7 @@ const checker = require("npm-license"), fs = require("fs"), glob = require("glob"), marked = require("marked"), + matter = require("gray-matter"), markdownlint = require("markdownlint"), os = require("os"), path = require("path"), @@ -76,9 +77,14 @@ const NODE = "node ", // intentional extra space MARKDOWNLINT_IGNORE_INSTANCE = ignore().add(fs.readFileSync(path.join(__dirname, ".markdownlintignore"), "utf-8")), MARKDOWN_FILES_ARRAY = MARKDOWNLINT_IGNORE_INSTANCE.filter(find("docs/").concat(ls(".")).filter(fileType("md"))), TEST_FILES = "\"tests/{bin,conf,lib,tools}/**/*.js\"", - PERF_ESLINTRC = path.join(PERF_TMP_DIR, "eslintrc.yml"), + PERF_ESLINTRC = path.join(PERF_TMP_DIR, "eslint.config.js"), 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"`, + + /* + * glob arguments with Windows separator `\` don't work: + * https://github.com/eslint/eslint/issues/16259 + */ + PERF_MULTIFILES_TARGETS = `"${TEMP_DIR}eslint/performance/eslint/{lib,tests/lib}/**/*.js"`, // Settings MOCHA_TIMEOUT = parseInt(process.env.ESLINT_MOCHA_TIMEOUT, 10) || 10000; @@ -161,7 +167,7 @@ function generateBlogPost(releaseInfo, prereleaseMajorVersion) { */ function generateFormatterExamples(formatterInfo) { const output = ejs.render(cat("./templates/formatter-examples.md.ejs"), formatterInfo); - const outputDir = path.join(DOCS_SRC_DIR, "user-guide/formatters/"), + const outputDir = path.join(DOCS_SRC_DIR, "use/formatters/"), filename = path.join(outputDir, "index.md"), htmlFilename = path.join(outputDir, "html-formatter-example.html"); @@ -282,6 +288,13 @@ function generateRelease() { generateBlogPost(releaseInfo); commitSiteToGit(`v${releaseInfo.version}`); + echo("Updating version in docs package"); + const docsPackagePath = path.join(__dirname, "docs", "package.json"); + const docsPackage = require(docsPackagePath); + + docsPackage.version = releaseInfo.version; + fs.writeFileSync(docsPackagePath, `${JSON.stringify(docsPackage, null, 4)}\n`); + echo("Updating commit with docs data"); exec("git add docs/ && git commit --amend --no-edit"); exec(`git tag -a -f v${releaseInfo.version} -m ${releaseInfo.version}`); @@ -332,9 +345,18 @@ function generatePrerelease(prereleaseId) { */ function publishRelease() { ReleaseOps.publishRelease(); + const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); + const isPreRelease = /[a-z]/u.test(releaseInfo.version); - // push to latest branch to trigger docs deploy - exec("git push origin HEAD:latest -f"); + /* + * for a pre-release, push to the "next" branch to trigger docs deploy + * for a release, push to the "latest" branch to trigger docs deploy + */ + if (isPreRelease) { + exec("git push origin HEAD:next -f"); + } else { + exec("git push origin HEAD:latest -f"); + } publishSite(); } @@ -434,8 +456,9 @@ function lintMarkdown(files) { */ function getFormatterResults() { const stripAnsi = require("strip-ansi"); + const formattersMetadata = require("./lib/cli-engine/formatters/formatters-meta.json"); - const formatterFiles = fs.readdirSync("./lib/cli-engine/formatters/"), + const formatterFiles = fs.readdirSync("./lib/cli-engine/formatters/").filter(fileName => !fileName.includes("formatters-meta.json")), rules = { "no-else-return": "warn", indent: ["warn", 4], @@ -476,7 +499,8 @@ function getFormatterResults() { ); data.formatterResults[name] = { - result: stripAnsi(formattedOutput) + result: stripAnsi(formattedOutput), + description: formattersMetadata.find(formatter => formatter.name === name).description }; } return data; @@ -506,11 +530,11 @@ target.lint = function([fix = false] = []) { * when analyzing `require()` calls from CJS modules in the `docs` directory. Since our release process does not run `npm install` * in the `docs` directory, linting would fail and break the release. Also, working on the main `eslint` package does not require * installing dependencies declared in `docs/package.json`, so most contributors will not have `docs/node_modules` locally. - * Therefore, we add `--ignore-pattern docs` to exclude linting the `docs` directory from this command. + * Therefore, we add `--ignore-pattern "docs/**"` to exclude linting the `docs` directory from this command. * There is a separate command `target.lintDocsJS` for linting JavaScript files in the `docs` directory. */ echo("Validating JavaScript files"); - lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} . --ignore-pattern docs`); + lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} . --ignore-pattern "docs/**"`); if (lastReturn.code !== 0) { errors++; } @@ -589,12 +613,12 @@ target.mocha = () => { echo("Running unit tests"); - lastReturn = exec(`${getBinFile("nyc")} -- ${MOCHA} --forbid-only -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`); + lastReturn = exec(`${getBinFile("c8")} -- ${MOCHA} --forbid-only -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`); if (lastReturn.code !== 0) { errors++; } - lastReturn = exec(`${getBinFile("nyc")} check-coverage --statement 98 --branch 97 --function 98 --lines 98`); + lastReturn = exec(`${getBinFile("c8")} check-coverage --statement 98 --branch 97 --function 98 --lines 98`); if (lastReturn.code !== 0) { errors++; } @@ -617,7 +641,6 @@ target.karma = () => { }; target.test = function() { - target.lint(); target.checkRuleFiles(); target.mocha(); target.karma(); @@ -695,7 +718,8 @@ target.checkRuleFiles = function() { const basename = path.basename(filename, ".js"); const docFilename = `docs/src/rules/${basename}.md`; const docText = cat(docFilename); - const docMarkdown = marked.lexer(docText, { gfm: true, silent: false }); + const docTextWithoutFrontmatter = matter(String(docText)).content; + const docMarkdown = marked.lexer(docTextWithoutFrontmatter, { gfm: true, silent: false }); const ruleCode = cat(filename); const knownHeaders = ["Rule Details", "Options", "Environments", "Examples", "Known Limitations", "When Not To Use It", "Compatibility"]; @@ -811,16 +835,16 @@ target.checkRuleFiles = function() { } // check eslint:recommended - const recommended = require("./conf/eslint-recommended"); + const recommended = require("./packages/js").configs.recommended; if (ruleDef.meta.docs.recommended) { if (recommended.rules[basename] !== "error") { - console.error(`Missing rule from eslint:recommended (./conf/eslint-recommended.js): ${basename}. If you just made a rule recommended then add an entry for it in this file.`); + console.error(`Missing rule from eslint:recommended (./packages/js/src/configs/eslint-recommended.js): ${basename}. If you just made a rule recommended then add an entry for it in this file.`); errors++; } } else { if (basename in recommended.rules) { - console.error(`Extra rule in eslint:recommended (./conf/eslint-recommended.js): ${basename}. If you just added a rule then don't add an entry for it in this file.`); + console.error(`Extra rule in eslint:recommended (./packages/js/src/configs/eslint-recommended.js): ${basename}. If you just added a rule then don't add an entry for it in this file.`); errors++; } } @@ -911,19 +935,21 @@ function downloadMultifilesTestTarget(cb) { * @returns {void} */ function createConfigForPerformanceTest() { - const content = [ - "root: true", - "env:", - " node: true", - " es6: true", - "rules:" - ]; + let rules = ""; for (const [ruleId] of builtinRules) { - content.push(` ${ruleId}: 1`); + rules += (` "${ruleId}": 1,\n`); + } + + const content = ` +module.exports = [{ + "languageOptions": {sourceType: "commonjs"}, + "rules": { + ${rules} } +}];`; - content.join("\n").to(PERF_ESLINTRC); + content.to(PERF_ESLINTRC); } /** @@ -983,7 +1009,7 @@ function time(cmd, runs, runNumber, results, cb) { function runPerformanceTest(title, targets, multiplier, cb) { const cpuSpeed = os.cpus()[0].speed, max = multiplier / cpuSpeed, - cmd = `${ESLINT}--config "${PERF_ESLINTRC}" --no-eslintrc --no-ignore ${targets}`; + cmd = `${ESLINT}--config "${PERF_ESLINTRC}" --no-config-lookup --no-ignore ${targets}`; echo(""); echo(title); diff --git a/README.md b/README.md index 309db3dc849..040654c1424 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,13 @@ # ESLint [Website](https://eslint.org) | -[Configuring](https://eslint.org/docs/user-guide/configuring) | +[Configure ESLint](https://eslint.org/docs/latest/use/configure) | [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) | +[Contribute to ESLint](https://eslint.org/docs/latest/contribute) | +[Report Bugs](https://eslint.org/docs/latest/contribute/report-bugs) | [Code of Conduct](https://eslint.org/conduct) | [Twitter](https://twitter.com/geteslint) | -[Mailing List](https://groups.google.com/group/eslint) | -[Chat Room](https://eslint.org/chat) +[Discord](https://eslint.org/chat) ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions: @@ -31,7 +30,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J 2. [Configuration](#configuration) 3. [Code of Conduct](#code-of-conduct) 4. [Filing Issues](#filing-issues) -5. [Frequently Asked Questions](#faq) +5. [Frequently Asked Questions](#frequently-asked-questions) 6. [Releases](#releases) 7. [Security Policy](#security-policy) 8. [Semantic Versioning Policy](#semantic-versioning-policy) @@ -41,7 +40,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J 12. [Sponsors](#sponsors) 13. [Technology Sponsors](#technology-sponsors) -## Installation and Usage +## Installation and Usage Prerequisites: [Node.js](https://nodejs.org/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) @@ -57,9 +56,9 @@ After that, you can run ESLint on any file or directory like this: ./node_modules/.bin/eslint yourfile.js ``` -## Configuration +## Configuration -After running `npm init @eslint/config`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this: +After running `npm init @eslint/config`, you'll have an `.eslintrc` file in your directory. In it, you'll see some rules configured like this: ```json { @@ -76,28 +75,28 @@ The names `"semi"` and `"quotes"` are the names of [rules](https://eslint.org/do * `"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](https://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/latest/use/configure)). -## Code of Conduct +## Code of Conduct ESLint adheres to the [JS Foundation Code of Conduct](https://eslint.org/conduct). -## Filing Issues +## Filing Issues Before filing an issue, please be sure to read the guidelines for what you're reporting: -* [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) +* [Bug Report](https://eslint.org/docs/latest/contribute/report-bugs) +* [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule) +* [Proposing a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change) +* [Request a Change](https://eslint.org/docs/latest/contribute/request-change) -## Frequently Asked Questions +## Frequently Asked Questions ### I'm using JSCS, should I migrate to ESLint? Yes. [JSCS has reached end of life](https://eslint.org/blog/2016/07/jscs-end-of-life) and is no longer supported. -We have prepared a [migration guide](https://eslint.org/docs/user-guide/migrating-from-jscs) to help you convert your JSCS settings to an ESLint configuration. +We have prepared a [migration guide](https://eslint.org/docs/latest/use/migrating-from-jscs) to help you convert your JSCS settings to an ESLint configuration. We are now at or near 100% compatibility with JSCS. If you try ESLint and believe we are not yet compatible with a JSCS rule/configuration, please create an issue (mentioning that it is a JSCS compatibility issue) and we will evaluate it as per our normal process. @@ -113,11 +112,11 @@ No, ESLint does both traditional linting (looking for problematic patterns) and ### Does ESLint support JSX? -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. +Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/latest/use/configure)). 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 ECMAScript versions does ESLint support? -ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021 and 2022. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring). +ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021 and 2022. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/latest/use/configure). ### What about experimental features? @@ -125,11 +124,11 @@ ESLint's parser only officially supports the latest final ECMAScript standard. W In other cases (including if rules need to warn on more or fewer cases due to new syntax, rather than just not crashing), we recommend you use other parsers and/or rule plugins. If you are using Babel, you can use [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) and [@babel/eslint-plugin](https://www.npmjs.com/package/@babel/eslint-plugin) 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](https://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/latest/contribute). 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://eslint.org/chat). +Open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat). ### Why doesn't ESLint lock dependency versions? @@ -141,15 +140,15 @@ We intentionally don't lock dependency versions so that we have the latest compa The Twilio blog has a [deeper dive](https://www.twilio.com/blog/lockfiles-nodejs) to learn more. -## Releases +## Releases We have scheduled releases every two weeks on Friday or Saturday. You can follow a [release issue](https://github.com/eslint/eslint/issues?q=is%3Aopen+is%3Aissue+label%3Arelease) for updates about the scheduling of any particular release. -## Security Policy +## Security Policy ESLint takes security seriously. We work hard to ensure that ESLint is safe for everyone and that security issues are addressed quickly and responsibly. Read the full [security policy](https://github.com/eslint/.github/blob/master/SECURITY.md). -## Semantic Versioning Policy +## Semantic Versioning Policy 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: @@ -182,7 +181,7 @@ ESLint follows [semantic versioning](https://semver.org). However, due to the na According to our policy, any minor update may report more linting 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. -## Stylistic Rule Updates +## Stylistic Rule Updates Stylistic rules are frozen according to [our policy](https://eslint.org/blog/2020/05/changes-to-rules-policies) on how we evaluate new rules and rule changes. This means: @@ -191,11 +190,11 @@ This means: * **New ECMAScript features**: We will also make sure stylistic rules are compatible with new ECMAScript features. * **New options**: We will **not** add any new options to stylistic rules unless an option is the only way to fix a bug or support a newly-added ECMAScript feature. -## License +## 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) -## Team +## Team These folks keep the project moving and are resources for help. @@ -213,11 +212,6 @@ The people who manage releases, review feature requests, and meet regularly to e Nicholas C. Zakas - -
-Brandon Mills -
-
Milos Djermanovic @@ -245,29 +239,19 @@ Nitin Kumar The people who review and fix bugs and help triage issues.
- -
-Brett Zamir -
-

Bryan Mishkin
- -
-Sara Soueidan -
-
- -
-Pig Fang +
+
+Brandon Mills
- -
-Anix +
+
+Francesco Trotta
@@ -276,22 +260,43 @@ YeonJuan
+### Website Team + +Team members who focus specifically on eslint.org + +
+ +
+Amaresh S M +
+
+ +
+Strek +
+
+ +
+Percy Ma +
+
+ -##
Sponsors +## Sponsors The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://opencollective.com/eslint) to get your logo on our README and website.

Platinum Sponsors

-

Automattic

Gold Sponsors

-

Salesforce Airbnb American Express

Silver Sponsors

-

Liftoff

Bronze Sponsors

-

launchdarkly Nx (by Nrwl) Anagram Solver VPS Icons8: free icons, photos, illustrations, and music Discord ThemeIsle Ignition HeroCoders

+

Chrome Frameworks Fund Automattic

Gold Sponsors

+

RIDI Salesforce Airbnb

Silver Sponsors

+

Sentry Liftoff American Express

Bronze Sponsors

+

PayDay Say ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

-## Technology Sponsors +## Technology Sponsors * Site search ([eslint.org](https://eslint.org)) is sponsored by [Algolia](https://www.algolia.com) * Hosting for ([eslint.org](https://eslint.org)) is sponsored by [Netlify](https://www.netlify.com) diff --git a/bin/eslint.js b/bin/eslint.js index 0f76fc92e1f..7094ac77bc4 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -9,9 +9,6 @@ "use strict"; -// to use V8's code cache to speed up instantiation time -require("v8-compile-cache"); - // must do this initialization *before* other requires in order to work if (process.argv.includes("--debug")) { require("debug").enable("eslint:*,-eslint:code-path,eslintrc:*"); @@ -137,6 +134,7 @@ ${message}`); // Otherwise, call the CLI. process.exitCode = await require("../lib/cli").execute( process.argv, - process.argv.includes("--stdin") ? await readStdin() : null + process.argv.includes("--stdin") ? await readStdin() : null, + true ); }()).catch(onFatalError); diff --git a/conf/globals.js b/conf/globals.js index 076ffb2af94..6018b7af15c 100644 --- a/conf/globals.js +++ b/conf/globals.js @@ -124,6 +124,10 @@ const es2022 = { ...es2021 }; +const es2023 = { + ...es2022 +}; + //----------------------------------------------------------------------------- // Exports @@ -140,5 +144,6 @@ module.exports = { es2019, es2020, es2021, - es2022 + es2022, + es2023 }; diff --git a/conf/rule-type-list.json b/conf/rule-type-list.json index f362aa412f9..d5823acc898 100644 --- a/conf/rule-type-list.json +++ b/conf/rule-type-list.json @@ -6,12 +6,12 @@ ], "deprecated": { "name": "Deprecated", - "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", + "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", "rules": [] }, "removed": { "name": "Removed", - "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", + "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", "rules": [ { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] }, { "removed": "global-strict", "replacedBy": ["strict"] }, diff --git a/docs/.eleventy.js b/docs/.eleventy.js index b3e302646c2..1bd1de1af2f 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -10,7 +10,7 @@ const Image = require("@11ty/eleventy-img"); const path = require("path"); const { slug } = require("github-slugger"); const yaml = require("js-yaml"); - +const { highlighter, lineNumberPlugin } = require("./src/_plugins/md-syntax-highlighter"); const { DateTime } = require("luxon"); @@ -25,8 +25,9 @@ module.exports = function(eleventyConfig) { * it's easier to see if URLs are broken. * * When a release is published, HEAD is pushed to the "latest" branch. - * Netlify deploys that branch as well, and in that case, we want the - * docs to be loaded from /docs/latest on eslint.org. + * When a pre-release is published, HEAD is pushed to the "next" branch. + * Netlify deploys those branches as well, and in that case, we want the + * docs to be loaded from /docs/latest or /docs/next on eslint.org. * * The path prefix is turned off for deploy previews so we can properly * see changes before deployed. @@ -38,6 +39,8 @@ module.exports = function(eleventyConfig) { pathPrefix = "/"; } else if (process.env.BRANCH === "latest") { pathPrefix = "/docs/latest/"; + } else if (process.env.BRANCH === "next") { + pathPrefix = "/docs/next/"; } //------------------------------------------------------------------------------ @@ -49,6 +52,7 @@ module.exports = function(eleventyConfig) { eleventyConfig.addGlobalData("site_name", siteName); eleventyConfig.addGlobalData("GIT_BRANCH", process.env.BRANCH); + eleventyConfig.addGlobalData("HEAD", process.env.BRANCH === "main"); eleventyConfig.addGlobalData("NOINDEX", process.env.BRANCH !== "latest"); eleventyConfig.addDataExtension("yml", contents => yaml.load(contents)); @@ -60,23 +64,12 @@ module.exports = function(eleventyConfig) { eleventyConfig.addFilter("jsonify", variable => JSON.stringify(variable)); - /** - * Takes in a string and converts to a slug - * @param {string} text text to be converted into slug - * @returns {string} slug to be used as anchors - */ - function slugify(text) { - return slug(text.replace(/[<>()[\]{}]/gu, "")) - // eslint-disable-next-line no-control-regex -- used regex from https://github.com/eslint/archive-website/blob/master/_11ty/plugins/markdown-plugins.js#L37 - .replace(/[^\u{00}-\u{FF}]/gu, ""); - } - eleventyConfig.addFilter("slugify", str => { if (!str) { return ""; } - return slugify(str); + return slug(str); }); eleventyConfig.addFilter("URIencode", str => { @@ -145,7 +138,8 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPlugin(eleventyNavigationPlugin); eleventyConfig.addPlugin(syntaxHighlight, { - alwaysWrapLineHighlights: true + alwaysWrapLineHighlights: true, + templateFormats: ["liquid", "njk"] }); eleventyConfig.addPlugin(pluginRss); eleventyConfig.addPlugin(pluginTOC, { @@ -186,30 +180,32 @@ module.exports = function(eleventyConfig) { } const markdownIt = require("markdown-it"); + const md = markdownIt({ html: true, linkify: true, typographer: true, highlight: (str, lang) => highlighter(md, str, lang) }) + .use(markdownItAnchor, { + slugify: s => slug(s) + }) + .use(markdownItContainer, "img-container", {}) + .use(markdownItContainer, "correct", {}) + .use(markdownItContainer, "incorrect", {}) + .use(markdownItContainer, "warning", { + render(tokens, idx) { + return generateAlertMarkup("warning", tokens, idx); + } + }) + .use(markdownItContainer, "tip", { + render(tokens, idx) { + return generateAlertMarkup("tip", tokens, idx); + } + }) + .use(markdownItContainer, "important", { + render(tokens, idx) { + return generateAlertMarkup("important", tokens, idx); + } + }) + .use(lineNumberPlugin) + .disable("code"); - eleventyConfig.setLibrary("md", - markdownIt({ html: true, linkify: true, typographer: true }) - .use(markdownItAnchor, { - slugify - }) - .use(markdownItContainer, "correct", {}) - .use(markdownItContainer, "incorrect", {}) - .use(markdownItContainer, "warning", { - render(tokens, idx) { - return generateAlertMarkup("warning", tokens, idx); - } - }) - .use(markdownItContainer, "tip", { - render(tokens, idx) { - return generateAlertMarkup("tip", tokens, idx); - } - }) - .use(markdownItContainer, "important", { - render(tokens, idx) { - return generateAlertMarkup("important", tokens, idx); - } - }) - .disable("code")); + eleventyConfig.setLibrary("md", md); //------------------------------------------------------------------------------ // Shortcodes @@ -247,7 +243,7 @@ module.exports = function(eleventyConfig) { eleventyConfig.addShortcode("fixable", () => `
- 🛠 Fixable + 🔧 Fixable

if some problems reported by the rule are automatically fixable by the --fix command line option

@@ -452,7 +448,7 @@ module.exports = function(eleventyConfig) { * URLs with a file extension, like main.css, main.js, sitemap.xml, etc. should not be rewritten */ eleventyConfig.setBrowserSyncConfig({ - middleware: (req, res, next) => { + middleware(req, res, next) { if (!/(?:\.[a-zA-Z][^/]*|\/)$/u.test(req.url)) { req.url += ".html"; } diff --git a/docs/.stylelintrc.json b/docs/.stylelintrc.json new file mode 100644 index 00000000000..ab3b3fd039d --- /dev/null +++ b/docs/.stylelintrc.json @@ -0,0 +1,33 @@ +{ + "extends": ["stylelint-config-standard-scss"], + "rules": { + "alpha-value-notation": "number", + "at-rule-empty-line-before": null, + "color-function-notation": "legacy", + "custom-property-empty-line-before": null, + "custom-property-pattern": null, + "declaration-block-no-duplicate-properties": [true, { + "ignore": ["consecutive-duplicates-with-different-values"] + }], + "declaration-block-no-redundant-longhand-properties": null, + "hue-degree-notation": "number", + "indentation": 4, + "max-line-length": null, + "no-descending-specificity": null, + "number-leading-zero": null, + "number-no-trailing-zeros": null, + "selector-class-pattern": null, + "value-keyword-case": null + }, + "overrides": [ + { + "files": [ + "**/*.html" + ], + "extends": ["stylelint-config-html/html", "stylelint-config-standard"] + } + ], + "ignoreFiles": [ + "_site/**" + ] + } diff --git a/docs/README.md b/docs/README.md index ee5f64e100e..dbf3c02e8f4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,11 +1,25 @@ # ESLint Documentation +## Install Dependencies + +Install the necessary dependencies for the documentation site by running this +from the `docs` folder: + +```shell +npm install +``` + ## Run Locally +Run this from the `docs` folder: + ```shell npm start ``` +Once the script finishes building the documentation site, you can visit it at +. + ## Scripts To update the links data file, run this from the root folder (not the `docs` folder): @@ -25,3 +39,7 @@ To autofix JS files, run this from the root folder (not the `docs` folder): ```shell npm run fix:docsjs ``` + +## License + +© OpenJS Foundation and ESLint contributors, [www.openjsf.org](https://www.openjsf.org/). Content licensed under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/). diff --git a/docs/package.json b/docs/package.json index 558d41f97a9..ab5bb049536 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "1.0.0", + "version": "8.37.0", "description": "", "main": "index.js", "keywords": [], @@ -10,12 +10,17 @@ "files": [], "scripts": { "images": "imagemin '_site/assets/images' --out-dir='_site/assets/images'", - "watch:sass": "sass --watch --poll src/assets/scss:src/assets/css", + "watch:postcss": "postcss src/assets/css -d src/assets/css --watch --poll", + "watch:sass": "sass --watch --poll src/assets/scss:src/assets/css --no-source-map", "watch:eleventy": "eleventy --serve --port=2023", - "build:sass": "sass --style=compressed src/assets/scss:src/assets/css --no-source-map", + "build:postcss": "postcss src/assets/css -d src/assets/css", + "build:sass": "sass src/assets/scss:src/assets/css --no-source-map", "build:eleventy": "npx @11ty/eleventy", - "start": "npm-run-all build:sass --parallel watch:*", - "build": "npm-run-all build:sass build:eleventy images" + "start": "npm-run-all build:sass build:postcss --parallel watch:*", + "build": "npm-run-all build:sass build:postcss build:eleventy images", + "lint:scss": "stylelint \"**/*.{scss,html}\"", + "lint:links": "cross-env NODE_OPTIONS=--max-old-space-size=4096 node tools/validate-links.js", + "lint:fix:scss": "npm run lint:scss -- --fix" }, "devDependencies": { "@11ty/eleventy": "^1.0.1", @@ -23,13 +28,18 @@ "@11ty/eleventy-navigation": "^0.3.2", "@11ty/eleventy-plugin-rss": "^1.1.1", "@11ty/eleventy-plugin-syntaxhighlight": "^3.1.2", + "@munter/tap-render": "^0.2.0", "@types/markdown-it": "^12.2.3", "algoliasearch": "^4.12.1", + "autoprefixer": "^10.4.13", + "cross-env": "^7.0.3", + "cssnano": "^5.1.14", "dom-parser": "^0.1.6", "eleventy-plugin-nesting-toc": "^1.3.0", "eleventy-plugin-page-assets": "^0.3.0", "eleventy-plugin-reading-time": "^0.0.1", - "github-slugger": "^1.4.0", + "github-slugger": "^1.5.0", + "hyperlink": "^5.0.4", "imagemin": "^8.0.1", "imagemin-cli": "^7.0.0", "js-yaml": "^3.14.1", @@ -39,10 +49,22 @@ "markdown-it-container": "^3.0.0", "netlify-cli": "^10.3.1", "npm-run-all": "^4.1.5", + "postcss-cli": "^10.0.0", + "postcss-html": "^1.5.0", + "prismjs": "^1.29.0", "rimraf": "^3.0.2", - "sass": "^1.52.1" + "sass": "^1.52.1", + "stylelint": "^14.13.0", + "stylelint-config-html": "^1.1.0", + "stylelint-config-standard": "^29.0.0", + "stylelint-config-standard-scss": "^5.0.0", + "tap-spot": "^1.1.2" }, "engines": { "node": ">=14.0.0" - } + }, + "browserslist": [ + "defaults", + "IE 11" + ] } diff --git a/docs/postcss.config.js b/docs/postcss.config.js new file mode 100644 index 00000000000..128e741f027 --- /dev/null +++ b/docs/postcss.config.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + plugins: [ + require("autoprefixer"), + require("cssnano") + ], + map: false +}; diff --git a/docs/src/_data/config.json b/docs/src/_data/config.json index 4acb4820247..9bd751d1809 100644 --- a/docs/src/_data/config.json +++ b/docs/src/_data/config.json @@ -1,4 +1,5 @@ { "lang": "en", - "version": "7.26.0" + "version": "7.26.0", + "showNextVersion": false } diff --git a/docs/src/_data/eslintVersion.js b/docs/src/_data/eslintVersion.js index cd60276b37a..24964276c0d 100644 --- a/docs/src/_data/eslintVersion.js +++ b/docs/src/_data/eslintVersion.js @@ -14,7 +14,7 @@ const path = require("path"); // Initialization //----------------------------------------------------------------------------- -const pkgPath = path.resolve(__dirname, "../../../package.json"); +const pkgPath = path.resolve(__dirname, "../../package.json"); const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); const { ESLINT_VERSION } = process.env; @@ -23,7 +23,7 @@ const { ESLINT_VERSION } = process.env; //----------------------------------------------------------------------------- /* - * Because we want to differentiate between the development branch and the + * Because we want to differentiate between the development branch and the * most recent release, we need a way to override the version. The * ESLINT_VERSION environment variable allows us to set this to override * the value displayed on the website. The most common case is we will set diff --git a/docs/src/_data/further_reading_links.json b/docs/src/_data/further_reading_links.json index dc81f7f1640..120dd37032f 100644 --- a/docs/src/_data/further_reading_links.json +++ b/docs/src/_data/further_reading_links.json @@ -698,5 +698,26 @@ "logo": "https://eslint.org/apple-touch-icon.png", "title": "Interesting bugs caught by no-constant-binary-expression - ESLint - Pluggable JavaScript Linter", "description": "A pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript. Maintain your code quality with ease." + }, + "https://github.com/tc39/proposal-class-static-block": { + "domain": "github.com", + "url": "https://github.com/tc39/proposal-class-static-block", + "logo": "https://github.com/fluidicon.png", + "title": "GitHub - tc39/proposal-class-static-block: ECMAScript class static initialization blocks", + "description": "ECMAScript class static initialization blocks. Contribute to tc39/proposal-class-static-block development by creating an account on GitHub." + }, + "https://tc39.es/ecma262/#sec-symbol-constructor": { + "domain": "tc39.es", + "url": "https://tc39.es/ecma262/#sec-symbol-constructor", + "logo": "https://tc39.es/ecma262/img/favicon.ico", + "title": "ECMAScript® 2023 Language Specification", + "description": null + }, + "https://tc39.es/ecma262/#sec-bigint-constructor": { + "domain": "tc39.es", + "url": "https://tc39.es/ecma262/#sec-bigint-constructor", + "logo": "https://tc39.es/ecma262/img/favicon.ico", + "title": "ECMAScript® 2023 Language Specification", + "description": null } } \ No newline at end of file diff --git a/docs/src/_data/languages.json b/docs/src/_data/languages.json index 529a9e3f2a2..7defcb63eb1 100644 --- a/docs/src/_data/languages.json +++ b/docs/src/_data/languages.json @@ -7,9 +7,9 @@ }, { "flag": "🇯🇵", - "code": "jp", + "code": "ja", "name": "Japanese - 日本語", - "url": "https://jp.eslint.org" + "url": "https://ja.eslint.org" }, { "flag": "🇫🇷", diff --git a/docs/src/_data/layout.js b/docs/src/_data/layout.js new file mode 100644 index 00000000000..2665a708914 --- /dev/null +++ b/docs/src/_data/layout.js @@ -0,0 +1 @@ +module.exports = "doc.html"; diff --git a/docs/src/_data/links.json b/docs/src/_data/links.json index d9b29733216..9bf8222a4f5 100644 --- a/docs/src/_data/links.json +++ b/docs/src/_data/links.json @@ -2,21 +2,18 @@ "github": "https://github.com/eslint/eslint", "twitter": "https://twitter.com/geteslint", "chat": "https://eslint.org/chat", - "group": "https://groups.google.com/group/eslint", - + "mastodon": "https://fosstodon.org/@eslint", "blog": "/blog", "docs": "/docs/latest/", "playground": "/play", - "getStarted": "/docs/latest/user-guide/getting-started", + "getStarted": "/docs/latest/use/getting-started", "sponsors": "/sponsors", "branding": "/branding", "store": "https://eslint.threadless.com", "team": "/team", - - "configuring": "https://eslint.org/docs/user-guide/configuring/", - "fixProblems": "https://eslint.org/docs/user-guide/command-line-interface#fixing-problems", - + "configuring": "https://eslint.org/docs/latest/use/configure/", + "fixProblems": "https://eslint.org/docs/latest/use/command-line-interface#fix-problems", "donate": "/donate", "openCollective": "https://opencollective.com/eslint", "githubSponsors": "https://github.com/sponsors/eslint" -} +} \ No newline at end of file diff --git a/docs/src/_data/rule_versions.json b/docs/src/_data/rule_versions.json index 98463d4bb6d..3e0b0ad761d 100644 --- a/docs/src/_data/rule_versions.json +++ b/docs/src/_data/rule_versions.json @@ -304,7 +304,10 @@ "wrap-iife": "0.0.9", "wrap-regex": "0.1.0", "yield-star-spacing": "2.0.0-alpha-1", - "yoda": "0.7.1" + "yoda": "0.7.1", + "logical-assignment-operators": "8.24.0", + "no-empty-static-block": "8.27.0", + "no-new-native-nonconstructor": "8.27.0" }, "removed": { "generator-star": "1.0.0-rc-1", diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 81e0500a9ea..2046ccd1450 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -21,7 +21,7 @@ }, { "name": "for-direction", - "description": "Enforce \"for\" loop update clause moving the counter in the right direction.", + "description": "Enforce \"for\" loop update clause moving the counter in the right direction", "recommended": true, "fixable": false, "hasSuggestions": false @@ -229,6 +229,13 @@ "fixable": false, "hasSuggestions": true }, + { + "name": "no-new-native-nonconstructor", + "description": "Disallow `new` operators with global non-constructor functions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, { "name": "no-new-symbol", "description": "Disallow `new` operators with the `Symbol` object", @@ -579,6 +586,13 @@ "fixable": false, "hasSuggestions": false }, + { + "name": "logical-assignment-operators", + "description": "Require or disallow logical assignment operator shorthand", + "recommended": false, + "fixable": true, + "hasSuggestions": true + }, { "name": "max-classes-per-file", "description": "Enforce a maximum number of classes per file", @@ -724,7 +738,7 @@ "description": "Disallow empty block statements", "recommended": true, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "no-empty-function", @@ -733,6 +747,13 @@ "fixable": false, "hasSuggestions": false }, + { + "name": "no-empty-static-block", + "description": "Disallow empty static blocks", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, { "name": "no-eq-null", "description": "Disallow `null` comparisons without type-checking operators", @@ -1046,7 +1067,7 @@ "description": "Disallow unnecessary `return await`", "recommended": false, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "no-script-url", @@ -1277,7 +1298,7 @@ "description": "Enforce using named capture group in regular expression", "recommended": false, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "prefer-numeric-literals", @@ -1295,7 +1316,7 @@ }, { "name": "prefer-object-spread", - "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.", + "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", "recommended": false, "fixable": true, "hasSuggestions": false @@ -1361,7 +1382,7 @@ "description": "Enforce the use of `u` flag on RegExp", "recommended": false, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "require-yield", @@ -1872,7 +1893,7 @@ ], "deprecated": { "name": "Deprecated", - "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", + "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", "rules": [ { "name": "callback-return", @@ -1988,7 +2009,7 @@ }, "removed": { "name": "Removed", - "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", + "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", "rules": [ { "removed": "generator-star", diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 56a986260b3..4242852b68d 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -270,7 +270,7 @@ "for-direction": { "type": "problem", "docs": { - "description": "Enforce \"for\" loop update clause moving the counter in the right direction.", + "description": "Enforce \"for\" loop update clause moving the counter in the right direction", "recommended": true, "url": "https://eslint.org/docs/rules/for-direction" }, @@ -531,6 +531,16 @@ }, "fixable": "whitespace" }, + "logical-assignment-operators": { + "type": "suggestion", + "docs": { + "description": "Require or disallow logical assignment operator shorthand", + "recommended": false, + "url": "https://eslint.org/docs/rules/logical-assignment-operators" + }, + "fixable": "code", + "hasSuggestions": true + }, "max-classes-per-file": { "type": "suggestion", "docs": { @@ -927,6 +937,7 @@ "fixable": "code" }, "no-empty": { + "hasSuggestions": true, "type": "suggestion", "docs": { "description": "Disallow empty block statements", @@ -958,6 +969,14 @@ "url": "https://eslint.org/docs/rules/no-empty-pattern" } }, + "no-empty-static-block": { + "type": "suggestion", + "docs": { + "description": "Disallow empty static blocks", + "recommended": false, + "url": "https://eslint.org/docs/rules/no-empty-static-block" + } + }, "no-eq-null": { "type": "suggestion", "docs": { @@ -1331,6 +1350,14 @@ "url": "https://eslint.org/docs/rules/no-new-func" } }, + "no-new-native-nonconstructor": { + "type": "problem", + "docs": { + "description": "Disallow `new` operators with global non-constructor functions", + "recommended": false, + "url": "https://eslint.org/docs/rules/no-new-native-nonconstructor" + } + }, "no-new-object": { "type": "suggestion", "docs": { @@ -1544,6 +1571,7 @@ } }, "no-return-await": { + "hasSuggestions": true, "type": "suggestion", "docs": { "description": "Disallow unnecessary `return await`", @@ -2089,7 +2117,8 @@ "description": "Enforce using named capture group in regular expression", "recommended": false, "url": "https://eslint.org/docs/rules/prefer-named-capture-group" - } + }, + "hasSuggestions": true }, "prefer-numeric-literals": { "type": "suggestion", @@ -2112,7 +2141,7 @@ "prefer-object-spread": { "type": "suggestion", "docs": { - "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.", + "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", "recommended": false, "url": "https://eslint.org/docs/rules/prefer-object-spread" }, @@ -2232,7 +2261,8 @@ "description": "Enforce the use of `u` flag on RegExp", "recommended": false, "url": "https://eslint.org/docs/rules/require-unicode-regexp" - } + }, + "hasSuggestions": true }, "require-yield": { "type": "suggestion", diff --git a/docs/src/_data/sites/en.yml b/docs/src/_data/sites/en.yml index c929da0c3c1..7c7de804bde 100644 --- a/docs/src/_data/sites/en.yml +++ b/docs/src/_data/sites/en.yml @@ -74,8 +74,8 @@ footer: title: Social Media twitter: Twitter chat: Discord - mailing_list: Google Group github: GitHub + mastodon: Mastodon theme_switcher: title: Theme Switcher light: Light @@ -86,7 +86,7 @@ footer: change_language: Change Language language: Language copyright: > - © OpenJS Foundation and ESLint contributors, www.openjsf.org + © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. links: open_jsf: The OpenJS Foundation terms: Terms of Use @@ -107,3 +107,11 @@ footer: actions: back_to_home: Back to homepage browse_docs: Browse the docs + +#------------------------------------------------------------------------------ +# Edit link +#------------------------------------------------------------------------------ + +edit_link: + start_with: https://github.com/eslint/eslint/edit/main/docs/ + text: Edit this page diff --git a/docs/src/_data/sites/zh-hans.yml b/docs/src/_data/sites/zh-hans.yml new file mode 100644 index 00000000000..0bff9291937 --- /dev/null +++ b/docs/src/_data/sites/zh-hans.yml @@ -0,0 +1,115 @@ +#------------------------------------------------------------------------------ +# Simplified Chinese Site Details +# The documentation site that is hosted at zh-hans.eslint.org/docs +# Author: Percy Ma +#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ +# Global Settings +#------------------------------------------------------------------------------ + +language: + code: zh-hans + flag: 🇨🇳 + name: 简体中文 +locale: zh-hans +hostname: zh-hans.eslint.org + +#------------------------------------------------------------------------------ +# Analytics +#------------------------------------------------------------------------------ + +google_analytics: + code: "G-6ELXTK7GZR" + +#------------------------------------------------------------------------------ +# Ads +#------------------------------------------------------------------------------ + +carbon_ads: + serve: "" + placement: "" + +#------------------------------------------------------------------------------ +# Shared +#------------------------------------------------------------------------------ + +shared: + get_started: 开始 + become_a_sponsor: 捐赠 + eslint_logo_alt: ESLint 图标 + description: > + 插件化、可配置的 JavaScript 代码检查工具,让你轻松地提高代码质量。 + title_format: PAGE_TITLE - ESLint - 插件化的 JavaScript 代码检查工具 + skip_to_content: 跳转到正文 + donate: 捐赠 + +#------------------------------------------------------------------------------ +# Navigation +#------------------------------------------------------------------------------ + +navigation: +- text: 团队 + link: team +- text: 博客 + link: blog +- text: 文档 + link: docs +- text: 商店 + link: store + target: _blank +- text: 演练场 + link: playground + +#------------------------------------------------------------------------------ +# Footer +#------------------------------------------------------------------------------ + +footer: + title: 准备修复你的 JavaScript 代码了吗? + description: 从 npm 安装或捐赠开始。 + secondary: 次要 + social_icons: + title: 社交媒体 + twitter: Twitter + chat: Discord + github: GitHub + theme_switcher: + title: 主题切换 + light: 浅色 + dark: 深色 + language_switcher: + title: 语言切换 + description: 切换到你所选择语言版本对应的 ESLint 网站。 + change_language: 更改语言 + language: 语言 + copyright: > + © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + links: + open_jsf: OpenJS 基金会 + terms: 使用条款 + privacy: 隐私策略 + bylaws: OpenJS 基金会章程 + trademark: 商标策略 + trademark_list: 商标列表 + cookies: Cookie 策略 + +#------------------------------------------------------------------------------ +# 404 Page +#------------------------------------------------------------------------------ + +404_page: + title: 404 错误 + subtitle: 页面未找到 + description: 对不起,你正在寻找的页面不存在或已被移动。 + actions: + back_to_home: 回到主页 + browse_docs: 浏览文档 + +#------------------------------------------------------------------------------ +# Edit link +#------------------------------------------------------------------------------ + +edit_link: + start_with: https://github.com/eslint/eslint/edit/main/docs/ + text: 编辑此页 diff --git a/docs/src/_includes/components/logo.html b/docs/src/_includes/components/logo.html index 1b874feb223..5e422b6fa3c 100644 --- a/docs/src/_includes/components/logo.html +++ b/docs/src/_includes/components/logo.html @@ -5,7 +5,7 @@ } [data-theme="dark"] #logo-center { - opacity: 60%; + opacity: 0.6; } Version - + + {% if config.showNextVersion == true %} + + {% endif %} {% for version in versions.items %}
@@ -95,6 +102,9 @@

{{ title }}

{% include "partials/docs-footer.html" %} + + + diff --git a/docs/src/_includes/partials/versions-list.html b/docs/src/_includes/partials/versions-list.html index 2e2fb2ff82a..7f07e4fa495 100644 --- a/docs/src/_includes/partials/versions-list.html +++ b/docs/src/_includes/partials/versions-list.html @@ -1,5 +1,8 @@
    -
  • HEAD
  • +
  • HEAD
  • + {% if config.showNextVersion == true %} +
  • NEXT
  • + {% endif %}
  • v{{ eslintVersion }}
  • {%- for version in versions.items -%}
  • v{{ version.number }}
  • diff --git a/docs/src/_plugins/md-syntax-highlighter.js b/docs/src/_plugins/md-syntax-highlighter.js new file mode 100644 index 00000000000..9ae0dcd26cc --- /dev/null +++ b/docs/src/_plugins/md-syntax-highlighter.js @@ -0,0 +1,91 @@ +/** + * MIT License + +Copyright (c) 2019-present, Yuxi (Evan) You + +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. + */ + +/** @typedef {import("markdown-it")} MarkdownIt */ + +const Prism = require("prismjs"); +const loadLanguages = require("prismjs/components/"); + +/** + * + * @param {MarkdownIt} md markdown-it + * @param {string} str code + * @param {string} lang code language + * @returns {string} highlighted result wrapped in pre + */ +const highlighter = function (md, str, lang) { + let result = ""; + if (lang) { + try { + loadLanguages([lang]); + result = Prism.highlight(str, Prism.languages[lang], lang); + } catch (err) { + console.log(lang, err); + // we still want to wrap the result later + result = md.utils.escapeHtml(str); + } + } else { + result = md.utils.escapeHtml(str); + } + + return `
    ${result}
    `; +}; + +/** + * + * modified from https://github.com/vuejs/vitepress/blob/main/src/node/markdown/plugins/lineNumbers.ts + * @param {MarkdownIt} md + * @license MIT License. See file header. + */ +const lineNumberPlugin = (md) => { + const fence = md.renderer.rules.fence; + md.renderer.rules.fence = (...args) => { + const [tokens, idx] = args; + const lang = tokens[idx].info.trim(); + const rawCode = fence(...args); + const code = rawCode.slice( + rawCode.indexOf(""), + rawCode.indexOf("") + ); + const lines = code.split("\n"); + const lineNumbersCode = [...Array(lines.length - 1)] + .map( + (line, index) => + `${index + 1}
    ` + ) + .join(""); + + const lineNumbersWrapperCode = ``; + + const finalCode = rawCode + .replace(/<\/pre>\n/, `${lineNumbersWrapperCode}`) + .replace(/"(language-\S*?)"/, '"$1 line-numbers-mode"') + .replace(//, ``) + + return finalCode; + }; +}; + +module.exports.highlighter = highlighter; +module.exports.lineNumberPlugin = lineNumberPlugin; diff --git a/docs/src/about/index.md b/docs/src/about/index.md index 5b3b359b45d..0f59d9cf39c 100644 --- a/docs/src/about/index.md +++ b/docs/src/about/index.md @@ -1,7 +1,5 @@ --- title: About -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/about/index.md --- diff --git a/docs/src/assets/fonts/Consolas.woff b/docs/src/assets/fonts/Consolas.woff new file mode 100644 index 00000000000..c4db8dd2ea3 Binary files /dev/null and b/docs/src/assets/fonts/Consolas.woff differ diff --git a/docs/src/assets/js/main.js b/docs/src/assets/js/main.js index 28ec0e1306b..bf3268266f4 100644 --- a/docs/src/assets/js/main.js +++ b/docs/src/assets/js/main.js @@ -1,3 +1,42 @@ +(function () { + // for sticky table of contents + const tocBody = document.querySelector(".docs-aside #js-toc-panel"); + const options = { + root: null, + rootMargin: `0px 0px -90% 0px`, + threshold: 1.0, + }; + const activeClassName = "active"; + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const activeAnchor = tocBody.querySelector( + `a.${activeClassName}` + ); + if (activeAnchor) { + activeAnchor.parentNode.classList.remove(activeClassName); + activeAnchor.classList.remove(activeClassName); + } + + const nextActiveAnchor = tocBody.querySelector( + `a[href="#${entry.target.id}"]` + ); + if (nextActiveAnchor) { + nextActiveAnchor.parentNode.classList.add(activeClassName); + nextActiveAnchor.classList.add(activeClassName); + } + } + }); + }, options); + if (window.matchMedia("(min-width: 1400px)").matches) { + document + .querySelectorAll( + "#main > div > h2[id], #main > div > h3[id], #main > div > h4[id]" // only h2, h3, h4 are shown in toc + ) + .forEach((el) => observer.observe(el)); + } +})(); + (function() { var toc_trigger = document.getElementById("js-toc-label"), toc = document.getElementById("js-toc-panel"), @@ -279,4 +318,4 @@ if (index) { document.addEventListener("DOMContentLoaded", () => { anchors.add(".docs-content h2:not(.c-toc__label), .docs-content h3, .docs-content h4"); -}); \ No newline at end of file +}); diff --git a/docs/src/assets/js/scroll-up-btn.js b/docs/src/assets/js/scroll-up-btn.js new file mode 100644 index 00000000000..cb77af1bcbe --- /dev/null +++ b/docs/src/assets/js/scroll-up-btn.js @@ -0,0 +1,13 @@ +(function () { + const scrollUpBtn = document.getElementById("scroll-up-btn"); + + if(window.innerWidth < 1400) { + window.addEventListener("scroll", function () { + if(document.body.scrollTop > 500 || document.documentElement.scrollTop > 500) { + scrollUpBtn.style.display = "flex"; + } else { + scrollUpBtn.style.display = "none"; + } + }); + } +})(); \ No newline at end of file diff --git a/docs/src/assets/js/search.js b/docs/src/assets/js/search.js index 66d3b646372..6d8eaa7b1b2 100644 --- a/docs/src/assets/js/search.js +++ b/docs/src/assets/js/search.js @@ -22,6 +22,8 @@ const resultsElement = document.querySelector('#search-results'); const resultsLiveRegion = document.querySelector('#search-results-announcement'); const searchInput = document.querySelector('#search'); const searchClearBtn = document.querySelector('#search__clear-btn'); +let activeIndex = -1; +let searchQuery; //----------------------------------------------------------------------------- // Helpers @@ -47,7 +49,6 @@ function clearSearchResults() { resultsElement.removeChild(resultsElement.firstChild); } resultsElement.innerHTML = ""; - searchClearBtn.setAttribute('hidden', ''); } /** @@ -66,6 +67,7 @@ function displaySearchResults(results) { list.classList.add('search-results__list'); resultsElement.append(list); resultsElement.setAttribute('data-results', 'true'); + activeIndex = -1; for (const result of results) { const listItem = document.createElement('li'); @@ -77,52 +79,131 @@ function displaySearchResults(results) { `.trim(); list.append(listItem); } - searchClearBtn.removeAttribute('hidden'); } else { resultsLiveRegion.innerHTML = "No results found."; resultsElement.innerHTML = "No results found."; resultsElement.setAttribute('data-results', 'false'); - searchClearBtn.setAttribute('hidden', ''); } } + +// Check if an element is currently scrollable +function isScrollable(element) { + return element && element.clientHeight < element.scrollHeight; +} + +// Ensure given child element is within the parent's visible scroll area +function maintainScrollVisibility(activeElement, scrollParent) { + const { offsetHeight, offsetTop } = activeElement; + const { offsetHeight: parentOffsetHeight, scrollTop } = scrollParent; + + const isAbove = offsetTop < scrollTop; + const isBelow = (offsetTop + offsetHeight) > (scrollTop + parentOffsetHeight); + + if (isAbove) { + scrollParent.scrollTo(0, offsetTop); + } + else if (isBelow) { + scrollParent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight); + } + +} + +/** + * Debounces the provided callback with a given delay. + * @param {Function} callback The callback that needs to be debounced. + * @param {Number} delay Time in ms that the timer should wait before the callback is executed. + * @returns {Function} Returns the new debounced function. + */ +function debounce(callback, delay) { + let timer; + return (...args) => { + if (timer) clearTimeout(timer); + timer = setTimeout(() => callback.apply(this, args), delay); + } +} + +const debouncedFetchSearchResults = debounce((query) => { + fetchSearchResults(query) + .then(displaySearchResults) + .catch(clearSearchResults); +}, 300); + //----------------------------------------------------------------------------- // Event Handlers //----------------------------------------------------------------------------- // listen for input changes -if(searchInput) +if (searchInput) searchInput.addEventListener('keyup', function (e) { const query = searchInput.value; - if(query.length) searchClearBtn.removeAttribute('hidden'); + if (query === searchQuery) return; + + if (query.length) searchClearBtn.removeAttribute('hidden'); else searchClearBtn.setAttribute('hidden', ''); if (query.length > 2) { - fetchSearchResults(query) - .then(displaySearchResults) - .catch(clearSearchResults); - document.addEventListener('click', function(e) { - if(e.target !== resultsElement) clearSearchResults(); + debouncedFetchSearchResults(query); + + document.addEventListener('click', function (e) { + if (e.target !== resultsElement) clearSearchResults(); }); } else { clearSearchResults(); } + + searchQuery = query + }); -if(resultsElement) - resultsElement.addEventListener('keydown', function(e) { - if(e.key === "Escape") { - clearSearchResults(); - } - }, true); -if(searchClearBtn) - searchClearBtn.addEventListener('click', function(e) { +if (searchClearBtn) + searchClearBtn.addEventListener('click', function (e) { searchInput.value = ''; searchInput.focus(); clearSearchResults(); + searchClearBtn.setAttribute('hidden', ''); }); + +document.addEventListener('keydown', function (e) { + + const searchResults = Array.from(document.querySelectorAll('.search-results__item')); + + if (e.key === 'Escape') { + e.preventDefault(); + if (searchResults.length) { + clearSearchResults(); + searchInput.focus(); + } + } + + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault(); + searchInput.focus(); + document.querySelector('.search').scrollIntoView({ behavior: "smooth", block: "start" }); + } + + if (!searchResults.length) return; + + switch (e.key) { + case "ArrowUp": + e.preventDefault(); + activeIndex = activeIndex - 1 < 0 ? searchResults.length - 1 : activeIndex - 1; + break; + case "ArrowDown": + e.preventDefault(); + activeIndex = activeIndex + 1 < searchResults.length ? activeIndex + 1 : 0; + break; + } + + if (activeIndex === -1) return; + const activeSearchResult = searchResults[activeIndex]; + activeSearchResult.querySelector('a').focus(); + if (isScrollable(resultsElement)) { + maintainScrollVisibility(activeSearchResult, resultsElement); + } +}); diff --git a/docs/src/assets/scss/carbon-ads.scss b/docs/src/assets/scss/carbon-ads.scss index ccf578e6d9a..bd7ea8e660c 100644 --- a/docs/src/assets/scss/carbon-ads.scss +++ b/docs/src/assets/scss/carbon-ads.scss @@ -1,7 +1,7 @@ .hero-ad { - @media all and (max-width: 800px) { - display: none; - } + @media all and (max-width: 800px) { + display: none; + } } #carbonads * { @@ -15,9 +15,9 @@ padding: .6em; font-size: 1rem; overflow: hidden; - border-radius: 4px; background-color: var(--body-background-color); border: 1px solid var(--border-color); + border-radius: 4px; border-radius: var(--border-radius); box-shadow: 0 1px 4px 1px hsla(0, 0%, 0%, 0.1); @@ -31,8 +31,8 @@ } .jumbotron #carbonads { - border: solid 1px hsla(250, 20%, 50%, .6); - background-color: hsla(0, 0%, 70%, .15); + border: solid 1px hsla(250, 20%, 50%, 0.6); + background-color: hsla(0, 0%, 70%, 0.15); } #carbonads a { diff --git a/docs/src/assets/scss/components/alert.scss b/docs/src/assets/scss/components/alert.scss index ddd5c693d6a..8235c1429c9 100644 --- a/docs/src/assets/scss/components/alert.scss +++ b/docs/src/assets/scss/components/alert.scss @@ -16,6 +16,7 @@ color: var(--color-rose-600); [data-theme="dark"] & { + border: 1px solid var(--color-rose-300); color: var(--color-rose-300); background-color: var(--color-rose-900); } @@ -27,6 +28,7 @@ [data-theme="dark"] & { color: var(--color-warning-300); + border: 1px solid var(--color-warning-300); background-color: var(--color-warning-900); } } @@ -37,24 +39,8 @@ [data-theme="dark"] & { color: var(--color-success-300); - background-color: var(--color-success-900); - } - } -} - - -[data-theme="dark"] { - .alert { - &.alert--warning { - border: 1px solid var(--color-rose-300); - } - - &.alert--important { - border: 1px solid var(--color-warning-300); - } - - &.alert--tip { border: 1px solid var(--color-success-300); + background-color: var(--color-success-900); } } } @@ -101,7 +87,6 @@ } } - .alert__learn-more { display: block; font-weight: 500; diff --git a/docs/src/assets/scss/components/buttons.scss b/docs/src/assets/scss/components/buttons.scss index bbe6451a616..ca0aa72a726 100644 --- a/docs/src/assets/scss/components/buttons.scss +++ b/docs/src/assets/scss/components/buttons.scss @@ -23,9 +23,7 @@ button { align-items: center; justify-content: center; border-radius: var(--border-radius); - - transition: background-color .2s linear, - border-color .2s linear; + transition: background-color .2s linear, border-color .2s linear; svg { color: inherit; diff --git a/docs/src/assets/scss/components/docs-index.scss b/docs/src/assets/scss/components/docs-index.scss index 2a4b86d140f..22e156eb39d 100644 --- a/docs/src/assets/scss/components/docs-index.scss +++ b/docs/src/assets/scss/components/docs-index.scss @@ -60,7 +60,6 @@ } .index-js [aria-expanded="true"] .index-icon { - -ms-transform: rotate(180deg); transform: rotate(180deg); } @@ -76,7 +75,7 @@ &[data-open="false"] { display: none; - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: block; } } @@ -84,7 +83,7 @@ &[data-open="true"] { display: block; - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: block; } } @@ -109,7 +108,7 @@ background-color: var(--secondary-button-hover-color); } - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: none; } @@ -143,7 +142,6 @@ } #ham-top { - transform: rotate(41deg); } diff --git a/docs/src/assets/scss/components/docs-navigation.scss b/docs/src/assets/scss/components/docs-navigation.scss index f42a2ee17c5..f47fce3a0a5 100644 --- a/docs/src/assets/scss/components/docs-navigation.scss +++ b/docs/src/assets/scss/components/docs-navigation.scss @@ -13,8 +13,7 @@ margin-bottom: 2rem; margin-block-end: 2rem; - - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { font-size: var(--step-0); margin-top: 0; margin-block-start: 0; @@ -50,9 +49,8 @@ } } - .docs-nav-panel { - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: flex; flex-direction: row; justify-content: center; @@ -63,7 +61,7 @@ } &[data-open="true"] { - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: flex; flex-direction: row; justify-content: center; @@ -72,7 +70,7 @@ } .docs-nav-panel .mobile-only { - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: none; } } @@ -83,11 +81,9 @@ align-items: center; margin-left: .5rem; margin-right: -10px; - margin-inline-start: .5rem; margin-inline-end: -10px; - svg { width: 40px; height: 40px; @@ -118,7 +114,6 @@ } #ham-top { - transform: rotate(41deg); } @@ -128,9 +123,7 @@ } } - - -@media all and (min-width: 1023px) { +@media all and (min-width: 1024px) { .docs-site-nav { flex-direction: row; grid-column: auto; @@ -151,5 +144,4 @@ order: 1; } } - } diff --git a/docs/src/assets/scss/components/hero.scss b/docs/src/assets/scss/components/hero.scss index 54c303e2216..44a7390e027 100644 --- a/docs/src/assets/scss/components/hero.scss +++ b/docs/src/assets/scss/components/hero.scss @@ -36,11 +36,15 @@ padding: 0 calc(1rem + 1vw); padding-bottom: 0; align-items: center; + max-width: 1700px; + + @media all and (min-width: 1700px) { + margin: auto; + } } } .hero--homepage { - .section-title { margin-bottom: 1.5rem; margin-block-end: 1.5rem; diff --git a/docs/src/assets/scss/components/index.scss b/docs/src/assets/scss/components/index.scss index a1b79a733f9..5989e1f48e7 100644 --- a/docs/src/assets/scss/components/index.scss +++ b/docs/src/assets/scss/components/index.scss @@ -45,7 +45,7 @@ background-color: var(--secondary-button-hover-color); } - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: none; } @@ -79,7 +79,6 @@ } #ham-top { - transform: rotate(41deg); } @@ -95,7 +94,7 @@ &[data-open="false"] { display: none; - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: block; } } @@ -103,7 +102,7 @@ &[data-open="true"] { display: block; - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: block; } } diff --git a/docs/src/assets/scss/components/language-switcher.scss b/docs/src/assets/scss/components/language-switcher.scss index 1aa9b2cea9f..364f23fed6c 100644 --- a/docs/src/assets/scss/components/language-switcher.scss +++ b/docs/src/assets/scss/components/language-switcher.scss @@ -18,11 +18,10 @@ flex: 1 0 10ch; } - -.switcher--language .switcher__select { +.switcher--language .switcher__select { flex: 1 0 12rem; - @media all and (max-width: 800px) { + @media all and (max-width: 799px) { max-width: 250px; } } diff --git a/docs/src/assets/scss/components/resources.scss b/docs/src/assets/scss/components/resources.scss index cf483cd0173..4ee2616d8db 100644 --- a/docs/src/assets/scss/components/resources.scss +++ b/docs/src/assets/scss/components/resources.scss @@ -7,14 +7,12 @@ overflow: hidden; margin-bottom: .5rem; margin-block-end: .5rem; - position: relative; transition: all .2s linear; &:hover { background-color: var(--lighter-background-color); } - } .resource__image { @@ -27,7 +25,6 @@ display: block; height: 100%; width: 100%; - // object-fit: cover; object-fit: contain; } } @@ -38,7 +35,6 @@ align-self: center; } - .resource__title { // a text-decoration: none; color: var(--headings-color); @@ -51,7 +47,7 @@ left: 0; offset-inline-start: 0; top: 0; - block-inline-start: 0; + offset-block-start: 0; width: 100%; height: 100%; } diff --git a/docs/src/assets/scss/components/rules.scss b/docs/src/assets/scss/components/rules.scss index ec5217272c2..4e0f4619c21 100644 --- a/docs/src/assets/scss/components/rules.scss +++ b/docs/src/assets/scss/components/rules.scss @@ -13,24 +13,24 @@ background: none; border: none; - @media screen and (min-width:768px){ + @media screen and (min-width: 768px) { &:not(:first-child)::after { content: ""; display: block; padding: 1px; border-left: 1px solid var(--divider-color); - left: 0px; + left: 0; } } - @media screen and (min-width:768px) and (max-width:1023px), screen and (min-width:1440px){ + @media screen and (min-width: 768px) and (max-width: 1023px), screen and (min-width: 1440px) { &:not(:first-child)::after { height: 70%; position: absolute; } } - @media screen and (min-width:1024px) and (max-width:1439px){ + @media screen and (min-width: 1024px) and (max-width: 1439px) { &:nth-child(2)::after { height: 70%; position: absolute; @@ -65,7 +65,7 @@ } } -.rule { +.rule:not(.token) { border-radius: var(--border-radius); background-color: var(--lightest-background-color); display: flex; @@ -171,7 +171,6 @@ a.rule__name { } .related-rules__list__item { - svg { color: inherit; } @@ -194,7 +193,7 @@ a.rule__name { } } -a.rule-list-item+a.rule-list-item::before { +a.rule-list-item + a.rule-list-item::before { content: ","; display: inline-block; margin-left: 5px; diff --git a/docs/src/assets/scss/components/search.scss b/docs/src/assets/scss/components/search.scss index 8038e3a2cd4..4b90582c4c6 100644 --- a/docs/src/assets/scss/components/search.scss +++ b/docs/src/assets/scss/components/search.scss @@ -1,11 +1,10 @@ [type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration { - -webkit-appearance: none; appearance: none; } -[type=search]::-ms-clear, -[type=search]::-ms-reveal { +[type="search"]::-ms-clear, +[type="search"]::-ms-reveal { display: none; width: 0; height: 0; @@ -65,17 +64,16 @@ .search .search-results { font-size: .875rem; background-color: var(--body-background-color); - position: relative; z-index: 10; width: 100%; border-radius: 0 0 var(--border-radius) var(--border-radius); border: 1px solid var(--divider-color); - position: rekative; + position: relative; top: .25rem; max-height: 400px; overflow-y: auto; - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { box-shadow: var(--shadow-lg); position: absolute; top: calc(100% + .25rem); @@ -84,6 +82,7 @@ &[data-results="true"] { padding: 0; } + &[data-results="false"] { padding: 1rem; } @@ -109,6 +108,10 @@ &:hover { background-color: var(--lightest-background-color); } + + &:focus-within { + background-color: var(--lightest-background-color); + } } .search .search-results__item__title { @@ -117,7 +120,6 @@ margin-bottom: 0; font-family: var(--text-font); - a { display: block; text-decoration: none; diff --git a/docs/src/assets/scss/components/social-icons.scss b/docs/src/assets/scss/components/social-icons.scss index 37902a9f588..eddd47f3ec7 100644 --- a/docs/src/assets/scss/components/social-icons.scss +++ b/docs/src/assets/scss/components/social-icons.scss @@ -11,7 +11,6 @@ li { margin: 0; - display: inline-flex; align-items: center; a { diff --git a/docs/src/assets/scss/components/tabs.scss b/docs/src/assets/scss/components/tabs.scss index f2672a1601a..8a7d866c514 100644 --- a/docs/src/assets/scss/components/tabs.scss +++ b/docs/src/assets/scss/components/tabs.scss @@ -27,11 +27,7 @@ align-items: center; justify-content: center; border-radius: var(--border-radius) var(--border-radius) 0 0; - - transition: background-color .2s linear, - border-color .2s linear; - - + transition: background-color .2s linear, border-color .2s linear; &:hover { color: var(--link-color); @@ -55,7 +51,6 @@ } } - .c-tabs__tabpanel__title { margin-bottom: 1.5rem; margin-block-end: 1.5rem; diff --git a/docs/src/assets/scss/components/theme-switcher.scss b/docs/src/assets/scss/components/theme-switcher.scss index 0fa59fe1845..d44aa9009d9 100644 --- a/docs/src/assets/scss/components/theme-switcher.scss +++ b/docs/src/assets/scss/components/theme-switcher.scss @@ -6,7 +6,6 @@ } .theme-switcher-label.theme-switcher-label { - font-size: inherit; color: inherit; font: inherit; font-family: var(--text-font); @@ -21,7 +20,7 @@ } .theme-switcher__button { - flex: 0; + flex-wrap: wrap; box-shadow: var(--shadow-xs); padding: .625rem .875rem; display: inline-flex; @@ -76,9 +75,3 @@ } } } - -.theme-switcher__button:hover { - .theme-switcher__icon { - color: var(--link-color); - } -} diff --git a/docs/src/assets/scss/components/toc.scss b/docs/src/assets/scss/components/toc.scss index d1f47a6faeb..96647b4c70f 100644 --- a/docs/src/assets/scss/components/toc.scss +++ b/docs/src/assets/scss/components/toc.scss @@ -1,23 +1,43 @@ .docs-toc { margin: 2rem 0; -} - -.docs-toc { - .docs-aside & { - display: none; - } @media all and (min-width: 1400px) { display: none; } .docs-aside & { + display: none; + @media all and (min-width: 1400px) { display: block; } } } +.docs-aside { + // for sticky table of contents in sidebar + .docs-toc.c-toc { + background-color: var(--body-background-color); + @media all and (min-width: 1400px) { + position: sticky; + top: 20px; + overflow-y: auto; // show scrollbar when toc is higher than viewport + padding-right: 5px; // push scrollbar away from content + max-height: calc(100vh - 32px); // minus element's margin-top + a.active { + color: var(--link-color); + font-weight: 500; + } + } + } + + .c-toc ol li.active::before { + @media all and (min-width: 1400px) { + color: var(--link-color); + } + } +} + .c-toc { ol { margin: 0; @@ -100,13 +120,10 @@ color: var(--color-neutral-400); [aria-expanded="true"] & { - -ms-transform: rotate(180deg); transform: rotate(180deg); } } - - .c-toc__panel { &[data-open="false"] { display: none; diff --git a/docs/src/assets/scss/docs-header.scss b/docs/src/assets/scss/docs-header.scss index 6ba51cec00f..15f21cf47ee 100644 --- a/docs/src/assets/scss/docs-header.scss +++ b/docs/src/assets/scss/docs-header.scss @@ -10,13 +10,16 @@ align-items: start; padding-top: 0; padding-bottom: 0; - padding-block-start: 0; padding-block-end: 0; + max-width: 1700px; @media all and (min-width: 1024px) { justify-content: space-between; } + @media all and (min-width: 1700px) { + margin: auto; + } } } diff --git a/docs/src/assets/scss/docs.scss b/docs/src/assets/scss/docs.scss index a9a07349728..ee40123891d 100644 --- a/docs/src/assets/scss/docs.scss +++ b/docs/src/assets/scss/docs.scss @@ -4,11 +4,6 @@ html { scroll-behavior: smooth; } -.docs { - max-width: 1700px; - margin: 0 auto; -} - .docs-aside__content { flex: 1; } @@ -18,12 +13,16 @@ html { flex: 1; display: flex; flex-direction: column; + max-width: 1700px; - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: grid; grid-template-columns: minmax(250px, 1fr) minmax(0, 3.5fr); align-items: stretch; } + @media all and (min-width: 1700px) { + margin: auto; + } } .docs-nav { @@ -31,17 +30,15 @@ html { grid-row: 1 / 2; padding-top: var(--space-l-xl); padding-block-start: var(--space-l-xl); - font-size: .875rem; - + font-size: 0.875rem; display: grid; grid-auto-rows: max-content; align-items: start; - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { padding: var(--space-l-xl) 0; padding-right: var(--space-s-l); padding-inline-end: var(--space-s-l); - border-right: 1px solid var(--divider-color); border-inline-end: 1px solid var(--divider-color); } @@ -58,7 +55,7 @@ html { grid-gap: 1rem; } - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { padding: 0; } @@ -73,17 +70,15 @@ html { @media all and (min-width: 800px) { padding-right: var(--space-s-l); padding-inline-end: var(--space-s-l); - border-right: 1px solid var(--divider-color); border-inline-end: 1px solid var(--divider-color); } - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { padding: var(--space-l-xl) var(--space-l-2xl); } } - .docs-aside { grid-column: 2 / 3; display: flex; @@ -131,6 +126,15 @@ div.incorrect { } } +div.img-container { + background-color: var(--img-background-color); + border-radius: var(--border-radius); + + img { + margin: 0 auto; + } +} + pre[class*="language-"] { position: relative; } @@ -138,12 +142,42 @@ pre[class*="language-"] { .c-btn.c-btn--playground { position: absolute; font-size: var(--step--1); - bottom: .5rem; - right: .5rem; - offset-block-end: .5rem; - offset-inline-end: .5rem; + bottom: 0.5rem; + right: 0.5rem; + offset-block-end: 0.5rem; + offset-inline-end: 0.5rem; @media all and (max-width: 768px) { display: none; } } + +@media (hover: none) { + .anchorjs-link { + opacity: 1; + } +} + +#scroll-up-btn { + width: 50px; + height: 50px; + display: none; + position: fixed; + right: 50px; + bottom: 35px; + font-size: 1.5rem; + border-radius: 50%; + color: var(--body-background-color); + text-decoration: none; + justify-content: center; + align-items: center; + background-color: var(--link-color); + + @media (max-width: 800px) { + right: 35px; + } + + @media (max-width: 600px) { + right: 25px; + } +} diff --git a/docs/src/assets/scss/eslint-site-footer.scss b/docs/src/assets/scss/eslint-site-footer.scss index 4a1e2cdc281..6ecb430c703 100644 --- a/docs/src/assets/scss/eslint-site-footer.scss +++ b/docs/src/assets/scss/eslint-site-footer.scss @@ -61,5 +61,4 @@ flex-direction: row; justify-content: space-between; } - } diff --git a/docs/src/assets/scss/eslint-site-header.scss b/docs/src/assets/scss/eslint-site-header.scss index 239d14d3428..892ebc7e625 100644 --- a/docs/src/assets/scss/eslint-site-header.scss +++ b/docs/src/assets/scss/eslint-site-header.scss @@ -10,7 +10,6 @@ align-items: start; padding-top: 0; padding-bottom: 0; - padding-block-start: 0; padding-block-end: 0; diff --git a/docs/src/assets/scss/forms.scss b/docs/src/assets/scss/forms.scss index e6d830c9897..3ca14525734 100644 --- a/docs/src/assets/scss/forms.scss +++ b/docs/src/assets/scss/forms.scss @@ -1,12 +1,10 @@ .c-custom-select { - -moz-appearance: none; - -webkit-appearance: none; appearance: none; box-sizing: border-box; display: block; width: 100%; max-width: 100%; - min-width: 0px; + min-width: 0; padding: .625rem .875rem; padding-right: calc(.875rem * 2.5); padding-inline-end: calc(.875rem * 2.5); @@ -19,14 +17,12 @@ background-color: var(--body-background-color); background-image: url("data:image/svg+xml,%3Csvg width='20' height='21' viewBox='0 0 20 21' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 7.60938L10 12.6094L15 7.60938' stroke='%23667085' stroke-width='1.66667' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"), linear-gradient(to bottom, var(--body-background-color) 0%, var(--body-background-color) 100%); background-repeat: no-repeat, repeat; - background-position: right .875rem top 50%, - 0 0; + background-position: right .875rem top 50%, 0 0; background-size: 1em auto, 100%; } .label__text.label__text { display: flex; - font-size: .875rem; align-items: center; gap: .5rem; font-size: .875rem; @@ -45,7 +41,6 @@ input { font: inherit; font-size: 1rem; display: block; - line-height: 1.3; min-width: 0; line-height: 1.3; max-width: 100%; diff --git a/docs/src/assets/scss/foundations.scss b/docs/src/assets/scss/foundations.scss index 27849b3c145..68e44651f4f 100644 --- a/docs/src/assets/scss/foundations.scss +++ b/docs/src/assets/scss/foundations.scss @@ -46,7 +46,6 @@ input:focus { box-shadow: 0 0 0 2px var(--link-color); } - *, *::before, *::after { @@ -63,6 +62,7 @@ html { } body { + font-size: var(--step-0); position: relative; margin: 0 auto; line-height: 1.5; @@ -81,7 +81,6 @@ body { offset-block-start: -30em; offset-inline-start: 0; offset-inline-end: auto; - z-index: 999; transition: top .1s linear; @@ -123,8 +122,12 @@ hr { width: 100%; margin: 0 auto; padding: var(--space-xl-3xl) calc(1rem + 1vw); -} + max-width: 1700px; + @media all and (min-width: 1700px) { + margin: auto; + } +} .section-head { .section-supporting-text { @@ -155,7 +158,6 @@ hr { font-size: var(--step-1); } - code, pre { font-family: var(--mono-font); @@ -170,11 +172,6 @@ code { } } -p:empty { - display: none; - margin: 0; -} - .c-icon { color: var(--icon-color); flex: none; @@ -214,7 +211,6 @@ a { } } - a { color: var(--link-color); transition: color .1s linear; @@ -249,7 +245,6 @@ h6 { overflow-wrap: break-word; } - ul, ol { margin-top: 0; @@ -259,7 +254,7 @@ ol { margin: 0 0 .75em; } - .person__bio & { + .person__bio & { padding-left: 1.5rem; padding-inline-start: 1.5rem; } @@ -326,7 +321,6 @@ nav { } } - .video { width: 90%; max-width: 1400px; @@ -347,12 +341,7 @@ nav { } } - /* typography */ -body { - font-size: var(--step-0); - line-height: 1.5; -} .eyebrow { color: var(--link-color); @@ -374,7 +363,6 @@ h6 { font-weight: 500; margin-top: 0; margin-block-start: 0; - } h2, @@ -382,7 +370,6 @@ h3, h4, h5, h6 { - .docs-main &, .components-main & { margin-top: 3rem; @@ -397,7 +384,6 @@ h6 { } } - small, caption, cite, diff --git a/docs/src/assets/scss/languages.scss b/docs/src/assets/scss/languages.scss index b3872e4cce1..9b29097f0d4 100644 --- a/docs/src/assets/scss/languages.scss +++ b/docs/src/assets/scss/languages.scss @@ -13,10 +13,10 @@ a { color: inherit; - display: block; width: 100%; padding: .75rem .1rem; text-decoration: none; + display: block; display: flex; align-items: center; border-bottom: 1px solid var(--divider-color); @@ -27,7 +27,10 @@ color: var(--link-color); &::after { - content: "✔️"; + content: " ✔️"; + white-space: pre; + color: rgba(100%, 0%, 0%, 0); + text-shadow: 0 0 0 var(--headings-color); } } diff --git a/docs/src/assets/scss/print.scss b/docs/src/assets/scss/print.scss index 446c585ef41..39dcc9470cd 100644 --- a/docs/src/assets/scss/print.scss +++ b/docs/src/assets/scss/print.scss @@ -1,11 +1,11 @@ *, -*:before, -*:after, -*:first-letter, -p:first-line, -div:first-line, -blockquote:first-line, -li:first-line { +*::before, +*::after, +*::first-letter, +p::first-line, +div::first-line, +blockquote::first-line, +li::first-line { background: transparent !important; color: #000 !important; box-shadow: none !important; @@ -64,7 +64,6 @@ h6 { font-size: 14pt; } - p, h2, h3 { @@ -110,7 +109,7 @@ a:visited { // font-size: 90%; // } -abbr[title]:after { +abbr[title]::after { content: " ("attr(title) ")"; } @@ -119,17 +118,17 @@ a[href^="http://"] { color: #000; } -a[href$=".jpg"]:after, -a[href$=".jpeg"]:after, -a[href$=".gif"]:after, -a[href$=".png"]:after { +a[href$=".jpg"]::after, +a[href$=".jpeg"]::after, +a[href$=".gif"]::after, +a[href$=".png"]::after { content: " ("attr(href) ") "; display: none; } /* Don't show links that are fragment identifiers, or use the `javascript:` pseudo protocol .. taken from html5boilerplate */ -a[href^="#"]:after, -a[href^="javascript:"]:after { +a[href^="#"]::after, +a[href^="javascript:"]::after { content: ""; } @@ -172,7 +171,7 @@ tr { page-break-inside: avoid; } -body>*:not(main), +body > *:not(main), aside, *[class*="sidebar"] { display: none; @@ -184,8 +183,8 @@ button, display: none; } -a[href^='http']:not([href*='mywebsite.com'])::after { - content: ' ('attr(href) ')'; +a[href^="http"]:not([href*="eslint.org"])::after { + content: " ("attr(href) ")"; } .resource a::after { @@ -196,7 +195,10 @@ ul { page-break-inside: avoid; } -.docs-toc, .docs-index, .docs-aside, #skip-link{ +.docs-toc, +.docs-index, +.docs-aside, +#skip-link { display: none; } @@ -205,3 +207,7 @@ ul { margin: 1cm; } } + +#scroll-up-btn { + display: none; +} diff --git a/docs/src/assets/scss/styles.scss b/docs/src/assets/scss/styles.scss index e07b280a89d..8907a6c4bf9 100644 --- a/docs/src/assets/scss/styles.scss +++ b/docs/src/assets/scss/styles.scss @@ -1,37 +1,35 @@ +@import "tokens/themes"; +@import "tokens/spacing"; +@import "tokens/typography"; +@import "tokens/ui"; -@import "tokens/themes.scss"; -@import "tokens/spacing.scss"; -@import "tokens/typography.scss"; -@import "tokens/ui.scss"; +@import "foundations"; +@import "syntax-highlighter"; +@import "docs-header"; +@import "docs-footer"; +@import "eslint-site-footer"; +@import "eslint-site-header"; +@import "forms"; +@import "docs"; +@import "versions"; +@import "languages"; -@import "foundations.scss"; -@import "syntax-highlighter.scss"; -@import "docs-header.scss"; -@import "docs-footer.scss"; -@import "eslint-site-footer.scss"; -@import "eslint-site-header.scss"; -@import "forms.scss"; -@import "docs.scss"; -@import "versions.scss"; -@import "languages.scss"; +@import "components/buttons"; +@import "components/docs-navigation"; +@import "components/toc"; +@import "components/search"; +@import "components/alert"; +@import "components/rules"; +@import "components/social-icons"; +@import "components/hero"; +@import "components/theme-switcher"; +@import "components/version-switcher"; +@import "components/language-switcher"; +@import "components/docs-index"; // docs index on the main docs pages +@import "components/index"; // used in component library +@import "components/tabs"; +@import "components/resources"; -@import "components/buttons.scss"; -@import "components/docs-navigation.scss"; -@import "components/toc.scss"; -@import "components/search.scss"; -@import "components/alert.scss"; -@import "components/rules.scss"; -@import "components/social-icons.scss"; -@import "components/hero.scss"; -@import "components/theme-switcher.scss"; -@import "components/version-switcher.scss"; -@import "components/language-switcher.scss"; -@import "components/docs-index.scss"; // docs index on the main docs pages -@import "components/index.scss"; // used in component library -@import "components/tabs.scss"; -@import "components/index.scss"; -@import "components/resources.scss"; +@import "carbon-ads"; -@import "carbon-ads.scss"; - -@import "utilities.scss"; +@import "utilities"; diff --git a/docs/src/assets/scss/syntax-highlighter.scss b/docs/src/assets/scss/syntax-highlighter.scss index 85823a9dc1e..cb744db0e38 100644 --- a/docs/src/assets/scss/syntax-highlighter.scss +++ b/docs/src/assets/scss/syntax-highlighter.scss @@ -1,9 +1,11 @@ code[class*="language-"], pre[class*="language-"] { - font-family: var(--mono-font), Consolas, + font-family: + var(--mono-font), + Consolas, Monaco, - 'Andale Mono', - 'Ubuntu Mono', + "Andale Mono", + "Ubuntu Mono", monospace; font-size: 1em; text-align: left; @@ -12,19 +14,12 @@ pre[class*="language-"] { word-break: normal; word-wrap: normal; line-height: 1.5; - - -moz-tab-size: 4; - -o-tab-size: 4; + font-variant-ligatures: none; tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; hyphens: none; } @media print { - code[class*="language-"], pre[class*="language-"] { text-shadow: none; @@ -36,26 +31,28 @@ pre[class*="language-"] { padding: 1.5rem; margin: 1.5rem 0; overflow: auto; - background-color: var(--color-neutral-50); border-radius: var(--border-radius); - background-color: var(--lightest-background-color); color: var(--color-neutral-900); [data-theme="dark"] & { color: var(--color-neutral-100); } + + &.line-numbers-mode { + padding-left: calc(1.5rem + 2.4em + 1.2rem); + } } -:not(pre)>code[class*="language-"], +:not(pre) > code[class*="language-"], pre[class*="language-"] { background-color: var(--lightest-background-color); } /* Inline code */ -:not(pre)>code[class*="language-"] { - padding: .1em; - border-radius: .3em; +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; white-space: normal; } @@ -63,19 +60,17 @@ pre[class*="language-"] { .token.prolog, .token.doctype, .token.cdata { - color: #6E7F8E; + color: #6e7f8e; [data-theme="dark"] & { - color: #8E9FAE; + color: #8e9fae; } } - .token.namespace { - opacity: .7; + opacity: 0.7; } - .token.selector, .token.attr-name, .token.string, @@ -85,7 +80,6 @@ pre[class*="language-"] { color: var(--link-color); } - .token.atrule, .token.attr-value, .token.keyword { @@ -105,25 +99,24 @@ pre[class*="language-"] { cursor: help; } -pre { - counter-reset: lineNumber; -} - -code .highlight-line { +.line-numbers-wrapper { + position: absolute; + top: 0; + left: 1.5rem; + text-align: right; + padding-top: 1.5rem; + font-size: 1em; + font-family: var(--mono-font), Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + line-height: 1.5; + color: var(--icon-color); font-variant-ligatures: none; -} -code .highlight-line:before { - -webkit-user-select: none; - color: var(--icon-color); - content: counter(lineNumber); - counter-increment: lineNumber; - display: inline-block; - font-variant-numeric: tabular-nums; - margin-right: 1.2em; - padding-right: 1.2em; - margin-inline-end: 1.2em; - padding-inline-end: 1.2em; - text-align: right; - width: 2.4em; + .line-number { + user-select: none; + color: var(--icon-color); + display: inline-block; + font-variant-numeric: tabular-nums; + text-align: right; + width: 1.2em; + } } diff --git a/docs/src/assets/scss/tokens/spacing.scss b/docs/src/assets/scss/tokens/spacing.scss index 173401c043a..2bc542459b5 100644 --- a/docs/src/assets/scss/tokens/spacing.scss +++ b/docs/src/assets/scss/tokens/spacing.scss @@ -6,15 +6,7 @@ --fluid-screen: 100vw; --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); -} - -@media screen and (min-width: 1023px) { - :root { - --fluid-screen: calc(var(--fluid-max-width) * 1px); - } -} -:root { --fc-3xs-min: (var(--fc-s-min) * 0.25); --fc-3xs-max: (var(--fc-s-max) * 0.25); diff --git a/docs/src/assets/scss/tokens/themes.scss b/docs/src/assets/scss/tokens/themes.scss index 11ced7f9ee5..de956b821ad 100644 --- a/docs/src/assets/scss/tokens/themes.scss +++ b/docs/src/assets/scss/tokens/themes.scss @@ -1,65 +1,65 @@ :root { /* Tier 1 variables */ // colors - --color-neutral-25: #FCFCFD; - --color-neutral-50: #F9FAFB; - --color-neutral-100: #F2F4F7; - --color-neutral-200: #E4E7EC; - --color-neutral-300: #D0D5DD; - --color-neutral-400: #98A2B3; + --color-neutral-25: #fcfcfd; + --color-neutral-50: #f9fafb; + --color-neutral-100: #f2f4f7; + --color-neutral-200: #e4e7ec; + --color-neutral-300: #d0d5dd; + --color-neutral-400: #98a2b3; --color-neutral-500: #667085; --color-neutral-600: #475467; --color-neutral-700: #344054; - --color-neutral-800: #1D2939; + --color-neutral-800: #1d2939; --color-neutral-900: #101828; - --color-primary-25: #FBFBFF; - --color-primary-50: #F6F6FE; - --color-primary-100: #ECECFD; - --color-primary-200: #DEDEFF; - --color-primary-300: #CCCCFA; - --color-primary-400: #B7B7FF; - --color-primary-500: #A0A0F5; - --color-primary-600: #8080F2; - --color-primary-700: #6358D4; - --color-primary-800: #4B32C3; - --color-primary-900: #341BAB; - - --color-warning-25: #FFFCF5; - --color-warning-50: #FFFAEB; - --color-warning-100: #FEF0C7; - --color-warning-200: #FEDF89; - --color-warning-300: #FEC84B; - --color-warning-400: #FDB022; - --color-warning-500: #F79009; - --color-warning-600: #DC6803; - --color-warning-700: #B54708; - --color-warning-800: #93370D; - --color-warning-900: #7A2E0E; - - --color-success-25: #F6FEF9; - --color-success-50: #ECFDF3; - --color-success-100: #D1FADF; - --color-success-200: #A6F4C5; - --color-success-300: #6CE9A6; - --color-success-400: #32D583; - --color-success-500: #12B76A; + --color-primary-25: #fbfbff; + --color-primary-50: #f6f6fe; + --color-primary-100: #ececfd; + --color-primary-200: #dedeff; + --color-primary-300: #ccccfa; + --color-primary-400: #b7b7ff; + --color-primary-500: #a0a0f5; + --color-primary-600: #8080f2; + --color-primary-700: #6358d4; + --color-primary-800: #4b32c3; + --color-primary-900: #341bab; + + --color-warning-25: #fffcf5; + --color-warning-50: #fffaeb; + --color-warning-100: #fef0c7; + --color-warning-200: #fedf89; + --color-warning-300: #fec84b; + --color-warning-400: #fdb022; + --color-warning-500: #f79009; + --color-warning-600: #dc6803; + --color-warning-700: #b54708; + --color-warning-800: #93370d; + --color-warning-900: #7a2e0e; + + --color-success-25: #f6fef9; + --color-success-50: #ecfdf3; + --color-success-100: #d1fadf; + --color-success-200: #a6f4c5; + --color-success-300: #6ce9a6; + --color-success-400: #32d583; + --color-success-500: #12b76a; --color-success-600: #039855; - --color-success-700: #027A48; - --color-success-800: #05603A; - --color-success-900: #054F31; - - --color-rose-25: #FFF5F6; - --color-rose-50: #FFF1F3; - --color-rose-100: #FFE4E8; - --color-rose-200: #FECDD6; - --color-rose-300: #FEA3B4; - --color-rose-400: #FD6F8E; - --color-rose-500: #F63D68; - --color-rose-600: #E31B54; - --color-rose-700: #C01048; - --color-rose-800: #A11043; - --color-rose-900: #89123E; + --color-success-700: #027a48; + --color-success-800: #05603a; + --color-success-900: #054f31; + + --color-rose-25: #fff5f6; + --color-rose-50: #fff1f3; + --color-rose-100: #ffe4e8; + --color-rose-200: #fecdd6; + --color-rose-300: #fea3b4; + --color-rose-400: #fd6f8e; + --color-rose-500: #f63d68; + --color-rose-600: #e31b54; + --color-rose-700: #c01048; + --color-rose-800: #a11043; + --color-rose-900: #89123e; /* Tier 2 variables */ --primary-button-background-color: var(--color-primary-800); @@ -79,7 +79,6 @@ --border-color: var(--color-neutral-300); --divider-color: var(--color-neutral-200); - --icon-color: var(--color-neutral-400); --dark-icon-color: var(--color-neutral-500); --link-color: var(--color-primary-800); @@ -97,14 +96,14 @@ --body-background-color: var(--color-neutral-900); --body-text-color: var(--color-neutral-300); --headings-color: #fff; - + --divider-color: var(--color-neutral-600); --border-color: var(--color-neutral-500); - + --icon-color: var(--body-text-color); --dark-icon-color: #fff; --link-color: var(--color-primary-400); - + --lighter-background-color: var(--color-neutral-800); --lightest-background-color: var(--color-neutral-800); --docs-lightest-background-color: var(--color-neutral-800); @@ -114,8 +113,6 @@ } } - - html[data-theme="light"] { --body-background-color: #fff; --body-text-color: var(--color-neutral-500); @@ -124,7 +121,6 @@ html[data-theme="light"] { --border-color: var(--color-neutral-300); --divider-color: var(--color-neutral-200); - --icon-color: var(--color-neutral-400); --dark-icon-color: var(--color-neutral-500); --link-color: var(--color-primary-800); @@ -135,6 +131,7 @@ html[data-theme="light"] { --hero-background-color: var(--color-neutral-25); --footer-background-color: var(--color-neutral-25); --outline-color: var(--color-brand); + --img-background-color: #fff; } html[data-theme="dark"] { @@ -155,4 +152,5 @@ html[data-theme="dark"] { --hero-background-color: var(--color-neutral-800); --footer-background-color: var(--color-neutral-800); --outline-color: #fff; + --img-background-color: var(--color-neutral-300); } diff --git a/docs/src/assets/scss/tokens/typography.scss b/docs/src/assets/scss/tokens/typography.scss index e9c24ef7978..a9e935b2a01 100644 --- a/docs/src/assets/scss/tokens/typography.scss +++ b/docs/src/assets/scss/tokens/typography.scss @@ -1,13 +1,5 @@ /* @link https://utopia.fyi/type/calculator?c=320,16,1.125,1280,16,1.25,6,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l */ -:root { - --fluid-min-width: 320; - --fluid-max-width: 1280; - - --fluid-screen: 100vw; - --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); -} - @media screen and (min-width: 1280px) { :root { --fluid-screen: calc(var(--fluid-max-width) * 1px); @@ -15,6 +7,12 @@ } :root { + --fluid-min-width: 320; + --fluid-max-width: 1280; + + --fluid-screen: 100vw; + --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); + --f--2-min: 12.64; --f--2-max: 10.24; --step--2: calc(((var(--f--2-min) / 16) * 1rem) + (var(--f--2-max) - var(--f--2-min)) * var(--fluid-bp)); @@ -50,30 +48,31 @@ --f-6-min: 32.44; --f-6-max: 61.04; --step-6: calc(((var(--f-6-min) / 16) * 1rem) + (var(--f-6-max) - var(--f-6-min)) * var(--fluid-bp)); -} -:root { - --mono-font: "Space Mono", monospace; - --text-font: "Inter", - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - Helvetica, - Arial, - sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol"; - --display-font: "Space Grotesk", - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - Helvetica, - Arial, - sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol"; + --mono-font: "Mono Punctuators", "Space Mono", monospace; + --text-font: + "Inter", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Helvetica, + Arial, + sans-serif, + "Apple Color Emoji", + "Twemoji Country Flags", + "Segoe UI Emoji", + "Segoe UI Symbol"; + --display-font: + "Space Grotesk", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Helvetica, + Arial, + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol"; } diff --git a/docs/src/assets/scss/tokens/ui.scss b/docs/src/assets/scss/tokens/ui.scss index 08759dba396..49380e12da8 100644 --- a/docs/src/assets/scss/tokens/ui.scss +++ b/docs/src/assets/scss/tokens/ui.scss @@ -1,8 +1,9 @@ :root { // elevations - --shadow-lg: 0px 12px 16px -4px rgba(16, 24, 40, 0.1), - 0px 4px 6px -2px rgba(16, 24, 40, 0.05); - --shadow-xs: 0px 1px 2px rgba(16, 24, 40, 0.05); + --shadow-lg: + 0 12px 16px -4px rgba(16, 24, 40, 0.1), + 0 4px 6px -2px rgba(16, 24, 40, 0.05); + --shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.05); --border-radius: .5rem; } diff --git a/docs/src/assets/scss/utilities.scss b/docs/src/assets/scss/utilities.scss index ede62ca53a9..c296358837e 100644 --- a/docs/src/assets/scss/utilities.scss +++ b/docs/src/assets/scss/utilities.scss @@ -1,5 +1,5 @@ .grid { - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: grid; grid-template-columns: repeat(12, 1fr); grid-gap: 2rem; @@ -11,8 +11,7 @@ clip: rect(0 0 0 0); clip-path: inset(100%); height: 1px; - overflow: - hidden; + overflow: hidden; position: absolute; width: 1px; white-space: nowrap; @@ -23,7 +22,7 @@ } .mobile-only { - @media all and (min-width: 1023px) { + @media all and (min-width: 1024px) { display: none; } } @@ -35,7 +34,6 @@ } .text.text { - font-size: inherit; color: inherit; font: inherit; font-family: var(--text-font); @@ -120,7 +118,6 @@ grid-column: 1 / 8; } - .span-1-12 { grid-column: 1 / -1; } diff --git a/docs/src/assets/scss/versions.scss b/docs/src/assets/scss/versions.scss index 2a5ba4fe055..f0979c64f4e 100644 --- a/docs/src/assets/scss/versions.scss +++ b/docs/src/assets/scss/versions.scss @@ -14,10 +14,10 @@ a { color: var(--link-color); - display: block; width: 100%; padding: 1rem .5rem; text-decoration: none; + display: block; display: flex; align-items: center; border-bottom: 1px solid var(--divider-color); @@ -28,7 +28,10 @@ color: var(--link-color); &::after { - content: "✔️"; + content: " ✔️"; + white-space: pre; + color: rgba(100%, 0%, 0%, 0); + text-shadow: 0 0 0 var(--headings-color); } } diff --git a/docs/src/developer-guide/architecture/index.md b/docs/src/contribute/architecture/index.md similarity index 95% rename from docs/src/developer-guide/architecture/index.md rename to docs/src/contribute/architecture/index.md index 1c580e469a2..3370e6df448 100644 --- a/docs/src/developer-guide/architecture/index.md +++ b/docs/src/contribute/architecture/index.md @@ -1,15 +1,15 @@ --- title: Architecture -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/architecture/index.md eleventyNavigation: key: architecture - parent: developer guide + parent: contribute to eslint title: Architecture - order: 1 + order: 5 --- -
    dependency graph
    +:::img-container +dependency graph +::: At a high level, there are a few key parts to ESLint: diff --git a/docs/src/developer-guide/code-conventions.md b/docs/src/contribute/code-conventions.md similarity index 66% rename from docs/src/developer-guide/code-conventions.md rename to docs/src/contribute/code-conventions.md index 9b639c36cb9..089e1247abf 100644 --- a/docs/src/developer-guide/code-conventions.md +++ b/docs/src/contribute/code-conventions.md @@ -1,8 +1,5 @@ --- title: Code Conventions -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/code-conventions.md - --- Code conventions for ESLint are determined by @@ -10,5 +7,8 @@ Code conventions for ESLint are determined by The rationales for the specific rules in use can be found by looking to the project documentation for any given rule. If the rule is one of our own, see -our own [rule documentation](https://eslint.org/docs/rules/) and otherwise, see +our own [rule documentation](../rules/) and otherwise, see the documentation of the plugin in which the rule can be found. + +If you need to make changes to a `package.json` file, please see the +[package.json conventions](./package-json-conventions). diff --git a/docs/src/contribute/code-of-conduct.md b/docs/src/contribute/code-of-conduct.md new file mode 100644 index 00000000000..3d82299c7e7 --- /dev/null +++ b/docs/src/contribute/code-of-conduct.md @@ -0,0 +1,10 @@ +--- +title: Code of Conduct +eleventyNavigation: + key: code of conduct + parent: contribute to eslint + title: Code of Conduct + order: 0 +--- + +ESLint welcomes contributions from everyone and adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). We kindly request that you read over this code of conduct before contributing. diff --git a/docs/src/contribute/core-rules.md b/docs/src/contribute/core-rules.md new file mode 100644 index 00000000000..5610c000e84 --- /dev/null +++ b/docs/src/contribute/core-rules.md @@ -0,0 +1,110 @@ +--- +title: Contribute to Core Rules +eleventyNavigation: + key: contribute core rule + parent: contribute to eslint + title: Contribute to Core Rules + order: 10 +--- + +The ESLint core rules are the rules included in the ESLint package. + +## Rule Writing Documentation + +For full reference information on writing rules, refer to [Custom Rules](../extend/custom-rules). Both custom rules and core rules have the same API. The primary difference between core and custom rules are: + +1. Core rules are included in the `eslint` package. +1. Core rules must adhere to the conventions documented on this page. + +## File Structure + +Each core 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/src/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 the conventions explained below. + +Here is the basic format of the source file for a rule: + +```js +/** + * @fileoverview Rule to disallow unnecessary semicolons + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary semicolons", + recommended: true, + url: "https://eslint.org/docs/rules/no-extra-semi" + }, + fixable: "code", + schema: [] // no options + }, + create: function(context) { + return { + // callback functions + }; + } +}; +``` + +## Rule Unit Tests + +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`. + +ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility to make it easy to write tests for rules. + +## Performance Testing + +To keep the linting process efficient and unobtrusive, it is useful to verify the performance impact of new rules or modifications to existing rules. + +To learn how to profile the performance of individual rules, refer to [Profile Rule Performance](../extend/custom-rules#profile-rule-performance) in the custom rules documentation. + +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 main +Switched to branch 'main' + +$ npm run perf +CPU Speed is 2200 with multiplier 7500000 +Performance Run #1: 1394.689313ms +Performance Run #2: 1423.295351ms +Performance Run #3: 1385.09515ms +Performance Run #4: 1382.406982ms +Performance Run #5: 1409.68566ms +Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms) + +$ git checkout my-rule-branch +Switched to branch 'my-rule-branch' + +$ npm run perf +CPU Speed is 2200 with multiplier 7500000 +Performance Run #1: 1443.736547ms +Performance Run #2: 1419.193291ms +Performance Run #3: 1436.018228ms +Performance Run #4: 1473.605485ms +Performance Run #5: 1457.455283ms +Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms) +``` + +## Rule Naming Conventions + +The rule naming conventions for ESLint are as follows: + +* Use dashes between words. +* If your rule only disallows 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. diff --git a/docs/src/developer-guide/development-environment.md b/docs/src/contribute/development-environment.md similarity index 70% rename from docs/src/developer-guide/development-environment.md rename to docs/src/contribute/development-environment.md index 75c0aaf5c3c..b84af4e3274 100644 --- a/docs/src/developer-guide/development-environment.md +++ b/docs/src/contribute/development-environment.md @@ -1,13 +1,10 @@ --- -title: Development Environment -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/development-environment.md +title: Set up a Development Environment eleventyNavigation: - key: set up a development environment - parent: developer guide - title: Set Up a Development Environment - order: 2 - + key: development environment + parent: contribute to eslint + title: Set up a Development Environment + order: 6 --- ESLint has a very lightweight development environment that makes updating code fast and easy. This is a step-by-step guide to setting up a local development environment that will let you contribute back to the project. @@ -18,10 +15,16 @@ Go to to download and install the latest stable version fo Most of the installers already come with [npm](https://www.npmjs.com/) but if for some reason npm 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 +## Step 2: Fork and Checkout Your Own ESLint Repository Go to and click the "Fork" button. Follow the [GitHub documentation](https://help.github.com/articles/fork-a-repo) for forking and cloning. +Clone your fork: + +```shell +git clone https://github.com//eslint +``` + Once you've cloned the repository, run `npm install` to get all the necessary dependencies: ```shell @@ -31,7 +34,9 @@ npm install You must be connected to the Internet for this step to work. You'll see a lot of utilities being downloaded. -## Step 3: Add the upstream source +**Note:** It's a good idea to re-run `npm install` whenever you pull from the main repository to ensure you have the latest development dependencies. + +## Step 3: Add the Upstream Source The *upstream source* is the main ESLint repository where active development happens. While you won't have push access to upstream, you will have pull access, allowing you to pull in the latest code whenever you want. @@ -45,7 +50,7 @@ Now, the remote `upstream` points to the upstream source. ## Step 4: Install the Yeoman Generator -[Yeoman](http://yeoman.io) is a scaffold generator that ESLint uses to help streamline development of new rules. If you don't already have Yeoman installed, you can install it via npm: +[Yeoman](https://yeoman.io) is a scaffold generator that ESLint uses to help streamline development of new rules. If you don't already have Yeoman installed, you can install it via npm: ```shell npm install -g yo @@ -59,7 +64,7 @@ npm install -g generator-eslint Please see the [generator documentation](https://github.com/eslint/generator-eslint) for instructions on how to use it. -## Step 5: Run the tests +## Step 5: Run the Tests Running the tests is the best way to ensure you have correctly set up your development environment. Make sure you're in the `eslint` directory and run: @@ -71,9 +76,24 @@ The testing takes a few minutes to complete. If any tests fail, that likely mean ## Reference Information +### Directory Structure + +The ESLint directory and file structure is as follows: + +* `bin` - executable files that are available when ESLint is installed +* `conf` - default configuration information +* `docs` - documentation for the project +* `lib` - contains the source code + * `formatters` - all source files defining formatters + * `rules` - all source files defining rules +* `tests` - the main unit test folder + * `lib` - tests for the source code + * `formatters` - tests for the formatters + * `rules` - tests for the rules + ### Workflow -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). +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](./pull-requests). ### Build Scripts diff --git a/docs/src/maintainer-guide/governance.md b/docs/src/contribute/governance.md similarity index 95% rename from docs/src/maintainer-guide/governance.md rename to docs/src/contribute/governance.md index da3548ba787..563840a01dc 100644 --- a/docs/src/maintainer-guide/governance.md +++ b/docs/src/contribute/governance.md @@ -1,12 +1,10 @@ --- title: Governance -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/maintainer-guide/governance.md eleventyNavigation: key: governance - parent: maintainer guide + parent: contribute to eslint title: Governance - order: 4 + order: 11 --- @@ -30,7 +28,7 @@ As Contributors gain experience and familiarity with the project, their profile ### Website Team Member -Website Team Members are community members who have shown that they are committed to the continued maintenance of [eslint.org](https://eslint.org/) through ongoing engagement with the community. Website Team Members are given push access to the `eslint.org` GitHub repository and must abide by the project's [Contribution Guidelines](../developer-guide/contributing/). +Website Team Members are community members who have shown that they are committed to the continued maintenance of [eslint.org](https://eslint.org/) through ongoing engagement with the community. Website Team Members are given push access to the `eslint.org` GitHub repository and must abide by the project's [Contribution Guidelines](../contribute/). Website Team Members: @@ -38,8 +36,8 @@ Website Team Members are community members who have shown that they are committe * Are expected to delete their public branches when they are no longer necessary. * Must submit pull requests for all changes. * Have their work reviewed by Reviewers and TSC members before acceptance into the repository. -* May label and close website-related issues (see [Managing Issues](issues.html)) -* May merge some pull requests (see [Managing Pull Requests](pullrequests.html)) +* May label and close website-related issues (see [Manage Issues](../maintain/manage-issues)) +* May merge some pull requests (see [Review Pull Requests](../maintain/review-pull-requests)) To become a Website Team Member: @@ -53,7 +51,7 @@ It is important to recognize that membership on the website team is a privilege, ### Committers -Committers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Committers are given push access to the project's GitHub repos and must abide by the project's [Contribution Guidelines](../developer-guide/contributing/). +Committers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Committers are given push access to the project's GitHub repos and must abide by the project's [Contribution Guidelines](../contribute/). Committers: @@ -61,8 +59,8 @@ Committers: * Are expected to delete their public branches when they are no longer necessary. * Must submit pull requests for all changes. * Have their work reviewed by TSC members before acceptance into the repository. -* May label and close issues (see [Managing Issues](issues.html)) -* May merge some pull requests (see [Managing Pull Requests](pullrequests.html)) +* May label and close issues (see [Manage Issues](../maintain/manage-issues)) +* May merge some pull requests (see [Review Pull Requests](../maintain/review-pull-requests)) To become a Committer: @@ -145,7 +143,7 @@ A Reviewer is invited to become a TSC member by existing TSC members. A nominati 1. Add the GitHub user to the "ESLint TSC" GitHub team 1. Set the GitHub user to be have the "Owner" role for the ESLint organization -1. Send a welcome email with a link to the [maintainer guide](./) and instructions for npm 2FA. +1. Send a welcome email with a link to the [Maintain ESLint documentation](../maintain/) and instructions for npm 2FA. 1. Invite to the Discord TSC channel 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 diff --git a/docs/src/developer-guide/contributing/index.md b/docs/src/contribute/index.md similarity index 61% rename from docs/src/developer-guide/contributing/index.md rename to docs/src/contribute/index.md index fdf9e2818a4..3a20390db3d 100644 --- a/docs/src/developer-guide/contributing/index.md +++ b/docs/src/contribute/index.md @@ -1,13 +1,9 @@ --- -title: Contributing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/contributing/index.md +title: Contribute to ESLint eleventyNavigation: - key: contributing - parent: developer guide - title: Contributing - order: 10 - + key: contribute to eslint + title: Contribute to ESLint + order: 3 --- One of the great things about open source projects is that anyone can contribute in any number of meaningful ways. ESLint couldn't exist without the help of the many contributors it's had since the project began, and we want you to feel like you can contribute and make a difference as well. @@ -18,34 +14,54 @@ This guide is intended for anyone who wants to contribute to an ESLint project. ESLint welcomes contributions from everyone and adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). We kindly request that you read over our code of conduct before contributing. -## [Bug Reporting](reporting-bugs) +## [Report Bugs](report-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) +## [Propose a New Rule](propose-new-rule) 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) +## [Propose a Rule Change](propose-rule-change) Want to make a change to an existing rule? This section explains the process and how we evaluate such proposals. -## Requesting a [Change](changes) +## [Request a Change](request-change) If you'd like to request a change other than a bug fix or new rule, this section explains that process. -## Reporting a security vulnerability +## [Architecture](architecture) + +Learn about the architecture of the ESLint project. + +## [Set up a Development Environment](development-environment) -To report a security vulnerability in ESLint, please use our [HackerOne program](https://hackerone.com/eslint). +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. -## [Working on Issues](working-on-issues) +## [Run the Tests](tests) + +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. + +## [Work on Issues](work-on-issue) Have some extra time and want to contribute? This section talks about the process of working on issues. -## Submitting a [Pull Request](pull-requests) +## [Submit a Pull Request](pull-requests) We're always looking for contributions from the community. This section explains the requirements for pull requests and the process of contributing code. -## Signing the CLA +## [Contribute to Core Rules](core-rules) + +This section explains how to add to the core rules of ESLint. + +## [Governance](governance) + +Describes the governance policy for ESLint, including the rights and privileges of individuals inside the project. + +## [Report a Security Vulnerability](report-security-vulnerability) + +To report a security vulnerability in ESLint, please create an advisory on Github. + +## Sign the CLA In order to submit code or documentation to an ESLint project, you will need to electronically sign our Contributor License Agreement. The CLA is the commonly used Apache-style template, and is you giving us permission to use your contribution. You only need to sign the CLA once for any OpenJS Foundation projects that use EasyCLA. You will be asked to sign the CLA in the first pull request you open. diff --git a/docs/src/contribute/package-json-conventions.md b/docs/src/contribute/package-json-conventions.md new file mode 100644 index 00000000000..99afe8b2222 --- /dev/null +++ b/docs/src/contribute/package-json-conventions.md @@ -0,0 +1,86 @@ +--- +title: Package.json Conventions +edit_link: https://github.com/eslint/eslint/edit/main/docs/src/contribute/package-json-conventions.md +--- + +The following applies to the "scripts" section of `package.json` files. + +## Names + +npm script names MUST contain only lower case letters, `:` to separate parts, `-` to separate words, and `+` to separate file extensions. Each part name SHOULD be either a full English word (e.g. `coverage` not `cov`) or a well-known initialism in all lowercase (e.g. `wasm`). + +Here is a summary of the proposal in ABNF. + +```abnf +name = life-cycle / main target? option* ":watch"? +life-cycle = "prepare" / "preinstall" / "install" / "postinstall" / "prepublish" / "preprepare" / "prepare" / "postprepare" / "prepack" / "postpack" / "prepublishOnly" +main = "build" / "lint" ":fix"? / "release" / "start" / "test" +target = ":" word ("-" word)* / extension ("+" extension)* +option = ":" word ("-" word)* +word = ALPHA + +extension = ( ALPHA / DIGIT )+ +``` + +## Order + +The script names MUST appear in the package.json file in alphabetical order. The other conventions outlined in this document ensure that alphabetical order will coincide with logical groupings. + +## Main Script Names + +With the exception of [npm life cycle scripts](https://docs.npmjs.com/cli/v8/using-npm/scripts#life-cycle-scripts) all script names MUST begin with one of the following names. + +### Build + +Scripts that generate a set of files from source code and / or data MUST have names that begin with `build`. + +If a package contains any `build:*` scripts, there MAY be a script named `build`. If so, SHOULD produce the same output as running each of the `build` scripts individually. It MUST produce a subset of the output from running those scripts. + +### Release + +Scripts that have public side effects (publishing the web site, committing to Git, etc.) MUST begin with `release`. + +### Lint + +Scripts that statically analyze files (mostly, but not limited to running `eslint` itself) MUST have names that begin with `lint`. + +If a package contains any `lint:*` scripts, there SHOULD be a script named `lint` and it MUST run all of the checks that would have been run if each `lint:*` script was called individually. + +If fixing is available, a linter MUST NOT apply fixes UNLESS the script contains the `:fix` modifier (see below). + +### Start + +A `start` script is used to start a server. As of this writing, no ESLint package has more than one `start` script, so there's no need `start` to have any modifiers. + +### Test + +Scripts that execute code in order to ensure the actual behavior matches expected behavior MUST have names that begin with `test`. + +If a package contains any `test:*` scripts, there SHOULD be a script named `test` and it MUST run of all of the tests that would have been run if each `test:*` script was called individually. + +A test script SHOULD NOT include linting. + +A test script SHOULD report test coverage when possible. + +## Modifiers + +One or more of the following modifiers MAY be appended to the standard script names above. If a target has modifiers, they MUST be in the order in which they appear below (e.g. `lint:fix:js:watch` not `lint:watch:js:fix`) + +### Fix + +If it's possible for a linter to fix problems that it finds, add a copy of the script with `:fix` appended to the end that also fixes. + +### Target + +The name of the target of the action being run. In the case of a `build` script, it SHOULD identify the build artifact(s), e.g. "javascript" or "css" or "website". In the case of a `lint` or `test` script, it SHOULD identify the item(s) being linted or tested. In the case of a `start` script, it SHOULD identify which server is starting. + +A target MAY refer to a list of affected file extensions (such as `cjs` or `less`) delimited by a `+`. If there is more than one extension, the list SHOULD be alphabetized. When a file extension has variants (such as `cjs` for CommonJS and `mjs` for ESM), the common part of the extension MAY be used instead of explicitly listing out all of the variants (e.g. `js` instead of `cjs+jsx+mjs`). + +The target SHOULD NOT refer to name of the name of the tool that's performing the action (`eleventy`, `webpack`, etc.) + +### Options + +Additional options that don't fit under the other modifiers. + +### Watch + +If a script watches the filesystem and responds to changes, add `:watch` to the script name. diff --git a/docs/src/developer-guide/contributing/new-rules.md b/docs/src/contribute/propose-new-rule.md similarity index 88% rename from docs/src/developer-guide/contributing/new-rules.md rename to docs/src/contribute/propose-new-rule.md index 9288618c985..7aa9d3a0e7c 100644 --- a/docs/src/developer-guide/contributing/new-rules.md +++ b/docs/src/contribute/propose-new-rule.md @@ -1,8 +1,10 @@ --- -title: New Rules -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/contributing/new-rules.md - +title: Propose a New Rule +eleventyNavigation: + key: propose rule + parent: contribute to eslint + title: Propose a New Rule + order: 2 --- ESLint is all about rules. For most of the project's lifetime, we've had over 200 rules, and that list continues to grow. However, we can't just accept any proposed rule because all rules need to work cohesively together. As such, we have some guidelines around which rules can be part of the ESLint core and which are better off as custom rules and plugins. @@ -24,7 +26,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, please see how to [create a pull request](/docs/developer-guide/contributing/pull-requests) or submit an issue by filling out a [new rule template](https://github.com/eslint/eslint/issues/new/choose). +If you want to propose a new rule, please see how to [create a pull request](pull-requests) or submit an issue by filling out a [new rule template](https://github.com/eslint/eslint/issues/new/choose). We need all of this information in order to determine whether or not the rule is a good core rule candidate. @@ -44,4 +46,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 [Custom Rules](../extend/custom-rules) and [Create Plugins](../extend/plugins) documentation for more information. diff --git a/docs/src/developer-guide/contributing/rule-changes.md b/docs/src/contribute/propose-rule-change.md similarity index 75% rename from docs/src/developer-guide/contributing/rule-changes.md rename to docs/src/contribute/propose-rule-change.md index d6c07562e81..d2d198b19bd 100644 --- a/docs/src/developer-guide/contributing/rule-changes.md +++ b/docs/src/contribute/propose-rule-change.md @@ -1,15 +1,17 @@ --- -title: Rule Changes -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/contributing/rule-changes.md - +title: Propose a Rule Change +eleventyNavigation: + key: propose rule change + parent: contribute to eslint + title: Propose a Rule Change + order: 3 --- Occasionally, a core ESLint rule needs to be changed. This is not necessarily a bug, but rather, an enhancement that makes a rule more configurable. In those situations, we will consider making changes to rules. ## Proposing a Rule Change -To propose a change to an existing rule, [create a pull request](/docs/developer-guide/contributing/pull-requests) or [new issue](https://github.com/eslint/eslint/issues/new/choose) and fill out the template. +To propose a change to an existing rule, [create a pull request](pull-requests) or [new issue](https://github.com/eslint/eslint/issues/new/choose) 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. @@ -17,7 +19,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](propose-new-rule#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/src/developer-guide/contributing/pull-requests.md b/docs/src/contribute/pull-requests.md similarity index 90% rename from docs/src/developer-guide/contributing/pull-requests.md rename to docs/src/contribute/pull-requests.md index e320f32e545..8854ee2fbb2 100644 --- a/docs/src/developer-guide/contributing/pull-requests.md +++ b/docs/src/contribute/pull-requests.md @@ -1,8 +1,10 @@ --- -title: Pull Requests -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/contributing/pull-requests.md - +title: Submit a Pull Request +eleventyNavigation: + key: submit pull request + parent: contribute to eslint + title: Submit a Pull Request + order: 9 --- If you want to contribute to an ESLint repo, please use a GitHub pull request. This is the fastest way for us to evaluate your code and to merge it into the code base. Please don't file an issue with snippets of code. Doing so means that we need to manually merge the changes in and update any appropriate tests. That decreases the likelihood that your code is going to get included in a timely manner. Please use pull requests. @@ -11,8 +13,8 @@ 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. 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. 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](./work-on-issue). Bug fixes, documentation changes, and other pull requests do not require an issue. After that, you're ready to start working on code. @@ -44,14 +46,14 @@ 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) as you go. Once you have finished, commit the changes to your branch: ```shell git add -A git commit ``` -All ESLint projects follow [Conventional Commits](https://www.conventionalcommits.org/) for our commit messages. Here's an example commit message: +All ESLint projects follow [Conventional Commits](https://www.conventionalcommits.org/) for our commit messages. (Note: we don’t support the optional scope in messages.) Here's an example commit message: ```txt tag: Short description of what you did @@ -77,7 +79,7 @@ The `tag` is one of the following: * `ci` - changes to our CI configuration files and scripts. * `perf` - a code change that improves performance. -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](work-on-issue#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 in the body of the commit message in the format `Fixes #1234`. If the commit doesn't completely fix the issue, then use `Refs #1234` instead of `Fixes #1234`. @@ -121,7 +123,7 @@ With your code ready to go, this is a good time to double-check your submission * Make separate pull requests for unrelated changes. Large pull requests with multiple unrelated changes may be closed without merging. * All changes must be accompanied by tests, even if the feature you're working on previously had no tests. * All user-facing changes must be accompanied by appropriate documentation. -* Follow the [Code Conventions](../code-conventions). +* Follow the [Code Conventions](./code-conventions). ### Step 6: Push your changes @@ -181,7 +183,7 @@ The commit messages in subsequent commits do not need to be in any specific form ### 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) and then you can rebase using these commands: ```shell git fetch upstream diff --git a/docs/src/developer-guide/contributing/reporting-bugs.md b/docs/src/contribute/report-bugs.md similarity index 59% rename from docs/src/developer-guide/contributing/reporting-bugs.md rename to docs/src/contribute/report-bugs.md index 8d713c46004..c1a14b1afd4 100644 --- a/docs/src/developer-guide/contributing/reporting-bugs.md +++ b/docs/src/contribute/report-bugs.md @@ -1,12 +1,14 @@ --- -title: Reporting Bugs -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/contributing/reporting-bugs.md - +title: Report Bugs +eleventyNavigation: + key: report bugs + parent: contribute to eslint + title: Report Bugs + order: 1 --- -If you think you've found a bug in ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new/choose) or a [pull request](/docs/developer-guide/contributing/pull-requests) on GitHub. +If you think you've found a bug in ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new/choose) or a [pull request](pull-requests) 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. -**Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please use the [mailing list](https://groups.google.com/group/eslint) or [chat](https://eslint.org/chat) instead of filing an issue. +**Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat) instead of filing an issue. diff --git a/docs/src/contribute/report-security-vulnerability.md b/docs/src/contribute/report-security-vulnerability.md new file mode 100644 index 00000000000..f68319fd34e --- /dev/null +++ b/docs/src/contribute/report-security-vulnerability.md @@ -0,0 +1,10 @@ +--- +title: Report a Security Vulnerability +eleventyNavigation: + key: report security vulnerability + parent: contribute to eslint + title: Report a Security Vulnerability + order: 11 +--- + +To report a security vulnerability in ESLint, please use our [create an advisory form](https://github.com/eslint/eslint/security/advisories/new) on GitHub. diff --git a/docs/src/developer-guide/contributing/changes.md b/docs/src/contribute/request-change.md similarity index 67% rename from docs/src/developer-guide/contributing/changes.md rename to docs/src/contribute/request-change.md index b6059676e1d..4e8df682376 100644 --- a/docs/src/developer-guide/contributing/changes.md +++ b/docs/src/contribute/request-change.md @@ -1,15 +1,17 @@ --- -title: Change Requests -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/contributing/changes.md - +title: Request a Change +eleventyNavigation: + key: request change + parent: contribute to eslint + title: Request a Change + order: 4 --- If you'd like to request a change to ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new/choose) on GitHub. Be sure to include the following information: 1. The version of ESLint you are using. -1. The problem you want to solve. -1. Your take on the correct solution to problem. +2. The problem you want to solve. +3. Your take on the correct solution to problem. If you're requesting a change to a rule, it's helpful to include this information as well: @@ -19,4 +21,4 @@ If you're requesting a change to a rule, it's helpful to include this informatio 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. -**Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please use the [mailing list](https://groups.google.com/group/eslint) or [chat](https://eslint.org/chat) instead of filing an issue. +**Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat) instead of filing an issue. diff --git a/docs/src/developer-guide/unit-tests.md b/docs/src/contribute/tests.md similarity index 91% rename from docs/src/developer-guide/unit-tests.md rename to docs/src/contribute/tests.md index a8ff6ecb5e3..9ad25500d82 100644 --- a/docs/src/developer-guide/unit-tests.md +++ b/docs/src/contribute/tests.md @@ -1,13 +1,10 @@ --- -title: Unit Tests -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/unit-tests.md +title: Run the Tests eleventyNavigation: - key: run the tests - parent: developer guide + key: run tests + parent: contribute to eslint title: Run the Tests - order: 3 - + order: 7 --- 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. diff --git a/docs/src/developer-guide/contributing/working-on-issues.md b/docs/src/contribute/work-on-issue.md similarity index 83% rename from docs/src/developer-guide/contributing/working-on-issues.md rename to docs/src/contribute/work-on-issue.md index 0065db77c0c..6205a37fd7e 100644 --- a/docs/src/developer-guide/contributing/working-on-issues.md +++ b/docs/src/contribute/work-on-issue.md @@ -1,18 +1,20 @@ --- -title: Working on Issues -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/contributing/working-on-issues.md - +title: Work on Issues +eleventyNavigation: + key: work on issues + parent: contribute to eslint + title: Work on Issues + order: 8 --- Our public [issues tracker](https://github.com/eslint/eslint/issues) lists all of the things we plan on doing as well as suggestions from the community. Before starting to work on an issue, be sure you read through the rest of this page. ## Issue Labels -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: +We use labels to indicate the status of issues. The most complete documentation on the labels is found in the [Maintain ESLint documentation](../maintain/manage-issues#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. 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 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 [Maintain ESLint](../maintain/manage-issues#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: 1. **Bugs** - problems with the project are actively affecting users. We want to get these resolved as quickly as possible. diff --git a/docs/src/developer-guide/index.md b/docs/src/developer-guide/index.md deleted file mode 100644 index 0b8f2e1cb1f..00000000000 --- a/docs/src/developer-guide/index.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Developer Guide -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/index.md -eleventyNavigation: - key: developer guide - title: Developer Guide - order: 2 - ---- - -This guide is intended for those who wish to: - -* Contribute code to ESLint -* Create their own rules for ESLint - -In order to work with ESLint as a developer, it's recommended that: - -* You know JavaScript, since ESLint is written in JavaScript. -* You have some familiarity with Node.js, since ESLint runs on it. -* You're comfortable with command-line programs. -* You understand unit tests and why they're important. - -If that sounds like you, then continue reading to get started. - -## Section 1: Get the [Source Code](source-code) - -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) - -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) - -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) - -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) - -You've developed library-specific rules for ESLint and you want to share them with the community. You can publish an ESLint plugin on npm. - -## Section 6: [Working with Custom Parsers](working-with-custom-parsers) - -If you aren't going to use the default parser of ESLint, this section explains about using custom parsers. - -## Section 7: [Node.js API](nodejs-api) - -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. - -## Section 8: [Contributing](contributing/) - -Once you've made changes that you want to share with the community, the next step is to submit those changes back via a pull request. diff --git a/docs/src/developer-guide/shareable-configs.md b/docs/src/developer-guide/shareable-configs.md deleted file mode 100644 index 0378a6e6152..00000000000 --- a/docs/src/developer-guide/shareable-configs.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -title: Shareable Configs -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/shareable-configs.md -eleventyNavigation: - key: shareable configs - parent: developer guide - title: Shareable Configs - order: 8 - ---- - -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`. - -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 = { - - globals: { - MyGlobal: true - }, - - rules: { - semi: [2, "always"] - } - -}; -``` - -Since `index.js` is just JavaScript, you can optionally read these settings from a file or generate them dynamically. - -## Publishing a Shareable Config - -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: - -```json -{ - "peerDependencies": { - "eslint": ">= 3" - } -} -``` - -If your shareable config depends on a plugin, you should also specify it as a `peerDependency` (plugins will be loaded relative to the end user's project, so the end user is required to install the plugins they need). However, if your shareable config depends on a third-party parser or another shareable config, you can specify these packages as `dependencies`. - -You can also test your shareable config on your computer before publishing by linking your module globally. Type: - -```bash -npm link -``` - -Then, in your project that wants to use your shareable config, type: - -```bash -npm link eslint-config-myconfig -``` - -Be sure to replace `eslint-config-myconfig` with the actual name of your module. - -## Using a Shareable Config - -Shareable configs are designed to work with the `extends` feature of `.eslintrc` files. Instead of using a file path for the value of `extends`, use your module name. For example: - -```json -{ - "extends": "eslint-config-myconfig" -} -``` - -You can also omit the `eslint-config-` and it will be automatically assumed by ESLint: - -```json -{ - "extends": "myconfig" -} -``` - -### 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/myconfig`, 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 - -It's possible to share multiple configs in the same npm package. You can specify a default config for the package by following the directions in the first section. You can specify additional configs by simply adding a new file to your npm package and then referencing it from your ESLint config. - -As an example, you can create a file called `my-special-config.js` in the root of your npm package and export a config, such as: - -```js -module.exports = { - rules: { - quotes: [2, "double"] - } -}; -``` - -Then, assuming you're using the package name `eslint-config-myconfig`, you can access the additional config via: - -```json -{ - "extends": "myconfig/my-special-config" -} -``` - -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. - -## Local Config File Resolution - -If you need to make multiple configs that can extend from each other and live in different directories, you can create a single shareable config that handles this scenario. - -As an example, let's assume you're using the package name `eslint-config-myconfig` and your package looks something like this: - -```text -myconfig -├── index.js -└─┬ lib - ├── defaults.js - ├── dev.js - ├── ci.js - └─┬ ci - ├── frontend.js - ├── backend.js - └── common.js -``` - -In your `index.js` you can do something like this: - -```js -module.exports = require('./lib/ci.js'); -``` - -Now inside your package you have `/lib/defaults.js`, which contains: - -```js -module.exports = { - rules: { - 'no-console': 1 - } -}; -``` - -Inside your `/lib/ci.js` you have - -```js -module.exports = require('./ci/backend'); -``` - -Inside your `/lib/ci/common.js` - -```js -module.exports = { - rules: { - 'no-alert': 2 - }, - extends: 'myconfig/lib/defaults' -}; -``` - -Despite being in an entirely different directory, you'll see that all `extends` must use the full package path to the config file you wish to extend. - -Now inside your `/lib/ci/backend.js` - -```js -module.exports = { - rules: { - 'no-console': 1 - }, - extends: 'myconfig/lib/ci/common' -}; -``` - -In the last file, you'll once again see that to properly resolve your config, you'll need include the full package path. - -## Further Reading - -* [npm Developer Guide](https://docs.npmjs.com/misc/developers) diff --git a/docs/src/developer-guide/source-code.md b/docs/src/developer-guide/source-code.md deleted file mode 100644 index 558434e15a8..00000000000 --- a/docs/src/developer-guide/source-code.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: Source Code -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/source-code.md -eleventyNavigation: - key: getting the source code - parent: developer guide - title: Getting the Source Code - order: 1 - ---- - -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: - -```shell -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 [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](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: - -```shell -cd eslint -npm install -``` - -Now when you run `eslint`, it will be running your local copy and showing your changes. - -**Note:** It's a good idea to re-run `npm install` whenever you pull from the main repository to ensure you have the latest development dependencies. - -## Directory structure - -The ESLint directory and file structure is as follows: - -* `bin` - executable files that are available when ESLint is installed -* `conf` - default configuration information -* `docs` - documentation for the project -* `lib` - contains the source code - * `formatters` - all source files defining formatters - * `rules` - all source files defining rules -* `tests` - the main unit test folder - * `lib` - tests for the source code - * `formatters` - tests for the formatters - * `rules` - tests for the rules diff --git a/docs/src/developer-guide/working-with-custom-parsers.md b/docs/src/developer-guide/working-with-custom-parsers.md deleted file mode 100644 index 0ae2c1c90b0..00000000000 --- a/docs/src/developer-guide/working-with-custom-parsers.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: Working with Custom Parsers -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/working-with-custom-parsers.md -eleventyNavigation: - key: working with custom parsers - parent: developer guide - title: Working with Custom Parsers - order: 7 - ---- - -If you want to use your own parser and provide additional capabilities for your rules, you can specify your own custom parser. If a `parseForESLint` method is exposed on the parser, this method will be used to parse the code. Otherwise, the `parse` method will be used. Both methods should take in the source code as the first argument, and an optional configuration object as the second argument (provided as `parserOptions` in a config file). The `parse` method should simply return the AST. The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. - -* `ast` should contain the AST. -* `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.parserServices`. Default is an empty object. -* `scopeManager` can be a [ScopeManager](./scope-manager-interface) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. Default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/eslint-scope). - * Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions which support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. -* `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. Default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/eslint-visitor-keys#evkkeys). - * Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions which support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. - -You can find an ESLint parser project [here](https://github.com/typescript-eslint/typescript-eslint). - -```json -{ - "parser": "./path/to/awesome-custom-parser.js" -} -``` - -```javascript -var espree = require("espree"); -// awesome-custom-parser.js -exports.parseForESLint = function(code, options) { - return { - ast: espree.parse(code, options), - services: { - foo: function() { - console.log("foo"); - } - }, - scopeManager: null, - visitorKeys: null - }; -}; - -``` - -## The AST specification - -The AST that custom parsers should create is based on [ESTree](https://github.com/estree/estree). The AST requires some additional properties about detail information of the source code. - -### All nodes: - -All nodes must have `range` property. - -* `range` (`number[]`) is an array of two numbers. Both numbers are a 0-based index which is the position in the array of source code characters. The first is the start position of the node, the second is the end position of the node. `code.slice(node.range[0], node.range[1])` must be the text of the node. This range does not include spaces/parentheses which are around the node. -* `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. On the other hand, `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property. - -The `parent` property of all nodes must be rewritable. ESLint sets each node's `parent` property to its parent node while traversing, before any rules have access to the AST. - -### The `Program` node: - -The `Program` node must have `tokens` and `comments` properties. Both properties are an array of the below Token interface. - -```ts -interface Token { - type: string; - loc: SourceLocation; - range: [number, number]; // See "All nodes:" section for details of `range` property. - value: string; -} -``` - -* `tokens` (`Token[]`) is the array of tokens which affect the behavior of programs. Arbitrary spaces can exist between tokens, so rules check the `Token#range` to detect spaces between tokens. This must be sorted by `Token#range[0]`. -* `comments` (`Token[]`) is the array of comment tokens. This must be sorted by `Token#range[0]`. - -The range indexes of all tokens and comments must not overlap with the range of other tokens and comments. - -### The `Literal` node: - -The `Literal` node must have `raw` property. - -* `raw` (`string`) is the source code of this literal. This is the same as `code.slice(node.range[0], node.range[1])`. diff --git a/docs/src/developer-guide/working-with-plugins.md b/docs/src/developer-guide/working-with-plugins.md deleted file mode 100644 index f03ba1a3ceb..00000000000 --- a/docs/src/developer-guide/working-with-plugins.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -title: Working with Plugins -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/working-with-plugins.md -eleventyNavigation: - key: working with plugings - parent: developer guide - title: Working with Plugins - order: 5 - ---- - -Each plugin is an npm module with a name in the format of `eslint-plugin-`, such as `eslint-plugin-jquery`. You can also use scoped packages in the format of `@/eslint-plugin-` such as `@jquery/eslint-plugin-jquery` or even `@/eslint-plugin` such as `@jquery/eslint-plugin`. - -## Create a Plugin - -The easiest way to start creating a plugin is to use the [Yeoman generator](https://www.npmjs.com/package/generator-eslint). The generator will guide you through setting up the skeleton of a plugin. - -### Rules in Plugins - -Plugins can expose additional rules for use in ESLint. To do so, the plugin must export a `rules` object containing a key-value mapping of rule ID to rule. The rule ID does not have to follow any naming convention (so it can just be `dollar-sign`, for instance). - -```js -module.exports = { - rules: { - "dollar-sign": { - create: function (context) { - // rule implementation ... - } - } - } -}; -``` - -To use the rule in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the rule name. So if this plugin were named `eslint-plugin-myplugin`, then in your configuration you'd refer to the rule by the name `myplugin/dollar-sign`. Example: `"rules": {"myplugin/dollar-sign": 2}`. - -### Environments in Plugins - -Plugins can expose additional environments for use in ESLint. To do so, the plugin must export an `environments` object. The keys of the `environments` object are the names of the different environments provided and the values are the environment settings. For example: - -```js -module.exports = { - environments: { - jquery: { - globals: { - $: false - } - } - } -}; -``` - -There's a `jquery` environment defined in this plugin. To use the environment in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the environment name. So if this plugin were named `eslint-plugin-myplugin`, then you would set the environment in your configuration to be `"myplugin/jquery"`. - -Plugin environments can define the following objects: - -1. `globals` - acts the same `globals` in a configuration file. The keys are the names of the globals and the values are `true` to allow the global to be overwritten and `false` to disallow. -1. `parserOptions` - acts the same as `parserOptions` in a configuration file. - -### Processors in Plugins - -You can also create plugins that would tell ESLint how to process files other than JavaScript. In order to create a processor, the object that is exported from your module has to conform to the following interface: - -```js -module.exports = { - processors: { - "processor-name": { - // takes text of the file and filename - preprocess: function(text, filename) { - // here, you can strip out any non-JS content - // and split into multiple strings to lint - - return [ // return an array of code blocks to lint - { text: code1, filename: "0.js" }, - { text: code2, filename: "1.js" }, - ]; - }, - - // takes a Message[][] and filename - postprocess: function(messages, filename) { - // `messages` argument contains two-dimensional array of Message objects - // where each top-level array item contains array of lint messages related - // to the text that was returned in array from preprocess() method - - // you need to return a one-dimensional array of the messages you want to keep - return [].concat(...messages); - }, - - supportsAutofix: true // (optional, defaults to false) - } - } -}; -``` - -**The `preprocess` method** takes the file contents and filename as arguments, and returns an array of code blocks to lint. The code blocks will be linted separately but still be registered to the filename. - -A code block has two properties `text` and `filename`; the `text` property is the content of the block and the `filename` property is the name of the block. Name of the block can be anything, but should include the file extension, that would tell the linter how to process the current block. The linter will check [`--ext` CLI option](../user-guide/command-line-interface#--ext) to see if the current block should be linted, and resolve `overrides` configs to check how to process the current block. - -It's up to the plugin to decide if it needs to return just one part, or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts, but for `.md` file where each JavaScript block might be independent, you can return multiple items. - -**The `postprocess` method** takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it. - -Reported problems have the following location information: - -```typescript -{ - line: number, - column: number, - - endLine?: number, - endColumn?: number -} -``` - -By default, ESLint will not perform autofixes when a processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: - -1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems will have a `fix` property, which is an object with the following schema: - - ```js - { - range: [number, number], - text: string - } - ``` - - The `range` property contains two indexes in the code, referring to the start and end location of a contiguous section of text that will be replaced. The `text` property refers to the text that will replace the given range. - - In the initial list of problems, the `fix` property will refer to a fix in the processed JavaScript. The `postprocess` method should transform the object to refer to a fix in the original, unprocessed file. - -2. Add a `supportsAutofix: true` property to the processor. - -You can have both rules and processors in a single plugin. You can also have multiple processors in one plugin. -To support multiple extensions, add each one to the `processors` element and point them to the same object. - -#### Specifying Processor in Config Files - -To use a processor, add its ID to a `processor` section in the config file. Processor ID is a concatenated string of plugin name and processor name with a slash as a separator. This can also be added to a `overrides` section of the config, to specify which processors should handle which files. - -For example: - -```yml -plugins: - - a-plugin -overrides: - - files: "*.md" - processor: a-plugin/markdown -``` - -See [Specifying Processor](../user-guide/configuring/plugins#specifying-processor) for details. - -#### File Extension-named Processor - -If a processor name starts with `.`, ESLint handles the processor as a **file extension-named processor** especially and applies the processor to the kind of files automatically. People don't need to specify the file extension-named processors in their config files. - -For example: - -```js -module.exports = { - processors: { - // This processor will be applied to `*.md` files automatically. - // Also, people can use this processor as "plugin-id/.md" explicitly. - ".md": { - preprocess(text, filename) { /* ... */ }, - postprocess(messageLists, filename) { /* ... */ } - } - } -} -``` - -### Configs in Plugins - -You can bundle configurations inside a plugin by specifying them under the `configs` key. This can be useful when you want to provide not just code style, but also some custom rules to support it. Multiple configurations are supported per plugin. Note that it is not possible to specify a default configuration for a given plugin and that users must specify in their configuration file when they want to use one. - -```js -// eslint-plugin-myPlugin - -module.exports = { - configs: { - myConfig: { - plugins: ["myPlugin"], - env: ["browser"], - rules: { - semi: "error", - "myPlugin/my-rule": "error", - "eslint-plugin-myPlugin/another-rule": "error" - } - }, - myOtherConfig: { - plugins: ["myPlugin"], - env: ["node"], - rules: { - "myPlugin/my-rule": "off", - "eslint-plugin-myPlugin/another-rule": "off", - "eslint-plugin-myPlugin/yet-another-rule": "error" - } - } - } -}; -``` - -If the example plugin above were called `eslint-plugin-myPlugin`, the `myConfig` and `myOtherConfig` configurations would then be usable by extending off of `"plugin:myPlugin/myConfig"` and `"plugin:myPlugin/myOtherConfig"`, respectively. - -```json -{ - "extends": ["plugin:myPlugin/myConfig"] -} - -``` - -**Note:** Please note that configuration will not enable any of the plugin's rules by default, and instead should be treated as a standalone config. This means that you must specify your plugin name in the `plugins` array as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the short or long plugin name. See [Configuring Plugins](../user-guide/configuring/plugins#configuring-plugins) for more information. - -### Peer Dependency - -To make clear that the plugin requires ESLint to work correctly you have to declare ESLint as a `peerDependency` in your `package.json`. -The plugin support was introduced in ESLint version `0.8.0`. Ensure the `peerDependency` points to ESLint `0.8.0` or later. - -```json -{ - "peerDependencies": { - "eslint": ">=0.8.0" - } -} -``` - -### Testing - -ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api#ruletester) utility to make it easy to test the rules of your plugin. - -### Linting - -ESLint plugins should be linted too! It's suggested to lint your plugin with the `recommended` configurations of: - -* [eslint](https://www.npmjs.com/package/eslint) -* [eslint-plugin-eslint-plugin](https://www.npmjs.com/package/eslint-plugin-eslint-plugin) -* [eslint-plugin-node](https://www.npmjs.com/package/eslint-plugin-node) - -## Share Plugins - -In order to make your plugin available to the community you have to publish it on npm. - -Recommended keywords: - -* `eslint` -* `eslintplugin` - -Add these keywords into your `package.json` file to make it easy for others to find. - -## Further Reading - -* [npm Developer Guide](https://docs.npmjs.com/misc/developers) diff --git a/docs/src/developer-guide/code-path-analysis.md b/docs/src/extend/code-path-analysis.md similarity index 96% rename from docs/src/developer-guide/code-path-analysis.md rename to docs/src/extend/code-path-analysis.md index 17daf3196cc..6cb7d2ac571 100644 --- a/docs/src/developer-guide/code-path-analysis.md +++ b/docs/src/extend/code-path-analysis.md @@ -1,7 +1,5 @@ --- title: Code Path Analysis Details -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/code-path-analysis.md --- @@ -16,7 +14,9 @@ if (a && b) { bar(); ``` +:::img-container ![Code Path Example](../assets/images/code-path-analysis/helo.svg) +::: ## Objects @@ -145,17 +145,23 @@ bar(); 1. First, the analysis advances to the end of loop. +:::img-container ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-while-1.svg) +::: 2. Second, it creates the looping path. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. +:::img-container ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-while-2.svg) +::: 3. Last, it advances to the end. +:::img-container ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-while-3.svg) +::: For example 2: @@ -170,29 +176,39 @@ bar(); First, the analysis advances to `ForStatement.update`. The `update` segment is hovered at first. +:::img-container ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-for-1.svg) +::: 2. Second, it advances to `ForStatement.body`. Of course the `body` segment is preceded by the `test` segment. It keeps the `update` segment hovering. +:::img-container ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-for-2.svg) +::: 3. Third, it creates the looping path from `body` segment to `update` segment. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. +:::img-container ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-for-3.svg) +::: 4. Fourth, also it creates the looping path from `update` segment to `test` segment. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. +:::img-container ![Loop Event's Example 4](../assets/images/code-path-analysis/loop-event-example-for-4.svg) +::: 5. Last, it advances to the end. +:::img-container ![Loop Event's Example 5](../assets/images/code-path-analysis/loop-event-example-for-5.svg) +::: ## Usage Examples @@ -338,7 +354,9 @@ See Also: console.log("Hello world!"); ``` +:::img-container ![Hello World](../assets/images/code-path-analysis/example-hello-world.svg) +::: ### `IfStatement` @@ -350,7 +368,9 @@ if (a) { } ``` +:::img-container ![`IfStatement`](../assets/images/code-path-analysis/example-ifstatement.svg) +::: ### `IfStatement` (chain) @@ -364,7 +384,9 @@ if (a) { } ``` +:::img-container ![`IfStatement` (chain)](../assets/images/code-path-analysis/example-ifstatement-chain.svg) +::: ### `SwitchStatement` @@ -385,7 +407,9 @@ switch (a) { } ``` +:::img-container ![`SwitchStatement`](../assets/images/code-path-analysis/example-switchstatement.svg) +::: ### `SwitchStatement` (has `default`) @@ -410,7 +434,9 @@ switch (a) { } ``` +:::img-container ![`SwitchStatement` (has `default`)](../assets/images/code-path-analysis/example-switchstatement-has-default.svg) +::: ### `TryStatement` (try-catch) @@ -433,7 +459,9 @@ It creates the paths from `try` block to `catch` block at: * The first throwable node (e.g. a function call) in the `try` block. * The end of the `try` block. +:::img-container ![`TryStatement` (try-catch)](../assets/images/code-path-analysis/example-trystatement-try-catch.svg) +::: ### `TryStatement` (try-finally) @@ -451,7 +479,9 @@ If there is not `catch` block, `finally` block has two current segments. At this time, `CodePath.currentSegments.length` is `2`. One is the normal path, and another is the leaving path (`throw` or `return`). +:::img-container ![`TryStatement` (try-finally)](../assets/images/code-path-analysis/example-trystatement-try-finally.svg) +::: ### `TryStatement` (try-catch-finally) @@ -467,7 +497,9 @@ try { last(); ``` +:::img-container ![`TryStatement` (try-catch-finally)](../assets/images/code-path-analysis/example-trystatement-try-catch-finally.svg) +::: ### `WhileStatement` @@ -481,7 +513,9 @@ while (a) { } ``` +:::img-container ![`WhileStatement`](../assets/images/code-path-analysis/example-whilestatement.svg) +::: ### `DoWhileStatement` @@ -492,7 +526,9 @@ do { } while (a); ``` +:::img-container ![`DoWhileStatement`](../assets/images/code-path-analysis/example-dowhilestatement.svg) +::: ### `ForStatement` @@ -506,7 +542,9 @@ for (let i = 0; i < 10; ++i) { } ``` +:::img-container ![`ForStatement`](../assets/images/code-path-analysis/example-forstatement.svg) +::: ### `ForStatement` (for ever) @@ -517,7 +555,9 @@ for (;;) { bar(); ``` +:::img-container ![`ForStatement` (for ever)](../assets/images/code-path-analysis/example-forstatement-for-ever.svg) +::: ### `ForInStatement` @@ -527,7 +567,9 @@ for (let key in obj) { } ``` +:::img-container ![`ForInStatement`](../assets/images/code-path-analysis/example-forinstatement.svg) +::: ### When there is a function @@ -546,8 +588,12 @@ It creates two code paths. * The global's +:::img-container ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-g.svg) +::: * The function's +:::img-container ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-f.svg) +::: diff --git a/docs/src/developer-guide/working-with-custom-formatters.md b/docs/src/extend/custom-formatters.md similarity index 66% rename from docs/src/developer-guide/working-with-custom-formatters.md rename to docs/src/extend/custom-formatters.md index 87bd728bd83..b95db0f8bd6 100644 --- a/docs/src/developer-guide/working-with-custom-formatters.md +++ b/docs/src/extend/custom-formatters.md @@ -1,18 +1,22 @@ --- -title: Working with Custom Formatters -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/working-with-custom-formatters.md +title: Custom Formatters eleventyNavigation: - key: working with custom formatters - parent: developer guide - title: Working with Custom Formatters - order: 6 + key: custom formatters + parent: extend eslint + title: Custom Formatters + order: 4 --- -While ESLint has some built-in formatters available to format the linting results, it's also possible to create and distribute your own custom formatters. You can include custom formatters in your project directly or create an npm package to distribute them separately. +Custom formatters let you display linting results in a format that best fits your needs, whether that's in a specific file format, a certain display style, or a format optimized for a particular tool. -Each formatter is just a function that receives a `results` object and a `context` and returns a string. For example, the following is how the `json` built-in formatter is implemented: +ESLint also has [built-in formatters](../use/formatters/) that you can use. + +You can include custom formatters in your project directly or create an npm package to distribute them separately. + +## Creating a Custom Formatter + +Each formatter is a function that receives a `results` object and a `context` as arguments and returns a string. For example, the following is how the built-in [JSON formatter](../use/formatters/#json) is implemented: ```js //my-awesome-formatter.js @@ -31,37 +35,17 @@ module.exports = async function(results) { }; ``` -To run ESLint with this formatter, you can use the `-f` (or `--format`) command line flag: +To run ESLint with this formatter, you can use the [`-f` (or `--format`)](../use/command-line-interface#-f---format) command line flag. You must begin the path to a locally defined custom formatter with a period (`.`), such as `./my-awesome-formatter.js` or `../formatters/my-awesome-formatter.js`. ```bash eslint -f ./my-awesome-formatter.js src/ ``` -In order to use a local file as a custom formatter, you must begin the filename with a dot (such as `./my-awesome-formatter.js` or `../formatters/my-awesome-formatter.js`). - -## Packaging the Custom Formatter +The remainder of this section contains reference information on how to work with custom formatter functions. -Custom formatters can also be distributed through npm packages. To do so, create an npm package with a name in the format of `eslint-formatter-*`, where `*` is the name of your formatter (such as `eslint-formatter-awesome`). Projects should then install the package and can use the custom formatter with the `-f` (or `--format`) flag like this: +### The `results` Argument -```bash -eslint -f awesome src/ -``` - -Because ESLint knows to look for packages beginning with `eslint-formatter-` when the specified formatter doesn't begin with a dot, there is no need to type `eslint-formatter-` when using a packaged custom formatter. - -Tips for `package.json`: - -* The `main` entry should be the JavaScript file implementing your custom formatter. -* Add these `keywords` to help users find your formatter: - * `"eslint"` - * `"eslint-formatter"` - * `"eslintformatter"` - -See all [formatters on npm](https://www.npmjs.com/search?q=eslint-formatter); - -## The `results` Argument - -The `results` object passed into a formatter is an array of objects containing the lint results for individual files. Here's some example output: +The `results` object passed into a formatter is an array of [`result`](#the-result-object) objects containing the linting results for individual files. Here's an example output: ```js [ @@ -103,7 +87,7 @@ The `results` object passed into a formatter is an array of objects containing t ] ``` -### The `result` Object +#### The `result` Object @@ -111,13 +95,13 @@ also be manually applied to that page. --> Each object in the `results` array is a `result` object. Each `result` object contains the path of the file that was linted and information about linting issues that were encountered. Here are the properties available on each `result` object: * **filePath**: The absolute path to the file that was linted. -* **messages**: An array of `message` objects. See below for more info about messages. +* **messages**: An array of [`message`](#the-message-object) 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. * **source**: The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present. * **output**: The source code for the given file with as many fixes applied as possible. This property is omitted if no fix is available. -### The `message` Object +##### The `message` Object Each `message` object contains information about the ESLint rule that was triggered by some source code. The properties available on each `message` object are: @@ -126,20 +110,27 @@ Each `message` object contains information about the ESLint rule that was trigge * **message**: the human readable description of the error. * **line**: the line where the issue is located. * **column**: the column where the issue is located. -* **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/spec.md#node-objects) +* **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/es5.md#node-objects) -## The `context` Argument +### The `context` Argument -The formatter function receives an object as the second argument. The object has two properties: +The formatter function receives a `context` object as its second argument. The object has the following properties: -* `cwd` ... The current working directory. This value comes from the `cwd` constructor option of the [ESLint](nodejs-api#-new-eslintoptions) class. -* `rulesMeta` ... The `meta` property values of rules. See the [Working with Rules](working-with-rules) page for more information about rules. +* `cwd`: The current working directory. This value comes from the `cwd` constructor option of the [ESLint](../integrate/nodejs-api#-new-eslintoptions) class. +* `maxWarningsExceeded` (optional): If `--max-warnings` was set and the number of warnings exceeded the limit, this property's value is an object containing two properties: + * `maxWarnings`: the value of the `--max-warnings` option + * `foundWarnings`: the number of lint warnings +* `rulesMeta`: The `meta` property values of rules. See the [Custom Rules](custom-rules) page for more information about rules. -For example, here's what the object would look like if one rule, `no-extra-semi`, had been run: +For example, here's what the object would look like if the rule `no-extra-semi` had been run: ```js { cwd: "/path/to/cwd", + maxWarningsExceeded: { + maxWarnings: 5, + foundWarnings: 6 + }, rulesMeta: { "no-extra-semi": { type: "suggestion", @@ -154,71 +145,33 @@ For example, here's what the object would look like if one rule, `no-extra-semi` unexpected: "Unnecessary semicolon." } } - } + }, } ``` -**Note:** if a linting is executed by deprecated `CLIEngine` class, the `context` argument may be a different value because it is up to the API users. Please check whether the `context` argument is an expected value or not if you want to support legacy environments. - -## Examples - -### Summary formatter - -A formatter that only cares about the total count of errors and warnings will look like this: - -```javascript -module.exports = function(results, context) { - // accumulate the errors and warnings - var summary = results.reduce( - function(seq, current) { - seq.errors += current.errorCount; - seq.warnings += current.warningCount; - return seq; - }, - { errors: 0, warnings: 0 } - ); - - if (summary.errors > 0 || summary.warnings > 0) { - return ( - "Errors: " + - summary.errors + - ", Warnings: " + - summary.warnings + - "\n" - ); - } - - return ""; -}; -``` +**Note:** if a linting is executed by the deprecated `CLIEngine` class, the `context` argument may be a different value because it is up to the API users. Please check whether the `context` argument is an expected value or not if you want to support legacy environments. -Running `eslint` with the previous custom formatter, +### Passing Arguments to Formatters -```bash -eslint -f ./my-awesome-formatter.js src/ -``` +While formatter functions do not receive arguments in addition to the results object and the context, it is possible to pass additional data into custom formatters using the methods described below. -Will produce the following output: +#### Using Environment Variables -```bash -Errors: 2, Warnings: 4 -``` +Custom formatters have access to environment variables and so can change their behavior based on environment variable data. -### Detailed formatter +Here's an example that uses a `FORMATTER_SKIP_WARNINGS` environment variable to determine whether to show warnings in the results: -A more complex report will look something like this: +```js +module.exports = function(results) { + var skipWarnings = process.env.FORMATTER_SKIP_WARNINGS === "true"; -```javascript -module.exports = function(results, context) { var results = results || []; - var summary = results.reduce( function(seq, current) { current.messages.forEach(function(msg) { var logMessage = { filePath: current.filePath, ruleId: msg.ruleId, - ruleUrl: context.rulesMeta[msg.ruleId].docs.url, message: msg.message, line: msg.line, column: msg.column @@ -242,14 +195,16 @@ module.exports = function(results, context) { ); if (summary.errors.length > 0 || summary.warnings.length > 0) { + var warnings = !skipWarnings ? summary.warnings : []; // skip the warnings in that case + var lines = summary.errors - .concat(summary.warnings) + .concat(warnings) .map(function(msg) { return ( "\n" + msg.type + " " + - msg.ruleId + (msg.ruleUrl ? " (" + msg.ruleUrl + ")" : "") + + msg.ruleId + "\n " + msg.filePath + ":" + @@ -265,48 +220,119 @@ module.exports = function(results, context) { }; ``` -So running `eslint` with this custom formatter: +You would run ESLint with this custom formatter and an environment variable set like this: ```bash -eslint -f ./my-awesome-formatter.js src/ +FORMATTER_SKIP_WARNINGS=true eslint -f ./my-awesome-formatter.js src/ ``` -The output will be +The output would be: ```bash -error space-infix-ops (https://eslint.org/docs/rules/space-infix-ops) +error space-infix-ops src/configs/bundler.js:6:8 -error semi (https://eslint.org/docs/rules/semi) + +error semi src/configs/bundler.js:6:10 -warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) - src/configs/bundler.js:5:6 -warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) - src/configs/bundler.js:6:6 -warning no-shadow (https://eslint.org/docs/rules/no-shadow) - src/configs/bundler.js:65:32 -warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) - src/configs/clean.js:3:6 ``` -## Passing Arguments to Formatters +#### Complex Argument Passing -While formatter functions do not receive arguments in addition to the results object and the context, it is possible to pass additional data into custom formatters using the methods described below. +If you find the custom formatter pattern doesn't provide enough options for the way you'd like to format ESLint results, the best option is to use ESLint's built-in [JSON formatter](../use/formatters/#json) and pipe the output to a second program. For example: -## Using Environment Variables +```bash +eslint -f json src/ | your-program-that-reads-JSON --option +``` -Custom formatters have access to environment variables and so can change their behavior based on environment variable data. Here's an example that uses a `AF_SKIP_WARNINGS` environment variable to determine whether or not to show warnings in the results: +In this example, the `your-program-that-reads-json` program can accept the raw JSON of ESLint results and process it before outputting its own format of the results. You can pass as many command line arguments to that program as are necessary to customize the output. -```js -module.exports = function(results) { - var skipWarnings = process.env.AF_SKIP_WARNINGS === "true"; //af stands for awesome-formatter +### Formatting for Terminals + +Modern terminals like [iTerm2](https://www.iterm2.com/) or [Guake](http://guake-project.org/) expect a specific results format to automatically open filenames when they are clicked. Most terminals support this format for that purpose: + +```bash +file:line:column +``` +## Packaging a Custom Formatter + +Custom formatters can be distributed through npm packages. To do so, create an npm package with a name in the format `eslint-formatter-*`, where `*` is the name of your formatter (such as `eslint-formatter-awesome`). Projects should then install the package and use the custom formatter with the [`-f` (or `--format`)](../use/command-line-interface#-f---format) flag like this: + +```bash +eslint -f awesome src/ +``` + +Because ESLint knows to look for packages beginning with `eslint-formatter-` when the specified formatter doesn't begin with a period, you do not need to type `eslint-formatter-` when using a packaged custom formatter. + +Tips for the `package.json` of a custom formatter: + +* The [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point must be the JavaScript file implementing your custom formatter. +* Add these [`keywords`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) to help users find your formatter: + * `"eslint"` + * `"eslint-formatter"` + * `"eslintformatter"` + +See all [custom formatters on npm](https://www.npmjs.com/search?q=eslint-formatter). + +## Examples + +### Summary Formatter + +A formatter that only reports on the total count of errors and warnings will look like this: + +```javascript +module.exports = function(results, context) { + // accumulate the errors and warnings + var summary = results.reduce( + function(seq, current) { + seq.errors += current.errorCount; + seq.warnings += current.warningCount; + return seq; + }, + { errors: 0, warnings: 0 } + ); + + if (summary.errors > 0 || summary.warnings > 0) { + return ( + "Errors: " + + summary.errors + + ", Warnings: " + + summary.warnings + + "\n" + ); + } + + return ""; +}; +``` + +Run `eslint` with the above summary formatter: + +```bash +eslint -f ./my-awesome-formatter.js src/ +``` + +Will produce the following output: + +```bash +Errors: 2, Warnings: 4 +``` + +### Detailed Formatter + +A more complex report could look like this: + +```javascript +module.exports = function(results, context) { var results = results || []; + var summary = results.reduce( function(seq, current) { current.messages.forEach(function(msg) { var logMessage = { filePath: current.filePath, ruleId: msg.ruleId, + ruleUrl: context.rulesMeta[msg.ruleId].docs.url, message: msg.message, line: msg.line, column: msg.column @@ -330,16 +356,14 @@ module.exports = function(results) { ); if (summary.errors.length > 0 || summary.warnings.length > 0) { - var warnings = !skipWarnings ? summary.warnings : []; // skip the warnings in that case - var lines = summary.errors - .concat(warnings) + .concat(summary.warnings) .map(function(msg) { return ( "\n" + msg.type + " " + - msg.ruleId + + msg.ruleId + (msg.ruleUrl ? " (" + msg.ruleUrl + ")" : "") + "\n " + msg.filePath + ":" + @@ -355,36 +379,25 @@ module.exports = function(results) { }; ``` -You would run ESLint with this custom formatter and an environment variable set like this: +When you run ESLint with this custom formatter: ```bash -AF_SKIP_WARNINGS=true eslint -f ./my-awesome-formatter.js src/ +eslint -f ./my-awesome-formatter.js src/ ``` -The output would be: +The output is: ```bash -error space-infix-ops +error space-infix-ops (https://eslint.org/docs/rules/space-infix-ops) src/configs/bundler.js:6:8 - -error semi +error semi (https://eslint.org/docs/rules/semi) src/configs/bundler.js:6:10 -``` - -### Complex Argument Passing - -If you find the custom formatter pattern doesn't provide enough options for the way you'd like to format ESLint results, the best option is to use ESLint's built-in [JSON formatter](https://eslint.org/docs/user-guide/formatters/) and pipe the output to a second program. For example: - -```bash -eslint -f json src/ | your-program-that-reads-JSON --option -``` - -In this example, the `your-program-that-reads-json` program can accept the raw JSON of ESLint results and process it before outputting its own format of the results. You can pass as many command line arguments to that program as are necessary to customize the output. - -## Note: Formatting for Terminals - -Modern terminals like [iTerm2](https://www.iterm2.com/) or [Guake](http://guake-project.org/) expect a specific results format to automatically open filenames when they are clicked. Most terminals support this format for that purpose: - -```bash -file:line:column +warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) + src/configs/bundler.js:5:6 +warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) + src/configs/bundler.js:6:6 +warning no-shadow (https://eslint.org/docs/rules/no-shadow) + src/configs/bundler.js:65:32 +warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) + src/configs/clean.js:3:6 ``` diff --git a/docs/src/extend/custom-parsers.md b/docs/src/extend/custom-parsers.md new file mode 100644 index 00000000000..388f54b726b --- /dev/null +++ b/docs/src/extend/custom-parsers.md @@ -0,0 +1,151 @@ +--- +title: Custom Parsers +eleventyNavigation: + key: custom parsers + parent: extend eslint + title: Custom Parsers + order: 5 + +--- + +ESLint custom parsers let you extend ESLint to support linting new non-standard JavaScript language features or custom syntax in your code. A parser is responsible for taking your code and transforming it into an abstract syntax tree (AST) that ESLint can then analyze and lint. + +## Creating a Custom Parser + +A custom parser is a JavaScript object with either a `parse` or `parseForESLint` method. The `parse` method only returns the AST, whereas `parseForESLint` also returns additional values that let the parser customize the behavior of ESLint even more. + +Both methods should take in the source code as the first argument, and an optional configuration object as the second argument, which is provided as [`parserOptions`](../use/configure/language-options#specifying-parser-options) in a configuration file. + +```javascript +// customParser.js + +const espree = require("espree"); + +// Logs the duration it takes to parse each file. +function parse(code, options) { + const label = `Parsing file "${options.filePath}"`; + console.time(label); + const ast = espree.parse(code, options); + console.timeEnd(label); + return ast; // Only the AST is returned. +}; + +module.exports = { parse }; +``` + +## `parse` Return Object + +The `parse` method should simply return the [AST](#ast-specification) object. + +## `parseForESLint` Return Object + +The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. + +* `ast` should contain the [AST](#ast-specification) object. +* `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.parserServices`. Default is an empty object. +* `scopeManager` can be a [ScopeManager](./scope-manager-interface) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. The default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/eslint-scope). + * Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions that support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. +* `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. The default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/eslint-visitor-keys#evkkeys). + * Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions that support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. + +## AST Specification + +The AST that custom parsers should create is based on [ESTree](https://github.com/estree/estree). The AST requires some additional properties about detail information of the source code. + +### All Nodes + +All nodes must have `range` property. + +* `range` (`number[]`) is an array of two numbers. Both numbers are a 0-based index which is the position in the array of source code characters. The first is the start position of the node, the second is the end position of the node. `code.slice(node.range[0], node.range[1])` must be the text of the node. This range does not include spaces/parentheses which are around the node. +* `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. The `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property. + +The `parent` property of all nodes must be rewritable. Before any rules have access to the AST, ESLint sets each node's `parent` property to its parent node while traversing. + +### The `Program` Node + +The `Program` node must have `tokens` and `comments` properties. Both properties are an array of the below `Token` interface. + +```ts +interface Token { + type: string; + loc: SourceLocation; + // See the "All Nodes" section for details of the `range` property. + range: [number, number]; + value: string; +} +``` + +* `tokens` (`Token[]`) is the array of tokens which affect the behavior of programs. Arbitrary spaces can exist between tokens, so rules check the `Token#range` to detect spaces between tokens. This must be sorted by `Token#range[0]`. +* `comments` (`Token[]`) is the array of comment tokens. This must be sorted by `Token#range[0]`. + +The range indexes of all tokens and comments must not overlap with the range of other tokens and comments. + +### The `Literal` Node + +The `Literal` node must have `raw` property. + +* `raw` (`string`) is the source code of this literal. This is the same as `code.slice(node.range[0], node.range[1])`. + +## Packaging a Custom Parser + +To publish your custom parser to npm, perform the following: + +1. Create a custom parser following the [Creating a Custom Parser](#creating-a-custom-parser) section above. +1. [Create an npm package](https://docs.npmjs.com/creating-node-js-modules) for the custom parser. +1. In your `package.json` file, set the [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) field as the file that exports your custom parser. +1. [Publish the npm package.](https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages) + +For more information on publishing an npm package, refer to the [npm documentation](https://docs.npmjs.com/). + +Once you've published the npm package, you can use it by adding the package to your project. For example: + +```shell +npm install eslint-parser-myparser --save-dev +``` + +Then add the custom parser to your ESLint configuration file with the `parser` property. For example: + +```js +// .eslintrc.js + +module.exports = { + parser: 'eslint-parser-myparser', + // ... rest of configuration +}; +``` + +To learn more about using ESLint parsers in your project, refer to [Configure a Parser](../use/configure/parser). + +## Example + +For a complex example of a custom parser, refer to the [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser) source code. + +A simple custom parser that provides a `context.parserServices.foo()` method to rules. + +```javascript +// awesome-custom-parser.js +var espree = require("espree"); +function parseForESLint(code, options) { + return { + ast: espree.parse(code, options), + services: { + foo: function() { + console.log("foo"); + } + }, + scopeManager: null, + visitorKeys: null + }; +}; + +module.exports = { parseForESLint }; +``` + +Include the custom parser in an ESLint configuration file: + +```js +// .eslintrc.json +{ + "parser": "./path/to/awesome-custom-parser.js" +} +``` diff --git a/docs/src/extend/custom-processors.md b/docs/src/extend/custom-processors.md new file mode 100644 index 00000000000..8f330883b03 --- /dev/null +++ b/docs/src/extend/custom-processors.md @@ -0,0 +1,179 @@ +--- +title: Custom Processors +eleventyNavigation: + key: custom processors + parent: create plugins + title: Custom Processors + order: 2 + +--- + +You can also create custom processors that tell ESLint how to process files other than standard JavaScript. For example, you could write a custom processor to extract and process JavaScript from Markdown files ([eslint-plugin-markdown](https://www.npmjs.com/package/eslint-plugin-markdown) includes a custom processor for this). + +## Custom Processor Specification + +In order to create a custom processor, the object exported from your module has to conform to the following interface: + +```js +module.exports = { + processors: { + "processor-name": { + // takes text of the file and filename + preprocess: function(text, filename) { + // here, you can strip out any non-JS content + // and split into multiple strings to lint + + return [ // return an array of code blocks to lint + { text: code1, filename: "0.js" }, + { text: code2, filename: "1.js" }, + ]; + }, + + // takes a Message[][] and filename + postprocess: function(messages, filename) { + // `messages` argument contains two-dimensional array of Message objects + // where each top-level array item contains array of lint messages related + // to the text that was returned in array from preprocess() method + + // you need to return a one-dimensional array of the messages you want to keep + return [].concat(...messages); + }, + + supportsAutofix: true // (optional, defaults to false) + } + } +}; +``` + +**The `preprocess` method** takes the file contents and filename as arguments, and returns an array of code blocks to lint. The code blocks will be linted separately but still be registered to the filename. + +A code block has two properties `text` and `filename`. The `text` property is the content of the block and the `filename` property is the name of the block. The name of the block can be anything, but should include the file extension, which tells the linter how to process the current block. The linter checks the [`--ext` CLI option](../use/command-line-interface#--ext) to see if the current block should be linted and resolves `overrides` configs to check how to process the current block. + +It's up to the plugin to decide if it needs to return just one part of the non-JavaScript file or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts. However, for `.md` files, you can return multiple items because each JavaScript block might be independent. + +**The `postprocess` method** takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it. + +Reported problems have the following location information in each lint message: + +```typescript +type LintMessage = { + + /// The 1-based line number where the message occurs. + line: number; + + /// The 1-based column number where the message occurs. + column: number; + + /// The 1-based line number of the end location. + endLine: number; + + /// The 1-based column number of the end location. + endColumn: number; + + /// If `true`, this is a fatal error. + fatal: boolean; + + /// Information for an autofix. + fix: Fix; + + /// The error message. + message: string; + + /// The ID of the rule which generated the message, or `null` if not applicable. + ruleId: string | null; + + /// The severity of the message. + severity: 0 | 1 | 2; + + /// Information for suggestions. + suggestions?: Suggestion[]; +}; + +type Fix = { + range: [number, number]; + text: string; +} + +type Suggestion = { + desc?: string; + messageId?: string; + fix: Fix; +} + +``` + +By default, ESLint does not perform autofixes when a custom processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: + +1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems have a `fix` property, which is an object with the following schema: + + ```typescript + { + range: [number, number], + text: string + } + ``` + + The `range` property contains two indexes in the code, referring to the start and end location of a contiguous section of text that will be replaced. The `text` property refers to the text that will replace the given range. + + In the initial list of problems, the `fix` property will refer to a fix in the processed JavaScript. The `postprocess` method should transform the object to refer to a fix in the original, unprocessed file. + +2. Add a `supportsAutofix: true` property to the processor. + +You can have both rules and custom processors in a single plugin. You can also have multiple processors in one plugin. To support multiple extensions, add each one to the `processors` element and point them to the same object. + +## Specifying Processor in Config Files + +To use a processor, add its ID to a `processor` section in the config file. Processor ID is a concatenated string of plugin name and processor name with a slash as a separator. This can also be added to a `overrides` section of the config, to specify which processors should handle which files. + +For example: + +```yml +plugins: + - a-plugin +overrides: + - files: "*.md" + processor: a-plugin/markdown +``` + +See [Specify a Processor](../use/configure/plugins#specify-a-processor) in the Plugin Configuration documentation for more details. + +## File Extension-named Processor + +If a custom processor name starts with `.`, ESLint handles the processor as a **file extension-named processor**. ESLint applies the processor to files with that filename extension automatically. Users don't need to specify the file extension-named processors in their config files. + +For example: + +```js +module.exports = { + processors: { + // This processor will be applied to `*.md` files automatically. + // Also, you can use this processor as "plugin-id/.md" explicitly. + ".md": { + preprocess(text, filename) { /* ... */ }, + postprocess(messageLists, filename) { /* ... */ } + } + // This processor will not be applied to any files automatically. + // To use this processor, you must explicitly specify it + // in your configuration as "plugin-id/markdown". + "markdown": { + preprocess(text, filename) { /* ... */ }, + postprocess(messageLists, filename) { /* ... */ } + } + } +} +``` + +You can also use the same custom processor with multiple filename extensions. The following example shows using the same processor for both `.md` and `.mdx` files: + +```js +const myCustomProcessor = { /* processor methods */ }; + +module.exports = { + // The same custom processor is applied to both + // `.md` and `.mdx` files. + processors: { + ".md": myCustomProcessor, + ".mdx": myCustomProcessor + } +} +``` diff --git a/docs/src/developer-guide/working-with-rules-deprecated.md b/docs/src/extend/custom-rules-deprecated.md similarity index 96% rename from docs/src/developer-guide/working-with-rules-deprecated.md rename to docs/src/extend/custom-rules-deprecated.md index a700780a85f..49faa4f13c7 100644 --- a/docs/src/developer-guide/working-with-rules-deprecated.md +++ b/docs/src/extend/custom-rules-deprecated.md @@ -1,11 +1,9 @@ --- title: Working with Rules (Deprecated) -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/working-with-rules-deprecated.md --- -**Note:** This page covers the deprecated rule format for ESLint <= 2.13.1. [This is the most recent rule format](./working-with-rules). +**Note:** This page covers the deprecated rule format for ESLint <= 2.13.1. [This is the most recent rule format](./custom-rules). Each rule in ESLint has two files named with its identifier (for example, `no-extra-semi`). @@ -39,13 +37,13 @@ module.exports.schema = []; // no options ## Rule Basics -`schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring/rules#configuring-rules) +`schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules) `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, ESLint calls that **visitor** function while going **down** the tree * if a key is a node type 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) +* if a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis) A rule can use the current node and its surrounding tree to report or fix problems. @@ -79,7 +77,7 @@ module.exports = function(context) { 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/language-options#specifying-parser-options)). +* `parserOptions` - the parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). * `id` - the rule ID. * `options` - an array of rule options. * `settings` - the `settings` from configuration. @@ -329,7 +327,7 @@ Keep in mind that comments are technically not a part of the AST and are only at ESLint analyzes code paths while traversing AST. You can access that code path objects with five events related to code paths. -[details here](./code-path-analysis) +[details here](code-path-analysis) ## Rule Unit Tests @@ -483,7 +481,7 @@ valid: [ ] ``` -The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../user-guide/configuring/language-options#specifying-parser-options). +The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../use/configure/language-options#specifying-parser-options). ### Write Several Tests @@ -578,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](../use/configure/) 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](../use/command-line-interface) using the `--rulesdir` option to specify the location of your runtime rules. diff --git a/docs/src/developer-guide/working-with-rules.md b/docs/src/extend/custom-rules.md similarity index 85% rename from docs/src/developer-guide/working-with-rules.md rename to docs/src/extend/custom-rules.md index be6d971a390..11ccc02563a 100644 --- a/docs/src/developer-guide/working-with-rules.md +++ b/docs/src/extend/custom-rules.md @@ -1,48 +1,27 @@ --- -title: Working with Rules -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/working-with-rules.md +title: Custom Rules eleventyNavigation: - key: working with rules - parent: developer guide - title: Working with Rules - order: 4 + key: custom rules + parent: create plugins + title: Custom Rules + order: 1 --- -**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). +You can create custom rules to use with ESLint. You might want to create a custom rule if the [core rules](../rules/) do not cover your use case. -Each rule in ESLint has three files named with its identifier (for example, `no-extra-semi`). +**Note:** This page covers the most recent rule format for ESLint >= 3.0.0. There is also a [deprecated rule format](./custom-rules-deprecated). -* 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/src/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. - -Here is the basic format of the source file for a rule: +Here's the basic format of a custom rule: ```js -/** - * @fileoverview Rule to disallow unnecessary semicolons - * @author Nicholas C. Zakas - */ - -"use strict"; +// customRule.js -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { type: "suggestion", - docs: { - description: "disallow unnecessary semicolons", - recommended: true, - url: "https://eslint.org/docs/rules/no-extra-semi" + description: "Description of the rule", }, fixable: "code", schema: [] // no options @@ -69,12 +48,12 @@ The source file for a rule exports an object with the following properties. * `docs` (object) is required for core rules of ESLint: * `description` (string) provides the short description of the rule in the [rules index](../rules/) - * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../user-guide/configuring/configuration-files#extending-configuration-files) enables the rule + * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../use/configure/configuration-files#extending-configuration-files) enables the rule * `url` (string) specifies the URL at which the full documentation can be accessed (enabling code editors to provide a helpful link on highlighted rule violations) 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](../use/command-line-interface#--fix) automatically fixes problems reported by the rule **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable. @@ -82,7 +61,7 @@ The source file for a rule exports an object with the following properties. **Important:** the `hasSuggestions` property is mandatory for rules that provide suggestions. If this property isn't set to `true`, ESLint will throw an error whenever the rule attempts to produce a suggestion. Omit the `hasSuggestions` property if the rule does not provide suggestions. -* `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring/rules#configuring-rules) +* `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules) * `deprecated` (boolean) indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. @@ -92,7 +71,7 @@ The source file for a rule exports an object with the following properties. * 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 an event name, ESLint calls that **handler** function for [code path analysis](./code-path-analysis) +* if a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis) A rule can use the current node and its surrounding tree to report or fix problems. @@ -129,17 +108,17 @@ 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/language-options#specifying-parser-options)). +* `parserOptions` - the parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). * `id` - the rule ID. -* `options` - an array of the [configured options](/docs/user-guide/configuring/rules#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/configuration-files#adding-shared-settings) from configuration. +* `options` - an array of the [configured options](../use/configure/rules) for this rule. This array does not include the rule severity. For more information, see [here](#contextoptions). +* `settings` - the [shared settings](../use/configure/configuration-files#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 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. -* `getCwd()` - returns the `cwd` passed to [Linter](./nodejs-api#linter). It is a path to a directory that should be considered as the current working directory. +* `getCwd()` - returns the `cwd` passed to [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered as the current working directory. * `getDeclaredVariables(node)` - returns a list of [variables](./scope-manager-interface#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. @@ -152,45 +131,13 @@ Additionally, the `context` object has the following methods: * Otherwise, if the node does not declare any variables, an empty array is returned. * `getFilename()` - returns the filename associated with the source. * `getPhysicalFilename()` - when linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `—stdin-filename` or `` if not specified. -* `getScope()` - returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. +* `getScope()` - (**Deprecated: Use `SourceCode.getScope(node)` instead.**) returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to 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) 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. -### context.getScope() - -This method returns the scope which has the following types: - -| AST Node Type | Scope Type | -|:--------------------------|:-----------| -| `Program` | `global` | -| `FunctionDeclaration` | `function` | -| `FunctionExpression` | `function` | -| `ArrowFunctionExpression` | `function` | -| `ClassDeclaration` | `class` | -| `ClassExpression` | `class` | -| `BlockStatement` ※1 | `block` | -| `SwitchStatement` ※1 | `switch` | -| `ForStatement` ※2 | `for` | -| `ForInStatement` ※2 | `for` | -| `ForOfStatement` ※2 | `for` | -| `WithStatement` | `with` | -| `CatchClause` | `catch` | -| others | ※3 | - -**※1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
    -**※2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
    -**※3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). - -The returned value is a [`Scope` object](scope-manager-interface) defined by the `eslint-scope` package. The `Variable` objects of global variables have some additional properties. - -* `variable.writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. -* `variable.eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. -* `variable.eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. -* `variable.eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. - ### context.report() The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: @@ -336,7 +283,7 @@ The `fix()` function can return the following values: * 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. +If you make a `fix()` function which returns multiple `fixing` objects, those `fixing` objects must not overlap. Best practices for fixes: @@ -358,7 +305,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`](../rules/quotes) rule. Note: Making fixes as small as possible is a best practice, but in some cases it may be correct to extend the range of the fix in order to intentionally prevent other rules from making fixes in a surrounding range in the same pass. For instance, if replacement text declares a new variable, it can be useful to prevent other changes in the scope of the variable as they might cause name collisions. @@ -378,6 +325,13 @@ context.report({ }); ``` +#### Conflicting Fixes + +Conflicting fixes are fixes that apply different changes to the same part of the source code. +There is no way to specify which of the conflicting fixes is applied. + +For example, if two fixes want to modify characters 0 through 5, only one is applied. + ### Providing Suggestions In some cases fixes aren't appropriate to be automatically applied, for example, if a fix potentially changes functionality or if there are multiple valid ways to fix a rule depending on the implementation intent (see the best practices for [applying fixes](#applying-fixes) listed above). In these cases, there is an alternative `suggest` option on `context.report()` that allows other tools, such as editors, to expose helpers for users to manually apply a suggestion. @@ -681,56 +635,85 @@ Finally, comments can be accessed through many of `sourceCode`'s methods using t Shebangs are represented by tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined above. +### Accessing Variable Scopes + +The `SourceCode#getScope(node)` method returns the scope of the given node. It is a useful method for finding information about the variables in a given scope and how they are used in other scopes. + +**Deprecated:** The `context.getScope()` is deprecated; make sure to use `SourceCode#getScope(node)` instead. + +#### Scope types + +The following table contains a list of AST node types and the scope type that they correspond to. For more information about the scope types, refer to the [`Scope` object documentation](./scope-manager-interface#scope-interface). + +| AST Node Type | Scope Type | +|:--------------------------|:-----------| +| `Program` | `global` | +| `FunctionDeclaration` | `function` | +| `FunctionExpression` | `function` | +| `ArrowFunctionExpression` | `function` | +| `ClassDeclaration` | `class` | +| `ClassExpression` | `class` | +| `BlockStatement` ※1 | `block` | +| `SwitchStatement` ※1 | `switch` | +| `ForStatement` ※2 | `for` | +| `ForInStatement` ※2 | `for` | +| `ForOfStatement` ※2 | `for` | +| `WithStatement` | `with` | +| `CatchClause` | `catch` | +| others | ※3 | + +**※1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
    +**※2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
    +**※3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). + +#### Scope Variables + +The `Scope#variables` property contains an array of [`Variable` objects](./scope-manager-interface#variable-interface). These are the variables declared in current scope. You can use these `Variable` objects to track references to a variable throughout the entire module. + +Inside of each `Variable`, the `Variable#references` property contains an array of [`Reference` objects](./scope-manager-interface#reference-interface). The `Reference` array contains all the locations where the variable is referenced in the module's source code. + +Also inside of each `Variable`, the `Variable#defs` property contains an array of [`Definition` objects](./scope-manager-interface#definition-interface). You can use the `Definitions` to find where the variable was defined. + +Global variables have the following additional properties: + +* `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. +* `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. +* `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. +* `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. + +For examples of using `SourceCode#getScope()` to track variables, refer to the source code for the following built-in rules: + +* [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `sourceCode.getScope()` at the `Program` node and inspects all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) +* [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `sourceCode.getScope()` at each scope to make sure that a variable is not declared twice in the same scope. ([no-redeclare](../rules/no-redeclare) documentation) + ### Accessing Code Paths ESLint analyzes code paths while traversing AST. You can access that code path objects with five events related to code paths. -[details here](./code-path-analysis) +[details here](code-path-analysis) ## Rule Unit Tests -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`. +ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility to make it easy to write tests for rules. -ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api#ruletester) utility to make it easy to write tests for rules. +## Rule Naming Conventions -## Performance Testing +While you can give a custom rule any name you'd like, the core rules have naming conventions that it could be clearer to apply to your custom rule. To learn more, refer to the [Core Rule Naming Conventions](../contribute/core-rules#rule-naming-conventions) documentation. -To keep the linting process efficient and unobtrusive, it is useful to verify the performance impact of new rules or modifications to existing rules. +## Runtime Rules -### Overall Performance +The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with. With runtime rules, you don't have to wait for the next version of ESLint or be disappointed that your rule isn't general enough to apply to the larger JavaScript community, just write your rules and include them at runtime. -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. +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: -```bash -$ git checkout main -Switched to branch 'main' - -$ npm run perf -CPU Speed is 2200 with multiplier 7500000 -Performance Run #1: 1394.689313ms -Performance Run #2: 1423.295351ms -Performance Run #3: 1385.09515ms -Performance Run #4: 1382.406982ms -Performance Run #5: 1409.68566ms -Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms) - -$ git checkout my-rule-branch -Switched to branch 'my-rule-branch' - -$ npm run perf -CPU Speed is 2200 with multiplier 7500000 -Performance Run #1: 1443.736547ms -Performance Run #2: 1419.193291ms -Performance Run #3: 1436.018228ms -Performance Run #4: 1473.605485ms -Performance Run #5: 1457.455283ms -Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms) -``` +1. Place all of your runtime rules in the same directory (e.g., `eslint_rules`). +2. Create a [configuration file](../use/configure/) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `"warn"` or `"error"` in the configuration file. +3. Run the [command line interface](../use/command-line-interface) using the `--rulesdir` option to specify the location of your runtime rules. -### Per-rule Performance +## Profile Rule Performance -ESLint has a built-in method to track performance of individual rules. Setting the `TIMING` environment variable will trigger the display, upon linting completion, of the ten longest-running rules, along with their individual running time (rule creation + rule execution) and relative performance impact as a percentage of total rule processing time (rule creation + rule execution). +ESLint has a built-in method to track the performance of individual rules. Setting the `TIMING` environment variable will trigger the display, upon linting completion, of the ten longest-running rules, along with their individual running time (rule creation + rule execution) and relative performance impact as a percentage of total rule processing time (rule creation + rule execution). ```bash $ TIMING=1 eslint lib @@ -758,21 +741,3 @@ quotes | 18.066 | 100.0% ``` To see a longer list of results (more than 10), set the environment variable to another value such as `TIMING=50` or `TIMING=all`. - -## Rule Naming Conventions - -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. -* Use dashes between words. - -## Runtime Rules - -The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with. With runtime rules, you don't have to wait for the next version of ESLint or be disappointed that your rule isn't general enough to apply to the larger JavaScript community, just write your rules and include them at runtime. - -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 (e.g., `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 `"warn"` or `"error"` 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. diff --git a/docs/src/extend/index.md b/docs/src/extend/index.md new file mode 100644 index 00000000000..9f623a6119d --- /dev/null +++ b/docs/src/extend/index.md @@ -0,0 +1,50 @@ +--- +title: Extend ESLint +eleventyNavigation: + key: extend eslint + title: Extend ESLint + order: 2 + +--- + +This guide is intended for those who wish to extend the functionality of ESLint. + +In order to extend ESLint, it's recommended that: + +* You know JavaScript, since ESLint is written in JavaScript. +* You have some familiarity with Node.js, since ESLint runs on it. +* You're comfortable with command-line programs. + +If that sounds like you, then continue reading to get started. + +## [Ways to Extend ESLint](ways-to-extend) + +This page summarizes the various ways that you can extend ESLint and how these extensions all fit together. + +## [Create Plugins](plugins) + +You've developed custom rules for ESLint and you want to share them with the community. You can publish an ESLint plugin on npm. + +## [Custom Rules](custom-rules) + +This section explains how to create custom rules to use with ESLint. + +## [Custom Formatters](custom-formatters) + +This section explains how you can create a custom formatter to control what ESLint outputs. + +## [Custom Parsers](custom-parsers) + +If you don't want to use the default parser of ESLint, this section explains how to create custom parsers. + +## [Custom Processors](custom-processors) + +This section explains how you can use a custom processor to have ESLint process files other than JavaScript. + +## [Share Configurations](shareable-configs) + +This section explains how you can bundle and share ESLint configuration in a JavaScript package. + +## [Node.js API Reference](../integrate/nodejs-api) + +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/src/extend/plugins.md b/docs/src/extend/plugins.md new file mode 100644 index 00000000000..9d7983e67f2 --- /dev/null +++ b/docs/src/extend/plugins.md @@ -0,0 +1,195 @@ +--- +title: Create Plugins +eleventyNavigation: + key: create plugins + parent: extend eslint + title: Create Plugins + order: 2 + +--- + +An ESLint plugin is an extension for ESLint that adds additional rules and configuration options. Plugins let you customize your ESLint configuration to enforce rules that are not included in the core ESLint package. Plugins can also provide additional environments, custom processors, and configurations. + +## Name a Plugin + +Each plugin is an npm module with a name in the format of `eslint-plugin-`, such as `eslint-plugin-jquery`. You can also use scoped packages in the format of `@/eslint-plugin-` such as `@jquery/eslint-plugin-jquery` or even `@/eslint-plugin` such as `@jquery/eslint-plugin`. + +## Create a Plugin + +The easiest way to start creating a plugin is to use the [Yeoman generator](https://www.npmjs.com/package/generator-eslint). The generator will guide you through setting up the skeleton of a plugin. + +### Metadata in Plugins + +For easier debugging and more effective caching of plugins, it's recommended to provide a name and version in a `meta` object at the root of your plugin, like this: + +```js +// preferred location of name and version +module.exports = { + meta: { + name: "eslint-plugin-custom", + version: "1.2.3" + } +}; +``` + +The `meta.name` property should match the npm package name for your plugin and the `meta.version` property should match the npm package version for your plugin. The easiest way to accomplish this is by reading this information from your `package.json`. + +As an alternative, you can also expose `name` and `version` properties at the root of your plugin, such as: + +```js +// alternate location of name and version +module.exports = { + name: "eslint-plugin-custom", + version: "1.2.3" +}; +``` + +While the `meta` object is the preferred way to provide the plugin name and version, this format is also acceptable and is provided for backward compatibility. + +### Rules in Plugins + +Plugins can expose custom rules for use in ESLint. To do so, the plugin must export a `rules` object containing a key-value mapping of rule ID to rule. The rule ID does not have to follow any naming convention (so it can just be `dollar-sign`, for instance). To learn more about creating custom rules in plugins, refer to [Custom Rules](custom-rules). + +```js +module.exports = { + rules: { + "dollar-sign": { + create: function (context) { + // rule implementation ... + } + } + } +}; +``` + +To use the rule in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the rule name. So if this plugin were named `eslint-plugin-myplugin`, then in your configuration you'd refer to the rule by the name `myplugin/dollar-sign`. Example: `"rules": {"myplugin/dollar-sign": 2}`. + +### Environments in Plugins + +Plugins can expose additional environments for use in ESLint. To do so, the plugin must export an `environments` object. The keys of the `environments` object are the names of the different environments provided and the values are the environment settings. For example: + +```js +module.exports = { + environments: { + jquery: { + globals: { + $: false + } + } + } +}; +``` + +There's a `jquery` environment defined in this plugin. To use the environment in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the environment name. So if this plugin were named `eslint-plugin-myplugin`, then you would set the environment in your configuration to be `"myplugin/jquery"`. + +Plugin environments can define the following objects: + +1. `globals` - acts the same `globals` in a configuration file. The keys are the names of the globals and the values are `true` to allow the global to be overwritten and `false` to disallow. +1. `parserOptions` - acts the same as `parserOptions` in a configuration file. + +### Processors in Plugins + +You can add processors to plugins by including the processor functions in the `processors` key. For more information on defining custom processors, refer to [Custom Processors](custom-processors). + +```js +module.exports = { + processors: { + // This processor will be applied to `*.md` files automatically. + ".md": { + preprocess(text, filename) { /* ... */ }, + postprocess(messages, filename) { /* ... */ } + } + "processor-name": { + preprocess: function(text, filename) {/* ... */}, + + postprocess: function(messages, filename) { /* ... */ }, + } + } +} +``` + +### Configs in Plugins + +You can bundle configurations inside a plugin by specifying them under the `configs` key. This can be useful when you want to bundle a set of custom rules with additional configuration. Multiple configurations are supported per plugin. + +You can include individual rules from a plugin in a config that's also included in the plugin. In the config, you must specify your plugin name in the `plugins` array as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the short or long plugin name. + +```js +// eslint-plugin-myPlugin + +module.exports = { + configs: { + myConfig: { + plugins: ["myPlugin"], + env: ["browser"], + rules: { + semi: "error", + "myPlugin/my-rule": "error", + "eslint-plugin-myPlugin/another-rule": "error" + } + }, + myOtherConfig: { + plugins: ["myPlugin"], + env: ["node"], + rules: { + "myPlugin/my-rule": "off", + "eslint-plugin-myPlugin/another-rule": "off", + "eslint-plugin-myPlugin/yet-another-rule": "error" + } + } + }, + rules: { + "my-rule": {/* rule definition */}, + "another-rule": {/* rule definition */}, + "yet-another-rule": {/* rule definition */} + } +}; +``` + +Plugins cannot force a specific configuration to be used. Users must manually include a plugin's configurations in their configuration file. + +If the example plugin above were called `eslint-plugin-myPlugin`, the `myConfig` and `myOtherConfig` configurations would then be usable in a configuration file by extending `"plugin:myPlugin/myConfig"` and `"plugin:myPlugin/myOtherConfig"`, respectively. + +```json +// .eslintrc.json + +{ + "extends": ["plugin:myPlugin/myConfig"] +} + +``` + +### Peer Dependency + +To make clear that the plugin requires ESLint to work correctly, you must declare ESLint as a peer dependency by mentioning it in the `peerDependencies` field of your plugin's `package.json`. + +Plugin support was introduced in ESLint version `0.8.0`. Ensure the `peerDependencies` points to ESLint `0.8.0` or later. + +```json +{ + "peerDependencies": { + "eslint": ">=0.8.0" + } +} +``` + +## Testing + +ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility to make it easy to test the rules of your plugin. + +## Linting + +ESLint plugins should be linted too! It's suggested to lint your plugin with the `recommended` configurations of: + +* [eslint](https://www.npmjs.com/package/eslint) +* [eslint-plugin-eslint-plugin](https://www.npmjs.com/package/eslint-plugin-eslint-plugin) +* [eslint-plugin-node](https://www.npmjs.com/package/eslint-plugin-node) + +## Share Plugins + +In order to make your plugin available to the community you have to publish it on npm. + +To make it easy for others to find your plugin, add these [keywords](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) to your `package.json` file: + +* `eslint` +* `eslintplugin` diff --git a/docs/src/developer-guide/scope-manager-interface.md b/docs/src/extend/scope-manager-interface.md similarity index 98% rename from docs/src/developer-guide/scope-manager-interface.md rename to docs/src/extend/scope-manager-interface.md index 4a6dc3302bb..60e26431c0c 100644 --- a/docs/src/developer-guide/scope-manager-interface.md +++ b/docs/src/extend/scope-manager-interface.md @@ -1,7 +1,5 @@ --- title: ScopeManager -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/scope-manager-interface.md --- diff --git a/docs/src/developer-guide/selectors.md b/docs/src/extend/selectors.md similarity index 89% rename from docs/src/developer-guide/selectors.md rename to docs/src/extend/selectors.md index 1ee40c7d6c4..5ea700dead1 100644 --- a/docs/src/developer-guide/selectors.md +++ b/docs/src/extend/selectors.md @@ -1,15 +1,13 @@ --- title: Selectors -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/selectors.md --- Some rules and APIs allow the use of selectors to query an AST. This page is intended to: 1. Explain what selectors are -1. Describe the syntax for creating selectors -1. Describe what selectors can be used for +2. Describe the syntax for creating selectors +3. Describe what selectors can be used for ## What is a selector? @@ -95,7 +93,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](../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: ```json { @@ -139,4 +137,16 @@ Using selectors in the `no-restricted-syntax` rule can give you a lot of control ### Known issues -Due to a [bug](https://github.com/estools/esquery/issues/68) in [esquery](https://github.com/estools/esquery), regular expressions that contain a forward-slash character `/` aren't properly parsed, so `[value=/some\/path/]` will be a syntax error. As a [workaround](https://github.com/estools/esquery/issues/68), you can replace the `/` character with its unicode counterpart, like so: `[value=/some\\u002Fpath/]`. +Due to a [bug](https://github.com/estools/esquery/issues/68) in [esquery](https://github.com/estools/esquery), regular expressions that contain a forward-slash character `/` aren't properly parsed, so `[value=/some\/path/]` will be a syntax error. As a [workaround](https://github.com/estools/esquery/issues/68), you can replace the `/` character with its unicode counterpart, like so: `[value=/some\u002Fpath/]`. + +For example, the following configuration disallows importing from `some/path`: + +```json +{ + "rules": { + "no-restricted-syntax": ["error", "ImportDeclaration[source.value=/^some\\u002Fpath$/]"] + } +} +``` + +Note that the `\` character needs to be escaped (`\\`) in JSON and string literals. diff --git a/docs/src/extend/shareable-configs.md b/docs/src/extend/shareable-configs.md new file mode 100644 index 00000000000..60b35321efd --- /dev/null +++ b/docs/src/extend/shareable-configs.md @@ -0,0 +1,237 @@ +--- +title: Share Configurations +eleventyNavigation: + key: share configs + parent: extend eslint + title: Share Configurations + order: 3 + +--- + +To share your ESLint configuration, create a **shareable config**. You can publish your shareable config on [npm](https://www.npmjs.com/) so that others can download and use it in their ESLint projects. + +This page explains how to create and publish a shareable config. + +## 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. + +The module name must take one of the following forms: + +* Begin with `eslint-config-`, such as `eslint-config-myconfig`. +* Be an npm [scoped module](https://docs.npmjs.com/misc/scope). To create a scoped module, name or prefix the module with `@scope/eslint-config`, such as `@scope/eslint-config` or `@scope/eslint-config-myconfig`. + +In your module, export the shareable config from the module's [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point file. The default main entry point is `index.js`. For example: + +```js +// index.js +module.exports = { + + globals: { + MyGlobal: true + }, + + rules: { + semi: [2, "always"] + } + +}; +``` + +Since the `index.js` file is just JavaScript, you can read these settings from a file or generate them dynamically. + +## Publishing a Shareable Config + +Once your shareable config is ready, you can [publish it to npm](https://docs.npmjs.com/getting-started/publishing-npm-packages) to share it with others. We recommend using the `eslint` and `eslintconfig` [keywords](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) in the `package.json` file so others can easily find your module. + +You should declare your dependency on ESLint in the `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: + +```json +{ + "peerDependencies": { + "eslint": ">= 3" + } +} +``` + +If your shareable config depends on a plugin, you should also specify it as a `peerDependency` (plugins will be loaded relative to the end user's project, so the end user is required to install the plugins they need). However, if your shareable config depends on a [custom parser](custom-parsers) or another shareable config, you can specify these packages as `dependencies` in the `package.json`. + +You can also test your shareable config on your computer before publishing by linking your module globally. Type: + +```bash +npm link +``` + +Then, in your project that wants to use your shareable config, type: + +```bash +npm link eslint-config-myconfig +``` + +Be sure to replace `eslint-config-myconfig` with the actual name of your module. + +## Using a Shareable Config + +To use a shareable config, include the config name in the `extends` field of a configuration file. For the value, use your module name. For example: + +```json +{ + "extends": "eslint-config-myconfig" +} +``` + +You can also omit the `eslint-config-` and it is automatically assumed by ESLint: + +```json +{ + "extends": "myconfig" +} +``` + +You cannot use shareable configs with the ESLint CLI [`--config`](../use/command-line-interface#-c---config) flag. + +### npm Scoped Modules + +npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported in a number of ways. + +You can use the module name: + +```json +{ + "extends": "@scope/eslint-config" +} +``` + +You can also omit the `eslint-config` and it is automatically assumed by ESLint: + +```json +{ + "extends": "@scope" +} +``` + +The module name can also be customized. For example, if you have a package named `@scope/eslint-config-myconfig`, the configuration can be specified as: + +```json +{ + "extends": "@scope/eslint-config-myconfig" +} +``` + +You could also omit `eslint-config` to specify the configuration as: + +```json +{ + "extends": "@scope/myconfig" +} +``` + +### Overriding Settings from Shareable Configs + +You can override settings from the shareable config by adding them directly into your `.eslintrc` file. + +## Sharing Multiple Configs + +You can share multiple configs in the same npm package. Specify a default config for the package by following the directions in the [Creating a Shareable Config](#creating-a-shareable-config) section. You can specify additional shareable configs by adding a new file to your npm package and then referencing it from your ESLint config. + +As an example, you can create a file called `my-special-config.js` in the root of your npm package and export a config, such as: + +```js +// my-special-config.js +module.exports = { + rules: { + quotes: [2, "double"] + } +}; +``` + +Then, assuming you're using the package name `eslint-config-myconfig`, you can access the additional config via: + +```json +{ + "extends": "myconfig/my-special-config" +} +``` + +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. + +**Important:** We strongly recommend always including a default config for your plugin to avoid errors. + +## Local Config File Resolution + +If you need to make multiple configs that can extend each other and live in different directories, you can create a single shareable config that handles this scenario. + +As an example, let's assume you're using the package name `eslint-config-myconfig` and your package looks something like this: + +```text +myconfig +├── index.js +└─┬ lib + ├── defaults.js + ├── dev.js + ├── ci.js + └─┬ ci + ├── frontend.js + ├── backend.js + └── common.js +``` + +In the `index.js` file, you can do something like this: + +```js +module.exports = require('./lib/ci.js'); +``` + +Now inside the package you have `/lib/defaults.js`, which contains: + +```js +module.exports = { + rules: { + 'no-console': 1 + } +}; +``` + +Inside `/lib/ci.js` you have: + +```js +module.exports = require('./ci/backend'); +``` + +Inside `/lib/ci/common.js`: + +```js +module.exports = { + rules: { + 'no-alert': 2 + }, + extends: 'myconfig/lib/defaults' +}; +``` + +Despite being in an entirely different directory, you'll see that all `extends` must use the full package path to the config file you wish to extend. + +Now inside `/lib/ci/backend.js`: + +```js +module.exports = { + rules: { + 'no-console': 1 + }, + extends: 'myconfig/lib/ci/common' +}; +``` + +In the last file, once again see that to properly resolve your config, you need to include the full package path. + +## Further Reading + +* [npm Developer Guide](https://docs.npmjs.com/misc/developers) diff --git a/docs/src/extend/ways-to-extend.md b/docs/src/extend/ways-to-extend.md new file mode 100644 index 00000000000..0432492b60e --- /dev/null +++ b/docs/src/extend/ways-to-extend.md @@ -0,0 +1,60 @@ +--- +title: Ways to Extend ESLint +eleventyNavigation: + key: ways to extend + parent: extend eslint + title: Ways to Extend ESLint + order: 1 +--- + +ESLint is highly pluggable and configurable. There are a variety of ways that you can extend ESLint's functionality. + +This page explains the ways to extend ESLint, and how these extensions all fit together. + +## Plugins + +Plugins let you add your own ESLint custom rules and custom processors to a project. You can publish a plugin as an npm module. + +Plugins are useful because your project may require some ESLint configuration that isn't included in the core `eslint` package. For example, if you're using a frontend JavaScript library like React or framework like Vue, these tools have some features that require custom rules outside the scope of the ESLint core rules. + +Often a plugin is paired with a configuration for ESLint that applies a set of features from the plugin to a project. You can include configurations in a plugin as well. + +For example, [`eslint-plugin-react`](https://www.npmjs.com/package/eslint-plugin-react) is an ESLint plugin that includes rules specifically for React projects. The rules include things like enforcing consistent usage of React component lifecycle methods and requiring the use of key props when rendering dynamic lists. + +To learn more about creating the extensions you can include in a plugin, refer to the following documentation: + +* [Custom Rules](custom-rules) +* [Custom Processors](custom-processors) +* [Configs in Plugins](plugins#configs-in-plugins) + +To learn more about bundling these extensions into a plugin, refer to [Plugins](plugins). + +## Shareable Configs + +ESLint shareable configs are pre-defined configurations for ESLint that you can use in your projects. They bundle rules and other configuration together in an npm package. Anything that you can put in a configuration file can be put in a shareable config. + +You can either publish a shareable config independently or as part of a plugin. + +For example, a popular shareable config is [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb), which contains a variety of rules in addition to some [parser options](../use/configure/language-options#specifying-parser-options). This is a set of rules for ESLint that is designed to match the style guide used by the [Airbnb JavaScript style guide](https://github.com/airbnb/javascript). By using the `eslint-config-airbnb` shareable config, you can automatically enforce the Airbnb style guide in your project without having to manually configure each rule. + +To learn more about creating a shareable config, refer to [Share Configuration](shareable-configs). + +## Custom Formatters + +Custom formatters take ESLint linting results and output the results in a format that you define. Custom formatters let you display linting results in a format that best fits your needs, whether that's in a specific file format, a certain display style, or a format optimized for a particular tool. You only need to create a custom formatter if the [built-in formatters](../use/formatters/) don't serve your use case. + +For example, the custom formatter [eslint-formatter-gitlab](https://www.npmjs.com/package/eslint-formatter-gitlab) can be used to display ESLint results in GitLab code quality reports. + +To learn more about creating a custom formatter, refer to [Custom Formatters](custom-formatters). + +## Custom Parsers + +ESLint custom parsers are a way to extend ESLint to support the linting of new language features or custom syntax in your code. A parser is responsible for taking your code and transforming it into an abstract syntax tree (AST) that ESLint can then analyze and lint. + +ESLint ships with a built-in JavaScript parser (Espree), but custom parsers allow you to lint other languages or to extend the linting capabilities of the built-in parser. + +For example, the custom parser [@typescript-eslint/parser](https://typescript-eslint.io/architecture/parser/) extends ESLint to lint TypeScript code. + +Custom parsers **cannot** be included in a plugin, unlike the other extension types. + +To learn more about creating a custom parser, refer to [Custom Parsers](custom-parsers). diff --git a/docs/src/developer-guide/nodejs-api.md b/docs/src/integrate/nodejs-api.md similarity index 91% rename from docs/src/developer-guide/nodejs-api.md rename to docs/src/integrate/nodejs-api.md index cefd83a1470..de035ca731c 100644 --- a/docs/src/developer-guide/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -1,13 +1,10 @@ --- -title: Node.js API -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/nodejs-api.md +title: Node.js API Reference eleventyNavigation: key: node.js api - parent: developer guide - title: Node.js API - order: 9 - + parent: extend eslint + title: Node.js API Reference + order: 6 --- While ESLint is designed to be run on the command line, it's possible to use ESLint programmatically through the Node.js API. The purpose of the Node.js API is to allow plugin and tool authors to use the ESLint functionality directly, without going through the command line interface. @@ -26,48 +23,92 @@ Here's a simple example of using the `ESLint` class: const { ESLint } = require("eslint"); (async function main() { - // 1. Create an instance. - const eslint = new ESLint(); + // 1. Create an instance. + const eslint = new ESLint(); - // 2. Lint files. - const results = await eslint.lintFiles(["lib/**/*.js"]); + // 2. Lint files. + const results = await eslint.lintFiles(["lib/**/*.js"]); - // 3. Format the results. - const formatter = await eslint.loadFormatter("stylish"); - const resultText = formatter.format(results); + // 3. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); - // 4. Output it. - console.log(resultText); + // 4. Output it. + console.log(resultText); })().catch((error) => { - process.exitCode = 1; - console.error(error); + process.exitCode = 1; + console.error(error); }); ``` -And here is an example that autofixes lint problems: +Here's an example that autofixes lint problems: ```js const { ESLint } = require("eslint"); (async function main() { - // 1. Create an instance with the `fix` option. - const eslint = new ESLint({ fix: true }); + // 1. Create an instance with the `fix` option. + const eslint = new ESLint({ fix: true }); + + // 2. Lint files. This doesn't modify target files. + const results = await eslint.lintFiles(["lib/**/*.js"]); + + // 3. Modify the files with the fixed code. + await ESLint.outputFixes(results); + + // 4. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); + + // 5. Output it. + console.log(resultText); +})().catch((error) => { + process.exitCode = 1; + console.error(error); +}); +``` - // 2. Lint files. This doesn't modify target files. - const results = await eslint.lintFiles(["lib/**/*.js"]); +And here is an example of using the `ESLint` class with `lintText` API: + +```js +const { ESLint } = require("eslint"); + +const testCode = ` + const name = "eslint"; + if(true) { + console.log("constant condition warning") + }; +`; + +(async function main() { + // 1. Create an instance + const eslint = new ESLint({ + useEslintrc: false, + overrideConfig: { + extends: ["eslint:recommended"], + parserOptions: { + sourceType: "module", + ecmaVersion: "latest", + }, + env: { + es2022: true, + node: true, + }, + }, + }); - // 3. Modify the files with the fixed code. - await ESLint.outputFixes(results); + // 2. Lint text. + const results = await eslint.lintText(testCode); - // 4. Format the results. - const formatter = await eslint.loadFormatter("stylish"); - const resultText = formatter.format(results); + // 3. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); - // 5. Output it. - console.log(resultText); + // 4. Output it. + console.log(resultText); })().catch((error) => { - process.exitCode = 1; - console.error(error); + process.exitCode = 1; + console.error(error); }); ``` @@ -411,8 +452,8 @@ This edit information means replacing the range of the `range` property by the ` The `LoadedFormatter` value is the object to convert the [LintResult] objects to text. The [eslint.loadFormatter()][eslint-loadformatter] method returns it. It has the following method: -* `format` (`(results: LintResult[]) => string | Promise`)
    - The method to convert the [LintResult] objects to text. +* `format` (`(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise`)
    + The method to convert the [LintResult] objects to text. `resultsMeta` is an object that will contain a `maxWarningsExceeded` object if `--max-warnings` was set and the number of warnings exceeded the limit. The `maxWarningsExceeded` object will contain two properties: `maxWarnings`, the value of the `--max-warnings` option, and `foundWarnings`, the number of lint warnings. --- @@ -465,11 +506,11 @@ const codeLines = SourceCode.splitLines(code); ## 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. Unless you are working in the browser, you probably want to use the [ESLint class](#eslint-class) class instead. +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. Unless you are working in the browser, you probably want to use the [ESLint class](#eslint-class) instead. The `Linter` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are: -* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules by calling `context.getCwd()` (see [The Context Object](./working-with-rules#the-context-object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. +* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules by calling `context.getCwd()` (see [The Context Object](../extend/custom-rules#the-context-object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. For example: @@ -491,8 +532,8 @@ The most important method on `Linter` is `verify()`, which initiates linting of * **Note**: If you want to lint text and have your configuration be read and processed, use [`ESLint#lintFiles()`][eslint-lintfiles] or [`ESLint#lintText()`][eslint-linttext] instead. * `options` - (optional) Additional options for this run. * `filename` - (optional) the filename to associate with the source code. - * `preprocess` - (optional) A function that [Processors in Plugins](/docs/developer-guide/working-with-plugins#processors-in-plugins) documentation describes as the `preprocess` method. - * `postprocess` - (optional) A function that [Processors in Plugins](/docs/developer-guide/working-with-plugins#processors-in-plugins) documentation describes as the `postprocess` method. + * `preprocess` - (optional) A function that [Processors in Plugins](../extend/plugins#processors-in-plugins) documentation describes as the `preprocess` method. + * `postprocess` - (optional) A function that [Processors in Plugins](../extend/plugins#processors-in-plugins) documentation describes as the `postprocess` method. * `filterCodeBlock` - (optional) A function that decides which code blocks the linter should adopt. The function receives two arguments. The first argument is the virtual filename of a code block. The second argument is the text of the code block. If the function returned `true` then the linter adopts the code block. If the function was omitted, the linter adopts only `*.js` code blocks. If you provided a `filterCodeBlock` function, it overrides this default behavior, so the linter doesn't adopt `*.js` code blocks automatically. * `disableFixes` - (optional) when set to `true`, the linter doesn't make either the `fix` or `suggestions` property of the lint result. * `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing ESLint rules. @@ -556,7 +597,7 @@ The information available for each linting message is: * `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). -* `suggestions` - an array of objects describing possible lint fixes for editors to programmatically enable (see details in the [Working with Rules docs](./working-with-rules#providing-suggestions)). +* `suggestions` - an array of objects describing possible lint fixes for editors to programmatically enable (see details in the [Working with Rules docs](../extend/custom-rules#providing-suggestions)). You can get the suppressed messages from the previous run by `getSuppressedMessages()` method. If there is not a previous run, `getSuppressedMessage()` will return an empty list. @@ -688,7 +729,7 @@ Map { ### 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-custom-parsers) as second argument. The default `"espree"` parser will already be loaded for every `Linter` instance. +with the name of the parser as first argument and the [parser object](../extend/custom-parsers) as second argument. The default `"espree"` parser will already be loaded for every `Linter` instance. ```js const Linter = require("eslint").Linter; @@ -768,7 +809,7 @@ 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)) +* The rule object itself (see ["working with rules"](../extend/custom-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: @@ -919,23 +960,14 @@ ruleTester.run("my-rule", myRule, { --- -[configuration object]: ../user-guide/configuring/ -[builtin-formatters]: https://eslint.org/docs/user-guide/formatters/ +[configuration object]: ../use/configure/ +[builtin-formatters]: ../use/formatters/ [third-party-formatters]: https://www.npmjs.com/search?q=eslintformatter -[eslint]: #eslint-class -[eslint-constructor]: #-new-eslintoptions [eslint-lintfiles]: #-eslintlintfilespatterns [eslint-linttext]: #-eslintlinttextcode-options -[eslint-getrulesmetaforresults]: #-eslintgetrulesmetaforresultsresults -[eslint-calculateconfigforfile]: #-eslintcalculateconfigforfilefilepath -[eslint-ispathignored]: #-eslintispathignoredfilepath [eslint-loadformatter]: #-eslintloadformatternameorpath -[eslint-version]: #-eslintversion -[eslint-outputfixes]: #-eslintoutputfixesresults -[eslint-geterrorresults]: #-eslintgeterrorresultsresults [lintresult]: #-lintresult-type [lintmessage]: #-lintmessage-type [suppressedlintmessage]: #-suppressedlintmessage-type [editinfo]: #-editinfo-type [loadedformatter]: #-loadedformatter-type -[linter]: #linter diff --git a/docs/src/library/rule-categories.md b/docs/src/library/rule-categories.md index 9f6688f8a73..8934fe7ecac 100644 --- a/docs/src/library/rule-categories.md +++ b/docs/src/library/rule-categories.md @@ -4,7 +4,7 @@ title: Rule categories ## Rule categories -The rule categories—namely “recommended”, “fixable”, and “hasSuggestions”—are shown in the [rules page](/rules/). They are rendered using the `ruleCategories` macro (imported from `/components/rule-categories.macro.html`). There is also an individual macro for each category type. +The rule categories—namely “recommended”, “fixable”, and “hasSuggestions”—are shown in the [rules page](../rules/). They are rendered using the `ruleCategories` macro (imported from `/components/rule-categories.macro.html`). There is also an individual macro for each category type. ```html { % from 'components/rule-categories.macro.html' import ruleCategories % } diff --git a/docs/src/maintain/index.md b/docs/src/maintain/index.md new file mode 100644 index 00000000000..29c3ab89f30 --- /dev/null +++ b/docs/src/maintain/index.md @@ -0,0 +1,30 @@ +--- +title: Maintain ESLint +eleventyNavigation: + key: maintain eslint + title: Maintain ESLint + order: 4 + +--- + +This guide is intended for those who work as part of the ESLint project team. + +## [How ESLint is Maintained](overview) + +Explains how ESLint is maintained, including information about team, governance, and funding. + +## [Manage Issues](manage-issues) + +Describes how to deal with issues when they're opened, how to interact with users who open issues, and how to close issues effectively. + +## [Review Pull Requests](review-pull-requests) + +Describes how to review incoming pull requests. + +## [Manage Releases](manage-releases) + +Describes how to do an ESLint project release. + +## [Working Groups](working-groups) + +Describes how working groups are created and how they function within the ESLint project. diff --git a/docs/src/maintainer-guide/issues.md b/docs/src/maintain/manage-issues.md similarity index 74% rename from docs/src/maintainer-guide/issues.md rename to docs/src/maintain/manage-issues.md index c97a9cc359f..6cfdbc93b19 100644 --- a/docs/src/maintainer-guide/issues.md +++ b/docs/src/maintain/manage-issues.md @@ -1,12 +1,10 @@ --- -title: Managing Issues -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/maintainer-guide/issues.md +title: Manage Issues eleventyNavigation: - key: managing issues - parent: maintainer guide - title: Managing Issues - order: 1 + key: manage issues + parent: maintain eslint + title: Manage Issues + order: 2 --- @@ -14,7 +12,7 @@ New issues are filed frequently, and how we respond to those issues directly aff ## Things to Keep in Mind -1. **Be nice.** Even if the people are being rude or aggressive on an issue, as a project team member you must be the mature one in the conversation. Do your best to work with everyone no matter their style. Remember, poor wording choice can also be a sign of someone who doesn't know English very well, so be sure to consider that when trying to determine the tone of someone's message. Being rude, even when someone is being rude to you, reflects poorly on the team and the project as a whole. +1. **Be nice.** Even if the people are being rude or aggressive on an issue, you must be the mature one in the conversation as a project team member. Do your best to work with everyone no matter their style. Remember, poor wording choice can also be a sign of someone who doesn't know English very well, so be sure to consider that when trying to determine the tone of someone's message. Being rude, even when someone is being rude to you, reflects poorly on the team and the project as a whole. 1. **Be inquisitive.** Ask questions on the issue whenever something isn't clear. Don't assume you understand what's being reported if there are details missing. Whenever you are unsure, it's best to ask for more information. 1. **Not all requests are equal.** It's unlikely we'll be able to accommodate every request, so don't be afraid to say that something doesn't fit into the scope of the project or isn't practical. It's better to give such feedback if that's the case. 1. **Close when appropriate.** Don't be afraid to close issues that you don't think will be done, or when it's become clear from the conversation that there's no further work to do. Issues can always be reopened if they are closed incorrectly, so feel free to close issues when appropriate. Just be sure to leave a comment explaining why the issue is being closed (if not closed by a commit). @@ -23,10 +21,10 @@ New issues are filed frequently, and how we respond to those issues directly aff There are four primary issue categories: -1. **Bug** - something isn't working the way it's expected to work. -1. **Enhancement** - a change to something that already exists. For instance, adding a new option to an existing rule or a bug in a rule where fixing it will result in the rule reporting more problems (in this case, use both "Bug" and "Enhancement"). -1. **Feature** - adding something that doesn't already exist. For example, adding a new rule, new formatter, or new command line flag. -1. **Question** - an inquiry about how something works that won't result in a code change. We'd prefer if people use the mailing list or chatroom for questions, but sometimes they'll open an issue. +1. **Bug**: Something isn't working the way it's expected to work. +1. **Enhancement**: A change to something that already exists. For instance, adding a new option to an existing rule or fixing a bug in a rule where fixing it will result in the rule reporting more problems (in this case, use both "Bug" and "Enhancement"). +1. **Feature**: Adding something that doesn't already exist. For example, adding a new rule, new formatter, or new command line flag. +1. **Question**: An inquiry about how something works that won't result in a code change. We prefer if people use GitHub Discussions or Discord for questions, but sometimes they'll open an issue. The first goal when evaluating an issue is to determine which category the issue falls into. @@ -34,17 +32,17 @@ The first goal when evaluating an issue is to determine which category the issue All of ESLint's issues, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/2). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: -* **Needs Triage** - issues that have not yet been reviewed by anyone -* **Triaging** - issues that someone has reviewed but has not been able to fully triage yet -* **Ready for Dev Team** - issues that have been triaged and have all of the information necessary for the dev team to take a look -* **Evaluating** - the dev team is evaluating these issues to determine whether to move forward or not -* **Feedback Needed** - a team member is requesting more input from the rest of the team before proceeding -* **Waiting for RFC** - the next step in the process is for an RFC to be written -* **RFC Opened** - an RFC is opened to address these issues -* **Blocked** - the issue can't move forward due to some dependency -* **Ready to Implement** - these issues have all of the details necessary to start implementation -* **PR Opened** - there is an open pull request for each of these issues -* **Completed** - the issue has been closed (either via pull request merge or by the team manually closing the issue) +* **Needs Triage**: Issues that have not yet been reviewed by anyone +* **Triaging**: Issues that someone has reviewed but has not been able to fully triage yet +* **Ready for Dev Team**: Issues that have been triaged and have all the information necessary for the dev team to take a look +* **Evaluating**: The dev team is evaluating these issues to determine whether to move forward or not +* **Feedback Needed**: A team member is requesting more input from the rest of the team before proceeding +* **Waiting for RFC**: The next step in the process is for an RFC to be written +* **RFC Opened**: An RFC is opened to address these issues +* **Blocked**: The issue can't move forward due to some dependency +* **Ready to Implement**: These issues have all the details necessary to start implementation +* **PR Opened**: There is an open pull request for each of these issues +* **Completed**: The issue has been closed (either via pull request merge or by the team manually closing the issue) We make every attempt to automate movement between as many columns as we can, but sometimes moving issues needs to be done manually. @@ -52,12 +50,12 @@ We make every attempt to automate movement between as many columns as we can, bu When an issue is opened, it is automatically added to the "Needs Triage" column in the Triage project. These issues need to be evaluated to determine next steps. Anyone on the support team or dev team can follow these steps to properly triage issues. -**Note:** If an issue is in the "Triaging" column, that means someone is already triaging it and you should let them finish. There's no need to comment on issues in the "Triaging" column unless someone asks for help. +**Note:** If an issue is in the "Triaging" column, that means someone is already triaging it, and you should let them finish. There's no need to comment on issues in the "Triaging" column unless someone asks for help. The steps for triaging an issue are: -1. Move the issue from "Needs Triage" to "Triaging" in the Triage project -1. Check: Has all of the information in the issue template been provided? +1. Move the issue from "Needs Triage" to "Triaging" in the Triage project. +1. Check: Has all the information in the issue template been provided? * **No:** If information is missing from the issue template, or you can't tell what is being requested, please ask the author to provide the missing information: * Add the "needs info" label to the issue so we know that this issue is stalled due to lack of information. * Don't move on to other steps until the necessary information has been provided. @@ -67,15 +65,15 @@ The steps for triaging an issue are: * If the issue is reporting a bug, try to reproduce the issue following the instructions in the issue. If you can reproduce the bug, please add the "repro:yes" label. (The bot will automatically remove the "repro:needed" label.) If you can't reproduce the bug, ask the author for more information about their environment or to clarify reproduction steps. * If the issue is reporting something that works as intended, please add the "works as intended" label and close the issue. * For all issues, please add labels describing the part of ESLint affected: - * "3rd party plugin" - related to third-party functionality (plugins, parsers, rules, etc.) - * "build" - related to commands run during a build (testing, linting, release scripts, etc.) - * "cli" - related to command line input or output, or to `CLIEngine` - * "core" - related to internal APIs - * "documentation" - related to content on eslint.org - * "infrastructure" - related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) - * "rule" - related to core rules - * If you can't properly triage the issue, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it - * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project + * **3rd party plugin**: Related to third-party functionality (plugins, parsers, rules, etc.) + * **build**: Related to commands run during a build (testing, linting, release scripts, etc.) + * **cli**: Related to command line input or output, or to `CLIEngine` + * **core**: Related to internal APIs + * **documentation**: Related to content on eslint.org + * **infrastructure**: Related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) + * **rule**: Related to core rules + * If you can't properly triage the issue, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it. + * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project. ## Evaluation Process @@ -83,12 +81,12 @@ When an issue has been moved to the "Ready for Dev Team" column, any dev team me 1. Move the issue into the "Evaluating" column. 1. Next steps: - * **Bugs:** if you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request. - * **New Rules:** if you are willing to champion the rule (meaning you believe it should be included in ESLint core and you will take ownership of the process for including it), add a comment saying you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. - * **Rule Changes:** if you are willing to champion the change and it would not be a breaking change (requiring a major version increment), add a comment saying that you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. - * **Breaking Changes:** if you suspect or can verify that a change would be breaking, label it as "Breaking". - * **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. + * **Bugs**: If you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request. + * **New Rules**: If you are willing to champion the rule (meaning you believe it should be included in ESLint core and you will take ownership of the process for including it), add a comment saying you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. + * **Rule Changes**: If you are willing to champion the change and it would not be a breaking change (requiring a major version increment), add a comment saying that you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. + * **Breaking Changes**: If you suspect or can verify that a change would be breaking, label it as "Breaking". + * **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. 1. If the issue can't be implemented because it needs an external dependency to be updated or needs to wait for another issue to be resolved, move the issue to the "Blocked" column. 1. If the issue has been accepted and an RFC is required as the next step, move the issue to the "Waiting for RFC" column and comment on the issue that an RFC is needed. @@ -112,7 +110,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 "good first issue" labels, as necessary. +Once consensus has been reached on inclusion, add the "accepted" label. Optionally, add "help wanted" and "good first issue" labels, as necessary. ## Consensus @@ -131,9 +129,9 @@ The issue will be discussed at the next TSC meeting and the resolution will be p In addition to the above, changes to the core (including CLI changes) that would result in a minor or major version release must be approved by the TSC by standard TSC motion. Add the label "tsc agenda" to the issue and it will be discussed at the next TSC meeting. In general, requests should meet the following criteria to be considered: -1. The feature or enhancement is in scope for the project and should be added to the roadmap -1. Someone is committed to including the change within the next year -1. There is reasonable certainty about who will do the work +1. The feature or enhancement is in scope for the project and should be added to the roadmap. +1. Someone is committed to including the change within the next year. +1. There is reasonable certainty about who will do the work. When a suggestion is too ambitious or would take too much time to complete, it's better not to accept the proposal. Stick to small, incremental changes and lay out a roadmap of where you'd like the project to go eventually. Don't let the project get bogged down in big features that will take a long time to complete. diff --git a/docs/src/maintainer-guide/releases.md b/docs/src/maintain/manage-releases.md similarity index 67% rename from docs/src/maintainer-guide/releases.md rename to docs/src/maintain/manage-releases.md index 8b1adee6b3d..7a5e763a009 100644 --- a/docs/src/maintainer-guide/releases.md +++ b/docs/src/maintain/manage-releases.md @@ -1,12 +1,10 @@ --- -title: Managing Releases -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/maintainer-guide/releases.md +title: Manage Releases eleventyNavigation: - key: managing releases - parent: maintainer guide - title: Managing Releases - order: 3 + key: manage releases + parent: maintain eslint + title: Manage Releases + order: 4 --- @@ -15,18 +13,20 @@ Releases are when a project formally publishes a new version so the community ca * 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 +## Release Manager -A two-person release team is assigned to each scheduled release. This two-person team is responsible for: +One member of the Technical Steering Committee (TSC) is assigned to manage each scheduled release. The release manager is determined at the TSC meeting the day before the release. + +The release manager is responsible for: 1. The scheduled release on Friday 1. Monitoring issues over the weekend 1. Determining if a patch release is necessary on Monday 1. Publishing the patch release (if necessary) -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. +The release manager should seek input from the whole team on the Monday following a release to double-check if a patch release is necessary. -At least one member of the release team needs to have access to eslint's two-factor authentication for npm in order to do a release. +The release manager needs to have access to ESLint's two-factor authentication for npm in order to do a release. ## Release Communication @@ -34,10 +34,10 @@ Each scheduled release should be associated with a release issue ([example](http ## Process -On the day of a scheduled release, the release team should follow these steps: +On the day of a scheduled release, the release manager should follow these steps: 1. Review open pull requests to see if any should be merged. In general, you can merge pull requests that: - * Have been open at least two days and have been reviewed (these are just waiting for merge). + * Have been open for at least two days and approved (these are just waiting for merge). * Important pull requests (as determined by the team). You should stop and have people review before merging if they haven't been already. * Documentation changes. * Small bugfixes written by a team member. @@ -51,19 +51,23 @@ On the day of a scheduled release, the release team should follow these steps: 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 bugfixes. 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: +All release-related communications occur in the `#team` channel on Discord. + +On the Monday following the scheduled release, the release manager 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: * A regression bug is causing people's lint builds to fail when it previously passed. * Any bug that is causing a lot of problems for users (frequently happens due to new functionality). 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. -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. +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 manager 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 -In general, we try not to do emergency releases (an emergency release is unplanned and isn't the regularly scheduled release or the anticipated patch release). Even if there is a regression, it's best to wait the weekend to see if any other problems arise so a patch release can fix as many issues as possible. +An emergency release is unplanned and isn't the regularly scheduled release or the anticipated patch release. + +In general, we try not to do emergency releases. Even if there is a regression, it's best to wait until Monday to see if any other problems arise so a patch release can fix as many issues as possible. The only real exception is if ESLint is completely unusable by most of the current users. For instance, we once pushed a release that errored for everyone because it was missing some core files. In that case, an emergency release is appropriate. diff --git a/docs/src/maintain/overview.md b/docs/src/maintain/overview.md new file mode 100644 index 00000000000..362637a8a3c --- /dev/null +++ b/docs/src/maintain/overview.md @@ -0,0 +1,44 @@ +--- +title: How ESLint is Maintained +eleventyNavigation: + key: how eslint is maintained + parent: maintain eslint + title: How ESLint is Maintained + order: 1 + +--- + +This page explains the different roles and structures involved in maintaining ESLint. + +## The ESLint Team + +The ESLint team works together to develop and maintain ESLint. To learn more about the different roles on the ESLint team, refer to [Governance](../contribute/governance). To see the current team members, refer to [Team](/team/). + +## Organization Structure + +ESLint is part of the [OpenJS Foundation](https://openjsf.org/), a nonprofit organization that supports open-source projects and communities in the JavaScript ecosystem. + +The OpenJS Foundation provides legal infrastructure for JavaScript projects like ESLint. It is the owner of the intellectual property related to ESLint, including copyrights and trademarks, and ensures the independence of the project. They are also a resource for ESLint if we need legal advice or representation. + +The OpenJS Foundation does not participate in the day-to-day functioning of ESLint. + +## Funding + +ESLint is funded through several sources, including: + +* [**Open Collective**](https://opencollective.com/eslint): A platform for financing open source projects. +* [**GitHub Sponsors**](https://github.com/sponsors/eslint): A platform for funding open source projects associated with Github. +* [**Tidelift**](https://tidelift.com/subscription/pkg/npm-eslint): A subscription service that lets enterprises manage and fund the open source projects that their organization uses. +* [**Carbon Ads**](https://www.carbonads.net/open-source): Developer-centric advertising provider used on [eslint.org](https://eslint.org/). +* [**Stackaid.us**](https://simulation.stackaid.us/github/eslint/eslint): Tool that developers can use to allocate funding to the open source projects they use. + +ESLint uses this funding for the following purposes: + +* Pay team members and contractors +* Fund projects +* Pay for services that keep ESLint running (web hosting, software subscriptions, etc.) +* Provide financial support to our dependencies and ecosystem + +## Joining the Maintainer Team + +ESLint is an open-source project, and anyone can contribute to the project. If you're interested in becoming part of the maintainer team, stop by our [Discord](https://eslint.org/chat) and introduce yourself. diff --git a/docs/src/maintainer-guide/pullrequests.md b/docs/src/maintain/review-pull-requests.md similarity index 88% rename from docs/src/maintainer-guide/pullrequests.md rename to docs/src/maintain/review-pull-requests.md index c9f7c4830fd..c1711dfd65c 100644 --- a/docs/src/maintainer-guide/pullrequests.md +++ b/docs/src/maintain/review-pull-requests.md @@ -1,12 +1,10 @@ --- -title: Reviewing Pull Requests -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/maintainer-guide/pullrequests.md +title: Review Pull Requests eleventyNavigation: - key: reviewing pull requests - parent: maintainer guide - title: Reviewing Pull Requests - order: 2 + key: review pull requests + parent: maintain eslint + title: Review Pull Requests + order: 3 --- @@ -30,10 +28,10 @@ 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. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the [Code Conventions](../contribute/code-conventions) documentation. 1. For code changes: * Are there tests that verify the change? If not, please ask for them. - * Is documentation needed for the change? If yes, please let the submitter know. + * Is documentation needed for the change? If yes, please ask the submitter to add the necessary documentation. 1. Are there any automated testing errors? If yes, please ask the submitter to check on them. 1. If you've reviewed the pull request and there are no outstanding issues, leave a comment "LGTM" to indicate your approval. If you would like someone else to verify the change, comment "LGTM but would like someone else to verify." @@ -93,17 +91,17 @@ If the pull request was created from a branch on the `eslint/eslint` repository There are several times when it's appropriate to close a pull request without merging: -1. The pull request addresses an issue that is already fixed -1. The pull request hasn't been updated in 30 days +1. The pull request addresses an issue that is already fixed. +1. The pull request hasn't been updated in 17 days. 1. The pull request submitter isn't willing to follow project guidelines. In any of these cases, please be sure to leave a comment stating why the pull request is being closed. ### Example Closing Comments -If a pull request hasn't been updated in 30 days: +If a pull request hasn't been updated in 17 days: -> Closing because there hasn't been activity for 30 days. If you're still interested in submitting this code, please feel free to resubmit. +> Closing because there hasn't been activity for 17 days. If you're still interested in submitting this code, please feel free to resubmit. If a pull request submitter isn't willing to follow project guidelines. diff --git a/docs/src/maintainer-guide/working-groups.md b/docs/src/maintain/working-groups.md similarity index 77% rename from docs/src/maintainer-guide/working-groups.md rename to docs/src/maintain/working-groups.md index 1f6d43a675d..659ac04f73d 100644 --- a/docs/src/maintainer-guide/working-groups.md +++ b/docs/src/maintain/working-groups.md @@ -1,11 +1,13 @@ --- title: Working Groups -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/maintainer-guide/working-groups.md - +eleventyNavigation: + key: working groups + parent: maintain eslint + title: Working Groups + order: 5 --- -The ESLint TSC may form working groups to focus on a specific area of the project. +The ESLint [Technical Steering Committee](../contribute/governance#technical-steering-committee-tsc) (TSC) may form working groups to focus on a specific area of the project. ## Creating a Working Group diff --git a/docs/src/maintainer-guide/index.md b/docs/src/maintainer-guide/index.md deleted file mode 100644 index ab745536884..00000000000 --- a/docs/src/maintainer-guide/index.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Maintainer Guide -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/maintainer-guide/index.md -eleventyNavigation: - key: maintainer guide - title: Maintainer Guide - order: 3 - ---- - -This guide is intended for those who work as part of the ESLint project team. - -## [Managing Issues](issues) - -Describes how to deal with issues when they're opened, when interacting with users, and how to close them effectively. - -## [Reviewing Pull Requests](pullrequests) - -Describes how to review incoming pull requests. - -## [Managing Releases](releases) - -Describes how to do an ESLint project release. - -## [Governance](governance) - -Describes the governance policy for ESLint, including the rights and privileges of individuals inside the project. - -## [Working Groups](working-groups) - -Describes how working groups are created and how they function within the ESLint project. diff --git a/docs/src/pages/index.md b/docs/src/pages/index.md index 016d9d0f422..c8f5f62dcaa 100644 --- a/docs/src/pages/index.md +++ b/docs/src/pages/index.md @@ -1,22 +1,23 @@ --- title: Documentation -layout: doc permalink: /index.html -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/pages/index.md --- Welcome to our documentation pages! What would you like to view? -## [User Guide](user-guide/) +## [Use ESLint in Your Project](use/) Intended for end users of ESLint. Contains information about core rules, configuration, command line options, formatters, and integrations, as well as guides for migrating from earlier versions of ESLint. -## [Developer Guide](developer-guide/) +## [Extend ESLint](extend/) -Intended for contributors to ESLint and people who wish to extend ESLint. Contains information about contributing to ESLint; creating custom -rules, configurations, plugins, and formatters; and information about our architecture and Node.js API. +Intended for people who wish to extend ESLint. Contains information about creating custom rules, configurations, plugins, and formatters; and information about our Node.js API. -## [Maintainer Guide](maintainer-guide/) +## [Contribute to ESLint](contribute/) + +Intended for people who wish to contribute to the ESLint project. Contains information about ways you can contribute, the project structure, and setting up the development environment. + +## [Maintain ESLint](maintain/) Intended for maintainers of ESLint. diff --git a/docs/src/pages/rules.md b/docs/src/pages/rules.md index 7ede8bf2277..996860e2c86 100644 --- a/docs/src/pages/rules.md +++ b/docs/src/pages/rules.md @@ -1,12 +1,11 @@ --- -title: Rules -layout: doc +title: Rules Reference permalink: /rules/index.html eleventyNavigation: key: rules - parent: user guide - title: Rules - order: 4 + parent: use eslint + title: Rules Reference + order: 5 --- {% from 'components/rule-categories.macro.html' import ruleCategories, recommended, fixable, hasSuggestions %} diff --git a/docs/src/rules/accessor-pairs.md b/docs/src/rules/accessor-pairs.md index 0ce706a7708..b84205d3493 100644 --- a/docs/src/rules/accessor-pairs.md +++ b/docs/src/rules/accessor-pairs.md @@ -1,7 +1,5 @@ --- title: accessor-pairs -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/accessor-pairs.md rule_type: suggestion related_rules: - no-dupe-keys diff --git a/docs/src/rules/array-bracket-newline.md b/docs/src/rules/array-bracket-newline.md index 04b527f8aff..4ad7e27b560 100644 --- a/docs/src/rules/array-bracket-newline.md +++ b/docs/src/rules/array-bracket-newline.md @@ -1,7 +1,5 @@ --- title: array-bracket-newline -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/array-bracket-newline.md rule_type: layout related_rules: - array-bracket-spacing diff --git a/docs/src/rules/array-bracket-spacing.md b/docs/src/rules/array-bracket-spacing.md index cc5589ef502..5b6dd8a7cac 100644 --- a/docs/src/rules/array-bracket-spacing.md +++ b/docs/src/rules/array-bracket-spacing.md @@ -1,7 +1,5 @@ --- title: array-bracket-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/array-bracket-spacing.md rule_type: layout related_rules: - space-in-parens diff --git a/docs/src/rules/array-callback-return.md b/docs/src/rules/array-callback-return.md index ebd7e977010..965d18537af 100644 --- a/docs/src/rules/array-callback-return.md +++ b/docs/src/rules/array-callback-return.md @@ -1,7 +1,5 @@ --- title: array-callback-return -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/array-callback-return.md rule_type: problem --- @@ -28,6 +26,8 @@ This rule finds callback functions of the following methods, then checks usage o * [`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.findLast`](https://tc39.es/ecma262/#sec-array.prototype.findlast) +* [`Array.prototype.findLastIndex`](https://tc39.es/ecma262/#sec-array.prototype.findlastindex) * [`Array.prototype.flatMap`](https://www.ecma-international.org/ecma-262/10.0/#sec-array.prototype.flatmap) * [`Array.prototype.forEach`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.foreach) (optional, based on `checkForEach` parameter) * [`Array.prototype.map`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.map) @@ -35,6 +35,7 @@ This rule finds callback functions of the following methods, then checks usage o * [`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) +* [`Array.prototype.toSorted`](https://tc39.es/ecma262/#sec-array.prototype.tosorted) * And above of typed arrays. Examples of **incorrect** code for this rule: diff --git a/docs/src/rules/array-element-newline.md b/docs/src/rules/array-element-newline.md index ec21c6fbaae..2b34bd53014 100644 --- a/docs/src/rules/array-element-newline.md +++ b/docs/src/rules/array-element-newline.md @@ -1,7 +1,5 @@ --- title: array-element-newline -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/array-element-newline.md rule_type: layout related_rules: - array-bracket-spacing diff --git a/docs/src/rules/arrow-body-style.md b/docs/src/rules/arrow-body-style.md index 050d4b11b76..1975af13eed 100644 --- a/docs/src/rules/arrow-body-style.md +++ b/docs/src/rules/arrow-body-style.md @@ -1,7 +1,5 @@ --- title: arrow-body-style -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/arrow-body-style.md rule_type: suggestion --- diff --git a/docs/src/rules/arrow-parens.md b/docs/src/rules/arrow-parens.md index 66c7fa8bca8..8b956c35664 100644 --- a/docs/src/rules/arrow-parens.md +++ b/docs/src/rules/arrow-parens.md @@ -1,7 +1,5 @@ --- title: arrow-parens -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/arrow-parens.md rule_type: layout further_reading: - https://github.com/airbnb/javascript#arrows--one-arg-parens @@ -135,7 +133,7 @@ var b = 0; if ((a) => b) { console.log('truthy value returned'); } else { - console.log('falsey value returned'); + console.log('falsy value returned'); } // outputs 'truthy value returned' ``` diff --git a/docs/src/rules/arrow-spacing.md b/docs/src/rules/arrow-spacing.md index a8feec96499..fa5a42410a4 100644 --- a/docs/src/rules/arrow-spacing.md +++ b/docs/src/rules/arrow-spacing.md @@ -1,7 +1,5 @@ --- title: arrow-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/arrow-spacing.md rule_type: layout --- diff --git a/docs/src/rules/block-scoped-var.md b/docs/src/rules/block-scoped-var.md index 4ef6548f6fc..699ce704121 100644 --- a/docs/src/rules/block-scoped-var.md +++ b/docs/src/rules/block-scoped-var.md @@ -1,7 +1,5 @@ --- title: block-scoped-var -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/block-scoped-var.md rule_type: suggestion further_reading: - https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html diff --git a/docs/src/rules/block-spacing.md b/docs/src/rules/block-spacing.md index ea3426a7854..415fc7db0bd 100644 --- a/docs/src/rules/block-spacing.md +++ b/docs/src/rules/block-spacing.md @@ -1,7 +1,5 @@ --- title: block-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/block-spacing.md rule_type: layout related_rules: - space-before-blocks diff --git a/docs/src/rules/brace-style.md b/docs/src/rules/brace-style.md index 6320fec42d3..456ccf2c2e1 100644 --- a/docs/src/rules/brace-style.md +++ b/docs/src/rules/brace-style.md @@ -1,7 +1,5 @@ --- title: brace-style -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/brace-style.md rule_type: layout related_rules: - block-spacing diff --git a/docs/src/rules/callback-return.md b/docs/src/rules/callback-return.md index b9db4659f82..d3d621814a3 100644 --- a/docs/src/rules/callback-return.md +++ b/docs/src/rules/callback-return.md @@ -1,7 +1,5 @@ --- title: callback-return -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/callback-return.md rule_type: suggestion related_rules: - handle-callback-err diff --git a/docs/src/rules/camelcase.md b/docs/src/rules/camelcase.md index 294f597e338..46757fd53b0 100644 --- a/docs/src/rules/camelcase.md +++ b/docs/src/rules/camelcase.md @@ -1,7 +1,5 @@ --- title: camelcase -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/camelcase.md rule_type: suggestion --- @@ -128,6 +126,8 @@ Examples of **correct** code for this rule with the `{ "properties": "never" }` var obj = { my_pref: 1 }; + +obj.foo_bar = "baz"; ``` ::: diff --git a/docs/src/rules/capitalized-comments.md b/docs/src/rules/capitalized-comments.md index 1334aaaa173..66d03d779eb 100644 --- a/docs/src/rules/capitalized-comments.md +++ b/docs/src/rules/capitalized-comments.md @@ -1,7 +1,5 @@ --- title: capitalized-comments -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/capitalized-comments.md rule_type: suggestion --- diff --git a/docs/src/rules/class-methods-use-this.md b/docs/src/rules/class-methods-use-this.md index 8b3df9643a4..2269fac8eff 100644 --- a/docs/src/rules/class-methods-use-this.md +++ b/docs/src/rules/class-methods-use-this.md @@ -1,7 +1,5 @@ --- title: class-methods-use-this -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/class-methods-use-this.md rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes @@ -120,9 +118,9 @@ This rule has two options: "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. 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. +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: +Examples of **incorrect** code for this rule when used without `"exceptMethods"`: ::: incorrect diff --git a/docs/src/rules/comma-dangle.md b/docs/src/rules/comma-dangle.md index 26d650210c2..1370874ed50 100644 --- a/docs/src/rules/comma-dangle.md +++ b/docs/src/rules/comma-dangle.md @@ -1,7 +1,5 @@ --- title: comma-dangle -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/comma-dangle.md rule_type: layout --- diff --git a/docs/src/rules/comma-spacing.md b/docs/src/rules/comma-spacing.md index ca10ca0a6a7..b6937856321 100644 --- a/docs/src/rules/comma-spacing.md +++ b/docs/src/rules/comma-spacing.md @@ -1,7 +1,5 @@ --- title: comma-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/comma-spacing.md rule_type: layout related_rules: - array-bracket-spacing diff --git a/docs/src/rules/comma-style.md b/docs/src/rules/comma-style.md index 1535ef667fa..99d14b27f4f 100644 --- a/docs/src/rules/comma-style.md +++ b/docs/src/rules/comma-style.md @@ -1,7 +1,5 @@ --- title: comma-style -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/comma-style.md rule_type: layout related_rules: - operator-linebreak diff --git a/docs/src/rules/complexity.md b/docs/src/rules/complexity.md index cb4bbdb5d2d..08dd839f5cd 100644 --- a/docs/src/rules/complexity.md +++ b/docs/src/rules/complexity.md @@ -1,7 +1,5 @@ --- title: complexity -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/complexity.md rule_type: suggestion related_rules: - max-depth diff --git a/docs/src/rules/computed-property-spacing.md b/docs/src/rules/computed-property-spacing.md index 96fb8b06132..065270d6caf 100644 --- a/docs/src/rules/computed-property-spacing.md +++ b/docs/src/rules/computed-property-spacing.md @@ -1,7 +1,5 @@ --- title: computed-property-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/computed-property-spacing.md rule_type: layout related_rules: - array-bracket-spacing diff --git a/docs/src/rules/consistent-return.md b/docs/src/rules/consistent-return.md index b948bc5607c..2d5f4e3f2ca 100644 --- a/docs/src/rules/consistent-return.md +++ b/docs/src/rules/consistent-return.md @@ -1,7 +1,5 @@ --- title: consistent-return -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/consistent-return.md rule_type: suggestion --- diff --git a/docs/src/rules/consistent-this.md b/docs/src/rules/consistent-this.md index 8947d92d2f4..8f029adaa93 100644 --- a/docs/src/rules/consistent-this.md +++ b/docs/src/rules/consistent-this.md @@ -1,7 +1,5 @@ --- title: consistent-this -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/consistent-this.md rule_type: suggestion --- diff --git a/docs/src/rules/constructor-super.md b/docs/src/rules/constructor-super.md index 1497b0188fb..93019eb2a79 100644 --- a/docs/src/rules/constructor-super.md +++ b/docs/src/rules/constructor-super.md @@ -1,7 +1,5 @@ --- title: constructor-super -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/constructor-super.md rule_type: problem --- diff --git a/docs/src/rules/curly.md b/docs/src/rules/curly.md index 15f28c6559f..20b2308bc4d 100644 --- a/docs/src/rules/curly.md +++ b/docs/src/rules/curly.md @@ -1,7 +1,5 @@ --- title: curly -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/curly.md rule_type: suggestion --- diff --git a/docs/src/rules/default-case-last.md b/docs/src/rules/default-case-last.md index 7a3c5d60524..b49057781c2 100644 --- a/docs/src/rules/default-case-last.md +++ b/docs/src/rules/default-case-last.md @@ -1,7 +1,5 @@ --- title: default-case-last -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/default-case-last.md rule_type: suggestion related_rules: - default-case diff --git a/docs/src/rules/default-case.md b/docs/src/rules/default-case.md index cab7be84046..1bad1a0df4c 100644 --- a/docs/src/rules/default-case.md +++ b/docs/src/rules/default-case.md @@ -1,7 +1,5 @@ --- title: default-case -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/default-case.md rule_type: suggestion related_rules: - no-fallthrough diff --git a/docs/src/rules/default-param-last.md b/docs/src/rules/default-param-last.md index b9e85c71bbd..7f64f7c4464 100644 --- a/docs/src/rules/default-param-last.md +++ b/docs/src/rules/default-param-last.md @@ -1,7 +1,5 @@ --- title: default-param-last -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/default-param-last.md rule_type: suggestion --- diff --git a/docs/src/rules/dot-location.md b/docs/src/rules/dot-location.md index 5650ddd3bf1..47ee575be97 100644 --- a/docs/src/rules/dot-location.md +++ b/docs/src/rules/dot-location.md @@ -1,7 +1,5 @@ --- title: dot-location -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/dot-location.md rule_type: layout related_rules: - newline-after-var diff --git a/docs/src/rules/dot-notation.md b/docs/src/rules/dot-notation.md index 3a40363ac42..7514a1fdad2 100644 --- a/docs/src/rules/dot-notation.md +++ b/docs/src/rules/dot-notation.md @@ -1,7 +1,5 @@ --- title: dot-notation -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/dot-notation.md rule_type: suggestion --- diff --git a/docs/src/rules/eol-last.md b/docs/src/rules/eol-last.md index a3a19f983bb..4c9d837c5c3 100644 --- a/docs/src/rules/eol-last.md +++ b/docs/src/rules/eol-last.md @@ -1,7 +1,5 @@ --- title: eol-last -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/eol-last.md rule_type: layout --- diff --git a/docs/src/rules/eqeqeq.md b/docs/src/rules/eqeqeq.md index 6c16d0454a6..3243844f102 100644 --- a/docs/src/rules/eqeqeq.md +++ b/docs/src/rules/eqeqeq.md @@ -1,7 +1,5 @@ --- title: eqeqeq -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/eqeqeq.md rule_type: suggestion --- @@ -141,7 +139,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/src/rules/for-direction.md b/docs/src/rules/for-direction.md index c9b99d6beae..832c87cde3d 100644 --- a/docs/src/rules/for-direction.md +++ b/docs/src/rules/for-direction.md @@ -1,7 +1,5 @@ --- title: for-direction -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/for-direction.md rule_type: problem --- diff --git a/docs/src/rules/func-call-spacing.md b/docs/src/rules/func-call-spacing.md index cc2005660b9..082c86d8140 100644 --- a/docs/src/rules/func-call-spacing.md +++ b/docs/src/rules/func-call-spacing.md @@ -1,7 +1,5 @@ --- title: func-call-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/func-call-spacing.md rule_type: layout related_rules: - no-spaced-func diff --git a/docs/src/rules/func-name-matching.md b/docs/src/rules/func-name-matching.md index fc45a652859..8ef1a1c8540 100644 --- a/docs/src/rules/func-name-matching.md +++ b/docs/src/rules/func-name-matching.md @@ -1,7 +1,5 @@ --- title: func-name-matching -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/func-name-matching.md rule_type: suggestion --- @@ -147,7 +145,7 @@ module['exports'] = function foo(name) {}; ## Options -This rule takes an optional string of "always" or "never" (when omitted, it defaults to "always"), and an optional options object with two properties `considerPropertyDescriptor` and `includeCommonJSModuleExports`. +This rule takes an optional string of `"always"` or `"never"` (when omitted, it defaults to `"always"`), and an optional options object with two properties `considerPropertyDescriptor` and `includeCommonJSModuleExports`. ### considerPropertyDescriptor diff --git a/docs/src/rules/func-names.md b/docs/src/rules/func-names.md index bdc0ea596f0..549806d8c26 100644 --- a/docs/src/rules/func-names.md +++ b/docs/src/rules/func-names.md @@ -1,7 +1,5 @@ --- title: func-names -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/func-names.md rule_type: suggestion further_reading: - https://web.archive.org/web/20201112040809/http://markdaggett.com/blog/2013/02/15/functions-explained/ diff --git a/docs/src/rules/func-style.md b/docs/src/rules/func-style.md index 8897692cec0..05b667790d4 100644 --- a/docs/src/rules/func-style.md +++ b/docs/src/rules/func-style.md @@ -1,7 +1,5 @@ --- title: func-style -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/func-style.md rule_type: suggestion further_reading: - https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html diff --git a/docs/src/rules/function-call-argument-newline.md b/docs/src/rules/function-call-argument-newline.md index 6a5ffb8c173..be44b2ed7e9 100644 --- a/docs/src/rules/function-call-argument-newline.md +++ b/docs/src/rules/function-call-argument-newline.md @@ -1,7 +1,5 @@ --- title: function-call-argument-newline -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/function-call-argument-newline.md rule_type: layout related_rules: - function-paren-newline diff --git a/docs/src/rules/function-paren-newline.md b/docs/src/rules/function-paren-newline.md index da1743ca9e7..4ba5a922c8f 100644 --- a/docs/src/rules/function-paren-newline.md +++ b/docs/src/rules/function-paren-newline.md @@ -1,7 +1,5 @@ --- title: function-paren-newline -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/function-paren-newline.md rule_type: layout --- diff --git a/docs/src/rules/generator-star-spacing.md b/docs/src/rules/generator-star-spacing.md index 8525a0879eb..3ef58d86da6 100644 --- a/docs/src/rules/generator-star-spacing.md +++ b/docs/src/rules/generator-star-spacing.md @@ -1,7 +1,5 @@ --- title: generator-star-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/generator-star-spacing.md rule_type: layout further_reading: - https://leanpub.com/understandinges6/read/#leanpub-auto-generators @@ -53,14 +51,14 @@ This rule aims to enforce spacing around the `*` of generator functions. ## Options -The rule takes one option, an object, which has two keys `before` and `after` having boolean values `true` or `false`. +The rule takes one option, an object, which has two keys `"before"` and `"after"` having boolean values `true` or `false`. -* `before` enforces spacing between the `*` and the `function` keyword. +* `"before"` enforces spacing between the `*` and the `function` keyword. If it is `true`, a space is required, otherwise spaces are disallowed. In object literal shorthand methods, spacing before the `*` is not checked, as they lack a `function` keyword. -* `after` enforces spacing between the `*` and the function name (or the opening parenthesis for anonymous generator functions). +* `"after"` enforces spacing between the `*` and the function name (or the opening parenthesis for anonymous generator functions). If it is `true`, a space is required, otherwise spaces are disallowed. The default is `{"before": true, "after": false}`. @@ -101,9 +99,9 @@ An example of a configuration with overrides: }] ``` -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. +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 diff --git a/docs/src/rules/generator-star.md b/docs/src/rules/generator-star.md index f944081a264..c2ddb18f05d 100644 --- a/docs/src/rules/generator-star.md +++ b/docs/src/rules/generator-star.md @@ -1,7 +1,5 @@ --- title: generator-star -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/generator-star.md further_reading: - https://leanpub.com/understandinges6/read/#leanpub-auto-generators --- diff --git a/docs/src/rules/getter-return.md b/docs/src/rules/getter-return.md index 31dbd108b70..0c8937d14cc 100644 --- a/docs/src/rules/getter-return.md +++ b/docs/src/rules/getter-return.md @@ -1,7 +1,5 @@ --- title: getter-return -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/getter-return.md rule_type: problem further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get diff --git a/docs/src/rules/global-require.md b/docs/src/rules/global-require.md index 6c07bbbcb7b..4a2d20a75d7 100644 --- a/docs/src/rules/global-require.md +++ b/docs/src/rules/global-require.md @@ -1,7 +1,5 @@ --- title: global-require -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/global-require.md rule_type: suggestion --- diff --git a/docs/src/rules/global-strict.md b/docs/src/rules/global-strict.md index 856b58d856d..80cbf9a0aa0 100644 --- a/docs/src/rules/global-strict.md +++ b/docs/src/rules/global-strict.md @@ -1,7 +1,5 @@ --- title: global-strict -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/global-strict.md --- diff --git a/docs/src/rules/grouped-accessor-pairs.md b/docs/src/rules/grouped-accessor-pairs.md index a9eaa212415..cfb274d6c1b 100644 --- a/docs/src/rules/grouped-accessor-pairs.md +++ b/docs/src/rules/grouped-accessor-pairs.md @@ -1,7 +1,5 @@ --- title: grouped-accessor-pairs -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/grouped-accessor-pairs.md rule_type: suggestion related_rules: - accessor-pairs diff --git a/docs/src/rules/guard-for-in.md b/docs/src/rules/guard-for-in.md index 8e3d22e2507..0cdecb43a53 100644 --- a/docs/src/rules/guard-for-in.md +++ b/docs/src/rules/guard-for-in.md @@ -1,9 +1,8 @@ --- title: guard-for-in -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/guard-for-in.md rule_type: suggestion related_rules: +- prefer-object-has-own - no-prototype-builtins further_reading: - https://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/ @@ -19,6 +18,10 @@ for (key in foo) { } ``` +For codebases that do not support ES2022, `Object.prototype.hasOwnProperty.call(foo, key)` can be used as a check that the property is not inherited. + +For codebases that do support ES2022, `Object.hasOwn(foo, key)` can be used as a shorter alternative; see [prefer-object-has-own](prefer-object-has-own). + Note that simply checking `foo.hasOwnProperty(key)` is likely to cause an error in some cases; see [no-prototype-builtins](no-prototype-builtins). ## Rule Details @@ -46,6 +49,12 @@ Examples of **correct** code for this rule: ```js /*eslint guard-for-in: "error"*/ +for (key in foo) { + if (Object.hasOwn(foo, key)) { + doSomething(key); + } +} + for (key in foo) { if (Object.prototype.hasOwnProperty.call(foo, key)) { doSomething(key); diff --git a/docs/src/rules/handle-callback-err.md b/docs/src/rules/handle-callback-err.md index 210b119c7d4..fddce708b46 100644 --- a/docs/src/rules/handle-callback-err.md +++ b/docs/src/rules/handle-callback-err.md @@ -1,7 +1,5 @@ --- title: handle-callback-err -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/handle-callback-err.md rule_type: suggestion further_reading: - https://github.com/maxogden/art-of-node#callbacks diff --git a/docs/src/rules/id-blacklist.md b/docs/src/rules/id-blacklist.md index 3c5cce27506..649b22e60de 100644 --- a/docs/src/rules/id-blacklist.md +++ b/docs/src/rules/id-blacklist.md @@ -1,7 +1,5 @@ --- title: id-blacklist -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/id-blacklist.md rule_type: suggestion --- diff --git a/docs/src/rules/id-denylist.md b/docs/src/rules/id-denylist.md index 9a782d613ad..850f5923e83 100644 --- a/docs/src/rules/id-denylist.md +++ b/docs/src/rules/id-denylist.md @@ -1,7 +1,5 @@ --- title: id-denylist -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/id-denylist.md rule_type: suggestion --- @@ -39,6 +37,8 @@ For example, to restrict the use of common generic identifiers: } ``` +**Note:** The first element of the array is for the rule severity (see [Configure Rules](../use/configure/rules). The other elements in the array are the identifiers that you want to disallow. + Examples of **incorrect** code for this rule with sample `"data", "callback"` restricted identifiers: ::: incorrect diff --git a/docs/src/rules/id-length.md b/docs/src/rules/id-length.md index a6b5c1a4d88..adf5e897817 100644 --- a/docs/src/rules/id-length.md +++ b/docs/src/rules/id-length.md @@ -1,7 +1,5 @@ --- title: id-length -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/id-length.md rule_type: suggestion related_rules: - max-len @@ -21,6 +19,8 @@ var x = 5; // too short; difficult to understand its purpose without context This rule enforces a minimum and/or maximum identifier length convention. +This rule counts [graphemes](https://unicode.org/reports/tr29/#Default_Grapheme_Cluster_Table) instead of using [`String length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length). + ## Options Examples of **incorrect** code for this rule with the default options: diff --git a/docs/src/rules/id-match.md b/docs/src/rules/id-match.md index db43aa80cda..08d5ff3ba9c 100644 --- a/docs/src/rules/id-match.md +++ b/docs/src/rules/id-match.md @@ -1,7 +1,5 @@ --- title: id-match -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/id-match.md rule_type: suggestion --- @@ -93,10 +91,10 @@ This rule has an object option: * `"properties": false` (default) does not check object properties * `"properties": true` requires object literal properties and member expression assignment properties to match the specified regular expression -* `"classFields": false` (default) does not class field names +* `"classFields": false` (default) does not check class field names * `"classFields": true` requires class field names to match the specified regular expression * `"onlyDeclarations": false` (default) requires all variable names to match the specified regular expression -* `"onlyDeclarations": true` requires only `var`, `function`, and `class` declarations to match the specified regular expression +* `"onlyDeclarations": true` requires only `var`, `const`, `let`, `function`, and `class` declarations to match the specified regular expression * `"ignoreDestructuring": false` (default) enforces `id-match` for destructured identifiers * `"ignoreDestructuring": true` does not check destructured identifiers diff --git a/docs/src/rules/implicit-arrow-linebreak.md b/docs/src/rules/implicit-arrow-linebreak.md index b7887761bad..09aaba18e03 100644 --- a/docs/src/rules/implicit-arrow-linebreak.md +++ b/docs/src/rules/implicit-arrow-linebreak.md @@ -1,7 +1,5 @@ --- title: implicit-arrow-linebreak -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/implicit-arrow-linebreak.md rule_type: layout related_rules: - brace-style diff --git a/docs/src/rules/indent-legacy.md b/docs/src/rules/indent-legacy.md index 9c887d802a4..97372ebc8ff 100644 --- a/docs/src/rules/indent-legacy.md +++ b/docs/src/rules/indent-legacy.md @@ -1,7 +1,5 @@ --- title: indent-legacy -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/indent-legacy.md rule_type: layout --- @@ -9,7 +7,7 @@ rule_type: layout This rule was **deprecated** in ESLint v4.0.0. -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. +ESLint 4.0.0 introduced a rewrite of the [`indent`](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. --- diff --git a/docs/src/rules/indent.md b/docs/src/rules/indent.md index dbe4f9bbc3f..7f81c0fd57b 100644 --- a/docs/src/rules/indent.md +++ b/docs/src/rules/indent.md @@ -1,7 +1,5 @@ --- title: indent -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/indent.md rule_type: layout --- @@ -83,7 +81,7 @@ if (a) { This rule has an object option: -* `"ignoredNodes"` can be used to disable indentation checking for any AST node. This accepts an array of [selectors](/docs/developer-guide/selectors). 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. +* `"ignoredNodes"` can be used to disable indentation checking for any AST node. This accepts an array of [selectors](../extend/selectors). 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. * `"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. It can also be `"first"`, indicating all the declarators should be aligned with the first declarator. * `"outerIIFEBody"` (default: 1) enforces indentation level for file-level IIFEs. This can also be set to `"off"` to disable checking for file-level IIFEs. diff --git a/docs/src/rules/init-declarations.md b/docs/src/rules/init-declarations.md index d5ccaef69aa..569e5079c72 100644 --- a/docs/src/rules/init-declarations.md +++ b/docs/src/rules/init-declarations.md @@ -1,7 +1,5 @@ --- title: init-declarations -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/init-declarations.md rule_type: suggestion --- diff --git a/docs/src/rules/jsx-quotes.md b/docs/src/rules/jsx-quotes.md index 5196e66790d..4f0d78ba78a 100644 --- a/docs/src/rules/jsx-quotes.md +++ b/docs/src/rules/jsx-quotes.md @@ -1,7 +1,5 @@ --- title: jsx-quotes -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/jsx-quotes.md rule_type: layout related_rules: - quotes diff --git a/docs/src/rules/key-spacing.md b/docs/src/rules/key-spacing.md index 1810ab1739e..d6e500053cf 100644 --- a/docs/src/rules/key-spacing.md +++ b/docs/src/rules/key-spacing.md @@ -1,7 +1,5 @@ --- title: key-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/key-spacing.md rule_type: layout --- diff --git a/docs/src/rules/keyword-spacing.md b/docs/src/rules/keyword-spacing.md index e90244cd618..a77e3ddc420 100644 --- a/docs/src/rules/keyword-spacing.md +++ b/docs/src/rules/keyword-spacing.md @@ -1,7 +1,5 @@ --- title: keyword-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/keyword-spacing.md rule_type: layout --- diff --git a/docs/src/rules/line-comment-position.md b/docs/src/rules/line-comment-position.md index a49fd00ae5a..d032ecca8e9 100644 --- a/docs/src/rules/line-comment-position.md +++ b/docs/src/rules/line-comment-position.md @@ -1,7 +1,5 @@ --- title: line-comment-position -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/line-comment-position.md rule_type: layout --- diff --git a/docs/src/rules/linebreak-style.md b/docs/src/rules/linebreak-style.md index ef3c071a079..71158115f34 100644 --- a/docs/src/rules/linebreak-style.md +++ b/docs/src/rules/linebreak-style.md @@ -1,7 +1,5 @@ --- title: linebreak-style -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/linebreak-style.md rule_type: layout --- diff --git a/docs/src/rules/lines-around-comment.md b/docs/src/rules/lines-around-comment.md index 44051246faa..924ba66cf1a 100644 --- a/docs/src/rules/lines-around-comment.md +++ b/docs/src/rules/lines-around-comment.md @@ -1,7 +1,5 @@ --- title: lines-around-comment -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/lines-around-comment.md rule_type: layout related_rules: - space-before-blocks @@ -35,6 +33,7 @@ This rule has an object option: * `"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 +* `"afterHashbangComment": true` requires an empty line after hashbang comments ### beforeBlockComment @@ -719,6 +718,35 @@ foo(); ::: +### afterHashbangComment + +Examples of **incorrect** code for this rule with the `{ "afterHashbangComment": true }` option: + +::: incorrect + +```js +#!foo +var day = "great" + +/*eslint lines-around-comment: ["error", { "afterHashbangComment": true }] */ +``` + +::: + +Examples of **correct** code for this rule with the `{ "afterHashbangComment": true }` option: + +::: correct + +```js +#!foo + +var day = "great" + +/*eslint lines-around-comment: ["error", { "afterHashbangComment": true }] */ +``` + +::: + ## When Not To Use It Many people enjoy a terser code style and don't mind comments bumping up against code. If you fall into that category this rule is not for you. diff --git a/docs/src/rules/lines-around-directive.md b/docs/src/rules/lines-around-directive.md index d3697545668..fb71775c7f1 100644 --- a/docs/src/rules/lines-around-directive.md +++ b/docs/src/rules/lines-around-directive.md @@ -1,7 +1,5 @@ --- title: lines-around-directive -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/lines-around-directive.md rule_type: layout related_rules: - lines-around-comment diff --git a/docs/src/rules/lines-between-class-members.md b/docs/src/rules/lines-between-class-members.md index 42ebf9d2a83..8daf2a2f78d 100644 --- a/docs/src/rules/lines-between-class-members.md +++ b/docs/src/rules/lines-between-class-members.md @@ -1,7 +1,5 @@ --- title: lines-between-class-members -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/lines-between-class-members.md rule_type: layout related_rules: - padded-blocks diff --git a/docs/src/rules/logical-assignment-operators.md b/docs/src/rules/logical-assignment-operators.md new file mode 100644 index 00000000000..3f9350031f3 --- /dev/null +++ b/docs/src/rules/logical-assignment-operators.md @@ -0,0 +1,130 @@ +--- +title: logical-assignment-operators +rule_type: suggestion +--- + +ES2021 introduces the assignment operator shorthand for the logical operators `||`, `&&` and `??`. +Before, this was only allowed for mathematical operations such as `+` or `*` (see the rule [operator-assignment](./operator-assignment)). +The shorthand can be used if the assignment target and the left expression of a logical expression are the same. +For example `a = a || b` can be shortened to `a ||= b`. + +## Rule Details + +This rule requires or disallows logical assignment operator shorthand. + +### Options + +This rule has a string and an object option. +String option: + +* `"always"` (default) +* `"never"` + +Object option (only available if string option is set to `"always"`): + +* `"enforceForIfStatements": false`(default) Do *not* check for equivalent `if` statements +* `"enforceForIfStatements": true` Check for equivalent `if` statements + +#### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +::: incorrect + +```js +/*eslint logical-assignment-operators: ["error", "always"]*/ + +a = a || b +a = a && b +a = a ?? b +a || (a = b) +a && (a = b) +a ?? (a = b) +``` + +::: + +Examples of **correct** code for this rule with the default `"always"` option: + +::: correct + +```js +/*eslint logical-assignment-operators: ["error", "always"]*/ + +a = b +a += b +a ||= b +a = b || c +a || (b = c) + +if (a) a = b +``` + +::: + +#### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +::: incorrect + +```js +/*eslint logical-assignment-operators: ["error", "never"]*/ + +a ||= b +a &&= b +a ??= b +``` + +::: + +Examples of **correct** code for this rule with the `"never"` option: + +::: correct + +```js +/*eslint logical-assignment-operators: ["error", "never"]*/ + +a = a || b +a = a && b +a = a ?? b +``` + +::: + +#### enforceForIfStatements + +This option checks for additional patterns with if statements which could be expressed with the logical assignment operator. + +::: incorrect + +Examples of **incorrect** code for this rule with the `["always", { enforceForIfStatements: true }]` option: + +```js +/*eslint logical-assignment-operators: ["error", "always", { enforceForIfStatements: true }]*/ + +if (a) a = b // <=> a &&= b +if (!a) a = b // <=> a ||= b + +if (a == null) a = b // <=> a ??= b +if (a === null || a === undefined) a = b // <=> a ??= b +``` + +::: + +Examples of **correct** code for this rule with the `["always", { enforceForIfStatements: true }]` option: + +::: correct + +```js +/*eslint logical-assignment-operators: ["error", "always", { enforceForIfStatements: true }]*/ + +if (a) b = c +if (a === 0) a = b +``` + +::: + +## When Not To Use It + +Use of logical operator assignment shorthand is a stylistic choice. Leaving this rule turned off would allow developers to choose which style is more readable on a case-by-case basis. diff --git a/docs/src/rules/max-classes-per-file.md b/docs/src/rules/max-classes-per-file.md index ef02619fe5b..77275597a47 100644 --- a/docs/src/rules/max-classes-per-file.md +++ b/docs/src/rules/max-classes-per-file.md @@ -1,7 +1,5 @@ --- title: max-classes-per-file -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/max-classes-per-file.md rule_type: suggestion --- diff --git a/docs/src/rules/max-depth.md b/docs/src/rules/max-depth.md index 1a31c279a38..1e0e86688a0 100644 --- a/docs/src/rules/max-depth.md +++ b/docs/src/rules/max-depth.md @@ -1,7 +1,5 @@ --- title: max-depth -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/max-depth.md rule_type: suggestion related_rules: - complexity diff --git a/docs/src/rules/max-len.md b/docs/src/rules/max-len.md index 216b48d80ac..be883a8ca0e 100644 --- a/docs/src/rules/max-len.md +++ b/docs/src/rules/max-len.md @@ -1,7 +1,5 @@ --- title: max-len -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/max-len.md rule_type: layout related_rules: - complexity diff --git a/docs/src/rules/max-lines-per-function.md b/docs/src/rules/max-lines-per-function.md index 66eeb481b55..45b8a62720f 100644 --- a/docs/src/rules/max-lines-per-function.md +++ b/docs/src/rules/max-lines-per-function.md @@ -1,7 +1,5 @@ --- title: max-lines-per-function -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/max-lines-per-function.md rule_type: suggestion related_rules: - complexity diff --git a/docs/src/rules/max-lines.md b/docs/src/rules/max-lines.md index d33153b0e3b..9aa4ae4d6d1 100644 --- a/docs/src/rules/max-lines.md +++ b/docs/src/rules/max-lines.md @@ -1,7 +1,5 @@ --- title: max-lines -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/max-lines.md rule_type: suggestion related_rules: - complexity diff --git a/docs/src/rules/max-nested-callbacks.md b/docs/src/rules/max-nested-callbacks.md index 4bdc36be86f..759fffa311a 100644 --- a/docs/src/rules/max-nested-callbacks.md +++ b/docs/src/rules/max-nested-callbacks.md @@ -1,7 +1,5 @@ --- title: max-nested-callbacks -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/max-nested-callbacks.md rule_type: suggestion related_rules: - complexity diff --git a/docs/src/rules/max-params.md b/docs/src/rules/max-params.md index 5a3a4ed8a2f..681723fb811 100644 --- a/docs/src/rules/max-params.md +++ b/docs/src/rules/max-params.md @@ -1,7 +1,5 @@ --- title: max-params -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/max-params.md rule_type: suggestion related_rules: - complexity diff --git a/docs/src/rules/max-statements-per-line.md b/docs/src/rules/max-statements-per-line.md index ee5a9b730ff..238ade787e8 100644 --- a/docs/src/rules/max-statements-per-line.md +++ b/docs/src/rules/max-statements-per-line.md @@ -1,7 +1,5 @@ --- title: max-statements-per-line -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/max-statements-per-line.md rule_type: layout related_rules: - max-depth diff --git a/docs/src/rules/max-statements.md b/docs/src/rules/max-statements.md index ec981ba424c..d41f7f2de4d 100644 --- a/docs/src/rules/max-statements.md +++ b/docs/src/rules/max-statements.md @@ -1,7 +1,5 @@ --- title: max-statements -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/max-statements.md rule_type: suggestion related_rules: - complexity diff --git a/docs/src/rules/multiline-comment-style.md b/docs/src/rules/multiline-comment-style.md index 243cbc58cfc..28e6648ad03 100644 --- a/docs/src/rules/multiline-comment-style.md +++ b/docs/src/rules/multiline-comment-style.md @@ -1,7 +1,5 @@ --- title: multiline-comment-style -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/multiline-comment-style.md rule_type: suggestion --- @@ -18,10 +16,10 @@ This rule aims to enforce a particular style for multiline comments. 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 +* `"bare-block"`: Disallows consecutive line comments in favor of block comments, and disallows block comments from having a `"*"` character before each line. This option ignores JSDoc comments. +* `"separate-lines"`: Disallows block comments in favor of consecutive line comments. By default, this option ignores JSDoc comments. To also apply this rule to JSDoc comments, set the `checkJSDoc` option to `true`. -The rule always ignores directive comments such as `/* eslint-disable */`. Additionally, unless the mode is `"starred-block"`, the rule ignores JSDoc comments. +The rule always ignores directive comments such as `/* eslint-disable */`. Examples of **incorrect** code for this rule with the default `"starred-block"` option: @@ -148,6 +146,39 @@ foo(); ::: +Examples of **incorrect** code for this rule with the `"separate-lines"` option and `checkJSDoc` set to `true`: + +::: incorrect + +```js + +/* eslint multiline-comment-style: ["error", "separate-lines", { "checkJSDoc": true }] */ + +/** + * I am a JSDoc comment + * and I'm not allowed + */ +foo(); + +``` + +::: + +Examples of **correct** code for this rule with the `"separate-lines"` option and `checkJSDoc` set to `true`: + +::: correct + +```js +/* eslint multiline-comment-style: ["error", "separate-lines", { "checkJSDoc": true }] */ + +// I am a JSDoc comment +// and I'm not allowed +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/src/rules/multiline-ternary.md b/docs/src/rules/multiline-ternary.md index 9f309efba45..7e4688d637a 100644 --- a/docs/src/rules/multiline-ternary.md +++ b/docs/src/rules/multiline-ternary.md @@ -1,7 +1,5 @@ --- title: multiline-ternary -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/multiline-ternary.md rule_type: layout related_rules: - operator-linebreak diff --git a/docs/src/rules/new-cap.md b/docs/src/rules/new-cap.md index 1309ee821f7..f75c65fcb76 100644 --- a/docs/src/rules/new-cap.md +++ b/docs/src/rules/new-cap.md @@ -1,7 +1,5 @@ --- title: new-cap -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/new-cap.md rule_type: suggestion --- @@ -26,6 +24,7 @@ This rule requires constructor names to begin with a capital letter. Certain bui * `RegExp` * `String` * `Symbol` +* `BigInt` Examples of **correct** code for this rule: diff --git a/docs/src/rules/new-parens.md b/docs/src/rules/new-parens.md index b51bdfe52d2..08292e8a978 100644 --- a/docs/src/rules/new-parens.md +++ b/docs/src/rules/new-parens.md @@ -1,7 +1,5 @@ --- title: new-parens -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/new-parens.md rule_type: layout --- diff --git a/docs/src/rules/newline-after-var.md b/docs/src/rules/newline-after-var.md index da6f6e22cb0..db11e25471c 100644 --- a/docs/src/rules/newline-after-var.md +++ b/docs/src/rules/newline-after-var.md @@ -1,7 +1,5 @@ --- title: newline-after-var -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/newline-after-var.md rule_type: layout --- diff --git a/docs/src/rules/newline-before-return.md b/docs/src/rules/newline-before-return.md index 62f12881a57..41fb7726c7b 100644 --- a/docs/src/rules/newline-before-return.md +++ b/docs/src/rules/newline-before-return.md @@ -1,7 +1,5 @@ --- title: newline-before-return -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/newline-before-return.md rule_type: layout related_rules: - newline-after-var diff --git a/docs/src/rules/newline-per-chained-call.md b/docs/src/rules/newline-per-chained-call.md index 88d677c9598..e2482cdbc20 100644 --- a/docs/src/rules/newline-per-chained-call.md +++ b/docs/src/rules/newline-per-chained-call.md @@ -1,7 +1,5 @@ --- title: newline-per-chained-call -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/newline-per-chained-call.md rule_type: layout --- diff --git a/docs/src/rules/no-alert.md b/docs/src/rules/no-alert.md index 4dcfdf97286..fe7ffd4de71 100644 --- a/docs/src/rules/no-alert.md +++ b/docs/src/rules/no-alert.md @@ -1,7 +1,5 @@ --- title: no-alert -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-alert.md rule_type: suggestion related_rules: - no-console diff --git a/docs/src/rules/no-array-constructor.md b/docs/src/rules/no-array-constructor.md index 9a96c5cc6a0..7a22df0e68a 100644 --- a/docs/src/rules/no-array-constructor.md +++ b/docs/src/rules/no-array-constructor.md @@ -1,7 +1,5 @@ --- title: no-array-constructor -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-array-constructor.md rule_type: suggestion related_rules: - no-new-object diff --git a/docs/src/rules/no-arrow-condition.md b/docs/src/rules/no-arrow-condition.md index a6ef8f21348..5882b3fb0de 100644 --- a/docs/src/rules/no-arrow-condition.md +++ b/docs/src/rules/no-arrow-condition.md @@ -1,7 +1,5 @@ --- title: no-arrow-condition -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-arrow-condition.md related_rules: - arrow-parens diff --git a/docs/src/rules/no-async-promise-executor.md b/docs/src/rules/no-async-promise-executor.md index b32f137caee..784c5ea5396 100644 --- a/docs/src/rules/no-async-promise-executor.md +++ b/docs/src/rules/no-async-promise-executor.md @@ -1,7 +1,5 @@ --- title: no-async-promise-executor -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-async-promise-executor.md rule_type: problem --- diff --git a/docs/src/rules/no-await-in-loop.md b/docs/src/rules/no-await-in-loop.md index 6c2c1041992..a150f5a911f 100644 --- a/docs/src/rules/no-await-in-loop.md +++ b/docs/src/rules/no-await-in-loop.md @@ -1,7 +1,5 @@ --- title: no-await-in-loop -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-await-in-loop.md rule_type: problem --- diff --git a/docs/src/rules/no-bitwise.md b/docs/src/rules/no-bitwise.md index 1cdbb60d9a7..3b7268fc976 100644 --- a/docs/src/rules/no-bitwise.md +++ b/docs/src/rules/no-bitwise.md @@ -1,7 +1,5 @@ --- title: no-bitwise -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-bitwise.md rule_type: suggestion --- diff --git a/docs/src/rules/no-buffer-constructor.md b/docs/src/rules/no-buffer-constructor.md index 96da4d15644..59b9dd8772d 100644 --- a/docs/src/rules/no-buffer-constructor.md +++ b/docs/src/rules/no-buffer-constructor.md @@ -1,7 +1,5 @@ --- title: no-buffer-constructor -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-buffer-constructor.md rule_type: problem further_reading: - https://nodejs.org/api/buffer.html diff --git a/docs/src/rules/no-caller.md b/docs/src/rules/no-caller.md index 2dfabff100b..13dddd0e663 100644 --- a/docs/src/rules/no-caller.md +++ b/docs/src/rules/no-caller.md @@ -1,7 +1,5 @@ --- title: no-caller -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-caller.md rule_type: suggestion --- diff --git a/docs/src/rules/no-case-declarations.md b/docs/src/rules/no-case-declarations.md index 589c84ec3f5..49e6f41f08d 100644 --- a/docs/src/rules/no-case-declarations.md +++ b/docs/src/rules/no-case-declarations.md @@ -1,7 +1,5 @@ --- title: no-case-declarations -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-case-declarations.md rule_type: suggestion related_rules: - no-fallthrough diff --git a/docs/src/rules/no-catch-shadow.md b/docs/src/rules/no-catch-shadow.md index 08a484fb047..23328e42fef 100644 --- a/docs/src/rules/no-catch-shadow.md +++ b/docs/src/rules/no-catch-shadow.md @@ -1,7 +1,5 @@ --- title: no-catch-shadow -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-catch-shadow.md rule_type: suggestion --- diff --git a/docs/src/rules/no-class-assign.md b/docs/src/rules/no-class-assign.md index 6c8c9c9658b..360192ca2f4 100644 --- a/docs/src/rules/no-class-assign.md +++ b/docs/src/rules/no-class-assign.md @@ -1,7 +1,5 @@ --- title: no-class-assign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-class-assign.md rule_type: problem --- diff --git a/docs/src/rules/no-comma-dangle.md b/docs/src/rules/no-comma-dangle.md index 3227dbded87..c1815bc5a0f 100644 --- a/docs/src/rules/no-comma-dangle.md +++ b/docs/src/rules/no-comma-dangle.md @@ -1,7 +1,5 @@ --- title: no-comma-dangle -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-comma-dangle.md --- diff --git a/docs/src/rules/no-compare-neg-zero.md b/docs/src/rules/no-compare-neg-zero.md index 04a88170c0a..18bc4ed919f 100644 --- a/docs/src/rules/no-compare-neg-zero.md +++ b/docs/src/rules/no-compare-neg-zero.md @@ -1,7 +1,5 @@ --- title: no-compare-neg-zero -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-compare-neg-zero.md rule_type: problem --- diff --git a/docs/src/rules/no-cond-assign.md b/docs/src/rules/no-cond-assign.md index f223ee7ec39..b72c2d09c67 100644 --- a/docs/src/rules/no-cond-assign.md +++ b/docs/src/rules/no-cond-assign.md @@ -1,7 +1,5 @@ --- title: no-cond-assign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-cond-assign.md rule_type: problem related_rules: - no-extra-parens diff --git a/docs/src/rules/no-confusing-arrow.md b/docs/src/rules/no-confusing-arrow.md index be4bb96fd5a..546e30321a8 100644 --- a/docs/src/rules/no-confusing-arrow.md +++ b/docs/src/rules/no-confusing-arrow.md @@ -1,7 +1,5 @@ --- title: no-confusing-arrow -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-confusing-arrow.md rule_type: suggestion related_rules: - no-constant-condition diff --git a/docs/src/rules/no-console.md b/docs/src/rules/no-console.md index 5fdfbae020c..9420c36ec03 100644 --- a/docs/src/rules/no-console.md +++ b/docs/src/rules/no-console.md @@ -1,7 +1,5 @@ --- title: no-console -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-console.md rule_type: suggestion related_rules: - no-alert diff --git a/docs/src/rules/no-const-assign.md b/docs/src/rules/no-const-assign.md index a0df9d6a276..f9f0ed172bb 100644 --- a/docs/src/rules/no-const-assign.md +++ b/docs/src/rules/no-const-assign.md @@ -1,7 +1,5 @@ --- title: no-const-assign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-const-assign.md rule_type: problem --- diff --git a/docs/src/rules/no-constant-binary-expression.md b/docs/src/rules/no-constant-binary-expression.md index 1d0bf89d7b1..792c61d529d 100644 --- a/docs/src/rules/no-constant-binary-expression.md +++ b/docs/src/rules/no-constant-binary-expression.md @@ -1,7 +1,5 @@ --- title: no-constant-binary-expression -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-constant-binary-expression.md rule_type: problem related_rules: - no-constant-condition @@ -55,6 +53,12 @@ const value4 = new Boolean(foo) === true; const objIsEmpty = someObj === {}; const arrIsEmpty = someArr === []; + +const shortCircuit1 = condition1 && false && condition2; + +const shortCircuit2 = condition1 || true || condition2; + +const shortCircuit3 = condition1 ?? "non-nullish" ?? condition2; ``` ::: diff --git a/docs/src/rules/no-constant-condition.md b/docs/src/rules/no-constant-condition.md index 959e3d6c87e..2ac613f7abf 100644 --- a/docs/src/rules/no-constant-condition.md +++ b/docs/src/rules/no-constant-condition.md @@ -1,7 +1,5 @@ --- title: no-constant-condition -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-constant-condition.md rule_type: problem related_rules: - no-constant-binary-expression @@ -76,6 +74,10 @@ do { } while (x = -1); var result = 0 ? a : b; + +if(input === "hello" || "bye"){ + output(input); +} ``` ::: @@ -104,6 +106,10 @@ do { } while (x); var result = x !== 0 ? a : b; + +if(input === "hello" || input === "bye"){ + output(input); +} ``` ::: diff --git a/docs/src/rules/no-constructor-return.md b/docs/src/rules/no-constructor-return.md index f6e941b1b96..e429d386acc 100644 --- a/docs/src/rules/no-constructor-return.md +++ b/docs/src/rules/no-constructor-return.md @@ -1,7 +1,5 @@ --- title: no-constructor-return -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-constructor-return.md rule_type: problem --- diff --git a/docs/src/rules/no-continue.md b/docs/src/rules/no-continue.md index 4ab311d6b61..af5cd8f98f1 100644 --- a/docs/src/rules/no-continue.md +++ b/docs/src/rules/no-continue.md @@ -1,7 +1,5 @@ --- title: no-continue -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-continue.md rule_type: suggestion --- diff --git a/docs/src/rules/no-control-regex.md b/docs/src/rules/no-control-regex.md index 3e957063222..18a5ce40148 100644 --- a/docs/src/rules/no-control-regex.md +++ b/docs/src/rules/no-control-regex.md @@ -1,7 +1,5 @@ --- title: no-control-regex -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-control-regex.md rule_type: problem related_rules: - no-div-regex diff --git a/docs/src/rules/no-debugger.md b/docs/src/rules/no-debugger.md index f9bd8b31f95..f30f9ae5805 100644 --- a/docs/src/rules/no-debugger.md +++ b/docs/src/rules/no-debugger.md @@ -1,7 +1,5 @@ --- title: no-debugger -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-debugger.md rule_type: problem related_rules: - no-alert diff --git a/docs/src/rules/no-delete-var.md b/docs/src/rules/no-delete-var.md index 6abedd7f1e4..65a5aa1614d 100644 --- a/docs/src/rules/no-delete-var.md +++ b/docs/src/rules/no-delete-var.md @@ -1,7 +1,5 @@ --- title: no-delete-var -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-delete-var.md rule_type: suggestion --- diff --git a/docs/src/rules/no-div-regex.md b/docs/src/rules/no-div-regex.md index 31653a94208..ccc7287dcbe 100644 --- a/docs/src/rules/no-div-regex.md +++ b/docs/src/rules/no-div-regex.md @@ -1,7 +1,5 @@ --- title: no-div-regex -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-div-regex.md rule_type: suggestion related_rules: - no-control-regex diff --git a/docs/src/rules/no-dupe-args.md b/docs/src/rules/no-dupe-args.md index 0556d70e9a0..79f791c4666 100644 --- a/docs/src/rules/no-dupe-args.md +++ b/docs/src/rules/no-dupe-args.md @@ -1,7 +1,5 @@ --- title: no-dupe-args -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-dupe-args.md rule_type: problem --- diff --git a/docs/src/rules/no-dupe-class-members.md b/docs/src/rules/no-dupe-class-members.md index cc32d47085a..aaea4ce324e 100644 --- a/docs/src/rules/no-dupe-class-members.md +++ b/docs/src/rules/no-dupe-class-members.md @@ -1,7 +1,5 @@ --- title: no-dupe-class-members -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-dupe-class-members.md rule_type: problem --- diff --git a/docs/src/rules/no-dupe-else-if.md b/docs/src/rules/no-dupe-else-if.md index b0010189e9f..54d160aaba2 100644 --- a/docs/src/rules/no-dupe-else-if.md +++ b/docs/src/rules/no-dupe-else-if.md @@ -1,7 +1,5 @@ --- title: no-dupe-else-if -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-dupe-else-if.md rule_type: problem related_rules: - no-duplicate-case diff --git a/docs/src/rules/no-dupe-keys.md b/docs/src/rules/no-dupe-keys.md index 45c8bbf3449..75fc9491fb6 100644 --- a/docs/src/rules/no-dupe-keys.md +++ b/docs/src/rules/no-dupe-keys.md @@ -1,7 +1,5 @@ --- title: no-dupe-keys -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-dupe-keys.md rule_type: problem --- diff --git a/docs/src/rules/no-duplicate-case.md b/docs/src/rules/no-duplicate-case.md index 5f221fabdc3..59a2a987495 100644 --- a/docs/src/rules/no-duplicate-case.md +++ b/docs/src/rules/no-duplicate-case.md @@ -1,7 +1,5 @@ --- title: no-duplicate-case -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-duplicate-case.md rule_type: problem --- diff --git a/docs/src/rules/no-duplicate-imports.md b/docs/src/rules/no-duplicate-imports.md index f029093e7fe..2d7a5538194 100644 --- a/docs/src/rules/no-duplicate-imports.md +++ b/docs/src/rules/no-duplicate-imports.md @@ -1,7 +1,5 @@ --- title: no-duplicate-imports -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-duplicate-imports.md rule_type: problem --- diff --git a/docs/src/rules/no-else-return.md b/docs/src/rules/no-else-return.md index 8380d5a7b2e..917c4455390 100644 --- a/docs/src/rules/no-else-return.md +++ b/docs/src/rules/no-else-return.md @@ -1,7 +1,5 @@ --- title: no-else-return -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-else-return.md rule_type: suggestion --- diff --git a/docs/src/rules/no-empty-character-class.md b/docs/src/rules/no-empty-character-class.md index 3d8f544e6ba..984f1e18330 100644 --- a/docs/src/rules/no-empty-character-class.md +++ b/docs/src/rules/no-empty-character-class.md @@ -1,7 +1,5 @@ --- title: no-empty-character-class -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-empty-character-class.md rule_type: problem --- diff --git a/docs/src/rules/no-empty-class.md b/docs/src/rules/no-empty-class.md index e9431716084..5a6760f7dac 100644 --- a/docs/src/rules/no-empty-class.md +++ b/docs/src/rules/no-empty-class.md @@ -1,7 +1,5 @@ --- title: no-empty-class -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-empty-class.md --- diff --git a/docs/src/rules/no-empty-function.md b/docs/src/rules/no-empty-function.md index 192de97a3e7..a9be4bb05d5 100644 --- a/docs/src/rules/no-empty-function.md +++ b/docs/src/rules/no-empty-function.md @@ -1,7 +1,5 @@ --- title: no-empty-function -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-empty-function.md rule_type: suggestion related_rules: - no-empty diff --git a/docs/src/rules/no-empty-label.md b/docs/src/rules/no-empty-label.md index b12d403aeec..9fd7da473e3 100644 --- a/docs/src/rules/no-empty-label.md +++ b/docs/src/rules/no-empty-label.md @@ -1,7 +1,5 @@ --- title: no-empty-label -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-empty-label.md related_rules: - no-labels diff --git a/docs/src/rules/no-empty-pattern.md b/docs/src/rules/no-empty-pattern.md index a259bf22b07..d510f592d55 100644 --- a/docs/src/rules/no-empty-pattern.md +++ b/docs/src/rules/no-empty-pattern.md @@ -1,7 +1,5 @@ --- title: no-empty-pattern -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-empty-pattern.md rule_type: problem --- diff --git a/docs/src/rules/no-empty-static-block.md b/docs/src/rules/no-empty-static-block.md new file mode 100644 index 00000000000..283a4e21b77 --- /dev/null +++ b/docs/src/rules/no-empty-static-block.md @@ -0,0 +1,56 @@ +--- +title: no-empty-static-block +layout: doc +rule_type: suggestion +related_rules: +- no-empty +- no-empty-function +further_reading: +- https://github.com/tc39/proposal-class-static-block +--- + +Empty static blocks, while not technically errors, usually occur due to refactoring that wasn't completed. They can cause confusion when reading code. + +## Rule Details + +This rule disallows empty static blocks. This rule ignores static blocks which contain a comment. + +Examples of **incorrect** code for this rule: + +::: incorrect + +```js +/*eslint no-empty-static-block: "error"*/ + +class Foo { + static {} +} +``` + +::: + +Examples of **correct** code for this rule: + +:::correct + +```js +/*eslint no-empty-static-block: "error"*/ + +class Foo { + static { + bar(); + } +} + +class Foo { + static { + // comment + } +} +``` + +::: + +## When Not To Use It + +This rule should not be used in environments prior to ES2022. diff --git a/docs/src/rules/no-empty.md b/docs/src/rules/no-empty.md index 70935afb3b6..64a9bc0a136 100644 --- a/docs/src/rules/no-empty.md +++ b/docs/src/rules/no-empty.md @@ -1,7 +1,5 @@ --- title: no-empty -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-empty.md rule_type: suggestion related_rules: - no-empty-function diff --git a/docs/src/rules/no-eq-null.md b/docs/src/rules/no-eq-null.md index f2e17ac66c1..43c95574ea3 100644 --- a/docs/src/rules/no-eq-null.md +++ b/docs/src/rules/no-eq-null.md @@ -1,7 +1,5 @@ --- title: no-eq-null -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-eq-null.md rule_type: suggestion --- diff --git a/docs/src/rules/no-eval.md b/docs/src/rules/no-eval.md index 1488c1132ce..662df5d3acd 100644 --- a/docs/src/rules/no-eval.md +++ b/docs/src/rules/no-eval.md @@ -1,7 +1,5 @@ --- title: no-eval -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-eval.md rule_type: suggestion related_rules: - no-implied-eval diff --git a/docs/src/rules/no-ex-assign.md b/docs/src/rules/no-ex-assign.md index 9835a658bee..395164d204b 100644 --- a/docs/src/rules/no-ex-assign.md +++ b/docs/src/rules/no-ex-assign.md @@ -1,7 +1,5 @@ --- title: no-ex-assign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-ex-assign.md rule_type: problem further_reading: - https://bocoup.com/blog/the-catch-with-try-catch diff --git a/docs/src/rules/no-extend-native.md b/docs/src/rules/no-extend-native.md index 065b214cbc0..9be6a089586 100644 --- a/docs/src/rules/no-extend-native.md +++ b/docs/src/rules/no-extend-native.md @@ -1,7 +1,5 @@ --- title: no-extend-native -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-extend-native.md rule_type: suggestion related_rules: - no-global-assign diff --git a/docs/src/rules/no-extra-bind.md b/docs/src/rules/no-extra-bind.md index a783de9de28..073425cf13e 100644 --- a/docs/src/rules/no-extra-bind.md +++ b/docs/src/rules/no-extra-bind.md @@ -1,7 +1,5 @@ --- title: no-extra-bind -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-extra-bind.md rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind diff --git a/docs/src/rules/no-extra-boolean-cast.md b/docs/src/rules/no-extra-boolean-cast.md index 5429d8c6fc3..e45509890f5 100644 --- a/docs/src/rules/no-extra-boolean-cast.md +++ b/docs/src/rules/no-extra-boolean-cast.md @@ -1,7 +1,5 @@ --- title: no-extra-boolean-cast -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-extra-boolean-cast.md rule_type: suggestion --- diff --git a/docs/src/rules/no-extra-label.md b/docs/src/rules/no-extra-label.md index 30a0ea2dcf7..9e916aab6a5 100644 --- a/docs/src/rules/no-extra-label.md +++ b/docs/src/rules/no-extra-label.md @@ -1,7 +1,5 @@ --- title: no-extra-label -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-extra-label.md rule_type: suggestion related_rules: - no-labels diff --git a/docs/src/rules/no-extra-parens.md b/docs/src/rules/no-extra-parens.md index 669130cb074..9b1b8df0725 100644 --- a/docs/src/rules/no-extra-parens.md +++ b/docs/src/rules/no-extra-parens.md @@ -1,7 +1,5 @@ --- title: no-extra-parens -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-extra-parens.md rule_type: layout related_rules: - arrow-parens @@ -40,6 +38,7 @@ This rule has an object option for exceptions to the `"all"` option: * `"enforceForSequenceExpressions": false` allows extra parentheses around sequence expressions * `"enforceForNewInMemberExpressions": false` allows extra parentheses around `new` expressions in member expressions * `"enforceForFunctionPrototypeMethods": false` allows extra parentheses around immediate `.call` and `.apply` method calls on function expressions and around function expressions in the same context. +* `"allowParensAfterCommentPattern": "any-string-pattern"` allows extra parentheses preceded by a comment that matches a regular expression. ### all @@ -62,6 +61,8 @@ for (a of (b)); typeof (a); +(Object.prototype.toString.call()); + (function(){} ? a() : b()); class A { @@ -84,8 +85,6 @@ Examples of **correct** code for this rule with the default `"all"` option: (0).toString(); -(Object.prototype.toString.call()); - ({}.toString.call()); (function(){}) ? a() : b(); @@ -324,6 +323,34 @@ const quux = (function () {}.apply()); ::: +### allowParensAfterCommentPattern + +To make this rule allow extra parentheses preceded by specific comments, set this option to a string pattern that will be passed to the [`RegExp` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp). + +Examples of **correct** code for this rule with the `"all"` and `{ "allowParensAfterCommentPattern": "@type" }` options: + +::: correct + +```js +/* eslint no-extra-parens: ["error", "all", { "allowParensAfterCommentPattern": "@type" }] */ + +const span = /**@type {HTMLSpanElement}*/(event.currentTarget); + +if (/** @type {Foo | Bar} */(options).baz) console.log('Lint free'); + +foo(/** @type {Bar} */ (bar), options, { + name: "name", + path: "path", +}); + +if (foo) { + /** @type {Bar} */ + (bar).prop = false; +} +``` + +::: + ### functions Examples of **incorrect** code for this rule with the `"functions"` option: diff --git a/docs/src/rules/no-extra-semi.md b/docs/src/rules/no-extra-semi.md index 65d32df2833..9755c5167fd 100644 --- a/docs/src/rules/no-extra-semi.md +++ b/docs/src/rules/no-extra-semi.md @@ -1,7 +1,5 @@ --- title: no-extra-semi -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-extra-semi.md rule_type: suggestion related_rules: - semi diff --git a/docs/src/rules/no-extra-strict.md b/docs/src/rules/no-extra-strict.md index 4aa4fdcdc5c..db0b8da4ff3 100644 --- a/docs/src/rules/no-extra-strict.md +++ b/docs/src/rules/no-extra-strict.md @@ -1,7 +1,5 @@ --- title: no-extra-strict -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-extra-strict.md further_reading: - https://es5.github.io/#C diff --git a/docs/src/rules/no-fallthrough.md b/docs/src/rules/no-fallthrough.md index ee9b4db2aab..c49293cad4a 100644 --- a/docs/src/rules/no-fallthrough.md +++ b/docs/src/rules/no-fallthrough.md @@ -1,7 +1,5 @@ --- title: no-fallthrough -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-fallthrough.md rule_type: problem related_rules: - default-case @@ -34,7 +32,7 @@ switch(foo) { } ``` -That works fine when you don't want a fallthrough, but what if the fallthrough is intentional, there is no way to indicate that in the language. It's considered a best practice to always indicate when a fallthrough is intentional using a comment which matches the `/falls?\s?through/i` regular expression: +That works fine when you don't want a fallthrough, but what if the fallthrough is intentional, there is no way to indicate that in the language. It's considered a best practice to always indicate when a fallthrough is intentional using a comment which matches the `/falls?\s?through/i` regular expression but isn't a directive: ```js switch(foo) { @@ -169,9 +167,11 @@ Note that the last `case` statement in these examples does not cause a warning b ## Options -This rule accepts a single options argument: +This rule has an object option: -* Set the `commentPattern` option to a regular expression string to change the test for intentional fallthrough comment +* Set the `commentPattern` option to a regular expression string to change the test for intentional fallthrough comment. If the fallthrough comment matches a directive, that takes precedence over `commentPattern`. + +* Set the `allowEmptyCase` option to `true` to allow empty cases regardless of the layout. By default, this rule does not require a fallthrough comment after an empty `case` only if the empty `case` and the next `case` are on the same line or on consecutive lines. ### commentPattern @@ -203,6 +203,33 @@ switch(foo) { ::: +### allowEmptyCase + +Examples of **correct** code for the `{ "allowEmptyCase": true }` option: + +::: correct + +```js +/* eslint no-fallthrough: ["error", { "allowEmptyCase": true }] */ + +switch(foo){ + case 1: + + case 2: doSomething(); +} + +switch(foo){ + case 1: + /* + Put a message here + */ + case 2: doSomething(); +} + +``` + +::: + ## When Not To Use It If you don't want to enforce that each `case` statement should end with a `throw`, `return`, `break`, or comment, then you can safely turn this rule off. diff --git a/docs/src/rules/no-floating-decimal.md b/docs/src/rules/no-floating-decimal.md index 02dea7d2060..30a43317d55 100644 --- a/docs/src/rules/no-floating-decimal.md +++ b/docs/src/rules/no-floating-decimal.md @@ -1,7 +1,5 @@ --- title: no-floating-decimal -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-floating-decimal.md rule_type: suggestion --- diff --git a/docs/src/rules/no-func-assign.md b/docs/src/rules/no-func-assign.md index b98652c3aa1..ffbcb46c6b8 100644 --- a/docs/src/rules/no-func-assign.md +++ b/docs/src/rules/no-func-assign.md @@ -1,7 +1,5 @@ --- title: no-func-assign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-func-assign.md rule_type: problem --- diff --git a/docs/src/rules/no-global-assign.md b/docs/src/rules/no-global-assign.md index e29789dc625..dcdd05d519e 100644 --- a/docs/src/rules/no-global-assign.md +++ b/docs/src/rules/no-global-assign.md @@ -1,7 +1,5 @@ --- title: no-global-assign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-global-assign.md rule_type: suggestion related_rules: - no-extend-native @@ -25,8 +23,8 @@ This rule disallows modifications to read-only global variables. ESLint has the capability to configure global variables as read-only. -* [Specifying Environments](../user-guide/configuring#specifying-environments) -* [Specifying Globals](../user-guide/configuring#specifying-globals) +* [Specifying Environments](../use/configure#specifying-environments) +* [Specifying Globals](../use/configure#specifying-globals) Examples of **incorrect** code for this rule: diff --git a/docs/src/rules/no-implicit-coercion.md b/docs/src/rules/no-implicit-coercion.md index 2739292eceb..a7d30542059 100644 --- a/docs/src/rules/no-implicit-coercion.md +++ b/docs/src/rules/no-implicit-coercion.md @@ -1,7 +1,5 @@ --- title: no-implicit-coercion -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-implicit-coercion.md rule_type: suggestion --- @@ -104,6 +102,8 @@ Examples of **correct** code for the default `{ "number": true }` option: var n = Number(foo); var n = parseFloat(foo); var n = parseInt(foo, 10); + +var n = foo * 1/4; // `* 1` is allowed when followed by the `/` operator ``` ::: diff --git a/docs/src/rules/no-implicit-globals.md b/docs/src/rules/no-implicit-globals.md index ce942e91c8f..1e5c5fad723 100644 --- a/docs/src/rules/no-implicit-globals.md +++ b/docs/src/rules/no-implicit-globals.md @@ -1,11 +1,10 @@ --- title: no-implicit-globals -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-implicit-globals.md rule_type: suggestion related_rules: - no-undef - no-global-assign +- no-unused-vars further_reading: - https://benalman.com/news/2010/11/immediately-invoked-function-expression/ - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Undeclared_var @@ -123,8 +122,8 @@ A read-only global variable can be a built-in ES global (e.g. `Array`), an envir (e.g. `window` in the browser environment), or a global variable defined as `readonly` in the configuration file or in a `/*global */` comment. -* [Specifying Environments](../user-guide/configuring#specifying-environments) -* [Specifying Globals](../user-guide/configuring#specifying-globals) +* [Specifying Environments](../use/configure#specifying-environments) +* [Specifying Globals](../use/configure#specifying-globals) Examples of **incorrect** code for this rule: @@ -256,6 +255,22 @@ window.MyGlobalFunction = (function() { ::: +### exported + +You can use `/* exported variableName */` block comments in the same way as in [`no-unused-vars`](./no-unused-vars). See the [`no-unused-vars` exported section](./no-unused-vars#exported) for details. + +Examples of **correct** code for `/* exported variableName */` operation: + +::: correct + +```js +/* exported global_var */ + +var global_var = 42; +``` + +::: + ## When Not To Use It In the case of a browser script, if you want to be able to explicitly declare variables and functions in the global scope, diff --git a/docs/src/rules/no-implied-eval.md b/docs/src/rules/no-implied-eval.md index 433c28360b3..542e39a2cef 100644 --- a/docs/src/rules/no-implied-eval.md +++ b/docs/src/rules/no-implied-eval.md @@ -1,7 +1,5 @@ --- title: no-implied-eval -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-implied-eval.md rule_type: suggestion related_rules: - no-eval diff --git a/docs/src/rules/no-import-assign.md b/docs/src/rules/no-import-assign.md index 980d230a501..ca4b912de81 100644 --- a/docs/src/rules/no-import-assign.md +++ b/docs/src/rules/no-import-assign.md @@ -1,7 +1,5 @@ --- title: no-import-assign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-import-assign.md rule_type: problem --- diff --git a/docs/src/rules/no-inline-comments.md b/docs/src/rules/no-inline-comments.md index 4b2d9c3f92d..687464e91c7 100644 --- a/docs/src/rules/no-inline-comments.md +++ b/docs/src/rules/no-inline-comments.md @@ -1,7 +1,5 @@ --- title: no-inline-comments -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-inline-comments.md rule_type: suggestion --- diff --git a/docs/src/rules/no-inner-declarations.md b/docs/src/rules/no-inner-declarations.md index 3f126f4344d..8828fabe7d2 100644 --- a/docs/src/rules/no-inner-declarations.md +++ b/docs/src/rules/no-inner-declarations.md @@ -1,7 +1,5 @@ --- title: no-inner-declarations -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-inner-declarations.md rule_type: problem --- diff --git a/docs/src/rules/no-invalid-regexp.md b/docs/src/rules/no-invalid-regexp.md index 42c8d7bf348..565b13cdaa8 100644 --- a/docs/src/rules/no-invalid-regexp.md +++ b/docs/src/rules/no-invalid-regexp.md @@ -1,7 +1,5 @@ --- title: no-invalid-regexp -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-invalid-regexp.md rule_type: problem further_reading: - https://es5.github.io/#x7.8.5 diff --git a/docs/src/rules/no-invalid-this.md b/docs/src/rules/no-invalid-this.md index 2fb5ec8b63c..f3aa6ed763e 100644 --- a/docs/src/rules/no-invalid-this.md +++ b/docs/src/rules/no-invalid-this.md @@ -1,7 +1,5 @@ --- title: no-invalid-this -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-invalid-this.md rule_type: suggestion --- diff --git a/docs/src/rules/no-irregular-whitespace.md b/docs/src/rules/no-irregular-whitespace.md index 5996fb53021..258733b0827 100644 --- a/docs/src/rules/no-irregular-whitespace.md +++ b/docs/src/rules/no-irregular-whitespace.md @@ -1,7 +1,5 @@ --- title: no-irregular-whitespace -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-irregular-whitespace.md rule_type: problem further_reading: - https://es5.github.io/#x7.2 diff --git a/docs/src/rules/no-iterator.md b/docs/src/rules/no-iterator.md index fba039adcbd..70186826239 100644 --- a/docs/src/rules/no-iterator.md +++ b/docs/src/rules/no-iterator.md @@ -1,7 +1,5 @@ --- title: no-iterator -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-iterator.md rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators diff --git a/docs/src/rules/no-label-var.md b/docs/src/rules/no-label-var.md index c0a1ec13c4c..885a20f131c 100644 --- a/docs/src/rules/no-label-var.md +++ b/docs/src/rules/no-label-var.md @@ -1,7 +1,5 @@ --- title: no-label-var -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-label-var.md rule_type: suggestion related_rules: - no-extra-label diff --git a/docs/src/rules/no-labels.md b/docs/src/rules/no-labels.md index 6df5f52fab6..d2437ea4dba 100644 --- a/docs/src/rules/no-labels.md +++ b/docs/src/rules/no-labels.md @@ -1,7 +1,5 @@ --- title: no-labels -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-labels.md rule_type: suggestion related_rules: - no-extra-label diff --git a/docs/src/rules/no-lone-blocks.md b/docs/src/rules/no-lone-blocks.md index 8255b3dafba..efce8248f1c 100644 --- a/docs/src/rules/no-lone-blocks.md +++ b/docs/src/rules/no-lone-blocks.md @@ -1,7 +1,5 @@ --- title: no-lone-blocks -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-lone-blocks.md rule_type: suggestion --- diff --git a/docs/src/rules/no-lonely-if.md b/docs/src/rules/no-lonely-if.md index 1ef30c57244..9b6eb4f49de 100644 --- a/docs/src/rules/no-lonely-if.md +++ b/docs/src/rules/no-lonely-if.md @@ -1,7 +1,5 @@ --- title: no-lonely-if -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-lonely-if.md rule_type: suggestion --- diff --git a/docs/src/rules/no-loop-func.md b/docs/src/rules/no-loop-func.md index e37fe82a1e0..659fd3e62da 100644 --- a/docs/src/rules/no-loop-func.md +++ b/docs/src/rules/no-loop-func.md @@ -1,7 +1,5 @@ --- title: no-loop-func -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-loop-func.md rule_type: suggestion --- diff --git a/docs/src/rules/no-loss-of-precision.md b/docs/src/rules/no-loss-of-precision.md index 3932866036e..1bec49266dc 100644 --- a/docs/src/rules/no-loss-of-precision.md +++ b/docs/src/rules/no-loss-of-precision.md @@ -1,13 +1,11 @@ --- title: no-loss-of-precision -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-loss-of-precision.md rule_type: problem --- -This rule would disallow the use of number literals that immediately lose precision at runtime when converted to a JS `Number` due to 64-bit floating-point rounding. +This rule would disallow the use of number literals that lose precision at runtime when converted to a JS `Number` due to 64-bit floating-point rounding. ## Rule Details diff --git a/docs/src/rules/no-magic-numbers.md b/docs/src/rules/no-magic-numbers.md index 2f862575646..638e0b54f4a 100644 --- a/docs/src/rules/no-magic-numbers.md +++ b/docs/src/rules/no-magic-numbers.md @@ -1,7 +1,5 @@ --- title: no-magic-numbers -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-magic-numbers.md rule_type: suggestion --- @@ -195,6 +193,45 @@ let head; ::: +### ignoreClassFieldInitialValues + +A boolean to specify if numbers used as initial values of class fields are considered okay. `false` by default. + +Examples of **correct** code for the `{ "ignoreClassFieldInitialValues": true }` option: + +::: correct + +```js +/*eslint no-magic-numbers: ["error", { "ignoreClassFieldInitialValues": true }]*/ + +class C { + foo = 2; + bar = -3; + #baz = 4; + static qux = 5; +} +``` + +::: + +Examples of **incorrect** code for the `{ "ignoreClassFieldInitialValues": true }` option: + +::: incorrect + +```js +/*eslint no-magic-numbers: ["error", { "ignoreClassFieldInitialValues": true }]*/ + +class C { + foo = 2 + 3; +} + +class D { + 2; +} +``` + +::: + ### enforceConst A boolean to specify if we should check for the const keyword in variable declaration of numbers. `false` by default. diff --git a/docs/src/rules/no-misleading-character-class.md b/docs/src/rules/no-misleading-character-class.md index a3d156a4a4c..a0d9e6d0693 100644 --- a/docs/src/rules/no-misleading-character-class.md +++ b/docs/src/rules/no-misleading-character-class.md @@ -1,7 +1,5 @@ --- title: no-misleading-character-class -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-misleading-character-class.md rule_type: problem --- diff --git a/docs/src/rules/no-mixed-operators.md b/docs/src/rules/no-mixed-operators.md index 9db3066925e..07ab40f7fe5 100644 --- a/docs/src/rules/no-mixed-operators.md +++ b/docs/src/rules/no-mixed-operators.md @@ -1,7 +1,5 @@ --- title: no-mixed-operators -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-mixed-operators.md rule_type: suggestion related_rules: - no-extra-parens diff --git a/docs/src/rules/no-mixed-requires.md b/docs/src/rules/no-mixed-requires.md index 6584e8fb7f1..3dcf6384fd4 100644 --- a/docs/src/rules/no-mixed-requires.md +++ b/docs/src/rules/no-mixed-requires.md @@ -1,7 +1,5 @@ --- title: no-mixed-requires -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-mixed-requires.md rule_type: suggestion --- diff --git a/docs/src/rules/no-mixed-spaces-and-tabs.md b/docs/src/rules/no-mixed-spaces-and-tabs.md index 0dc791a9a35..29f82e2191f 100644 --- a/docs/src/rules/no-mixed-spaces-and-tabs.md +++ b/docs/src/rules/no-mixed-spaces-and-tabs.md @@ -1,7 +1,5 @@ --- title: no-mixed-spaces-and-tabs -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-mixed-spaces-and-tabs.md rule_type: layout further_reading: - https://www.emacswiki.org/emacs/SmartTabs diff --git a/docs/src/rules/no-multi-assign.md b/docs/src/rules/no-multi-assign.md index a6b08849cb4..7cc4581d152 100644 --- a/docs/src/rules/no-multi-assign.md +++ b/docs/src/rules/no-multi-assign.md @@ -1,7 +1,5 @@ --- title: no-multi-assign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-multi-assign.md rule_type: suggestion related_rules: - max-statements-per-line diff --git a/docs/src/rules/no-multi-spaces.md b/docs/src/rules/no-multi-spaces.md index e8a38dd96b9..b23a97964fe 100644 --- a/docs/src/rules/no-multi-spaces.md +++ b/docs/src/rules/no-multi-spaces.md @@ -1,7 +1,5 @@ --- title: no-multi-spaces -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-multi-spaces.md rule_type: layout related_rules: - key-spacing diff --git a/docs/src/rules/no-multi-str.md b/docs/src/rules/no-multi-str.md index 3548be839d8..67444b42736 100644 --- a/docs/src/rules/no-multi-str.md +++ b/docs/src/rules/no-multi-str.md @@ -1,7 +1,5 @@ --- title: no-multi-str -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-multi-str.md rule_type: suggestion --- diff --git a/docs/src/rules/no-multiple-empty-lines.md b/docs/src/rules/no-multiple-empty-lines.md index 5606beacf6c..5457c54c1f0 100644 --- a/docs/src/rules/no-multiple-empty-lines.md +++ b/docs/src/rules/no-multiple-empty-lines.md @@ -1,7 +1,5 @@ --- title: no-multiple-empty-lines -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-multiple-empty-lines.md rule_type: layout --- @@ -33,6 +31,7 @@ Examples of **incorrect** code for this rule with the default `{ "max": 2 }` opt var foo = 5; + var bar = 3; ``` @@ -47,6 +46,7 @@ Examples of **correct** code for this rule with the default `{ "max": 2 }` optio var foo = 5; + var bar = 3; ``` @@ -63,8 +63,10 @@ Examples of **incorrect** code for this rule with the `{ max: 2, maxEOF: 0 }` op var foo = 5; + var bar = 3; + ``` ::: @@ -78,6 +80,7 @@ Examples of **correct** code for this rule with the `{ max: 2, maxEOF: 0 }` opti var foo = 5; + var bar = 3; ``` @@ -87,29 +90,37 @@ var bar = 3; **Incorrect**: +::: incorrect + ```js -1 /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎ -2 ⏎ -3 var foo = 5;⏎ -4 ⏎ -5 ⏎ -6 var bar = 3;⏎ -7 ⏎ -8 +/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎ +⏎ +var foo = 5;⏎ +⏎ +⏎ +var bar = 3;⏎ +⏎ + ``` +::: + **Correct**: +::: correct + ```js -1 /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎ -2 ⏎ -3 var foo = 5;⏎ -4 ⏎ -5 ⏎ -6 var bar = 3;⏎ -7 +/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎ +⏎ +var foo = 5;⏎ +⏎ +⏎ +var bar = 3;⏎ + ``` +::: + ### maxBOF Examples of **incorrect** code for this rule with the `{ max: 2, maxBOF: 1 }` options: @@ -119,8 +130,10 @@ Examples of **incorrect** code for this rule with the `{ max: 2, maxBOF: 1 }` op ```js /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxBOF": 1 }]*/ + var foo = 5; + var bar = 3; ``` @@ -135,6 +148,7 @@ Examples of **correct** code for this rule with the `{ max: 2, maxBOF: 1 }` opti var foo = 5; + var bar = 3; ``` diff --git a/docs/src/rules/no-native-reassign.md b/docs/src/rules/no-native-reassign.md index 43772e027fc..c2c658f7705 100644 --- a/docs/src/rules/no-native-reassign.md +++ b/docs/src/rules/no-native-reassign.md @@ -1,7 +1,5 @@ --- title: no-native-reassign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-native-reassign.md rule_type: suggestion related_rules: - no-extend-native @@ -26,8 +24,8 @@ This rule disallows modifications to read-only global variables. ESLint has the capability to configure global variables as read-only. -* [Specifying Environments](../user-guide/configuring#specifying-environments) -* [Specifying Globals](../user-guide/configuring#specifying-globals) +* [Specifying Environments](../use/configure#specifying-environments) +* [Specifying Globals](../use/configure#specifying-globals) Examples of **incorrect** code for this rule: diff --git a/docs/src/rules/no-negated-condition.md b/docs/src/rules/no-negated-condition.md index 3c8ff950388..932c2e8df65 100644 --- a/docs/src/rules/no-negated-condition.md +++ b/docs/src/rules/no-negated-condition.md @@ -1,7 +1,5 @@ --- title: no-negated-condition -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-negated-condition.md rule_type: suggestion --- diff --git a/docs/src/rules/no-negated-in-lhs.md b/docs/src/rules/no-negated-in-lhs.md index 02329fd2dfc..2a27116f6e7 100644 --- a/docs/src/rules/no-negated-in-lhs.md +++ b/docs/src/rules/no-negated-in-lhs.md @@ -1,7 +1,5 @@ --- title: no-negated-in-lhs -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-negated-in-lhs.md rule_type: problem --- diff --git a/docs/src/rules/no-nested-ternary.md b/docs/src/rules/no-nested-ternary.md index 92e6a4124c0..58bcad30808 100644 --- a/docs/src/rules/no-nested-ternary.md +++ b/docs/src/rules/no-nested-ternary.md @@ -1,7 +1,5 @@ --- title: no-nested-ternary -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-nested-ternary.md rule_type: suggestion related_rules: - no-ternary diff --git a/docs/src/rules/no-new-func.md b/docs/src/rules/no-new-func.md index a82454440f1..8289e3ed9e7 100644 --- a/docs/src/rules/no-new-func.md +++ b/docs/src/rules/no-new-func.md @@ -1,7 +1,5 @@ --- title: no-new-func -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-new-func.md rule_type: suggestion --- diff --git a/docs/src/rules/no-new-native-nonconstructor.md b/docs/src/rules/no-new-native-nonconstructor.md new file mode 100644 index 00000000000..c9b75679a1e --- /dev/null +++ b/docs/src/rules/no-new-native-nonconstructor.md @@ -0,0 +1,74 @@ +--- +title: no-new-native-nonconstructor +layout: doc +rule_type: problem +related_rules: +- no-obj-calls +further_reading: +- https://tc39.es/ecma262/#sec-symbol-constructor +- https://tc39.es/ecma262/#sec-bigint-constructor +--- + + + +It is a convention in JavaScript that global variables beginning with an uppercase letter typically represent classes that can be instantiated using the `new` operator, such as `new Array` and `new Map`. Confusingly, JavaScript also provides some global variables that begin with an uppercase letter that cannot be called using the `new` operator and will throw an error if you attempt to do so. These are typically functions that are related to data types and are easy to mistake for classes. Consider the following example: + +```js +// throws a TypeError +let foo = new Symbol("foo"); + +// throws a TypeError +let result = new BigInt(9007199254740991); +``` + +Both `new Symbol` and `new BigInt` throw a type error because they are functions and not classes. It is easy to make this mistake by assuming the uppercase letters indicate classes. + +## Rule Details + +This rule is aimed at preventing the accidental calling of native JavaScript global functions with the `new` operator. These functions are: + +* `Symbol` +* `BigInt` + +## Examples + +Examples of **incorrect** code for this rule: + +::: incorrect + +```js +/*eslint no-new-native-nonconstructor: "error"*/ +/*eslint-env es2022*/ + +var foo = new Symbol('foo'); +var bar = new BigInt(9007199254740991); +``` + +::: + +Examples of **correct** code for this rule: + +::: correct + +```js +/*eslint no-new-native-nonconstructor: "error"*/ +/*eslint-env es2022*/ + +var foo = Symbol('foo'); +var bar = BigInt(9007199254740991); + +// Ignores shadowed Symbol. +function baz(Symbol) { + const qux = new Symbol("baz"); +} +function quux(BigInt) { + const corge = new BigInt(9007199254740991); +} + +``` + +::: + +## When Not To Use It + +This rule should not be used in ES3/5 environments. diff --git a/docs/src/rules/no-new-object.md b/docs/src/rules/no-new-object.md index fb007c89e41..41b02f87406 100644 --- a/docs/src/rules/no-new-object.md +++ b/docs/src/rules/no-new-object.md @@ -1,7 +1,5 @@ --- title: no-new-object -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-new-object.md rule_type: suggestion related_rules: - no-array-constructor diff --git a/docs/src/rules/no-new-require.md b/docs/src/rules/no-new-require.md index 7b684ecf29b..1aa663e6669 100644 --- a/docs/src/rules/no-new-require.md +++ b/docs/src/rules/no-new-require.md @@ -1,7 +1,5 @@ --- title: no-new-require -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-new-require.md rule_type: suggestion --- diff --git a/docs/src/rules/no-new-symbol.md b/docs/src/rules/no-new-symbol.md index d28383f1e2c..44c34a4eef0 100644 --- a/docs/src/rules/no-new-symbol.md +++ b/docs/src/rules/no-new-symbol.md @@ -1,7 +1,5 @@ --- title: no-new-symbol -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-new-symbol.md rule_type: problem further_reading: - https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects diff --git a/docs/src/rules/no-new-wrappers.md b/docs/src/rules/no-new-wrappers.md index ee5c8546d7f..da0a860ca15 100644 --- a/docs/src/rules/no-new-wrappers.md +++ b/docs/src/rules/no-new-wrappers.md @@ -1,7 +1,5 @@ --- title: no-new-wrappers -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-new-wrappers.md rule_type: suggestion related_rules: - no-array-constructor diff --git a/docs/src/rules/no-new.md b/docs/src/rules/no-new.md index b62a2975943..9eeda70095a 100644 --- a/docs/src/rules/no-new.md +++ b/docs/src/rules/no-new.md @@ -1,7 +1,5 @@ --- title: no-new -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-new.md rule_type: suggestion --- diff --git a/docs/src/rules/no-nonoctal-decimal-escape.md b/docs/src/rules/no-nonoctal-decimal-escape.md index ba58cbf6386..6ca59fdf79a 100644 --- a/docs/src/rules/no-nonoctal-decimal-escape.md +++ b/docs/src/rules/no-nonoctal-decimal-escape.md @@ -1,7 +1,5 @@ --- title: no-nonoctal-decimal-escape -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-nonoctal-decimal-escape.md rule_type: suggestion related_rules: - no-octal-escape diff --git a/docs/src/rules/no-obj-calls.md b/docs/src/rules/no-obj-calls.md index 04cfe61b695..8d72e4ce277 100644 --- a/docs/src/rules/no-obj-calls.md +++ b/docs/src/rules/no-obj-calls.md @@ -1,7 +1,5 @@ --- title: no-obj-calls -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-obj-calls.md rule_type: problem further_reading: - https://es5.github.io/#x15.8 @@ -19,13 +17,17 @@ The [ECMAScript 2015 specification](https://www.ecma-international.org/ecma-262/ > The Reflect object also does not have a `[[Call]]` internal method; it is not possible to invoke the Reflect object as a function. -And the [ECMAScript 2017 specification](https://www.ecma-international.org/ecma-262/8.0/index.html#sec-atomics-object) makes it clear that `Atomics` cannot be invoked: +The [ECMAScript 2017 specification](https://www.ecma-international.org/ecma-262/8.0/index.html#sec-atomics-object) makes it clear that `Atomics` cannot be invoked: > The Atomics object does not have a `[[Call]]` internal method; it is not possible to invoke the Atomics object as a function. +And the [ECMAScript Internationalization API Specification](https://tc39.es/ecma402/#intl-object) makes it clear that `Intl` cannot be invoked: + +> The Intl object does not have a `[[Call]]` internal method; it is not possible to invoke the Intl object as a function. + ## Rule Details -This rule disallows calling the `Math`, `JSON`, `Reflect` and `Atomics` objects as functions. +This rule disallows calling the `Math`, `JSON`, `Reflect`, `Atomics` and `Intl` objects as functions. This rule also disallows using these objects as constructors with the `new` operator. @@ -35,7 +37,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-obj-calls: "error"*/ -/*eslint-env es2017*/ +/*eslint-env es2017, browser */ var math = Math(); @@ -52,6 +54,10 @@ var newReflect = new Reflect(); var atomics = Atomics(); var newAtomics = new Atomics(); + +var intl = Intl(); + +var newIntl = new Intl(); ``` ::: @@ -62,7 +68,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-obj-calls: "error"*/ -/*eslint-env es2017*/ +/*eslint-env es2017, browser*/ function area(r) { return Math.PI * r * r; @@ -73,6 +79,8 @@ var object = JSON.parse("{}"); var value = Reflect.get({ x: 1, y: 2 }, "x"); var first = Atomics.load(foo, 0); + +var segmenterFr = new Intl.Segmenter("fr", { granularity: "word" }); ``` ::: diff --git a/docs/src/rules/no-octal-escape.md b/docs/src/rules/no-octal-escape.md index 366b3940b67..ae8fecdce32 100644 --- a/docs/src/rules/no-octal-escape.md +++ b/docs/src/rules/no-octal-escape.md @@ -1,7 +1,5 @@ --- title: no-octal-escape -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-octal-escape.md rule_type: suggestion --- diff --git a/docs/src/rules/no-octal.md b/docs/src/rules/no-octal.md index cb86480d630..6a8fa7709a4 100644 --- a/docs/src/rules/no-octal.md +++ b/docs/src/rules/no-octal.md @@ -1,7 +1,5 @@ --- title: no-octal -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-octal.md rule_type: suggestion --- diff --git a/docs/src/rules/no-param-reassign.md b/docs/src/rules/no-param-reassign.md index 694c861c109..2bddf7b9921 100644 --- a/docs/src/rules/no-param-reassign.md +++ b/docs/src/rules/no-param-reassign.md @@ -1,7 +1,5 @@ --- title: no-param-reassign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-param-reassign.md rule_type: suggestion further_reading: - https://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments/ diff --git a/docs/src/rules/no-path-concat.md b/docs/src/rules/no-path-concat.md index c1887926d37..840b80c3520 100644 --- a/docs/src/rules/no-path-concat.md +++ b/docs/src/rules/no-path-concat.md @@ -1,7 +1,5 @@ --- title: no-path-concat -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-path-concat.md rule_type: suggestion --- diff --git a/docs/src/rules/no-plusplus.md b/docs/src/rules/no-plusplus.md index 4c1df56a7de..ba787f1968f 100644 --- a/docs/src/rules/no-plusplus.md +++ b/docs/src/rules/no-plusplus.md @@ -1,7 +1,5 @@ --- title: no-plusplus -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-plusplus.md rule_type: suggestion --- diff --git a/docs/src/rules/no-process-env.md b/docs/src/rules/no-process-env.md index dd95ec90492..2130a89443f 100644 --- a/docs/src/rules/no-process-env.md +++ b/docs/src/rules/no-process-env.md @@ -1,7 +1,5 @@ --- title: no-process-env -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-process-env.md rule_type: suggestion further_reading: - https://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files diff --git a/docs/src/rules/no-process-exit.md b/docs/src/rules/no-process-exit.md index 8af40c50f90..6928a0d4843 100644 --- a/docs/src/rules/no-process-exit.md +++ b/docs/src/rules/no-process-exit.md @@ -1,7 +1,5 @@ --- title: no-process-exit -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-process-exit.md rule_type: suggestion --- diff --git a/docs/src/rules/no-promise-executor-return.md b/docs/src/rules/no-promise-executor-return.md index ffbc12124e4..28891624822 100644 --- a/docs/src/rules/no-promise-executor-return.md +++ b/docs/src/rules/no-promise-executor-return.md @@ -1,7 +1,5 @@ --- title: no-promise-executor-return -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-promise-executor-return.md rule_type: problem related_rules: - no-async-promise-executor diff --git a/docs/src/rules/no-proto.md b/docs/src/rules/no-proto.md index c8c15e4f403..629cbbf9a9b 100644 --- a/docs/src/rules/no-proto.md +++ b/docs/src/rules/no-proto.md @@ -1,7 +1,5 @@ --- title: no-proto -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-proto.md rule_type: suggestion further_reading: - https://johnresig.com/blog/objectgetprototypeof/ diff --git a/docs/src/rules/no-prototype-builtins.md b/docs/src/rules/no-prototype-builtins.md index ece2bb9f1c1..e3da23e9cdc 100644 --- a/docs/src/rules/no-prototype-builtins.md +++ b/docs/src/rules/no-prototype-builtins.md @@ -1,7 +1,5 @@ --- title: no-prototype-builtins -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-prototype-builtins.md rule_type: problem --- diff --git a/docs/src/rules/no-redeclare.md b/docs/src/rules/no-redeclare.md index 141339a63c6..e66f0570fc6 100644 --- a/docs/src/rules/no-redeclare.md +++ b/docs/src/rules/no-redeclare.md @@ -1,7 +1,5 @@ --- title: no-redeclare -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-redeclare.md rule_type: suggestion related_rules: - no-shadow diff --git a/docs/src/rules/no-regex-spaces.md b/docs/src/rules/no-regex-spaces.md index f1c7b748b5a..f6ef344bd17 100644 --- a/docs/src/rules/no-regex-spaces.md +++ b/docs/src/rules/no-regex-spaces.md @@ -1,7 +1,5 @@ --- title: no-regex-spaces -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-regex-spaces.md rule_type: suggestion related_rules: - no-div-regex diff --git a/docs/src/rules/no-reserved-keys.md b/docs/src/rules/no-reserved-keys.md index 66cc08e4317..bef6b376e1f 100644 --- a/docs/src/rules/no-reserved-keys.md +++ b/docs/src/rules/no-reserved-keys.md @@ -1,7 +1,5 @@ --- title: no-reserved-keys -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-reserved-keys.md further_reading: - https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names diff --git a/docs/src/rules/no-restricted-exports.md b/docs/src/rules/no-restricted-exports.md index 0136a1f6d19..2f83643f9cb 100644 --- a/docs/src/rules/no-restricted-exports.md +++ b/docs/src/rules/no-restricted-exports.md @@ -1,7 +1,5 @@ --- title: no-restricted-exports -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-restricted-exports.md rule_type: suggestion --- @@ -19,8 +17,16 @@ By default, this rule doesn't disallow any names. Only the names you specify in This rule has an object option: * `"restrictedNamedExports"` is an array of strings, where each string is a name to be restricted. +* `"restrictDefaultExports"` is an object option with boolean properties to restrict certain default export declarations. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. The following properties are allowed: + * `direct`: restricts `export default` declarations. + * `named`: restricts `export { foo as default };` declarations. + * `defaultFrom`: restricts `export { default } from 'foo';` declarations. + * `namedFrom`: restricts `export { foo as default } from 'foo';` declarations. + * `namespaceFrom`: restricts `export * as default from 'foo';` declarations. -Examples of **incorrect** code for this rule: +### restrictedNamedExports + +Examples of **incorrect** code for the `"restrictedNamedExports"` option: ::: incorrect @@ -52,7 +58,7 @@ export { "👍" } from "some_module"; ::: -Examples of **correct** code for this rule: +Examples of **correct** code for the `"restrictedNamedExports"` option: ::: correct @@ -84,11 +90,11 @@ export { "👍" as thumbsUp } from "some_module"; ::: -### Default exports +#### Default exports -By design, this rule doesn't disallow `export default` declarations. If you configure `"default"` as a restricted name, that restriction will apply only to named export declarations. +By design, the `"restrictedNamedExports"` option doesn't disallow `export default` declarations. If you configure `"default"` as a restricted name, that restriction will apply only to named export declarations. -Examples of additional **incorrect** code for this rule: +Examples of additional **incorrect** code for the `"restrictedNamedExports": ["default"]` option: ::: incorrect @@ -112,7 +118,7 @@ export { default } from "some_module"; ::: -Examples of additional **correct** code for this rule: +Examples of additional **correct** code for the `"restrictedNamedExports": ["default"]` option: ::: correct @@ -124,6 +130,85 @@ export default function foo() {} ::: +### restrictDefaultExports + +This option allows you to restrict certain `default` declarations. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. This option accepts the following properties: + +#### direct + +Examples of **incorrect** code for the `"restrictDefaultExports": { "direct": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "direct": true } }]*/ + +export default foo; +export default 42; +export default function foo() {} +``` + +::: + +#### named + +Examples of **incorrect** code for the `"restrictDefaultExports": { "named": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "named": true } }]*/ + +const foo = 123; + +export { foo as default }; +``` + +::: + +#### defaultFrom + +Examples of **incorrect** code for the `"restrictDefaultExports": { "defaultFrom": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "defaultFrom": true } }]*/ + +export { default } from 'foo'; +export { default as default } from 'foo'; +``` + +::: + +#### namedFrom + +Examples of **incorrect** code for the `"restrictDefaultExports": { "namedFrom": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "namedFrom": true } }]*/ + +export { foo as default } from 'foo'; +``` + +::: + +#### namespaceFrom + +Examples of **incorrect** code for the `"restrictDefaultExports": { "namespaceFrom": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "namespaceFrom": true } }]*/ + +export * as default from 'foo'; +``` + +::: + ## Known Limitations This rule doesn't inspect the content of source modules in re-export declarations. In particular, if you are re-exporting everything from another module's export, that export may include a restricted name. This rule cannot detect such cases. diff --git a/docs/src/rules/no-restricted-globals.md b/docs/src/rules/no-restricted-globals.md index a3f602beee6..58a4ad00817 100644 --- a/docs/src/rules/no-restricted-globals.md +++ b/docs/src/rules/no-restricted-globals.md @@ -1,7 +1,5 @@ --- title: no-restricted-globals -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-restricted-globals.md rule_type: suggestion related_rules: - no-restricted-properties diff --git a/docs/src/rules/no-restricted-imports.md b/docs/src/rules/no-restricted-imports.md index 72768cd0d42..ece0ceb5490 100644 --- a/docs/src/rules/no-restricted-imports.md +++ b/docs/src/rules/no-restricted-imports.md @@ -1,7 +1,5 @@ --- title: no-restricted-imports -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-restricted-imports.md rule_type: suggestion --- diff --git a/docs/src/rules/no-restricted-modules.md b/docs/src/rules/no-restricted-modules.md index 3718edcdbc9..3ed5bd86117 100644 --- a/docs/src/rules/no-restricted-modules.md +++ b/docs/src/rules/no-restricted-modules.md @@ -1,7 +1,5 @@ --- title: no-restricted-modules -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-restricted-modules.md rule_type: suggestion --- diff --git a/docs/src/rules/no-restricted-properties.md b/docs/src/rules/no-restricted-properties.md index 30ff9ac28e5..77eecc61133 100644 --- a/docs/src/rules/no-restricted-properties.md +++ b/docs/src/rules/no-restricted-properties.md @@ -1,7 +1,5 @@ --- title: no-restricted-properties -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-restricted-properties.md rule_type: suggestion related_rules: - no-restricted-globals diff --git a/docs/src/rules/no-restricted-syntax.md b/docs/src/rules/no-restricted-syntax.md index 9b73a9c3e62..25d419c149d 100644 --- a/docs/src/rules/no-restricted-syntax.md +++ b/docs/src/rules/no-restricted-syntax.md @@ -1,7 +1,5 @@ --- title: no-restricted-syntax -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-restricted-syntax.md rule_type: suggestion related_rules: - no-alert @@ -15,7 +13,7 @@ JavaScript has a lot of language features, and not everyone likes all of them. A 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/eslint-visitor-keys/blob/main/lib/visitor-keys.js) and use [AST Explorer](https://astexplorer.net/) with the espree 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. +You can also specify [AST selectors](../extend/selectors) to restrict, allowing much more precise control over syntax patterns. ## Rule Details diff --git a/docs/src/rules/no-return-assign.md b/docs/src/rules/no-return-assign.md index c180685655c..89277580f98 100644 --- a/docs/src/rules/no-return-assign.md +++ b/docs/src/rules/no-return-assign.md @@ -1,7 +1,5 @@ --- title: no-return-assign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-return-assign.md rule_type: suggestion --- diff --git a/docs/src/rules/no-return-await.md b/docs/src/rules/no-return-await.md index d05f6367070..8f42100cfff 100644 --- a/docs/src/rules/no-return-await.md +++ b/docs/src/rules/no-return-await.md @@ -1,7 +1,5 @@ --- title: no-return-await -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-return-await.md rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function diff --git a/docs/src/rules/no-script-url.md b/docs/src/rules/no-script-url.md index f4bc56e0191..838a2f41301 100644 --- a/docs/src/rules/no-script-url.md +++ b/docs/src/rules/no-script-url.md @@ -1,7 +1,5 @@ --- title: no-script-url -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-script-url.md rule_type: suggestion further_reading: - https://stackoverflow.com/questions/13497971/what-is-the-matter-with-script-targeted-urls diff --git a/docs/src/rules/no-self-assign.md b/docs/src/rules/no-self-assign.md index 74eac23cdf3..ebdd586943e 100644 --- a/docs/src/rules/no-self-assign.md +++ b/docs/src/rules/no-self-assign.md @@ -1,7 +1,5 @@ --- title: no-self-assign -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-self-assign.md rule_type: problem --- diff --git a/docs/src/rules/no-self-compare.md b/docs/src/rules/no-self-compare.md index a0ba9bdd0cb..bf0562993df 100644 --- a/docs/src/rules/no-self-compare.md +++ b/docs/src/rules/no-self-compare.md @@ -1,7 +1,5 @@ --- title: no-self-compare -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-self-compare.md rule_type: problem --- diff --git a/docs/src/rules/no-sequences.md b/docs/src/rules/no-sequences.md index 6e9ab59d2e5..cb2b99c50e1 100644 --- a/docs/src/rules/no-sequences.md +++ b/docs/src/rules/no-sequences.md @@ -1,7 +1,5 @@ --- title: no-sequences -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-sequences.md rule_type: suggestion --- diff --git a/docs/src/rules/no-setter-return.md b/docs/src/rules/no-setter-return.md index 2538cd8f797..ceb4558c15a 100644 --- a/docs/src/rules/no-setter-return.md +++ b/docs/src/rules/no-setter-return.md @@ -1,7 +1,5 @@ --- title: no-setter-return -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-setter-return.md rule_type: problem related_rules: - getter-return diff --git a/docs/src/rules/no-shadow-restricted-names.md b/docs/src/rules/no-shadow-restricted-names.md index 64a00184d41..308772edaf3 100644 --- a/docs/src/rules/no-shadow-restricted-names.md +++ b/docs/src/rules/no-shadow-restricted-names.md @@ -1,7 +1,5 @@ --- title: no-shadow-restricted-names -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-shadow-restricted-names.md rule_type: suggestion related_rules: - no-shadow diff --git a/docs/src/rules/no-shadow.md b/docs/src/rules/no-shadow.md index 5d8ccf732e1..e4a081f0a30 100644 --- a/docs/src/rules/no-shadow.md +++ b/docs/src/rules/no-shadow.md @@ -1,7 +1,5 @@ --- title: no-shadow -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-shadow.md rule_type: suggestion related_rules: - no-shadow-restricted-names diff --git a/docs/src/rules/no-space-before-semi.md b/docs/src/rules/no-space-before-semi.md index 03a39fe47a8..4c6650f0b2f 100644 --- a/docs/src/rules/no-space-before-semi.md +++ b/docs/src/rules/no-space-before-semi.md @@ -1,7 +1,5 @@ --- title: no-space-before-semi -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-space-before-semi.md related_rules: - semi diff --git a/docs/src/rules/no-spaced-func.md b/docs/src/rules/no-spaced-func.md index 5858cea9b44..099efcf8d1f 100644 --- a/docs/src/rules/no-spaced-func.md +++ b/docs/src/rules/no-spaced-func.md @@ -1,7 +1,5 @@ --- title: no-spaced-func -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-spaced-func.md rule_type: layout --- diff --git a/docs/src/rules/no-sparse-arrays.md b/docs/src/rules/no-sparse-arrays.md index d948d943b48..ec41bb23df4 100644 --- a/docs/src/rules/no-sparse-arrays.md +++ b/docs/src/rules/no-sparse-arrays.md @@ -1,7 +1,5 @@ --- title: no-sparse-arrays -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-sparse-arrays.md rule_type: problem further_reading: - https://www.nczonline.net/blog/2007/09/09/inconsistent-array-literals/ diff --git a/docs/src/rules/no-sync.md b/docs/src/rules/no-sync.md index 1e48dd3bb8d..33fbec47fc0 100644 --- a/docs/src/rules/no-sync.md +++ b/docs/src/rules/no-sync.md @@ -1,7 +1,5 @@ --- title: no-sync -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-sync.md rule_type: suggestion --- diff --git a/docs/src/rules/no-tabs.md b/docs/src/rules/no-tabs.md index 756070950ef..316db6203f3 100644 --- a/docs/src/rules/no-tabs.md +++ b/docs/src/rules/no-tabs.md @@ -1,7 +1,5 @@ --- title: no-tabs -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-tabs.md rule_type: layout --- diff --git a/docs/src/rules/no-template-curly-in-string.md b/docs/src/rules/no-template-curly-in-string.md index ff968df3c97..699e0a84b97 100644 --- a/docs/src/rules/no-template-curly-in-string.md +++ b/docs/src/rules/no-template-curly-in-string.md @@ -1,7 +1,5 @@ --- title: no-template-curly-in-string -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-template-curly-in-string.md rule_type: problem --- diff --git a/docs/src/rules/no-ternary.md b/docs/src/rules/no-ternary.md index 28c935c8b1b..b4ff2584634 100644 --- a/docs/src/rules/no-ternary.md +++ b/docs/src/rules/no-ternary.md @@ -1,7 +1,5 @@ --- title: no-ternary -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-ternary.md rule_type: suggestion related_rules: - no-nested-ternary diff --git a/docs/src/rules/no-this-before-super.md b/docs/src/rules/no-this-before-super.md index 0493064867c..f1425b5ed35 100644 --- a/docs/src/rules/no-this-before-super.md +++ b/docs/src/rules/no-this-before-super.md @@ -1,7 +1,5 @@ --- title: no-this-before-super -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-this-before-super.md rule_type: problem --- diff --git a/docs/src/rules/no-throw-literal.md b/docs/src/rules/no-throw-literal.md index 798d5c80eea..621bb11a09c 100644 --- a/docs/src/rules/no-throw-literal.md +++ b/docs/src/rules/no-throw-literal.md @@ -1,7 +1,5 @@ --- title: no-throw-literal -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-throw-literal.md rule_type: suggestion --- diff --git a/docs/src/rules/no-trailing-spaces.md b/docs/src/rules/no-trailing-spaces.md index 40e70c52ca1..fba4f7df9a2 100644 --- a/docs/src/rules/no-trailing-spaces.md +++ b/docs/src/rules/no-trailing-spaces.md @@ -1,7 +1,5 @@ --- title: no-trailing-spaces -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-trailing-spaces.md rule_type: layout --- diff --git a/docs/src/rules/no-undef-init.md b/docs/src/rules/no-undef-init.md index 6bfa7386c8f..f49ee8c7de4 100644 --- a/docs/src/rules/no-undef-init.md +++ b/docs/src/rules/no-undef-init.md @@ -1,7 +1,5 @@ --- title: no-undef-init -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-undef-init.md rule_type: suggestion related_rules: - no-undefined diff --git a/docs/src/rules/no-undef.md b/docs/src/rules/no-undef.md index e1e4c6780a2..0bc7a28279f 100644 --- a/docs/src/rules/no-undef.md +++ b/docs/src/rules/no-undef.md @@ -1,7 +1,5 @@ --- title: no-undef -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-undef.md rule_type: problem related_rules: - no-global-assign @@ -14,7 +12,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, or specified in the [`globals` key in the configuration file](../user-guide/configuring/language-options#using-configuration-files-1). A common use case for these is if you intentionally use globals that are defined elsewhere (e.g. in a script sourced from HTML). +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](../use/configure/language-options#using-configuration-files-1). 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: @@ -98,7 +96,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](../user-guide/configuring/language-options#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](../use/configure/language-options#specifying-environments). A few examples are given below. ### browser diff --git a/docs/src/rules/no-undefined.md b/docs/src/rules/no-undefined.md index e4c561b346e..9b0be204b2f 100644 --- a/docs/src/rules/no-undefined.md +++ b/docs/src/rules/no-undefined.md @@ -1,7 +1,5 @@ --- title: no-undefined -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-undefined.md rule_type: suggestion related_rules: - no-undef-init @@ -59,6 +57,8 @@ if (foo === undefined) { function foo(undefined) { // ... } + +bar(undefined, "lorem"); ``` ::: @@ -79,6 +79,8 @@ if (typeof foo === "undefined") { } global.undefined = "foo"; + +bar(void 0, "lorem"); ``` ::: diff --git a/docs/src/rules/no-underscore-dangle.md b/docs/src/rules/no-underscore-dangle.md index 82c7af71720..d35aefeb72d 100644 --- a/docs/src/rules/no-underscore-dangle.md +++ b/docs/src/rules/no-underscore-dangle.md @@ -1,7 +1,5 @@ --- title: no-underscore-dangle -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-underscore-dangle.md rule_type: suggestion --- @@ -62,6 +60,8 @@ This rule has an object option: * `"allowAfterThisConstructor": false` (default) disallows dangling underscores in members of the `this.constructor` object * `"enforceInMethodNames": false` (default) allows dangling underscores in method names * `"enforceInClassFields": false` (default) allows dangling underscores in es2022 class fields names +* `"allowInArrayDestructuring": true` (default) allows dangling underscores in variable names assigned by array destructuring +* `"allowInObjectDestructuring": true` (default) allows dangling underscores in variable names assigned by object destructuring * `"allowFunctionParams": true` (default) allows dangling underscores in function parameter names ### allow @@ -184,6 +184,50 @@ class Foo { ::: +### allowInArrayDestructuring + +Examples of **incorrect** code for this rule with the `{ "allowInArrayDestructuring": false }` option: + +::: incorrect + +```js +/*eslint no-underscore-dangle: ["error", { "allowInArrayDestructuring": false }]*/ + +const [_foo, _bar] = list; +const [foo_, ..._bar] = list; +const [foo, [bar, _baz]] = list; +``` + +::: + +### allowInObjectDestructuring + +Examples of **incorrect** code for this rule with the `{ "allowInObjectDestructuring": false }` option: + +::: incorrect + +```js +/*eslint no-underscore-dangle: ["error", { "allowInObjectDestructuring": false }]*/ + +const { foo, bar: _bar } = collection; +const { foo, bar, _baz } = collection; +``` + +::: + +Examples of **correct** code for this rule with the `{ "allowInObjectDestructuring": false }` option: + +::: correct + +```js +/*eslint no-underscore-dangle: ["error", { "allowInObjectDestructuring": false }]*/ + +const { foo, bar, _baz: { a, b } } = collection; +const { foo, bar, _baz: baz } = collection; +``` + +::: + ### allowFunctionParams Examples of **incorrect** code for this rule with the `{ "allowFunctionParams": false }` option: diff --git a/docs/src/rules/no-unexpected-multiline.md b/docs/src/rules/no-unexpected-multiline.md index 5e27842c8e1..158a38642a4 100644 --- a/docs/src/rules/no-unexpected-multiline.md +++ b/docs/src/rules/no-unexpected-multiline.md @@ -1,7 +1,5 @@ --- title: no-unexpected-multiline -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unexpected-multiline.md rule_type: problem related_rules: - func-call-spacing diff --git a/docs/src/rules/no-unmodified-loop-condition.md b/docs/src/rules/no-unmodified-loop-condition.md index 3deaa86122d..29932091697 100644 --- a/docs/src/rules/no-unmodified-loop-condition.md +++ b/docs/src/rules/no-unmodified-loop-condition.md @@ -1,7 +1,5 @@ --- title: no-unmodified-loop-condition -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unmodified-loop-condition.md rule_type: problem --- diff --git a/docs/src/rules/no-unneeded-ternary.md b/docs/src/rules/no-unneeded-ternary.md index 6bd9d96ae8b..ca275810e02 100644 --- a/docs/src/rules/no-unneeded-ternary.md +++ b/docs/src/rules/no-unneeded-ternary.md @@ -1,7 +1,5 @@ --- title: no-unneeded-ternary -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unneeded-ternary.md rule_type: suggestion related_rules: - no-ternary diff --git a/docs/src/rules/no-unreachable-loop.md b/docs/src/rules/no-unreachable-loop.md index b5e79ef1d2a..89b9a76bdd8 100644 --- a/docs/src/rules/no-unreachable-loop.md +++ b/docs/src/rules/no-unreachable-loop.md @@ -1,7 +1,5 @@ --- title: no-unreachable-loop -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unreachable-loop.md rule_type: problem related_rules: - no-unreachable diff --git a/docs/src/rules/no-unreachable.md b/docs/src/rules/no-unreachable.md index 789815b1c03..4f762084b41 100644 --- a/docs/src/rules/no-unreachable.md +++ b/docs/src/rules/no-unreachable.md @@ -1,7 +1,5 @@ --- title: no-unreachable -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unreachable.md rule_type: problem --- diff --git a/docs/src/rules/no-unsafe-finally.md b/docs/src/rules/no-unsafe-finally.md index 6451bc93706..661fcdf24b6 100644 --- a/docs/src/rules/no-unsafe-finally.md +++ b/docs/src/rules/no-unsafe-finally.md @@ -1,7 +1,5 @@ --- title: no-unsafe-finally -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unsafe-finally.md rule_type: problem --- diff --git a/docs/src/rules/no-unsafe-negation.md b/docs/src/rules/no-unsafe-negation.md index 3bbd5b8f5f3..522e4ab4d1a 100644 --- a/docs/src/rules/no-unsafe-negation.md +++ b/docs/src/rules/no-unsafe-negation.md @@ -1,7 +1,5 @@ --- title: no-unsafe-negation -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unsafe-negation.md rule_type: problem --- diff --git a/docs/src/rules/no-unsafe-optional-chaining.md b/docs/src/rules/no-unsafe-optional-chaining.md index a04520df000..e4cc6f83166 100644 --- a/docs/src/rules/no-unsafe-optional-chaining.md +++ b/docs/src/rules/no-unsafe-optional-chaining.md @@ -1,7 +1,5 @@ --- title: no-unsafe-optional-chaining -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unsafe-optional-chaining.md rule_type: problem --- diff --git a/docs/src/rules/no-unused-expressions.md b/docs/src/rules/no-unused-expressions.md index 769f483527c..c67a2a61706 100644 --- a/docs/src/rules/no-unused-expressions.md +++ b/docs/src/rules/no-unused-expressions.md @@ -1,7 +1,5 @@ --- title: no-unused-expressions -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unused-expressions.md rule_type: suggestion --- diff --git a/docs/src/rules/no-unused-labels.md b/docs/src/rules/no-unused-labels.md index 5ebd95ca233..971c5672203 100644 --- a/docs/src/rules/no-unused-labels.md +++ b/docs/src/rules/no-unused-labels.md @@ -1,7 +1,5 @@ --- title: no-unused-labels -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unused-labels.md rule_type: suggestion related_rules: - no-extra-label diff --git a/docs/src/rules/no-unused-private-class-members.md b/docs/src/rules/no-unused-private-class-members.md index f277c677eb8..c92a8827be1 100644 --- a/docs/src/rules/no-unused-private-class-members.md +++ b/docs/src/rules/no-unused-private-class-members.md @@ -1,7 +1,5 @@ --- title: no-unused-private-class-members -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unused-private-class-members.md rule_type: problem --- diff --git a/docs/src/rules/no-unused-vars.md b/docs/src/rules/no-unused-vars.md index 06a83cab24c..1b3e59ce93b 100644 --- a/docs/src/rules/no-unused-vars.md +++ b/docs/src/rules/no-unused-vars.md @@ -1,7 +1,5 @@ --- title: no-unused-vars -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-unused-vars.md rule_type: problem --- @@ -249,25 +247,6 @@ 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/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: - -::: correct - -```js -/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ -// 'foo' and 'bar' were ignored because they have a rest property sibling. -var { foo, ...coords } = data; - -var bar; -({ bar, ...coords } = data); -``` - -::: - ### argsIgnorePattern The `argsIgnorePattern` option specifies exceptions not to check for usage: arguments whose names match a regexp pattern. For example, variables whose names begin with an underscore. @@ -287,47 +266,6 @@ foo(); ::: -### destructuredArrayIgnorePattern - -The `destructuredArrayIgnorePattern` option specifies exceptions not to check for usage: elements of array destructuring patterns whose names match a regexp pattern. For example, variables whose names begin with an underscore. - -Examples of **correct** code for the `{ "destructuredArrayIgnorePattern": "^_" }` option: - -::: correct - -```js -/*eslint no-unused-vars: ["error", { "destructuredArrayIgnorePattern": "^_" }]*/ - -const [a, _b, c] = ["a", "b", "c"]; -console.log(a+c); - -const { x: [_a, foo] } = bar; -console.log(foo); - -function baz([_c, x]) { - x; -} -baz(); - -function test({p: [_q, r]}) { - r; -} -test(); - -let _m, n; -foo.forEach(item => { - [_m, n] = item; - console.log(n); -}); - -let _o, p; -_o = 1; -[_o, p] = foo; -p; -``` - -::: - ### caughtErrors The `caughtErrors` option is used for `catch` block arguments validation. @@ -397,6 +335,66 @@ try { ::: +### destructuredArrayIgnorePattern + +The `destructuredArrayIgnorePattern` option specifies exceptions not to check for usage: elements of array destructuring patterns whose names match a regexp pattern. For example, variables whose names begin with an underscore. + +Examples of **correct** code for the `{ "destructuredArrayIgnorePattern": "^_" }` option: + +::: correct + +```js +/*eslint no-unused-vars: ["error", { "destructuredArrayIgnorePattern": "^_" }]*/ + +const [a, _b, c] = ["a", "b", "c"]; +console.log(a+c); + +const { x: [_a, foo] } = bar; +console.log(foo); + +function baz([_c, x]) { + x; +} +baz(); + +function test({p: [_q, r]}) { + r; +} +test(); + +let _m, n; +foo.forEach(item => { + [_m, n] = item; + console.log(n); +}); + +let _o, p; +_o = 1; +[_o, p] = foo; +p; +``` + +::: + +### ignoreRestSiblings + +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: + +::: correct + +```js +/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ +// 'foo' and 'bar' were ignored because they have a rest property sibling. +var { foo, ...coords } = data; + +var bar; +({ bar, ...coords } = data); +``` + +::: + ## When Not To Use It If you don't want to be notified about unused variables or function arguments, you can safely turn this rule off. diff --git a/docs/src/rules/no-use-before-define.md b/docs/src/rules/no-use-before-define.md index 01b2c387d6e..726f5fd861b 100644 --- a/docs/src/rules/no-use-before-define.md +++ b/docs/src/rules/no-use-before-define.md @@ -1,7 +1,5 @@ --- title: no-use-before-define -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-use-before-define.md rule_type: problem --- diff --git a/docs/src/rules/no-useless-backreference.md b/docs/src/rules/no-useless-backreference.md index 5178a2ec937..d97f9896096 100644 --- a/docs/src/rules/no-useless-backreference.md +++ b/docs/src/rules/no-useless-backreference.md @@ -1,7 +1,5 @@ --- title: no-useless-backreference -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-useless-backreference.md rule_type: problem related_rules: - no-control-regex diff --git a/docs/src/rules/no-useless-call.md b/docs/src/rules/no-useless-call.md index 1999cf163df..76ae7eeda63 100644 --- a/docs/src/rules/no-useless-call.md +++ b/docs/src/rules/no-useless-call.md @@ -1,7 +1,5 @@ --- title: no-useless-call -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-useless-call.md rule_type: suggestion related_rules: - prefer-spread diff --git a/docs/src/rules/no-useless-catch.md b/docs/src/rules/no-useless-catch.md index 4ca8cc331b0..3e312fc888d 100644 --- a/docs/src/rules/no-useless-catch.md +++ b/docs/src/rules/no-useless-catch.md @@ -1,7 +1,5 @@ --- title: no-useless-catch -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-useless-catch.md rule_type: suggestion --- diff --git a/docs/src/rules/no-useless-computed-key.md b/docs/src/rules/no-useless-computed-key.md index be4aa353d9f..f0acb66cc0d 100644 --- a/docs/src/rules/no-useless-computed-key.md +++ b/docs/src/rules/no-useless-computed-key.md @@ -1,7 +1,5 @@ --- title: no-useless-computed-key -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-useless-computed-key.md rule_type: suggestion --- diff --git a/docs/src/rules/no-useless-concat.md b/docs/src/rules/no-useless-concat.md index 57cf297c250..4cdf32d5d71 100644 --- a/docs/src/rules/no-useless-concat.md +++ b/docs/src/rules/no-useless-concat.md @@ -1,7 +1,5 @@ --- title: no-useless-concat -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-useless-concat.md rule_type: suggestion --- diff --git a/docs/src/rules/no-useless-constructor.md b/docs/src/rules/no-useless-constructor.md index 1830b2e085f..cfdae434d72 100644 --- a/docs/src/rules/no-useless-constructor.md +++ b/docs/src/rules/no-useless-constructor.md @@ -1,7 +1,5 @@ --- title: no-useless-constructor -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-useless-constructor.md rule_type: suggestion --- diff --git a/docs/src/rules/no-useless-escape.md b/docs/src/rules/no-useless-escape.md index b52f569d923..cd28fda75a8 100644 --- a/docs/src/rules/no-useless-escape.md +++ b/docs/src/rules/no-useless-escape.md @@ -1,7 +1,5 @@ --- title: no-useless-escape -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-useless-escape.md rule_type: suggestion --- diff --git a/docs/src/rules/no-useless-rename.md b/docs/src/rules/no-useless-rename.md index 97ee34c7b30..d65541c2ad0 100644 --- a/docs/src/rules/no-useless-rename.md +++ b/docs/src/rules/no-useless-rename.md @@ -1,7 +1,5 @@ --- title: no-useless-rename -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-useless-rename.md rule_type: suggestion related_rules: - object-shorthand diff --git a/docs/src/rules/no-useless-return.md b/docs/src/rules/no-useless-return.md index e52673601f1..961f0aef5ca 100644 --- a/docs/src/rules/no-useless-return.md +++ b/docs/src/rules/no-useless-return.md @@ -1,7 +1,5 @@ --- title: no-useless-return -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-useless-return.md rule_type: suggestion --- diff --git a/docs/src/rules/no-var.md b/docs/src/rules/no-var.md index dbd266b4523..d1e02ad14d7 100644 --- a/docs/src/rules/no-var.md +++ b/docs/src/rules/no-var.md @@ -1,7 +1,5 @@ --- title: no-var -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-var.md rule_type: suggestion --- diff --git a/docs/src/rules/no-void.md b/docs/src/rules/no-void.md index 6ca4a15599c..6120cf4d142 100644 --- a/docs/src/rules/no-void.md +++ b/docs/src/rules/no-void.md @@ -1,7 +1,5 @@ --- title: no-void -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-void.md rule_type: suggestion related_rules: - no-undef-init diff --git a/docs/src/rules/no-warning-comments.md b/docs/src/rules/no-warning-comments.md index 6ddef3c5de4..966d42ece75 100644 --- a/docs/src/rules/no-warning-comments.md +++ b/docs/src/rules/no-warning-comments.md @@ -1,7 +1,5 @@ --- title: no-warning-comments -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-warning-comments.md rule_type: suggestion --- @@ -21,8 +19,9 @@ This rule reports comments that include any of the predefined terms specified in This rule has an options object literal: -* `"terms"`: optional array of terms to match. Defaults to `["todo", "fixme", "xxx"]`. Terms are matched case-insensitive and as whole words: `fix` would match `FIX` but not `fixing`. Terms can consist of multiple words: `really bad idea`. -* `"location"`: optional string that configures where in your comments to check for matches. Defaults to `"start"`. The other value is match `anywhere` in comments. +* `"terms"`: optional array of terms to match. Defaults to `["todo", "fixme", "xxx"]`. Terms are matched case-insensitively and as whole words: `fix` would match `FIX` but not `fixing`. Terms can consist of multiple words: `really bad idea`. +* `"location"`: optional string that configures where in your comments to check for matches. Defaults to `"start"`. The start is from the first non-decorative character, ignoring whitespace, new lines and characters specified in `decoration`. The other value is match `anywhere` in comments. +* `"decoration"`: optional array of characters that are ignored at the start of a comment, when location is `"start"`. Defaults to `[]`. Any sequence of whitespace or the characters from this property are ignored. This option is ignored when location is `"anywhere"`. Example of **incorrect** code for the default `{ "terms": ["todo", "fixme", "xxx"], "location": "start" }` options: @@ -31,6 +30,9 @@ Example of **incorrect** code for the default `{ "terms": ["todo", "fixme", "xxx ```js /*eslint no-warning-comments: "error"*/ +/* +FIXME +*/ function callback(err, results) { if (err) { console.error(err); @@ -73,7 +75,7 @@ Examples of **incorrect** code for the `{ "terms": ["todo", "fixme", "any other // TODO: this // todo: this too // Even this: TODO -/* /* +/* * The same goes for this TODO comment * Or a fixme * as well as any other term @@ -101,6 +103,54 @@ Examples of **correct** code for the `{ "terms": ["todo", "fixme", "any other te ::: +### Decoration Characters + +Examples of **incorrect** code for the `{ "decoration": ["*"] }` options: + +::: incorrect + +```js +/*eslint no-warning-comments: ["error", { "decoration": ["*"] }]*/ + +//***** todo decorative asterisks are ignored *****// +/** + * TODO new lines and asterisks are also ignored in block comments. + */ +``` + +::: + +Examples of **incorrect** code for the `{ "decoration": ["/", "*"] }` options: + +::: incorrect + +```js +/*eslint no-warning-comments: ["error", { "decoration": ["/", "*"] }]*/ + +////// TODO decorative slashes and whitespace are ignored ////// +//***** todo decorative asterisks are also ignored *****// +/** + * TODO new lines are also ignored in block comments. + */ +``` + +::: + +Examples of **correct** code for the `{ "decoration": ["/", "*"] }` options: + +::: correct + +```js +/*eslint no-warning-comments: ["error", { "decoration": ["/", "*"] }]*/ + +//!TODO preceded by non-decoration character +/** + *!TODO preceded by non-decoration character in a block comment + */ +``` + +::: + ## When Not To Use It * If you have a large code base that was not developed with a policy to not use such warning terms, you might get hundreds of warnings / errors which might be counter-productive if you can't fix all of them (e.g. if you don't get the time to do it) as you might overlook other warnings / errors or get used to many of them and don't pay attention on it anymore. diff --git a/docs/src/rules/no-whitespace-before-property.md b/docs/src/rules/no-whitespace-before-property.md index 1a46c56b698..db79ca2a897 100644 --- a/docs/src/rules/no-whitespace-before-property.md +++ b/docs/src/rules/no-whitespace-before-property.md @@ -1,7 +1,5 @@ --- title: no-whitespace-before-property -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-whitespace-before-property.md rule_type: layout --- diff --git a/docs/src/rules/no-with.md b/docs/src/rules/no-with.md index 6e1740acdf3..4fc3f75841d 100644 --- a/docs/src/rules/no-with.md +++ b/docs/src/rules/no-with.md @@ -1,7 +1,5 @@ --- title: no-with -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-with.md rule_type: suggestion further_reading: - https://web.archive.org/web/20200717110117/https://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/ diff --git a/docs/src/rules/no-wrap-func.md b/docs/src/rules/no-wrap-func.md index 22c57a390fd..b0b64be8d14 100644 --- a/docs/src/rules/no-wrap-func.md +++ b/docs/src/rules/no-wrap-func.md @@ -1,7 +1,5 @@ --- title: no-wrap-func -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/no-wrap-func.md --- diff --git a/docs/src/rules/nonblock-statement-body-position.md b/docs/src/rules/nonblock-statement-body-position.md index 6c6c2cfdccb..78d332026e7 100644 --- a/docs/src/rules/nonblock-statement-body-position.md +++ b/docs/src/rules/nonblock-statement-body-position.md @@ -1,7 +1,5 @@ --- title: nonblock-statement-body-position -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/nonblock-statement-body-position.md rule_type: layout further_reading: - https://jscs-dev.github.io/rule/requireNewlineBeforeSingleStatementsInIf @@ -36,7 +34,7 @@ if (foo) bar(); This rule aims to enforce a consistent location for single-line statements. -Note that this rule does not enforce the usage of single-line statements in general. If you would like to disallow single-line statements, use the [`curly`](/docs/rules/curly) rule instead. +Note that this rule does not enforce the usage of single-line statements in general. If you would like to disallow single-line statements, use the [`curly`](curly) rule instead. ### Options @@ -184,4 +182,4 @@ while (foo) ## When Not To Use It -If you're not concerned about consistent locations of single-line statements, you should not turn on this rule. You can also disable this rule if you're using the `"all"` option for the [`curly`](/docs/rules/curly) rule, because this will disallow single-line statements entirely. +If you're not concerned about consistent locations of single-line statements, you should not turn on this rule. You can also disable this rule if you're using the `"all"` option for the [`curly`](curly) rule, because this will disallow single-line statements entirely. diff --git a/docs/src/rules/object-curly-newline.md b/docs/src/rules/object-curly-newline.md index 6c992c7aa27..a9b8875a592 100644 --- a/docs/src/rules/object-curly-newline.md +++ b/docs/src/rules/object-curly-newline.md @@ -1,7 +1,5 @@ --- title: object-curly-newline -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/object-curly-newline.md rule_type: layout related_rules: - comma-spacing diff --git a/docs/src/rules/object-curly-spacing.md b/docs/src/rules/object-curly-spacing.md index 0b36ade9ea9..4027c9ab755 100644 --- a/docs/src/rules/object-curly-spacing.md +++ b/docs/src/rules/object-curly-spacing.md @@ -1,7 +1,5 @@ --- title: object-curly-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/object-curly-spacing.md rule_type: layout related_rules: - array-bracket-spacing diff --git a/docs/src/rules/object-property-newline.md b/docs/src/rules/object-property-newline.md index 0b8a657b0b9..50214bdb7c0 100644 --- a/docs/src/rules/object-property-newline.md +++ b/docs/src/rules/object-property-newline.md @@ -1,7 +1,5 @@ --- title: object-property-newline -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/object-property-newline.md rule_type: layout related_rules: - brace-style diff --git a/docs/src/rules/object-shorthand.md b/docs/src/rules/object-shorthand.md index aa05bb3f083..d694b57b029 100644 --- a/docs/src/rules/object-shorthand.md +++ b/docs/src/rules/object-shorthand.md @@ -1,7 +1,5 @@ --- title: object-shorthand -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/object-shorthand.md rule_type: suggestion related_rules: - no-useless-rename diff --git a/docs/src/rules/one-var-declaration-per-line.md b/docs/src/rules/one-var-declaration-per-line.md index b2b21d447d8..e9b159fc992 100644 --- a/docs/src/rules/one-var-declaration-per-line.md +++ b/docs/src/rules/one-var-declaration-per-line.md @@ -1,7 +1,5 @@ --- title: one-var-declaration-per-line -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/one-var-declaration-per-line.md rule_type: suggestion related_rules: - one-var diff --git a/docs/src/rules/one-var.md b/docs/src/rules/one-var.md index 1835b6b2be8..d9a50ed8671 100644 --- a/docs/src/rules/one-var.md +++ b/docs/src/rules/one-var.md @@ -1,7 +1,5 @@ --- title: one-var -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/one-var.md rule_type: suggestion --- diff --git a/docs/src/rules/operator-assignment.md b/docs/src/rules/operator-assignment.md index 9b6d4e3c9b0..2c7713717b6 100644 --- a/docs/src/rules/operator-assignment.md +++ b/docs/src/rules/operator-assignment.md @@ -1,7 +1,5 @@ --- title: operator-assignment -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/operator-assignment.md rule_type: suggestion --- diff --git a/docs/src/rules/operator-linebreak.md b/docs/src/rules/operator-linebreak.md index fedb7e92a32..f3cab6e4f03 100644 --- a/docs/src/rules/operator-linebreak.md +++ b/docs/src/rules/operator-linebreak.md @@ -1,7 +1,5 @@ --- title: operator-linebreak -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/operator-linebreak.md rule_type: layout related_rules: - comma-style diff --git a/docs/src/rules/padded-blocks.md b/docs/src/rules/padded-blocks.md index 2665620d4b5..fd57f2a2905 100644 --- a/docs/src/rules/padded-blocks.md +++ b/docs/src/rules/padded-blocks.md @@ -1,7 +1,5 @@ --- title: padded-blocks -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/padded-blocks.md rule_type: layout related_rules: - lines-between-class-members diff --git a/docs/src/rules/padding-line-between-statements.md b/docs/src/rules/padding-line-between-statements.md index 8eef570e1b5..1b16881c3ee 100644 --- a/docs/src/rules/padding-line-between-statements.md +++ b/docs/src/rules/padding-line-between-statements.md @@ -1,7 +1,5 @@ --- title: padding-line-between-statements -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/padding-line-between-statements.md rule_type: layout --- diff --git a/docs/src/rules/prefer-arrow-callback.md b/docs/src/rules/prefer-arrow-callback.md index 37f5c9d3b21..0d724938447 100644 --- a/docs/src/rules/prefer-arrow-callback.md +++ b/docs/src/rules/prefer-arrow-callback.md @@ -1,7 +1,5 @@ --- title: prefer-arrow-callback -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-arrow-callback.md rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions diff --git a/docs/src/rules/prefer-const.md b/docs/src/rules/prefer-const.md index 1e9268f84c4..19801eb952a 100644 --- a/docs/src/rules/prefer-const.md +++ b/docs/src/rules/prefer-const.md @@ -1,7 +1,5 @@ --- title: prefer-const -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-const.md rule_type: suggestion related_rules: - no-var @@ -107,7 +105,7 @@ for (const a of [1, 2, 3]) { // `end` is never reassigned, but we cannot separate the declarations without modifying the scope. for (let i = 0, end = 10; i < end; ++i) { - console.log(a); + console.log(i); } // `predicate` is only assigned once but cannot be separately declared as `const` diff --git a/docs/src/rules/prefer-destructuring.md b/docs/src/rules/prefer-destructuring.md index 3ee5a5b74d2..c98abdc0c90 100644 --- a/docs/src/rules/prefer-destructuring.md +++ b/docs/src/rules/prefer-destructuring.md @@ -1,7 +1,5 @@ --- title: prefer-destructuring -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-destructuring.md rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment diff --git a/docs/src/rules/prefer-exponentiation-operator.md b/docs/src/rules/prefer-exponentiation-operator.md index c814948366c..4544010a579 100644 --- a/docs/src/rules/prefer-exponentiation-operator.md +++ b/docs/src/rules/prefer-exponentiation-operator.md @@ -1,7 +1,5 @@ --- title: prefer-exponentiation-operator -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-exponentiation-operator.md rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation diff --git a/docs/src/rules/prefer-named-capture-group.md b/docs/src/rules/prefer-named-capture-group.md index c67847d2348..4f60319b0b0 100644 --- a/docs/src/rules/prefer-named-capture-group.md +++ b/docs/src/rules/prefer-named-capture-group.md @@ -1,7 +1,5 @@ --- title: prefer-named-capture-group -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-named-capture-group.md rule_type: suggestion related_rules: - no-invalid-regexp diff --git a/docs/src/rules/prefer-numeric-literals.md b/docs/src/rules/prefer-numeric-literals.md index c0506d1b6b1..a90fd7dffd2 100644 --- a/docs/src/rules/prefer-numeric-literals.md +++ b/docs/src/rules/prefer-numeric-literals.md @@ -1,7 +1,5 @@ --- title: prefer-numeric-literals -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-numeric-literals.md rule_type: suggestion --- diff --git a/docs/src/rules/prefer-object-has-own.md b/docs/src/rules/prefer-object-has-own.md index 3445ae17836..911702f1de8 100644 --- a/docs/src/rules/prefer-object-has-own.md +++ b/docs/src/rules/prefer-object-has-own.md @@ -1,7 +1,5 @@ --- title: prefer-object-has-own -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-object-has-own.md rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn diff --git a/docs/src/rules/prefer-object-spread.md b/docs/src/rules/prefer-object-spread.md index 73b9aa8c498..b1927350ece 100644 --- a/docs/src/rules/prefer-object-spread.md +++ b/docs/src/rules/prefer-object-spread.md @@ -1,7 +1,5 @@ --- title: prefer-object-spread -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-object-spread.md rule_type: suggestion --- diff --git a/docs/src/rules/prefer-promise-reject-errors.md b/docs/src/rules/prefer-promise-reject-errors.md index 9959737a680..c4874c28b15 100644 --- a/docs/src/rules/prefer-promise-reject-errors.md +++ b/docs/src/rules/prefer-promise-reject-errors.md @@ -1,7 +1,5 @@ --- title: prefer-promise-reject-errors -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-promise-reject-errors.md rule_type: suggestion related_rules: - no-throw-literal diff --git a/docs/src/rules/prefer-reflect.md b/docs/src/rules/prefer-reflect.md index 2d388c413e4..e2a632eb6c6 100644 --- a/docs/src/rules/prefer-reflect.md +++ b/docs/src/rules/prefer-reflect.md @@ -1,7 +1,5 @@ --- title: prefer-reflect -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-reflect.md rule_type: suggestion related_rules: - no-useless-call @@ -12,6 +10,8 @@ related_rules: This rule was **deprecated** in ESLint v3.9.0 and will not be replaced. The original intent of this rule now seems misguided as we have come to understand that `Reflect` methods are not actually intended to replace the `Object` counterparts the rule suggests, but rather exist as low-level primitives to be used with proxies in order to replicate the default behavior of various previously existing functionality. +**Please note**: This rule contains an incorrect behavior - it will suggest you to use `Reflect.getOwnPropertyNames` rather than `Object.getOwnPropertyNames`, but the former one doesn't exist in the [specification](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflection). We suggest using the `exceptions` option with `"getOwnPropertyNames"` to avoid this false suggestion. + The ES6 Reflect API comes with a handful of methods which somewhat deprecate methods on old constructors: * [`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) @@ -321,47 +321,6 @@ Reflect.isExtensible({}) ::: -### Reflect.getOwnPropertyNames - -Deprecates `Object.getOwnPropertyNames()` - -Examples of **incorrect** code for this rule when used without exceptions: - -::: incorrect - -```js -/*eslint prefer-reflect: "error"*/ - -Object.getOwnPropertyNames({}) -``` - -::: - -Examples of **correct** code for this rule when used without exceptions: - -::: correct - -```js -/*eslint prefer-reflect: "error"*/ - -Reflect.getOwnPropertyNames({}) -``` - -::: - -Examples of **correct** code for this rule with the `{ "exceptions": ["getOwnPropertyNames"] }` option: - -::: correct - -```js -/*eslint prefer-reflect: ["error", { "exceptions": ["getOwnPropertyNames"] }]*/ - -Object.getOwnPropertyNames({}) -Reflect.getOwnPropertyNames({}) -``` - -::: - ### Reflect.preventExtensions Deprecates `Object.preventExtensions()` diff --git a/docs/src/rules/prefer-regex-literals.md b/docs/src/rules/prefer-regex-literals.md index 34ac8aaa341..2159c977104 100644 --- a/docs/src/rules/prefer-regex-literals.md +++ b/docs/src/rules/prefer-regex-literals.md @@ -1,7 +1,5 @@ --- title: prefer-regex-literals -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-regex-literals.md rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions diff --git a/docs/src/rules/prefer-rest-params.md b/docs/src/rules/prefer-rest-params.md index 2962713ffd8..cf234260290 100644 --- a/docs/src/rules/prefer-rest-params.md +++ b/docs/src/rules/prefer-rest-params.md @@ -1,7 +1,5 @@ --- title: prefer-rest-params -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-rest-params.md rule_type: suggestion related_rules: - prefer-spread diff --git a/docs/src/rules/prefer-spread.md b/docs/src/rules/prefer-spread.md index 24febc0841f..4b696b55cac 100644 --- a/docs/src/rules/prefer-spread.md +++ b/docs/src/rules/prefer-spread.md @@ -1,7 +1,5 @@ --- title: prefer-spread -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-spread.md rule_type: suggestion related_rules: - no-useless-call diff --git a/docs/src/rules/prefer-template.md b/docs/src/rules/prefer-template.md index 026992e0aec..c2397b5b2f7 100644 --- a/docs/src/rules/prefer-template.md +++ b/docs/src/rules/prefer-template.md @@ -1,7 +1,5 @@ --- title: prefer-template -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/prefer-template.md rule_type: suggestion related_rules: - no-useless-concat diff --git a/docs/src/rules/quote-props.md b/docs/src/rules/quote-props.md index 8e93e98ba43..b994b82507e 100644 --- a/docs/src/rules/quote-props.md +++ b/docs/src/rules/quote-props.md @@ -1,7 +1,5 @@ --- title: quote-props -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/quote-props.md rule_type: suggestion further_reading: - https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names diff --git a/docs/src/rules/quotes.md b/docs/src/rules/quotes.md index 9260c442339..38d21b428f9 100644 --- a/docs/src/rules/quotes.md +++ b/docs/src/rules/quotes.md @@ -1,7 +1,5 @@ --- title: quotes -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/quotes.md rule_type: layout --- diff --git a/docs/src/rules/radix.md b/docs/src/rules/radix.md index e9220d71cab..8c11f8736f2 100644 --- a/docs/src/rules/radix.md +++ b/docs/src/rules/radix.md @@ -1,7 +1,5 @@ --- title: radix -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/radix.md rule_type: suggestion further_reading: - https://davidwalsh.name/parseint-radix diff --git a/docs/src/rules/require-atomic-updates.md b/docs/src/rules/require-atomic-updates.md index 43797e5079e..fac686bcdb9 100644 --- a/docs/src/rules/require-atomic-updates.md +++ b/docs/src/rules/require-atomic-updates.md @@ -1,7 +1,5 @@ --- title: require-atomic-updates -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/require-atomic-updates.md rule_type: problem --- diff --git a/docs/src/rules/require-await.md b/docs/src/rules/require-await.md index f2f0bd948a3..a17eb972dca 100644 --- a/docs/src/rules/require-await.md +++ b/docs/src/rules/require-await.md @@ -1,7 +1,5 @@ --- title: require-await -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/require-await.md rule_type: suggestion related_rules: - require-yield diff --git a/docs/src/rules/require-jsdoc.md b/docs/src/rules/require-jsdoc.md index 6802a31f107..20516fb0188 100644 --- a/docs/src/rules/require-jsdoc.md +++ b/docs/src/rules/require-jsdoc.md @@ -1,7 +1,5 @@ --- title: require-jsdoc -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/require-jsdoc.md rule_type: suggestion related_rules: - valid-jsdoc diff --git a/docs/src/rules/require-unicode-regexp.md b/docs/src/rules/require-unicode-regexp.md index 8b914281232..f3277066a5b 100644 --- a/docs/src/rules/require-unicode-regexp.md +++ b/docs/src/rules/require-unicode-regexp.md @@ -1,7 +1,5 @@ --- title: require-unicode-regexp -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/require-unicode-regexp.md rule_type: suggestion --- diff --git a/docs/src/rules/require-yield.md b/docs/src/rules/require-yield.md index 1086eb5a587..e4f975faa3e 100644 --- a/docs/src/rules/require-yield.md +++ b/docs/src/rules/require-yield.md @@ -1,7 +1,5 @@ --- title: require-yield -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/require-yield.md rule_type: suggestion related_rules: - require-await diff --git a/docs/src/rules/rest-spread-spacing.md b/docs/src/rules/rest-spread-spacing.md index c124bb01ee8..b27b5378d23 100644 --- a/docs/src/rules/rest-spread-spacing.md +++ b/docs/src/rules/rest-spread-spacing.md @@ -1,7 +1,5 @@ --- title: rest-spread-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/rest-spread-spacing.md rule_type: layout further_reading: - https://github.com/tc39/proposal-object-rest-spread @@ -60,7 +58,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](/docs/user-guide/configuring#specifying-parser-options) to learn more. +Please read the user guide's section on [configuring parser options](../use/configure#specifying-parser-options) to learn more. ## Options diff --git a/docs/src/rules/semi-spacing.md b/docs/src/rules/semi-spacing.md index b7131b40a34..4ed43ac570e 100644 --- a/docs/src/rules/semi-spacing.md +++ b/docs/src/rules/semi-spacing.md @@ -1,7 +1,5 @@ --- title: semi-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/semi-spacing.md rule_type: layout related_rules: - semi diff --git a/docs/src/rules/semi-style.md b/docs/src/rules/semi-style.md index 2082539ffe2..66db3af0f75 100644 --- a/docs/src/rules/semi-style.md +++ b/docs/src/rules/semi-style.md @@ -1,7 +1,5 @@ --- title: semi-style -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/semi-style.md rule_type: layout related_rules: - no-extra-semi diff --git a/docs/src/rules/semi.md b/docs/src/rules/semi.md index cb5a37b0f28..63f2070d218 100644 --- a/docs/src/rules/semi.md +++ b/docs/src/rules/semi.md @@ -1,7 +1,5 @@ --- title: semi -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/semi.md rule_type: layout related_rules: - no-extra-semi @@ -78,7 +76,7 @@ This rule has two options, a string option and an object option. 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 `-`) +* `"never"` disallows semicolons at the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) Object option (when `"always"`): @@ -88,7 +86,7 @@ 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 `-`. +* `"beforeStatementContinuationChars": "never"` disallows semicolons at the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`. **Note:** `beforeStatementContinuationChars` does not apply to class fields because class fields are not statements. diff --git a/docs/src/rules/sort-imports.md b/docs/src/rules/sort-imports.md index f82c892c6bc..d48ead6f66e 100644 --- a/docs/src/rules/sort-imports.md +++ b/docs/src/rules/sort-imports.md @@ -1,7 +1,5 @@ --- title: sort-imports -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/sort-imports.md rule_type: suggestion related_rules: - sort-keys diff --git a/docs/src/rules/sort-keys.md b/docs/src/rules/sort-keys.md index f4047cde3e5..0fd00aeff7f 100644 --- a/docs/src/rules/sort-keys.md +++ b/docs/src/rules/sort-keys.md @@ -1,7 +1,5 @@ --- title: sort-keys -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/sort-keys.md rule_type: suggestion related_rules: - sort-imports diff --git a/docs/src/rules/sort-vars.md b/docs/src/rules/sort-vars.md index bcb82427471..e9527045f42 100644 --- a/docs/src/rules/sort-vars.md +++ b/docs/src/rules/sort-vars.md @@ -1,7 +1,5 @@ --- title: sort-vars -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/sort-vars.md rule_type: suggestion related_rules: - sort-keys diff --git a/docs/src/rules/space-after-function-name.md b/docs/src/rules/space-after-function-name.md index 047f7be757a..65809c2c055 100644 --- a/docs/src/rules/space-after-function-name.md +++ b/docs/src/rules/space-after-function-name.md @@ -1,7 +1,5 @@ --- title: space-after-function-name -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-after-function-name.md --- diff --git a/docs/src/rules/space-after-keywords.md b/docs/src/rules/space-after-keywords.md index a7805adebb1..7f5d63d9986 100644 --- a/docs/src/rules/space-after-keywords.md +++ b/docs/src/rules/space-after-keywords.md @@ -1,7 +1,5 @@ --- title: space-after-keywords -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-after-keywords.md --- @@ -9,7 +7,7 @@ Enforces consistent spacing after keywords. (removed) This rule was **removed** in ESLint v2.0 and replaced by the [keyword-spacing](keyword-spacing) rule. -(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#--fix) automatically fixed problems reported by this rule. +(fixable) The `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixed problems reported by this rule. Some style guides will require or disallow spaces following the certain keywords. diff --git a/docs/src/rules/space-before-blocks.md b/docs/src/rules/space-before-blocks.md index 742665f529f..a7bcad2c70a 100644 --- a/docs/src/rules/space-before-blocks.md +++ b/docs/src/rules/space-before-blocks.md @@ -1,7 +1,5 @@ --- title: space-before-blocks -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-before-blocks.md rule_type: layout related_rules: - keyword-spacing diff --git a/docs/src/rules/space-before-function-paren.md b/docs/src/rules/space-before-function-paren.md index f825ee9d779..1948d903cf0 100644 --- a/docs/src/rules/space-before-function-paren.md +++ b/docs/src/rules/space-before-function-paren.md @@ -1,11 +1,8 @@ --- title: space-before-function-paren -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-before-function-paren.md rule_type: layout related_rules: -- space-after-keywords -- space-return-throw-case +- keyword-spacing --- diff --git a/docs/src/rules/space-before-function-parentheses.md b/docs/src/rules/space-before-function-parentheses.md index 1b2ed43e6e6..a5c0eceb01f 100644 --- a/docs/src/rules/space-before-function-parentheses.md +++ b/docs/src/rules/space-before-function-parentheses.md @@ -1,7 +1,5 @@ --- title: space-before-function-parentheses -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-before-function-parentheses.md related_rules: - space-after-keywords diff --git a/docs/src/rules/space-before-keywords.md b/docs/src/rules/space-before-keywords.md index bc484717779..9c2f7358109 100644 --- a/docs/src/rules/space-before-keywords.md +++ b/docs/src/rules/space-before-keywords.md @@ -1,7 +1,5 @@ --- title: space-before-keywords -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-before-keywords.md related_rules: - space-after-keywords @@ -15,7 +13,7 @@ Enforces consistent spacing before keywords. (removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [keyword-spacing](keyword-spacing) rule. -(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#--fix) automatically fixed problems reported by this rule. +(fixable) The `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixed problems reported by this rule. Keywords are syntax elements of JavaScript, such as `function` and `if`. These identifiers have special meaning to the language and so often appear in a different color in code editors. As an important part of the language, style guides often refer to the spacing that should be used around keywords. For example, you might have a style guide that says keywords should be always be preceded by spaces, which would mean `if-else` statements must look like this: diff --git a/docs/src/rules/space-in-brackets.md b/docs/src/rules/space-in-brackets.md index 15941dedb84..d07023dc9ae 100644 --- a/docs/src/rules/space-in-brackets.md +++ b/docs/src/rules/space-in-brackets.md @@ -1,7 +1,5 @@ --- title: space-in-brackets -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-in-brackets.md related_rules: - array-bracket-spacing diff --git a/docs/src/rules/space-in-parens.md b/docs/src/rules/space-in-parens.md index fea11f6e857..1d6ca52bdf1 100644 --- a/docs/src/rules/space-in-parens.md +++ b/docs/src/rules/space-in-parens.md @@ -1,7 +1,5 @@ --- title: space-in-parens -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-in-parens.md rule_type: layout related_rules: - array-bracket-spacing diff --git a/docs/src/rules/space-infix-ops.md b/docs/src/rules/space-infix-ops.md index c7a7d4ad444..984e3b37f41 100644 --- a/docs/src/rules/space-infix-ops.md +++ b/docs/src/rules/space-infix-ops.md @@ -1,7 +1,5 @@ --- title: space-infix-ops -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-infix-ops.md rule_type: layout --- diff --git a/docs/src/rules/space-return-throw-case.md b/docs/src/rules/space-return-throw-case.md index ca65dd75840..8a9242280ab 100644 --- a/docs/src/rules/space-return-throw-case.md +++ b/docs/src/rules/space-return-throw-case.md @@ -1,7 +1,5 @@ --- title: space-return-throw-case -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-return-throw-case.md --- @@ -9,7 +7,7 @@ Requires spaces after `return`, `throw`, and `case` keywords. (removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [keyword-spacing](keyword-spacing) rule. -(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#--fix) automatically fixed problems reported by this rule. +(fixable) The `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixed problems reported by this rule. Require spaces following `return`, `throw`, and `case`. diff --git a/docs/src/rules/space-unary-ops.md b/docs/src/rules/space-unary-ops.md index adac27429f1..a33735b5565 100644 --- a/docs/src/rules/space-unary-ops.md +++ b/docs/src/rules/space-unary-ops.md @@ -1,7 +1,5 @@ --- title: space-unary-ops -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-unary-ops.md rule_type: layout --- diff --git a/docs/src/rules/space-unary-word-ops.md b/docs/src/rules/space-unary-word-ops.md index 15629ae821c..4bb12622056 100644 --- a/docs/src/rules/space-unary-word-ops.md +++ b/docs/src/rules/space-unary-word-ops.md @@ -1,7 +1,5 @@ --- title: space-unary-word-ops -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/space-unary-word-ops.md --- diff --git a/docs/src/rules/spaced-comment.md b/docs/src/rules/spaced-comment.md index 35ff1baf486..f15e16c72e8 100644 --- a/docs/src/rules/spaced-comment.md +++ b/docs/src/rules/spaced-comment.md @@ -1,7 +1,5 @@ --- title: spaced-comment -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/spaced-comment.md rule_type: suggestion related_rules: - spaced-line-comment diff --git a/docs/src/rules/spaced-line-comment.md b/docs/src/rules/spaced-line-comment.md index 78d0ace2d28..9ddc648221b 100644 --- a/docs/src/rules/spaced-line-comment.md +++ b/docs/src/rules/spaced-line-comment.md @@ -1,7 +1,5 @@ --- title: spaced-line-comment -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/spaced-line-comment.md related_rules: - spaced-comment diff --git a/docs/src/rules/strict.md b/docs/src/rules/strict.md index 4fbf09af1fa..c559b00710d 100644 --- a/docs/src/rules/strict.md +++ b/docs/src/rules/strict.md @@ -1,7 +1,5 @@ --- title: strict -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/strict.md rule_type: suggestion --- @@ -49,7 +47,7 @@ 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](/docs/user-guide/configuring/language-options#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](../use/configure/language-options#specifying-parser-options): * `"sourceType": "module"` that is, files are **ECMAScript** modules * `"impliedStrict": true` property in the `ecmaFeatures` object @@ -75,8 +73,8 @@ 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](/docs/user-guide/configuring/language-options#specifying-environments) -* `"globalReturn": true` property in the `ecmaFeatures` object of [parser options](/docs/user-guide/configuring/language-options#specifying-parser-options) +* `node` or `commonjs` [environments](../use/configure/language-options#specifying-environments) +* `"globalReturn": true` property in the `ecmaFeatures` object of [parser options](../use/configure/language-options#specifying-parser-options) 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. @@ -342,4 +340,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](/docs/user-guide/configuring/rules#disabling-rules) 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](../use/configure/rules#disabling-rules) 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/src/rules/switch-colon-spacing.md b/docs/src/rules/switch-colon-spacing.md index 4dfff02896d..63df48f23e1 100644 --- a/docs/src/rules/switch-colon-spacing.md +++ b/docs/src/rules/switch-colon-spacing.md @@ -1,7 +1,5 @@ --- title: switch-colon-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/switch-colon-spacing.md rule_type: layout --- diff --git a/docs/src/rules/symbol-description.md b/docs/src/rules/symbol-description.md index 44be78bd783..438cb013ae9 100644 --- a/docs/src/rules/symbol-description.md +++ b/docs/src/rules/symbol-description.md @@ -1,7 +1,5 @@ --- title: symbol-description -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/symbol-description.md rule_type: suggestion further_reading: - https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-description diff --git a/docs/src/rules/template-curly-spacing.md b/docs/src/rules/template-curly-spacing.md index 4eca3b980e3..b96a30c870d 100644 --- a/docs/src/rules/template-curly-spacing.md +++ b/docs/src/rules/template-curly-spacing.md @@ -1,7 +1,5 @@ --- title: template-curly-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/template-curly-spacing.md rule_type: layout --- diff --git a/docs/src/rules/template-tag-spacing.md b/docs/src/rules/template-tag-spacing.md index 7289ddf27f4..da0cfa8884b 100644 --- a/docs/src/rules/template-tag-spacing.md +++ b/docs/src/rules/template-tag-spacing.md @@ -1,7 +1,5 @@ --- title: template-tag-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/template-tag-spacing.md rule_type: layout further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals diff --git a/docs/src/rules/unicode-bom.md b/docs/src/rules/unicode-bom.md index fb9857e2cab..1bae51ce64e 100644 --- a/docs/src/rules/unicode-bom.md +++ b/docs/src/rules/unicode-bom.md @@ -1,7 +1,5 @@ --- title: unicode-bom -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/unicode-bom.md rule_type: layout --- diff --git a/docs/src/rules/use-isnan.md b/docs/src/rules/use-isnan.md index cb27f751df2..d6eda9d5d91 100644 --- a/docs/src/rules/use-isnan.md +++ b/docs/src/rules/use-isnan.md @@ -1,7 +1,5 @@ --- title: use-isnan -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/use-isnan.md rule_type: problem --- diff --git a/docs/src/rules/valid-jsdoc.md b/docs/src/rules/valid-jsdoc.md index b12f6b96f47..33b48dcbf3d 100644 --- a/docs/src/rules/valid-jsdoc.md +++ b/docs/src/rules/valid-jsdoc.md @@ -1,7 +1,5 @@ --- title: valid-jsdoc -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/valid-jsdoc.md rule_type: suggestion related_rules: - require-jsdoc diff --git a/docs/src/rules/valid-typeof.md b/docs/src/rules/valid-typeof.md index 45a30a3f368..5269cfddba7 100644 --- a/docs/src/rules/valid-typeof.md +++ b/docs/src/rules/valid-typeof.md @@ -1,7 +1,5 @@ --- title: valid-typeof -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/valid-typeof.md rule_type: problem further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof diff --git a/docs/src/rules/vars-on-top.md b/docs/src/rules/vars-on-top.md index 72384d32935..20d1440aee4 100644 --- a/docs/src/rules/vars-on-top.md +++ b/docs/src/rules/vars-on-top.md @@ -1,7 +1,5 @@ --- title: vars-on-top -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/vars-on-top.md rule_type: suggestion further_reading: - https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html diff --git a/docs/src/rules/wrap-iife.md b/docs/src/rules/wrap-iife.md index 3c6158f64c8..af8ec85c785 100644 --- a/docs/src/rules/wrap-iife.md +++ b/docs/src/rules/wrap-iife.md @@ -1,7 +1,5 @@ --- title: wrap-iife -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/wrap-iife.md rule_type: layout --- diff --git a/docs/src/rules/wrap-regex.md b/docs/src/rules/wrap-regex.md index 7b0204ed47d..d4cff7df988 100644 --- a/docs/src/rules/wrap-regex.md +++ b/docs/src/rules/wrap-regex.md @@ -1,7 +1,5 @@ --- title: wrap-regex -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/wrap-regex.md rule_type: layout --- diff --git a/docs/src/rules/yield-star-spacing.md b/docs/src/rules/yield-star-spacing.md index d1d1ee5f291..c5e47380926 100644 --- a/docs/src/rules/yield-star-spacing.md +++ b/docs/src/rules/yield-star-spacing.md @@ -1,7 +1,5 @@ --- title: yield-star-spacing -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/yield-star-spacing.md rule_type: layout further_reading: - https://leanpub.com/understandinges6/read/#leanpub-auto-generators diff --git a/docs/src/rules/yoda.md b/docs/src/rules/yoda.md index 4d2ba28cc53..28b86dd7d11 100644 --- a/docs/src/rules/yoda.md +++ b/docs/src/rules/yoda.md @@ -1,7 +1,5 @@ --- title: yoda -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/rules/yoda.md rule_type: suggestion further_reading: - https://en.wikipedia.org/wiki/Yoda_conditions diff --git a/docs/src/static/feed.njk b/docs/src/static/feed.njk index da59847d097..d32fb4713ea 100644 --- a/docs/src/static/feed.njk +++ b/docs/src/static/feed.njk @@ -1,6 +1,7 @@ ---json { "permalink": "feed.xml", + "layout": false, "eleventyExcludeFromCollections": true, "metadata": { "title": "ESLint Blog", diff --git a/docs/src/static/sitemap.njk b/docs/src/static/sitemap.njk index e92a4e56844..47ba2665169 100644 --- a/docs/src/static/sitemap.njk +++ b/docs/src/static/sitemap.njk @@ -1,5 +1,6 @@ --- permalink: /sitemap.xml +layout: false eleventyExcludeFromCollections: true --- diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md new file mode 100644 index 00000000000..d88e35cfa04 --- /dev/null +++ b/docs/src/use/command-line-interface.md @@ -0,0 +1,763 @@ +--- +title: Command Line Interface Reference +eleventyNavigation: + key: command line interface + parent: use eslint + title: Command Line Interface Reference + order: 4 + +--- + +The ESLint Command Line Interface (CLI) lets you execute linting from the terminal. The CLI has a variety of options that you can pass to configure ESLint. + +## Run the CLI + +ESLint requires Node.js for installation. Follow the instructions in the [Getting Started Guide](getting-started) to install ESLint. + +Most users use [`npx`](https://docs.npmjs.com/cli/v8/commands/npx) to run ESLint on the command line like this: + +```shell +npx eslint [options] [file|dir|glob]* +``` + +Such as: + +```shell +# Run on two files +npx eslint file1.js file2.js + +# Run on multiple files +npx eslint lib/** +``` + +Please note that when passing a glob as a parameter, it is expanded by your shell. The results of the expansion can vary depending on your shell, and its configuration. If you want to use node `glob` syntax, you have to quote your parameter (using double quotes if you need it to run in Windows), as follows: + +```shell +npx eslint "lib/**" +``` + +**Note:** You can also use alternative package managers such as [Yarn](https://yarnpkg.com/) or [pnpm](https://pnpm.io/) to run ESLint. Please refer to your package manager's documentation for the correct syntax. + +## Pass Multiple Values to an Option + +Options that accept multiple values can be specified by repeating the option or with a comma-delimited list (other than [`--ignore-pattern`](#--ignore-pattern), which does not allow the second style). + +Examples of options that accept multiple values: + +```shell +npx eslint --ext .jsx --ext .js lib/ +# OR +npx eslint --ext .jsx,.js lib/ +``` + +## Options + +You can view all the CLI options by running `npx eslint -h`. + +```txt +eslint [options] file.js [file.js] [dir] + +Basic configuration: + --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 + --global [String] Define global variables + --parser String Specify the parser to be used + --parser-options Object Specify parser options + --resolve-plugins-relative-to path::String A folder where plugins should be resolved from, CWD by default + +Specify rules and plugins: + --plugin [String] Specify plugins + --rule Object Specify rules + --rulesdir [path::String] Load additional rules from this directory. Deprecated: Use rules from plugins + +Fix problems: + --fix Automatically fix problems + --fix-dry-run Automatically fix problems without saving the changes to the file system + --fix-type Array Specify the types of fixes to apply (directive, problem, suggestion, layout) + +Ignore 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) + +Use stdin: + --stdin Lint code provided on - default: false + --stdin-filename String Specify filename to process STDIN as + +Handle warnings: + --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 + +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 + --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or content - default: metadata + +Miscellaneous: + --init Run config initialization wizard - default: false + --env-info Output execution environment information - default: false + --no-error-on-unmatched-pattern Prevent errors when pattern is unmatched + --exit-on-fatal-error Exit with exit code 2 in case of fatal error - 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 +``` + +### Basic Configuration + +#### `--no-eslintrc` + +Disables use of configuration from `.eslintrc.*` and `package.json` files. + +* **Argument Type**: No argument. + +##### `--no-eslintrc` example + +```shell +npx eslint --no-eslintrc file.js +``` + +#### `-c`, `--config` + +This option allows you to specify an additional configuration file for ESLint (see [Configure ESLint](configure/) for more). + +* **Argument Type**: String. Path to file. +* **Multiple Arguments**: No + +##### `-c`, `--config` example + +```shell +npx eslint -c ~/my-eslint.json file.js +``` + +This example uses the configuration file at `~/my-eslint.json`. + +If `.eslintrc.*` and/or `package.json` files are also used for configuration (i.e., `--no-eslintrc` was not specified), the configurations are merged. Options from this configuration file have precedence over the options from `.eslintrc.*` and `package.json` files. + +#### `--env` + +This option enables specific environments. + +* **Argument Type**: String. One of the available environments. +* **Multiple Arguments**: Yes + +Details about the global variables defined by each environment are available in the [Specifying Environments](configure/language-options#specifying-environments) 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. + +##### `--env` example + +```shell +npx eslint --env browser,node file.js +npx eslint --env browser --env node file.js +``` + +#### `--ext` + +This option allows you to specify which file extensions ESLint uses when searching for target files in the directories you specify. + +* **Argument Type**: String. File extension. +* **Multiple Arguments**: Yes +* **Default Value**: `.js` and the files that match the `overrides` entries of your configuration. + +`--ext` is only used when the patterns to lint are directories. If you use glob patterns or file names, then `--ext` is ignored. For example, `npx eslint "lib/*" --ext .js` matches all files within the `lib/` directory, regardless of extension. + +##### `--ext` example + +```shell +# Use only .ts extension +npx eslint . --ext .ts + +# Use both .js and .ts +npx eslint . --ext .js --ext .ts + +# Also use both .js and .ts +npx eslint . --ext .js,.ts +``` + +#### `--global` + +This option defines global variables so that they are not flagged as undefined by the [`no-undef`](../rules/no-undef) rule. + +* **Argument Type**: String. Name of the global variable. Any specified global variables are assumed to be read-only by default, but appending `:true` to a variable's name ensures that `no-undef` also allows writes. +* **Multiple Arguments**: Yes + +##### `--global` example + +```shell +npx eslint --global require,exports:true file.js +npx eslint --global require --global exports:true +``` + +#### `--parser` + +This option allows you to specify a parser to be used by ESLint. + +* **Argument Type**: String. Parser to be used by ESLint. +* **Multiple Arguments**: No +* **Default Value**: `espree` + +##### `--parser` example + +```shell +# Use TypeScript ESLint parser +npx eslint --parser @typescript-eslint/parser file.ts +``` + +#### `--parser-options` + +This option allows you to specify parser options to be used by ESLint. The available parser options are determined by the parser being used. + +* **Argument Type**: Key/value pair separated by colon (`:`). +* **Multiple Arguments**: Yes + +##### `--parser-options` example + +```shell +echo '3 ** 4' | npx eslint --stdin --parser-options ecmaVersion:6 # fails with a parsing error +echo '3 ** 4' | npx eslint --stdin --parser-options ecmaVersion:7 # succeeds, yay! +``` + +#### `--resolve-plugins-relative-to` + +Changes the directory where plugins are resolved from. + +* **Argument Type**: String. Path to directory. +* **Multiple Arguments**: No +* **Default Value**: By default, plugins are resolved from the directory in which your configuration file is found. + +This option should be used when plugins were installed by someone other than the end user. It should be set to the project directory of the project that has a dependency on the necessary plugins. + +For example: + +* When using a config file that is located outside of the current project (with the `--config` flag), if the config uses plugins which are installed locally to itself, `--resolve-plugins-relative-to` should be set to the directory containing the config file. +* If an integration has dependencies on ESLint and a set of plugins, and the tool invokes ESLint on behalf of the user with a preset configuration, the tool should set `--resolve-plugins-relative-to` to the top-level directory of the tool. + +##### `--resolve-plugins-relative-to` example + +```shell +npx eslint --config ~/personal-eslintrc.js \ +--resolve-plugins-relative-to /usr/local/lib/ +``` + +### Specify Rules and Plugins + +#### `--plugin` + +This option specifies a plugin to load. + +* **Argument Type**: String. Plugin name. You can optionally omit the prefix `eslint-plugin-` from the plugin name. +* **Multiple Arguments**: Yes + +Before using the plugin, you have to install it using npm. + +##### `--plugin` example + +```shell +npx eslint --plugin jquery file.js +npx eslint --plugin eslint-plugin-mocha file.js +``` + +#### `--rule` + +This option specifies the rules to be used. + +* **Argument Type**: Rules and their configuration specified with [levn](https://github.com/gkz/levn#levn--) format. +* **Multiple Arguments**: Yes + +These rules are merged with any rules specified with configuration files. If the rule is defined in a plugin, you have to prefix the rule ID with the plugin name and a `/`. + +To ignore rules in `.eslintrc` configuration files and only run rules specified in the command line, use the `--rules` flag in combination with the [`--no-eslintrc`](#--no-eslintrc) flag. + +##### `--rule` example + +```shell +# Apply single rule +npx eslint --rule 'quotes: [error, double]' +# Apply multiple rules +npx eslint --rule 'guard-for-in: error' --rule 'brace-style: [error, 1tbs]' +# Apply rule from jquery plugin +npx eslint --rule 'jquery/dollar-sign: error' +# Only apply rule from the command line +npx eslint --rule 'quotes: [error, double]' --no-eslintrc +``` + +#### `--rulesdir` + +**Deprecated**: Use rules from plugins instead. + +This option allows you to specify another directory from which to load rules files. This allows you to dynamically load new rules at run time. This is useful when you have custom rules that aren't suitable for being bundled with ESLint. + +* **Argument Type**: String. Path to directory. The rules in your custom rules directory must follow the same format as bundled rules to work properly. +* **Multiple Arguments**: Yes. + +Note that, as with core rules and plugin rules, you still need to enable the rules in configuration or via the `--rule` CLI option in order to actually run those rules during linting. Specifying a rules directory with `--rulesdir` does not automatically enable the rules within that directory. + +##### `--rulesdir` example + +```shell +npx eslint --rulesdir my-rules/ file.js +npx eslint --rulesdir my-rules/ --rulesdir my-other-rules/ file.js +``` + +### Fix Problems + +#### `--fix` + +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. + +* **Argument Type**: No argument. + +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 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` example + +```shell +npx eslint --fix file.js +``` + +#### `--fix-dry-run` + +This option has the same effect as `--fix` with the difference that the fixes are not saved to the file system. Because the default formatter does not output the fixed code, you'll have to use another formatter (e.g. `--format json`) to get the fixes. + +* **Argument Type**: No argument. + +This makes it possible to fix code from `stdin` when used with the `--stdin` flag. + +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. + +##### `--fix-dry-run` example + +```shell +getSomeText | npx eslint --stdin --fix-dry-run --format json +``` + +#### `--fix-type` + +This option allows you to specify the type of fixes to apply when using either `--fix` or `--fix-dry-run`. + +* **Argument Type**: String. One of the following fix types: + 1. `problem` - fix potential errors in the code + 1. `suggestion` - apply fixes to the code that improve it + 1. `layout` - apply fixes that do not change the program structure (AST) + 1. `directive` - apply fixes to inline directives such as `// eslint-disable` +* **Multiple Arguments**: Yes + +This option is helpful if you are using another program to format your code, but you would still like ESLint to apply other types of fixes. + +##### `--fix-type` example + +```shell +npx eslint --fix --fix-type suggestion . +npx eslint --fix --fix-type suggestion --fix-type problem . +npx eslint --fix --fix-type suggestion,layout . +``` + +### Ignore Files + +#### `--ignore-path` + +This option allows you to specify the file to use as your `.eslintignore`. + +* **Argument Type**: String. Path to file. +* **Multiple Arguments**: No +* **Default Value**: By default, ESLint looks for `.eslintignore` in the current working directory. + +**Note:** `--ignore-path` is not supported when using [flat configuration](./configure/configuration-files-new) (`eslint.config.js`). + +##### `--ignore-path` example + +```shell +npx eslint --ignore-path tmp/.eslintignore file.js +npx eslint --ignore-path .gitignore file.js +``` + +#### `--no-ignore` + +Disables excluding of files from `.eslintignore` files, `--ignore-path` flags, `--ignore-pattern` flags, and the `ignorePatterns` property in config files. + +* **Argument Type**: No argument. + +##### `--no-ignore` example + +```shell +npx eslint --no-ignore file.js +``` + +#### `--ignore-pattern` + +This option allows you to specify patterns of files to ignore (in addition to those in `.eslintignore`). + +* **Argument Type**: String. The supported syntax is the same as for [`.eslintignore` files](configure/ignore#the-eslintignore-file), which use the same patterns as the [`.gitignore` specification](https://git-scm.com/docs/gitignore). You should quote your patterns in order to avoid shell interpretation of glob patterns. +* **Multiple Arguments**: Yes + +##### `--ignore-pattern` example + +```shell +npx eslint --ignore-pattern "/lib/" --ignore-pattern "/src/vendor/*" . +``` + +### Use stdin + +#### `--stdin` + +This option tells ESLint to read and lint source code from STDIN instead of from files. You can use this to pipe code to ESLint. + +* **Argument Type**: No argument. + +##### `--stdin` example + +```shell +cat myfile.js | npx eslint --stdin +``` + +#### `--stdin-filename` + +This option allows you to specify a filename to process STDIN as. + +* **Argument Type**: String. Path to file. +* **Multiple Arguments**: No + +This is useful when processing files from STDIN and you have rules which depend on the filename. + +##### `--stdin-filename` example + +```shell +cat myfile.js | npx eslint --stdin --stdin-filename myfile.js +``` + +### Handle Warnings + +#### `--quiet` + +This option allows you to disable reporting on warnings. If you enable this option, only errors are reported by ESLint. + +* **Argument Type**: No argument. + +##### `--quiet` example + +```shell +npx eslint --quiet file.js +``` + +#### `--max-warnings` + +This option allows you to specify a warning threshold, which can be used to force ESLint to exit with an error status if there are too many warning-level rule violations in your project. + +* **Argument Type**: Integer. The maximum number of warnings to allow. To prevent this behavior, do not use this option or specify `-1` as the argument. +* **Multiple Arguments**: No + +Normally, if ESLint runs and finds no errors (only warnings), it exits with a success exit status. However, if `--max-warnings` is specified and the total warning count is greater than the specified threshold, ESLint exits with an error status. + +##### `--max-warnings` example + +```shell +npx eslint --max-warnings 10 file.js +``` + +### Output + +#### `-o`, `--output-file` + +Write the output of linting results to a specified file. + +* **Argument Type**: String. Path to file. +* **Multiple Arguments**: No + +##### `-o`, `--output-file` example + +```shell +npx eslint -o ./test/test.html +``` + +#### `-f`, `--format` + +This option specifies the output format for the console. + +* **Argument Type**: String. One of the [built-in formatters](formatters/) or a custom formatter. +* **Multiple Arguments**: No +* **Default Value**: [`stylish`](formatters/#stylish) + +If you are using a custom formatter defined in a local file, you can specify the path to the custom formatter file. + +An npm-installed formatter is resolved with or without `eslint-formatter-` prefix. + +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: + +```shell +# Saves the output into the `results.txt` file. +npx eslint -f compact file.js > results.txt +``` + +##### `-f`, `--format` example + +Use the built-in `compact` formatter: + +```shell +npx eslint --format compact file.js +``` + +Use a local custom formatter: + +```shell +npx eslint -f ./customformat.js file.js +``` + +Use an npm-installed formatter: + +```shell +npm install eslint-formatter-pretty + +# Then run one of the following commands +npx eslint -f pretty file.js +# or alternatively +npx eslint -f eslint-formatter-pretty file.js +``` + +#### `--color` and `--no-color` + +These options force the enabling/disabling of colorized output. + +* **Argument Type**: No argument. + +You can use these options to override the default behavior, which is to enable colorized output unless no TTY is detected, such as when piping `eslint` through `cat` or `less`. + +##### `--color` and `--no-color` example + +```shell +npx eslint --color file.js | cat +npx eslint --no-color file.js +``` + +### Inline Configuration Comments + +#### `--no-inline-config` + +This option prevents inline comments like `/*eslint-disable*/` or +`/*global foo*/` from having any effect. + +* **Argument Type**: No argument. + +This allows you to set an ESLint config without files modifying it. All inline config comments are ignored, such as: + +* `/*eslint-disable*/` +* `/*eslint-enable*/` +* `/*global*/` +* `/*eslint*/` +* `/*eslint-env*/` +* `// eslint-disable-line` +* `// eslint-disable-next-line` + +##### `--no-inline-config` example + +```shell +npx 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. + +* **Argument Type**: No argument. + +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 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 becomes unused since ESLint is no longer generating an incorrect report. This results in a new reported error for the unused directive if the `report-unused-disable-directives` option is used. +::: + +##### `--report-unused-disable-directives` example + +```shell +npx eslint --report-unused-disable-directives file.js +``` + +### Caching + +#### `--cache` + +Store the info about processed files in order to only operate on the changed ones. Enabling this option can dramatically improve ESLint's run time performance by ensuring that only changed files are linted. +The cache is stored in `.eslintcache` by default. + +* **Argument Type**: No argument. + +If you run ESLint with `--cache` and then run ESLint without `--cache`, the `.eslintcache` file will be deleted. This is necessary because the results of the lint might change and make `.eslintcache` invalid. If you want to control when the cache file is deleted, then use `--cache-location` to specify an alternate location for the cache file. + +Autofixed files are not placed in the cache. Subsequent linting that does not trigger an autofix will place it in the cache. + +##### `--cache` example + +```shell +npx eslint --cache file.js +``` + +#### `--cache-file` + +**Deprecated**: Use `--cache-location` instead. + +Path to the cache file. If none specified `.eslintcache` is used. The file is created in the directory where the `eslint` command is executed. + +#### `--cache-location` + +Specify the path to the cache location. Can be a file or a directory. + +* **Argument Type**: String. Path to file or directory. If a directory is specified, a cache file is created inside the specified folder. The name of the file is based on the hash of the current working directory, e.g.: `.cache_hashOfCWD`. +* **Multiple Arguments**: No +* **Default Value**: If no location is specified, `.eslintcache` is used. The file is created in the directory where the `eslint` command is executed. + +If the directory for the cache does not exist make sure you add a trailing `/` on \*nix systems or `\` on Windows. Otherwise, the path is assumed to be a file. + +##### `--cache-location` example + +```shell +npx eslint "src/**/*.js" --cache --cache-location "/Users/user/.eslintcache/" +``` + +#### `--cache-strategy` + +Strategy for the cache to use for detecting changed files. + +* **Argument Type**: String. One of the following values: + 1. `metadata` + 1. `content` +* **Multiple Arguments**: No +* **Default Value**: `metadata` + +The `content` strategy can be useful in cases where the modification time of your files changes even if their contents have not. For example, this can happen during git operations like `git clone` because git does not track file modification time. + +##### `--cache-strategy` example + +```shell +npx eslint "src/**/*.js" --cache --cache-strategy content +``` + +### Miscellaneous + +#### `--init` + +This option runs `npm init @eslint/config` to start the config initialization wizard. It's designed to help new users quickly create an `.eslintrc` file by answering a few questions. When you use this flag, the CLI does not perform linting. + +* **Argument Type**: No argument. + +The resulting configuration file is created in the current directory. + +##### `--init` example + +```shell +npx eslint --init +``` + +#### `--env-info` + +This option outputs information about the execution environment, including the version of Node.js, npm, and local and global installations of ESLint. + +* **Argument Type**: No argument. + +The ESLint team may ask for this information to help solve bugs. When you use this flag, the CLI does not perform linting. + +##### `--env-info` example + +```shell +npx eslint --env-info +``` + +#### `--no-error-on-unmatched-pattern` + +This option prevents errors when a quoted glob pattern or `--ext` is unmatched. This does not prevent errors when your shell can't match a glob. + +* **Argument Type**: No argument. + +##### `--no-error-on-unmatched-pattern` example + +```shell +npx eslint --no-error-on-unmatched-pattern --ext .ts "lib/*" +``` + +#### `--exit-on-fatal-error` + +This option causes ESLint to exit with exit code 2 if one or more fatal parsing errors occur. Without this option, ESLint reports fatal parsing errors as rule violations. + +* **Argument Type**: No argument. + +##### `--exit-on-fatal-error` example + +```shell +npx eslint --exit-on-fatal-error file.js +``` + +#### `--debug` + +This option outputs debugging information to the console. Add this flag to an ESLint command line invocation in order to get extra debugging information while the command runs. + +* **Argument Type**: No argument. + +This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs. + +##### `--debug` example + +```shell +npx eslint --debug test.js +``` + +#### `-h`, `--help` + +This option outputs the help menu, displaying all of the available options. All other options are ignored when this is present. When you use this flag, the CLI does not perform linting. + +* **Argument Type**: No argument. + +##### `-h`, `--help` example + +```shell +npx eslint --help +``` + +#### `-v`, `--version` + +This option outputs the current ESLint version onto the console. All other options are ignored when this is present. When you use this flag, the CLI does not perform linting. + +* **Argument Type**: No argument. + +##### `-v`, `--version` example + +```shell +npx eslint --version +``` + +#### `--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. When you use this flag, the CLI does not perform linting. + +* **Argument Type**: String. Path to file. +* **Multiple Arguments**: No + +##### `--print-config` example + +```shell +npx eslint --print-config file.js +``` + +## Exit Codes + +When linting files, ESLint exits with one of the following exit codes: + +* `0`: Linting was successful and there are no linting errors. If the [`--max-warnings`](#--max-warnings) flag is set to `n`, the number of linting warnings is at most `n`. +* `1`: Linting was successful and there is at least one linting error, or there are more linting warnings than allowed by the `--max-warnings` option. +* `2`: Linting was unsuccessful due to a configuration problem or an internal error. diff --git a/docs/src/user-guide/configuring/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md similarity index 72% rename from docs/src/user-guide/configuring/configuration-files-new.md rename to docs/src/use/configure/configuration-files-new.md index 98d8a5f3f8b..a57bbf0f235 100644 --- a/docs/src/user-guide/configuring/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -1,22 +1,22 @@ --- title: Configuration Files (New) -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/configuring/configuration-files-new.md eleventyNavigation: key: configuration files - parent: configuring + parent: configure title: Configuration Files (New) order: 1 --- ::: warning -This is an experimental feature that is not enabled by default. You can use the configuration system described on this page by using the `FlatESLint` class, the `FlatRuleTester` class, or by setting `configType: "flat"` in the `Linter` class. +This is an experimental feature. To opt-in, place an `eslint.config.js` file in the root of your project or set the `ESLINT_USE_FLAT_CONFIG` environment variable to `true`. To opt-out, even in the presence of an `eslint.config.js` file, set the environment variable to `false`. If you are using the API, you can use the configuration system described on this page by using the `FlatESLint` class, the `FlatRuleTester` class, or by setting `configType: "flat"` in the `Linter` class. ::: +You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. + ## Configuration File -The ESLint configuration file is named `eslint.config.js` and should be placed in the root directory of your project and export an array of configuration objects. Here's an example: +The ESLint configuration file is named `eslint.config.js`. It should be placed in the root directory of your project and export an array of [configuration objects](#configuration-objects). Here's an example: ```js export default [ @@ -29,20 +29,20 @@ export default [ ] ``` -Here, the configuration array contains just one configuration object. The configuration object enables two rules: `semi` and `prefer-const`. These rules will be applied to all of the files ESLint processes using this config file. +In this example, the configuration array contains just one configuration object. The configuration object enables two rules: `semi` and `prefer-const`. These rules are applied to all of the files ESLint processes using this config file. ## Configuration Objects Each configuration object contains all of the information ESLint needs to execute on a set of files. Each configuration object is made up of these properties: -* `files` - An array of glob patterns indicating the files that the configuration object should apply to. If not specified, the configuration object applies to all files. +* `files` - An array of glob patterns indicating the files that the configuration object should apply to. If not specified, the configuration object applies to all files matched by any other configuration object. * `ignores` - An array of glob patterns indicating the files that the configuration object should not apply to. If not specified, the configuration object applies to all files matched by `files`. * `languageOptions` - An object containing settings related to how JavaScript is configured for linting. * `ecmaVersion` - The version of ECMAScript to support. May be any year (i.e., `2022`) or version (i.e., `5`). Set to `"latest"` for the most recent supported version. (default: `"latest"`) * `sourceType` - The type of JavaScript source code. Possible values are `"script"` for traditional script files, `"module"` for ECMAScript modules (ESM), and `"commonjs"` for CommonJS files. (default: `"module"` for `.js` and `.mjs` files; `"commonjs"` for `.cjs` files) * `globals` - An object specifying additional objects that should be added to the global scope during linting. - * `parser` - Either an object containing a `parse()` method or a string indicating the name of a parser inside of a plugin (i.e., `"pluginName/parserName"`). (default: `"@/espree"`) - * `parserOptions` - An object specifying additional options that are passed directly to the `parser()` method on the parser. The available options are parser-dependent. + * `parser` - An object containing a `parse()` method or a `parseForESLint()` method. (default: [`espree`](https://github.com/eslint/espree)) + * `parserOptions` - An object specifying additional options that are passed directly to the `parse()` or `parseForESLint()` method on the parser. The available options are parser-dependent. * `linterOptions` - An object containing settings related to the linting process. * `noInlineConfig` - A Boolean value indicating if inline configuration is allowed. * `reportUnusedDisableDirectives` - A Boolean value indicating if unused disable directives should be tracked and reported. @@ -57,7 +57,7 @@ Each configuration object contains all of the information ESLint needs to execut Patterns specified in `files` and `ignores` use [`minimatch`](https://www.npmjs.com/package/minimatch) syntax and are evaluated relative to the location of the `eslint.config.js` file. ::: -You can use a combination of `files` and `ignores` to determine which files should apply the configuration object and which should not. By default, ESLint matches `**/*.js`, `**/*.cjs`, and `**/*.mjs`. Because config objects that don't specify `files` or `ignores` apply to all files that have been matched by any other configuration object, by default config objects will apply to any JavaScript files passed to ESLint. For example: +You can use a combination of `files` and `ignores` to determine which files should apply the configuration object and which should not. By default, ESLint matches `**/*.js`, `**/*.cjs`, and `**/*.mjs`. Because config objects that don't specify `files` or `ignores` apply to all files that have been matched by any other configuration object, those config objects apply to any JavaScript files passed to ESLint by default. For example: ```js export default [ @@ -69,15 +69,15 @@ export default [ ]; ``` -With this configuration, the `semi` rule is enabled for all files that match the default files in ESLint. So if you pass `example.js` to ESLint, the `semi` rule will be applied. If you pass a non-JavaScript file, like `example.txt`, the `semi` rule will not be applied because there are no other configuration objects that match that filename. (ESLint will output an error message letting you know that the file was ignored due to missing configuration.) +With this configuration, the `semi` rule is enabled for all files that match the default files in ESLint. So if you pass `example.js` to ESLint, the `semi` rule is applied. If you pass a non-JavaScript file, like `example.txt`, the `semi` rule is not applied because there are no other configuration objects that match that filename. (ESLint outputs an error message letting you know that the file was ignored due to missing configuration.) #### Excluding files with `ignores` -You can limit which files a configuration object applies to by specifying a combination of `files` and `ignores` patterns. For example, you may want certain rules to apply only to files in your `src` directory, like this: +You can limit which files a configuration object applies to by specifying a combination of `files` and `ignores` patterns. For example, you may want certain rules to apply only to files in your `src` directory: ```js export default [ - { + { files: ["src/**/*.js"], rules: { semi: "error" @@ -86,11 +86,11 @@ export default [ ]; ``` -Here, only the JavaScript files in the `src` directory will have the `semi` rule applied. If you run ESLint on files in another directory, this configuration object will be skipped. By adding `ignores`, you can also remove some of the files in `src` from this configuration object: +Here, only the JavaScript files in the `src` directory have the `semi` rule applied. If you run ESLint on files in another directory, this configuration object is skipped. By adding `ignores`, you can also remove some of the files in `src` from this configuration object: ```js export default [ - { + { files: ["src/**/*.js"], ignores: ["**/*.config.js"], rules: { @@ -104,7 +104,7 @@ This configuration object matches all JavaScript files in the `src` directory ex ```js export default [ - { + { files: ["src/**/*.js"], ignores: ["**/*.config.js", "!**/eslint.config.js"], rules: { @@ -114,13 +114,13 @@ export default [ ]; ``` -Here, the configuration object excludes files ending with `.config.js` except for `eslint.config.js`. That file will still have `semi` applied. +Here, the configuration object excludes files ending with `.config.js` except for `eslint.config.js`. That file still has `semi` applied. If `ignores` is used without `files` and any other setting, then the configuration object applies to all files except the ones specified in `ignores`, for example: ```js export default [ - { + { ignores: ["**/*.config.js"], rules: { semi: "error" @@ -133,17 +133,17 @@ This configuration object applies to all files except those ending with `.config #### Globally ignoring files with `ignores` -If `ignores` is used without any other keys in the configuration object, then the patterns act as additional global ignores, similar to those found in `.eslintignore`. Here's an example: +If `ignores` is used without any other keys in the configuration object, then the patterns act as global ignores. Here's an example: ```js export default [ - { + { ignores: [".config/*"] } ]; ``` -This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the patterns found in `.eslintignore`. +This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the default patterns, which are `["**/node_modules/**", ".git/**"]`. #### Cascading configuration objects @@ -157,16 +157,16 @@ export default [ globals: { MY_CUSTOM_GLOBAL: "readonly" } - } + } }, - { + { files: ["tests/**/*.js"], languageOptions: { globals: { it: "readonly", describe: "readonly" } - } + } } ]; ``` @@ -194,7 +194,7 @@ export default [ #### Reporting unused disable directives -Disable directives such as `/*eslint-disable*/` and `/*eslint-disable-next-line*/` are used to disable ESLint rules around certain portions of code. As code changes, it's possible for these directives to no longer be needed because the code has changed in such a way that the rule will no longer be triggered. You can enable reporting of these unused disable directives by setting the `reportUnusedDisableDirectives` option to `true`, as in this example: +Disable directives such as `/*eslint-disable*/` and `/*eslint-disable-next-line*/` are used to disable ESLint rules around certain portions of code. As code changes, it's possible for these directives to no longer be needed because the code has changed in such a way that the rule is no longer triggered. You can enable reporting of these unused disable directives by setting the `reportUnusedDisableDirectives` option to `true`, as in this example: ```js export default [ @@ -233,8 +233,8 @@ export default [ ESLint can evaluate your code in one of three ways: 1. ECMAScript module (ESM) - Your code has a module scope and is run in strict mode. -1. CommonJS - Your code has a top-level function scope and runs in nonstrict mode. -1. Script - Your code has a shared global scope and runs in nonstrict mode. +1. CommonJS - Your code has a top-level function scope and runs in non-strict mode. +1. Script - Your code has a shared global scope and runs in non-strict mode. You can specify which of these modes your code is intended to run in by specifying the `sourceType` property. This property can be set to `"module"`, `"commonjs"`, or `"script"`. By default, `sourceType` is set to `"module"` for `.js` and `.mjs` files and is set to `"commonjs"` for `.cjs` files. Here's an example: @@ -251,7 +251,7 @@ export default [ #### Configuring a custom parser and its options -In many cases, you can use the default parser that ESLint ships with for parsing your JavaScript code. You can optionally override the default parser by using the `parser` property. The `parser` property can be either a string in the format `"pluginName/parserName"` (indicating to retrieve the parser from a plugin) or an object containing either a `parse()` method or a `parseForESLint()` method. For example, you can use the [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser) package to allow ESLint to parse experimental syntax: +In many cases, you can use the default parser that ESLint ships with for parsing your JavaScript code. You can optionally override the default parser by using the `parser` property. The `parser` property must be an object containing either a `parse()` method or a `parseForESLint()` method. For example, you can use the [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser) package to allow ESLint to parse experimental syntax: ```js import babelParser from "@babel/eslint-parser"; @@ -266,7 +266,7 @@ export default [ ]; ``` -This configuration ensures that the Babel parser, rather than the default, will be used to parse all files ending with `.js` and `.mjs`. +This configuration ensures that the Babel parser, rather than the default Espree parser, is used to parse all files ending with `.js` and `.mjs`. You can also pass options directly to the custom parser by using the `parserOptions` property. This property is an object whose name-value pairs are specific to the parser that you are using. For the Babel parser, you might pass in options like this: @@ -330,7 +330,13 @@ For historical reasons, the boolean value `false` and the string value `"readabl ### Using plugins in your configuration -Plugins are used to share rules, processors, configurations, parsers, and more across ESLint projects. Plugins are specified in a configuration object using the `plugins` key, which is an object where the name of the plugin is the property name and the value is the plugin object itself. Here's an example: +Plugins are used to share rules, processors, configurations, parsers, and more across ESLint projects. + +#### Using plugin rules + +You can use specific rules included in a plugin. To do this, specify the plugin +in a configuration object using the `plugins` key. The value for the `plugin` key +is an object where the name of the plugin is the property name and the value is the plugin object itself. Here's an example: ```js import jsdoc from "eslint-plugin-jsdoc"; @@ -340,11 +346,11 @@ export default [ files: ["**/*.js"], plugins: { jsdoc: jsdoc - } + }, rules: { "jsdoc/require-description": "error", "jsdoc/check-values": "error" - } + } } ]; ``` @@ -361,11 +367,11 @@ export default [ files: ["**/*.js"], plugins: { jsdoc - } + }, rules: { "jsdoc/require-description": "error", "jsdoc/check-values": "error" - } + } } ]; ``` @@ -380,17 +386,42 @@ export default [ files: ["**/*.js"], plugins: { jsd: jsdoc - } + }, rules: { "jsd/require-description": "error", "jsd/check-values": "error" - } + } } ]; ``` This configuration object uses `jsd` as the prefix plugin instead of `jsdoc`. +#### Using configurations included in plugins + +You can use a configuration included in a plugin by adding that configuration +directly to the `eslint.config.js` configurations array. +Often, you do this for a plugin's recommended configuration. Here's an example: + +```js +import jsdoc from "eslint-plugin-jsdoc"; + +export default [ + // configuration included in plugin + jsdoc.configs.recommended, + // other configuration objects... + { + files: ["**/*.js"], + plugins: { + jsdoc: jsdoc + }, + rules: { + "jsdoc/require-description": "warn", + } + } +]; +``` + ### Using processors Processors allow ESLint to transform text into pieces of code that ESLint can lint. You can specify the processor to use for a given file type by defining a `processor` property that contains either the processor name in the format `"pluginName/processorName"` to reference a processor in a plugin or an object containing both a `preprocess()` and a `postprocess()` method. For example, to extract JavaScript code blocks from a Markdown file, you might add this to your configuration: @@ -404,7 +435,7 @@ export default [ plugins: { markdown }, - processor: "markdown/markdown" + processor: "markdown/markdown", settings: { sharedData: "Hello" } @@ -412,7 +443,7 @@ export default [ ]; ``` -This configuration object specifies that there is a processor called `"markdown"` contained in the plugin named `"markdown"` and will apply the processor to all files ending with `.md`. +This configuration object specifies that there is a processor called `"markdown"` contained in the plugin named `"markdown"`. The configuration applies the processor to all files ending with `.md`. Processors may make named code blocks that function as filenames in configuration objects, such as `0.js` and `1.js`. ESLint handles such a named code block as a child of the original file. You can specify additional configuration objects for named code blocks. For example, the following disables the `strict` rule for the named code blocks which end with `.js` in markdown files. @@ -425,7 +456,7 @@ export default [ plugins: { markdown }, - processor: "markdown/markdown" + processor: "markdown/markdown", settings: { sharedData: "Hello" } @@ -455,7 +486,7 @@ export default [ ]; ``` -This configuration object specifies that the [`semi`](/docs/latest/rules/semi) rule should be enabled with a severity of `"error"`. You can also provide options to a rule by specifying an array where the first item is the severity and each item after that is an option for the rule. For example, you can switch the `semi` rule to disallow semicolons by passing `"never"` as an option: +This configuration object specifies that the [`semi`](../../rules/semi) rule should be enabled with a severity of `"error"`. You can also provide options to a rule by specifying an array where the first item is the severity and each item after that is an option for the rule. For example, you can switch the `semi` rule to disallow semicolons by passing `"never"` as an option: ```js export default [ @@ -474,7 +505,7 @@ Each rule specifies its own options and can be any valid JSON data type. Please There are three possible severities you can specify for a rule * `"error"` (or `2`) - the reported problem should be treated as an error. When using the ESLint CLI, errors cause the CLI to exit with a nonzero code. -* `"warn"` (or `1`) - the reported problem should be treated as a warning. When using the ESLint CLI, warnings are reported but do not change the exit code. If only errors are reported, the exit code will be 0. +* `"warn"` (or `1`) - the reported problem should be treated as a warning. When using the ESLint CLI, warnings are reported but do not change the exit code. If only warnings are reported, the exit code is 0. * `"off"` (or `0`) - the rule should be turned off completely. #### Rule configuration cascade @@ -517,7 +548,7 @@ Here, the second configuration object only overrides the severity, so the final ### Configuring shared settings -ESLint supports adding shared settings into configuration files. Plugins use `settings` to specify information that should be shared across all of its rules. You can add a `settings` object to a configuration object and it will be supplied to every rule being executed. This may be useful if you are adding custom rules and want them to have access to the same information. Here's an example: +ESLint supports adding shared settings into configuration files. When you add a `settings` object to a configuration object, it is supplied to every rule. By convention, plugins namespace the settings they are interested in to avoid collisions with others. Plugins can use `settings` to specify the information that should be shared across all of their rules. This may be useful if you are adding custom rules and want them to have access to the same information. Here's an example: ```js export default [ @@ -531,16 +562,18 @@ export default [ ### Using predefined configurations -ESLint has two predefined configurations: +ESLint has two predefined configurations for JavaScript: -* `eslint:recommended` - enables the rules that ESLint recommends everyone use to avoid potential errors -* `eslint:all` - enables all of the rules shipped with ESLint +* `js.configs.recommended` - enables the rules that ESLint recommends everyone use to avoid potential errors +* `js.configs.all` - enables all of the rules shipped with ESLint -To include these predefined configurations, you can insert the string values into the returned array and then make any modifications to other properties in subsequent configuration objects: +To include these predefined configurations, install the `@eslint/js` package and then make any modifications to other properties in subsequent configuration objects: ```js +import js from "@eslint/js"; + export default [ - "eslint:recommended", + js.configs.recommended, { rules: { semi: ["warn", "always"] @@ -549,18 +582,29 @@ export default [ ]; ``` -Here, the `eslint:recommended` predefined configuration is applied first and then another configuration object adds the desired configuration for `semi`. +Here, the `js.configs.recommended` predefined configuration is applied first and then another configuration object adds the desired configuration for `semi`. + +You can apply these predefined configs to just a subset of files by specifying a config object with a `files` key, like this: + +```js +import js from "@eslint/js"; + +export default [ + { + files: ["**/src/safe/*.js"], + ...js.configs.recommended + } +]; +``` ## Configuration File Resolution -When ESLint is run on the command line, it first checks the current working directory for `eslint.config.js`, and if not found, will look to the next parent directory for the file. This search continues until either the file is found or the root directory is reached. +When ESLint is run on the command line, it first checks the current working directory for `eslint.config.js`. If the file is not found, it looks to the next parent directory for the file. This search continues until either the file is found or the root directory is reached. -You can prevent this search for `eslint.config.js` by using the `-c` or `--config--file` option on the command line to specify an alternate configuration file, such as: +You can prevent this search for `eslint.config.js` by setting the `ESLINT_USE_FLAT_CONFIG` environment variable to `true` and using the `-c` or `--config` option on the command line to specify an alternate configuration file, such as: ```shell -npx eslint -c some-other-file.js **/*.js +ESLINT_USE_FLAT_CONFIG=true npx eslint -c some-other-file.js **/*.js ``` -In this case, ESLint will not search for `eslint.config.js` and will instead use `some-other-file.js`. - -Each configuration file exports one or more configuration object. A configuration object +In this case, ESLint does not search for `eslint.config.js` and instead uses `some-other-file.js`. diff --git a/docs/src/user-guide/configuring/configuration-files.md b/docs/src/use/configure/configuration-files.md similarity index 72% rename from docs/src/user-guide/configuring/configuration-files.md rename to docs/src/use/configure/configuration-files.md index 085b4420fb7..f9c7aa341f3 100644 --- a/docs/src/user-guide/configuring/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -1,15 +1,15 @@ --- title: Configuration Files -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/configuring/configuration-files.md eleventyNavigation: key: configuration files - parent: configuring + parent: configure title: Configuration Files - order: 1 + order: 2 --- +You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. + ## Configuration File Formats ESLint supports configuration files in several formats: @@ -20,7 +20,7 @@ ESLint supports configuration files in several formats: * **JSON** - use `.eslintrc.json` to define the configuration structure. ESLint's JSON files also allow JavaScript-style comments. * **package.json** - create an `eslintConfig` property in your `package.json` file and define your configuration there. -If there are multiple configuration files in the same directory, ESLint will only use one. The priority order is as follows: +If there are multiple configuration files in the same directory, ESLint only uses one. The priority order is as follows: 1. `.eslintrc.js` 1. `.eslintrc.cjs` @@ -33,7 +33,7 @@ If there are multiple configuration files in the same directory, ESLint will onl 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 (`/`), the home directory of the current user (`~/`), or when `root: true` is specified. See [Cascading and Hierarchy](#cascading-and-hierarchy) below for more details on this. Configuration files can be 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 first way to use configuration files is via `.eslintrc.*` and `package.json` files. ESLint automatically looks 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 (`/`), the home directory of the current user (`~/`), or when `root: true` is specified. See [Cascading and Hierarchy](#cascading-and-hierarchy) below for more details on this. Configuration files can be 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 way to use configuration files is to save the file wherever you would like and pass its location to the CLI using the `--config` option, such as: @@ -41,7 +41,7 @@ The second way to use configuration files is to save the file wherever you would eslint -c myconfig.json myfiletotest.js ``` -If you are using one configuration file and want ESLint to ignore any `.eslintrc.*` files, make sure to use [`--no-eslintrc`](https://eslint.org/docs/user-guide/command-line-interface#--no-eslintrc) along with the [`-c`](https://eslint.org/docs/user-guide/command-line-interface#-c---config) flag. +If you are using one configuration file and want ESLint to ignore any `.eslintrc.*` files, make sure to use [`--no-eslintrc`](../command-line-interface#--no-eslintrc) along with the [`--config`](../../use/command-line-interface#-c---config) flag. Here's an example JSON configuration file that uses the `typescript-eslint` parser to support TypeScript syntax: @@ -72,7 +72,7 @@ Here's an example JSON configuration file that uses the `typescript-eslint` pars ### 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 for JSON files and YAML-style comments for YAML files. ESLint safely ignores comments in configuration files. This allows your configuration files to be more human-friendly. +Both the JSON and YAML configuration file formats support comments (`package.json` files should not include them). You can use JavaScript-style comments for JSON files and YAML-style comments for YAML files. ESLint safely ignores comments in configuration files. This allows your configuration files to be more human-friendly. For JavaScript-style comments: @@ -102,7 +102,7 @@ rules: ## Adding Shared Settings -ESLint supports adding shared settings into configuration files. Plugins use `settings` to specify information that should be shared across all of its rules. You can add `settings` object to ESLint configuration file and it will be supplied to every rule being executed. This may be useful if you are adding custom rules and want them to have access to the same information and be easily configurable. +ESLint supports adding shared settings into configuration files. Plugins use `settings` to specify the information that should be shared across all of its rules. You can add a `settings` object to the ESLint configuration file and it is supplied to every executed rule. This may be useful if you are adding custom rules and want them to have access to the same information and be easily configurable. In JSON: @@ -124,7 +124,7 @@ And in YAML: ## Cascading and Hierarchy -When using `.eslintrc.*` and `package.json` files for configuration, you can take advantage of configuration cascading. Suppose you have the following structure: +When using `.eslintrc.*` and `package.json` files for configuration, you can take advantage of configuration cascading. Suppose your project has the following structure: ```text your-project @@ -136,9 +136,9 @@ your-project └── test.js ``` -The configuration cascade works based on the location of the file being linted. If there is a `.eslintrc` file in the same directory as the file being linted, then that configuration takes precedence. ESLint then searches up the directory structure, merging any `.eslintrc` files it finds along the way until reaching either a `.eslintrc` file with `root: true` or the root directory. +The configuration cascade works based on the location of the file being linted. If there is an `.eslintrc` file in the same directory as the file being linted, then that configuration takes precedence. ESLint then searches up the directory structure, merging any `.eslintrc` files it finds along the way until reaching either an `.eslintrc` file with `root: true` or the root directory. -In the same way, if there is a `package.json` file in the root directory with an `eslintConfig` field, the configuration it describes will apply to all subdirectories beneath it, but the configuration described by the `.eslintrc` file in the `tests/` directory will override it where there are conflicting specifications. +In the same way, if there is a `package.json` file in the root directory with an `eslintConfig` field, the configuration it describes is applied to all subdirectories beneath it. However, the configuration described by the `.eslintrc` file in the `tests/` directory overrides conflicting specifications. ```text your-project @@ -150,9 +150,9 @@ your-project └── test.js ``` -If there is a `.eslintrc` and a `package.json` file found in the same directory, `.eslintrc` will take priority and `package.json` file will not be used. +If there is an `.eslintrc` and a `package.json` file found in the same directory, `.eslintrc` takes priority and the `package.json` file is not used. -By default, ESLint will look for configuration files in all parent folders up to the root directory. This can be useful if you want all of your projects to follow a certain convention, but can sometimes lead to unexpected results. To limit ESLint to a specific project, place `"root": true` inside the `.eslintrc.*` file or `eslintConfig` field of the `package.json` file or in the `.eslintrc.*` file at your project's root level. ESLint will stop looking in parent folders once it finds a configuration with `"root": true`. +By default, ESLint looks for configuration files in all parent folders up to the root directory. This can be useful if you want all of your projects to follow a certain convention, but can sometimes lead to unexpected results. To limit ESLint to a specific project, place `"root": true` inside the `.eslintrc.*` file or `eslintConfig` field of the `package.json` file or in the `.eslintrc.*` file at your project's root level. ESLint stops looking in parent folders once it finds a configuration with `"root": true`. ```js { @@ -167,7 +167,7 @@ And in YAML: root: true ``` -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. +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/` are used, but the `.eslintrc` file in `projectA/` is not. ```text home @@ -195,7 +195,7 @@ The complete configuration hierarchy, from highest to lowest precedence, is as f 1. `.eslintrc.*` or `package.json` file in the same directory as the linted file 1. Continue searching for `.eslintrc.*` and `package.json` files in ancestor directories up to and including the root directory or until a config with `"root": true` is found. -Please note that the [home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir) (`~/`) is also considered a root directory in this context and searching for configuration files will stop there as well. And with the [removal of support for Personal Configuration Files](https://eslint.org/docs/user-guide/configuring/configuration-files#personal-configuration-files-deprecated) from the 8.0.0 release forward, configuration files present in that directory will be ignored. +Please note that the [home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir) (`~/`) is also considered a root directory in this context and searching for configuration files stops there as well. And with the [removal of support for Personal Configuration Files](configuration-files#personal-configuration-files-deprecated) from the 8.0.0 release forward, configuration files present in that directory are ignored. ## Extending Configuration Files @@ -232,7 +232,7 @@ The `rules` property can do any of the following to extend (or override) the set ### Using a shareable configuration package -A [sharable configuration](https://eslint.org/docs/developer-guide/shareable-configs) is an npm package that exports a configuration object. Make sure that you have installed the package in your project root directory, so that ESLint can require it. +A [sharable configuration](../../extend/shareable-configs) is an npm package that exports a configuration object. Make sure that you have installed the package in your project root directory, so that ESLint can require it. The `extends` property value can omit the `eslint-config-` prefix of the package name. @@ -251,7 +251,7 @@ rules: ### Using `eslint:recommended` -Using `"eslint:recommended"` in the `extends` property enables a subset of core rules that report common problems (these rules are identified with a checkmark (recommended) on the [rules page](https://eslint.org/docs/rules/)). +Using `"eslint:recommended"` in the `extends` property enables a subset of core rules that report common problems (these rules are identified with a checkmark (recommended) on the [rules page](../../rules/)). Here's an example of extending `eslint:recommended` and overriding some of the set configuration options: @@ -279,9 +279,9 @@ module.exports = { ### Using a configuration from a plugin -A [plugin](https://eslint.org/docs/developer-guide/working-with-plugins) is an npm package that can add various extensions to ESLint. A plugin can perform numerous functions, including but not limited to adding new rules and exporting [shareable configurations](https://eslint.org/docs/developer-guide/working-with-plugins#configs-in-plugins). Make sure the package has been installed in a directory where ESLint can require it. +A [plugin](../../extend/plugins) is an npm package that can add various extensions to ESLint. A plugin can perform numerous functions, including but not limited to adding new rules and exporting [shareable configurations](../../extend/plugins#configs-in-plugins). Make sure the package has been installed in a directory where ESLint can require it. -The `plugins` [property value](./plugins#configuring-plugins) can omit the `eslint-plugin-` prefix of the package name. +The `plugins` [property value](./plugins#configure-plugins) can omit the `eslint-plugin-` prefix of the package name. The `extends` property value can consist of: @@ -332,9 +332,9 @@ The `extends` property value can be `"eslint:all"` to enable all core rules in t **Important:** This configuration is **not recommended for production use** because it changes with every minor and major version of ESLint. Use it at your own risk. -You might enable all core rules as a shortcut to explore rules and options while you decide on the configuration for a project, especially if you rarely override options or disable rules. The default options for rules are not endorsements by ESLint (for example, the default option for the [`quotes`](https://eslint.org/docs/rules/quotes) rule does not mean double quotes are better than single quotes). +You might enable all core rules as a shortcut to explore rules and options while you decide on the configuration for a project, especially if you rarely override options or disable rules. The default options for rules are not endorsements by ESLint (for example, the default option for the [`quotes`](../../rules/quotes) rule does not mean double quotes are better than single quotes). -If your configuration extends `eslint:all`, after you upgrade to a newer major or minor version of ESLint, review the reported problems before you use the `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#--fix), so you know if a new fixable rule will make changes to the code. +If your configuration extends `eslint:all`, after you upgrade to a newer major or minor version of ESLint, review the reported problems before you use the `--fix` option on the [command line](../command-line-interface#--fix), so you know if a new fixable rule will make changes to the code. Example of a configuration file in JavaScript format: @@ -360,7 +360,7 @@ module.exports = { ## Configuration Based on Glob Patterns -v4.1.0+. 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`). +**v4.1.0+.** Sometimes a more fine-controlled configuration is necessary, like if the configuration for files within the same directory has to be different. In this case, you can provide configurations under the `overrides` key that 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`). Glob patterns in overrides use [minimatch syntax](https://github.com/isaacs/minimatch). @@ -390,11 +390,11 @@ In your `.eslintrc.json`: Here is how overrides work in a configuration file: -* 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`. +* 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` is 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 `root` and `ignorePatterns`. * A glob specific configuration can have an `extends` setting, but the `root` property in the extended configs is ignored. The `ignorePatterns` property in the extended configs is used only for the files the glob specific configuration matched. - * Nested `overrides` setting will be applied only if the glob patterns of both of the parent config and the child config matched. This is the same when the extended configs have an `overrides` setting. + * Nested `overrides` settings are applied only if the glob patterns of both the parent config and the child config are matched. This is the same when the extended configs have an `overrides` setting. * 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. @@ -424,11 +424,11 @@ If a config is provided via the `--config` CLI option, the glob patterns in the If you specified directories with CLI (e.g., `eslint lib`), ESLint searches target files in the directory to lint. The target files are `*.js` or the files that match any of `overrides` entries (but exclude entries that are any of `files` end with `*`). -If you specified the [`--ext`](https://eslint.org/docs/user-guide/command-line-interface#ext) command line option along with directories, the target files are only the files that have specified file extensions regardless of `overrides` entries. +If you specified the [`--ext`](../command-line-interface#--ext) command line option along with directories, the target files are only the files that have specified file extensions regardless of `overrides` entries. ## Personal Configuration Files (deprecated) -⚠️ **This feature has been deprecated**. This feature will be removed in the 8.0.0 release. If you want to continue to use personal configuration files, please use the [`--config` CLI option](https://eslint.org/docs/user-guide/command-line-interface#-c---config). For more information regarding this decision, please see [RFC 28](https://github.com/eslint/rfcs/pull/28) and [RFC 32](https://github.com/eslint/rfcs/pull/32). +⚠️ **This feature has been deprecated**. This feature was removed in the 8.0.0 release. If you want to continue to use personal configuration files, please use the [`--config` CLI option](../command-line-interface#-c---config). For more information regarding this decision, please see [RFC 28](https://github.com/eslint/rfcs/pull/28) and [RFC 32](https://github.com/eslint/rfcs/pull/32). `~/` refers to [the home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir). The personal configuration file being referred to here is `~/.eslintrc.*` file, which is currently handled differently than other configuration files. @@ -444,4 +444,4 @@ If `eslint` could find configuration files in the project, `eslint` ignores `~/. `~/.eslintrc.*` files load shareable configs and custom parsers from `~/node_modules/` – similarly to `require()` – in the user's home directory. Please note that it doesn't load global-installed packages. -`~/.eslintrc.*` files load plugins from `$CWD/node_modules` by default in order to identify plugins uniquely. If you want to use plugins with `~/.eslintrc.*` files, plugins must be installed locally per project. Alternatively, you can use the [`--resolve-plugins-relative-to` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--resolve-plugins-relative-to) to change the location from which ESLint loads plugins. +`~/.eslintrc.*` files load plugins from `$CWD/node_modules` by default in order to identify plugins uniquely. If you want to use plugins with `~/.eslintrc.*` files, plugins must be installed locally per project. Alternatively, you can use the [`--resolve-plugins-relative-to` CLI option](../command-line-interface#--resolve-plugins-relative-to) to change the location from which ESLint loads plugins. diff --git a/docs/src/user-guide/configuring/ignoring-code.md b/docs/src/use/configure/ignore.md similarity index 73% rename from docs/src/user-guide/configuring/ignoring-code.md rename to docs/src/use/configure/ignore.md index 119d1873b78..ffc23428ee0 100644 --- a/docs/src/user-guide/configuring/ignoring-code.md +++ b/docs/src/use/configure/ignore.md @@ -1,18 +1,22 @@ --- -title: Ignoring Code -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/configuring/ignoring-code.md +title: Ignore Files eleventyNavigation: - key: ignoring code - parent: configuring - title: Ignoring Code - order: 5 + key: ignore files + parent: configure + title: Ignore Files + order: 7 --- +You can configure ESLint to ignore certain files and directories while linting by specifying one or more glob patterns. +You can ignore files in the following ways: + +* Add `ignorePatterns` to a configuration file. +* Create a dedicated file that contains the ignore patterns (`.eslintignore` by default). + ## `ignorePatterns` in Config Files -You can tell ESLint to ignore specific files and directories using `ignorePatterns` in your config files. `ignorePatterns` patterns follow the same rules as `.eslintignore`. Please see the [the `.eslintignore` file documentation](./ignoring-code#the-eslintignore-file) to learn more. +You can tell ESLint to ignore specific files and directories using `ignorePatterns` in your config files. `ignorePatterns` patterns follow the same rules as `.eslintignore`. Please see the [`.eslintignore` file documentation](#the-eslintignore-file) to learn more. ```json { @@ -33,13 +37,13 @@ If a config is provided via the `--config` CLI option, the ignore patterns that ## The `.eslintignore` File -You can tell ESLint to ignore specific files and directories by creating an `.eslintignore` file in your project's root directory. The `.eslintignore` file is a plain text file where each line is a glob pattern indicating which paths should be omitted from linting. For example, the following will omit all JavaScript files: +You can tell ESLint to ignore specific files and directories by creating an `.eslintignore` file in your project's root directory. The `.eslintignore` file is a plain text file where each line is a glob pattern indicating which paths should be omitted from linting. For example, the following omits all JavaScript files: ```text **/*.js ``` -When ESLint is run, it looks in the current working directory to find an `.eslintignore` file before determining which files to lint. If this file is found, then those preferences are applied when traversing directories. Only one `.eslintignore` file can be used at a time, so `.eslintignore` files other than the one in the current working directory will not be used. +When ESLint is run, it looks in the current working directory to find an `.eslintignore` file before determining which files to lint. If this file is found, then those preferences are applied when traversing directories. Only one `.eslintignore` file can be used at a time, so `.eslintignore` files other than the one in the current working directory are not used. Globs are matched using [node-ignore](https://github.com/kaelzhang/node-ignore), so a number of features are available: @@ -63,17 +67,17 @@ Please see [`.gitignore`](https://git-scm.com/docs/gitignore)'s specification fo In addition to any patterns in the `.eslintignore` file, ESLint always follows a couple of implicit ignore rules even if the `--no-ignore` flag is passed. The implicit rules are as follows: * `node_modules/` is ignored. -* dot-files (except for `.eslintrc.*`), as well as dot-folders and their contents, are ignored. +* dot-files (except for `.eslintrc.*`) as well as dot-folders and their contents are ignored. There are also some exceptions to these rules: -* If the path to lint is a glob pattern or directory path and contains a dot-folder, all dot-files and dot-folders will be linted. This includes dot-files and dot-folders that are buried deeper in the directory structure. +* If the path to lint is a glob pattern or directory path and contains a dot-folder, all dot-files and dot-folders are linted. This includes dot-files and dot-folders that are buried deeper in the directory structure. - For example, `eslint .config/` will lint all dot-folders and dot-files in the `.config` directory, including immediate children as well as children that are deeper in the directory structure. + For example, `eslint .config/` would lint all dot-folders and dot-files in the `.config` directory, including immediate children as well as children that are deeper in the directory structure. -* If the path to lint is a specific file path and the `--no-ignore` flag has been passed, ESLint will lint the file regardless of the implicit ignore rules. +* If the path to lint is a specific file path and the `--no-ignore` flag has been passed, ESLint would lint the file regardless of the implicit ignore rules. - For example, `eslint .config/my-config-file.js --no-ignore` will cause `my-config-file.js` to be linted. It should be noted that the same command without the `--no-ignore` line will not lint the `my-config-file.js` file. + For example, `eslint .config/my-config-file.js --no-ignore` would cause `my-config-file.js` to be linted. It should be noted that the same command without the `--no-ignore` line would not lint the `my-config-file.js` file. * Allowlist and denylist rules specified via `--ignore-pattern` or `.eslintignore` are prioritized above implicit ignore rules. @@ -107,11 +111,11 @@ You can also use your `.gitignore` file: eslint --ignore-path .gitignore file.js ``` -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`. +Any file that follows the standard ignore file format can be used. Keep in mind that specifying `--ignore-path` means that the existing `.eslintignore` file is not 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. +If an `.eslintignore` file is not found and an alternate file is not specified, ESLint looks in `package.json` for the `eslintIgnore` key to check for files to ignore. ```json { @@ -129,7 +133,7 @@ If an `.eslintignore` file is not found and an alternate file is not specified, ## 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: +When you pass directories to ESLint, files and directories are silently ignored. If you pass a specific file to ESLint, then ESLint creates a warning that the file was skipped. For example, suppose you have an `.eslintignore` file that looks like this: ```text foo.js @@ -152,7 +156,7 @@ foo.js This message occurs because ESLint is unsure if you wanted to actually lint the file or not. As the message indicates, you can use `--no-ignore` to omit using the ignore rules. -Consider another scenario where you may want to run ESLint on a specific dot-file or dot-folder, but have forgotten to specifically allow those files in your `.eslintignore` file. You would run something like this: +Consider another scenario where you want to run ESLint on a specific dot-file or dot-folder, but have forgotten to specifically allow those files in your `.eslintignore` file. You would run something like this: ```shell eslint .config/foo.js @@ -167,4 +171,4 @@ You would see this warning: ✖ 1 problem (0 errors, 1 warning) ``` -This message occurs because, normally, this file would be ignored by ESLint's implicit ignore rules (as mentioned above). A negated ignore rule in your `.eslintignore` file would override the implicit rule and reinclude this file for linting. Additionally, in this specific case, `--no-ignore` could be used to lint the file as well. +This message occurs because, normally, this file would be ignored by ESLint's implicit ignore rules (as mentioned above). A negated ignore rule in your `.eslintignore` file would override the implicit rule and reinclude this file for linting. Additionally, in this case, `--no-ignore` could be used to lint the file as well. diff --git a/docs/src/user-guide/configuring/index.md b/docs/src/use/configure/index.md similarity index 71% rename from docs/src/user-guide/configuring/index.md rename to docs/src/use/configure/index.md index f9351dd75f2..e228383c944 100644 --- a/docs/src/user-guide/configuring/index.md +++ b/docs/src/use/configure/index.md @@ -1,19 +1,17 @@ --- -title: Configuring ESLint -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/configuring/index.md +title: Configure ESLint eleventyNavigation: - key: configuring - parent: user guide - title: Configuring - order: 2 + key: configure + parent: use eslint + title: Configure ESLint + order: 3 --- ESLint is designed to be flexible and configurable for your use case. 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 fit the needs of 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 a [`.eslintrc.*`](./configuration-files#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). +2. **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 a [`.eslintrc.*`](./configuration-files#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). Here are some of the options that you can configure in ESLint: @@ -36,27 +34,30 @@ All of these options give you fine-grained control over how ESLint treats your c * [Configuration Based on Glob Patterns](./configuration-files#configuration-based-on-glob-patterns) * [Personal Configuration Files](./configuration-files#personal-configuration-files-deprecated) -[**Language Options**](language-options) +[**Configure Language Options**](language-options) * [Specifying Environments](./language-options#specifying-environments) * [Specifying Globals](./language-options#specifying-globals) * [Specifying Parser Options](./language-options#specifying-parser-options) -[**Rules**](rules) +[**Configure Rules**](rules) -* [Configuring Rules](./rules#configuring-rules) +* [Configuring Rules](./rules) * [Disabling Rules](./rules#disabling-rules) -[**Plugins**](plugins) +[**Configure Plugins**](plugins) -* [Specifying Parser](./plugins#specifying-parser) -* [Specifying Processor](./plugins#specifying-processor) -* [Configuring Plugins](./plugins#configuring-plugins) +* [Configure Plugins](./plugins#configure-plugins) +* [Specify a Processor](./plugins#specify-a-processor) -[**Ignoring Code**](ignoring-code) +[**Configure a Parser**](./parser) -* [`ignorePatterns` in Config Files](./ignoring-code#ignorepatterns-in-config-files) -* [The `.eslintignore` File](./ignoring-code#the-eslintignore-file) -* [Using an Alternate File](./ignoring-code#using-an-alternate-file) -* [Using eslintIgnore in package.json](./ignoring-code#using-eslintignore-in-packagejson) -* [Ignored File Warnings](./ignoring-code#ignored-file-warnings) +* [Configure a Custom Parser](./parser#configure-a-custom-parser) + +[**Ignore Files**](ignore) + +* [`ignorePatterns` in Config Files](./ignore#ignorepatterns-in-config-files) +* [The `.eslintignore` File](./ignore#the-eslintignore-file) +* [Using an Alternate File](./ignore#using-an-alternate-file) +* [Using eslintIgnore in package.json](./ignore#using-eslintignore-in-packagejson) +* [Ignored File Warnings](./ignore#ignored-file-warnings) diff --git a/docs/src/user-guide/configuring/language-options.md b/docs/src/use/configure/language-options.md similarity index 78% rename from docs/src/user-guide/configuring/language-options.md rename to docs/src/use/configure/language-options.md index 379c678f48c..ef21c35d57e 100644 --- a/docs/src/user-guide/configuring/language-options.md +++ b/docs/src/use/configure/language-options.md @@ -1,15 +1,15 @@ --- -title: Language Options -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/configuring/language-options.md +title: Configure Language Options eleventyNavigation: - key: configuring language options - parent: configuring - title: Configuring Language Options - order: 2 + key: configure language options + parent: configure + title: Configure Language Options + order: 3 --- +The JavaScript ecosystem has a variety of runtimes, versions, extensions, and frameworks. Each of these can have different supported syntax and global variables. ESLint lets you configure language options specific to the JavaScript used in your project, like custom global variables. You can also use plugins to extend ESLint to support your project's language options. + ## Specifying Environments An environment provides predefined global variables. The available environments are: @@ -53,7 +53,7 @@ Environments can be specified inside of a file, in configuration files or using ### Using configuration comments -To specify environments using a comment inside of your JavaScript file, use the following format: +To specify environments with a comment inside of a JavaScript file, use the following format: ```js /* eslint-env node, mocha */ @@ -63,7 +63,7 @@ This enables Node.js and Mocha environments. ### Using configuration files -To specify environments in a configuration file, use the `env` key and specify which environments you want to enable by setting each to `true`. For example, the following enables the browser and Node.js environments: +To specify environments in a configuration file, use the `env` key. Specify which environments you want to enable by setting each to `true`. For example, the following enables the browser and Node.js environments: ```json { @@ -168,7 +168,7 @@ And in YAML: These examples allow `var1` to be overwritten in your code, but disallow it for `var2`. -Globals can be disabled with the string `"off"`. For example, in an environment where most ES2015 globals are available but `Promise` is unavailable, you might use this config: +Globals can be disabled by setting their value to `"off"`. For example, in an environment where most ES2015 globals are available but `Promise` is unavailable, you might use this config: ```json { @@ -181,21 +181,19 @@ Globals can be disabled with the string `"off"`. For example, in an environment } ``` -For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of older values is deprecated. +For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of these older values is deprecated. ## Specifying Parser Options -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. +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 and JSX 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. -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 } }`. `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically. +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 } }`. Setting `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically. -Parser options are set in your `.eslintrc.*` file by using the `parserOptions` property. The available options are: +Parser options are set in your `.eslintrc.*` file with the `parserOptions` property. The available options are: -* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, or 13 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), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), 2021 (same as 12), or 2022 (same as 13) to use the year-based naming. You can also set "latest" to use the most recently supported version. +* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, 13, or 14 to specify the version of ECMAScript syntax you want to use. You can also set it to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), 2021 (same as 12), 2022 (same as 13), or 2023 (same as 14) to use the year-based naming. You can also set `"latest"` to use the most recently supported version. * `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules. * `allowReserved` - allow the use of reserved words as identifiers (if `ecmaVersion` is 3). * `ecmaFeatures` - an object indicating which additional language features you'd like to use: diff --git a/docs/src/use/configure/parser.md b/docs/src/use/configure/parser.md new file mode 100644 index 00000000000..76d94d15fc1 --- /dev/null +++ b/docs/src/use/configure/parser.md @@ -0,0 +1,38 @@ +--- +title: Configure a Parser +eleventyNavigation: + key: configure parser + parent: configure + title: Configure a Parser + order: 6 +--- + +You can use custom parsers to convert JavaScript code into an abstract syntax tree for ESLint to evaluate. You might want to add a custom parser if your code isn't compatible with ESLint's default parser, Espree. + +## Configure a Custom 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 if the parser meets the following requirements: + +1. It must be a Node module loadable from the config file where the parser is used. Usually, this means you should install the parser package separately using npm. +1. It must conform to the [parser interface](../../extend/custom-parsers). + +Note that even with these compatibilities, there are no guarantees that an external parser works correctly with ESLint. ESLint does not fix bugs related to incompatibilities with other parsers. + +To indicate the npm module to use as your parser, specify it using the `parser` option in your `.eslintrc` file. For example, the following specifies to use Esprima instead of Espree: + +```json +{ + "parser": "esprima", + "rules": { + "semi": "error" + } +} +``` + +The following parsers are compatible with ESLint: + +* [Esprima](https://www.npmjs.com/package/esprima) +* [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint. +* [@typescript-eslint/parser](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. + +Note that 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. diff --git a/docs/src/user-guide/configuring/plugins.md b/docs/src/use/configure/plugins.md similarity index 59% rename from docs/src/user-guide/configuring/plugins.md rename to docs/src/use/configure/plugins.md index 3314ef9a130..3026592c418 100644 --- a/docs/src/user-guide/configuring/plugins.md +++ b/docs/src/use/configure/plugins.md @@ -1,95 +1,23 @@ --- -title: Plugins -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/configuring/plugins.md +title: Configure Plugins eleventyNavigation: - key: configuring plugins - parent: configuring - title: Configuring Plugins - order: 4 + key: configure plugins + parent: configure + title: Configure Plugins + order: 5 --- -## Specifying Parser +You can extend ESLint with plugins in a variety of different ways. Plugins can include: -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: +* Custom rules to validate if your code meets a certain expectation, and what to do if it does not meet that expectation. +* Custom configurations. +* Custom environments. +* Custom processors to extract JavaScript code from other kinds of files or preprocess code before linting. -1. It must be a Node module loadable from the config file where the parser is used. Usually, this means you should install the parser package separately using npm. -1. It must conform to the [parser interface](../../developer-guide/working-with-custom-parsers). +## Configure Plugins -Note that even with these compatibilities, there are no guarantees that an external parser will work correctly with ESLint and ESLint will not fix bugs related to incompatibilities with other parsers. - -To indicate the npm module to use as your parser, specify it using the `parser` option in your `.eslintrc` file. For example, the following specifies to use Esprima instead of Espree: - -```json -{ - "parser": "esprima", - "rules": { - "semi": "error" - } -} -``` - -The following parsers are compatible with ESLint: - -* [Esprima](https://www.npmjs.com/package/esprima) -* [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint. -* [@typescript-eslint/parser](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. - -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. - -## Specifying Processor - -Plugins may provide processors. Processors can extract JavaScript code from other kinds of files, then let ESLint lint the JavaScript code or processors can convert JavaScript code in preprocessing for some purpose. - -To specify processors in a configuration file, use the `processor` key with the concatenated string of a plugin name and a processor name by a slash. For example, the following enables the processor `a-processor` that the plugin `a-plugin` provided: - -```json -{ - "plugins": ["a-plugin"], - "processor": "a-plugin/a-processor" -} -``` - -To specify processors for specific kinds of files, use the combination of the `overrides` key and the `processor` key. For example, the following uses the processor `a-plugin/markdown` for `*.md` files. - -```json -{ - "plugins": ["a-plugin"], - "overrides": [ - { - "files": ["*.md"], - "processor": "a-plugin/markdown" - } - ] -} -``` - -Processors may make named code blocks such as `0.js` and `1.js`. ESLint handles such a named code block as a child file of the original file. You can specify additional configurations for named code blocks in the `overrides` section of the config. For example, the following disables the `strict` rule for the named code blocks which end with `.js` in markdown files. - -```json -{ - "plugins": ["a-plugin"], - "overrides": [ - { - "files": ["*.md"], - "processor": "a-plugin/markdown" - }, - { - "files": ["**/*.md/*.js"], - "rules": { - "strict": "off" - } - } - ] -} -``` - -ESLint checks the file path of named code blocks then ignores those if any `overrides` entry didn't match the file path. Be sure to add an `overrides` entry if you want to lint named code blocks other than `*.js`. - -## Configuring Plugins - -ESLint supports the use of third-party plugins. Before using the plugin, you have to install it using npm. +ESLint supports the use of third-party plugins. Before using a plugin, you have to install it using npm. To configure plugins inside of a configuration file, use the `plugins` key, which contains a list of plugin names. The `eslint-plugin-` prefix can be omitted from the plugin name. @@ -113,14 +41,16 @@ And in YAML: **Notes:** -1. Plugins are resolved relative to the config file. In other words, ESLint will load the plugin as a user would obtain by running `require('eslint-plugin-pluginname')` in the config file. +1. Plugins are resolved relative to the config file. In other words, ESLint loads the plugin as a user would obtain by running `require('eslint-plugin-pluginname')` in the config file. 2. Plugins in the base configuration (loaded by `extends` setting) are relative to the derived config file. For example, if `./.eslintrc` has `extends: ["foo"]` and the `eslint-config-foo` has `plugins: ["bar"]`, ESLint finds the `eslint-plugin-bar` from `./node_modules/` (rather than `./node_modules/eslint-config-foo/node_modules/`) or ancestor directories. Thus every plugin in the config file and base configurations is resolved uniquely. ### Naming convention #### Include a plugin -The `eslint-plugin-` prefix can be omitted for non-scoped packages +The `eslint-plugin-` prefix can be omitted for both non-scoped and scoped packages. + +A non-scoped package: ```js { @@ -132,7 +62,7 @@ The `eslint-plugin-` prefix can be omitted for non-scoped packages } ``` -The same rule does apply to scoped packages: +A scoped package: ```js { @@ -147,7 +77,7 @@ The same rule does apply to scoped packages: #### Use a plugin -When using rules, environments or configs defined by plugins, they must be referenced following the convention: +Rules, environments, and configurations defined in plugins must be referenced with the following convention: * `eslint-plugin-foo` → `foo/a-rule` * `@foo/eslint-plugin` → `@foo/a-config` @@ -180,3 +110,52 @@ For example: // ... } ``` + +### Specify a Processor + +Plugins may provide processors. Processors can extract JavaScript code from other kinds of files, then let ESLint lint the JavaScript code. Alternatively, processors can convert JavaScript code during preprocessing. + +To specify processors in a configuration file, use the `processor` key with the concatenated string of a plugin name and a processor name by a slash. For example, the following enables the processor `a-processor` that the plugin `a-plugin` provided: + +```json +{ + "plugins": ["a-plugin"], + "processor": "a-plugin/a-processor" +} +``` + +To specify processors for specific kinds of files, use the combination of the `overrides` key and the `processor` key. For example, the following uses the processor `a-plugin/markdown` for `*.md` files. + +```json +{ + "plugins": ["a-plugin"], + "overrides": [ + { + "files": ["*.md"], + "processor": "a-plugin/markdown" + } + ] +} +``` + +Processors may make named code blocks such as `0.js` and `1.js`. ESLint handles such a named code block as a child file of the original file. You can specify additional configurations for named code blocks in the `overrides` section of the config. For example, the following disables the `strict` rule for the named code blocks which end with `.js` in markdown files. + +```json +{ + "plugins": ["a-plugin"], + "overrides": [ + { + "files": ["*.md"], + "processor": "a-plugin/markdown" + }, + { + "files": ["**/*.md/*.js"], + "rules": { + "strict": "off" + } + } + ] +} +``` + +ESLint checks the file path of named code blocks then ignores those if any `overrides` entry didn't match the file path. Be sure to add an `overrides` entry if you want to lint named code blocks other than `*.js`. diff --git a/docs/src/user-guide/configuring/rules.md b/docs/src/use/configure/rules.md similarity index 72% rename from docs/src/user-guide/configuring/rules.md rename to docs/src/use/configure/rules.md index ebc070f8cd7..fd93c2919a0 100644 --- a/docs/src/user-guide/configuring/rules.md +++ b/docs/src/use/configure/rules.md @@ -1,23 +1,29 @@ --- -title: Rules -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/configuring/rules.md +title: Configure Rules eleventyNavigation: - key: configuring rules - parent: configuring - title: Configuring Rules - order: 3 + key: configure rules + parent: configure + title: Configure Rules + order: 4 --- -## Configuring Rules +Rules are the core building block of ESLint. A rule validates if your code meets a certain expectation, and what to do if it does not meet that expectation. Rules can also contain additional configuration options specific to that rule. -ESLint comes with a large number of built-in rules and you can add more rules through plugins. You can modify which rules your project uses either using configuration comments or configuration files. To change a rule setting, you must set the rule ID equal to one of these values: +ESLint comes with a large number of [built-in rules](../../rules/) and you can add more rules through plugins. You can modify which rules your project uses with either configuration comments or configuration files. + +## Rule Severities + +To change a rule's severity, set the rule ID equal to 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 is 1 when triggered) +Rules are typically set to `"error"` to enforce compliance with the rule during continuous integration testing, pre-commit checks, and pull request merging because doing so causes ESLint to exit with a non-zero exit code. + +If you don't want to enforce compliance with a rule but would still like ESLint to report the rule's violations, set the severity to `"warn"`. This is typically used when introducing a new rule that will eventually be set to `"error"`, when a rule is flagging something other than a potential buildtime or runtime error (such as an unused variable), or when a rule cannot determine with certainty that a problem has been found (when a rule might have false positives and need manual review). + ### Using configuration comments To configure rules inside of a file using configuration comments, use a comment in the following format: @@ -42,6 +48,8 @@ If a rule has additional options, you can specify them using array literal synta This comment specifies the "double" option for the [`quotes`](../../rules/quotes) rule. The first item in the array is always the rule severity (number or string). +#### Configuration Comment Descriptions + Configuration comments can include descriptions to explain why the comment is necessary. The description must occur after the configuration and is separated from the configuration by two or more consecutive `-` characters. For example: ```js @@ -87,7 +95,11 @@ rules: - double ``` -To configure a rule which is defined within a plugin you have to prefix the rule ID with the plugin name and a `/`. For example: +## Rules from Plugins + +To configure a rule that is defined within a plugin, prefix the rule ID with the plugin name and `/`. + +In a configuration file, for example: ```json { @@ -118,7 +130,9 @@ rules: plugin1/rule1: error ``` -In these configuration files, the rule `plugin1/rule1` comes from the plugin named `plugin1`. You can also use this format with configuration comments, such as: +In these configuration files, the rule `plugin1/rule1` comes from the plugin named `plugin1`, which is contained in an npm package named `eslint-plugin-plugin1`. + +You can also use this format with configuration comments, such as: ```js /* eslint "plugin1/rule1": "error" */ @@ -130,7 +144,7 @@ In these configuration files, the rule `plugin1/rule1` comes from the plugin nam ### Using configuration comments -To temporarily disable rule warnings in your file, use block comments in the following format: +To disable rule warnings in a part of a file, use block comments in the following format: ```js /* eslint-disable */ @@ -151,7 +165,7 @@ console.log('bar'); /* eslint-enable no-alert, no-console */ ``` -**Note:** `/* eslint-enable */` without any specific rules listed will cause all disabled rules to be re-enabled. +**Note:** `/* eslint-enable */` without any specific rules listed causes all disabled rules to be re-enabled. To disable rule warnings in an entire file, put a `/* eslint-disable */` block comment at the top of the file: @@ -233,7 +247,11 @@ foo(); // eslint-disable-line example/rule-name foo(); /* eslint-disable-line example/rule-name */ ``` -Configuration comments can include descriptions to explain why the comment is necessary. The description must come after the configuration and needs to be separated from the configuration by two or more consecutive `-` characters. For example: +**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. + +#### Comment descriptions + +Configuration comments can include descriptions to explain why disabling or re-enabling the rule is necessary. The description must come after the configuration and needs to be separated from the configuration by two or more consecutive `-` characters. For example: ```js // eslint-disable-next-line no-console -- Here's a description about why this configuration is necessary. @@ -246,8 +264,6 @@ console.log('hello'); console.log('hello'); ``` -**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. - ### Using configuration files To disable rules inside of a configuration file for a group of files, use the `overrides` key along with a `files` key. For example: @@ -268,7 +284,7 @@ To disable rules inside of a configuration file for a group of files, use the `o ### Disabling Inline Comments -To disable all inline config comments, use the `noInlineConfig` setting. For example: +To disable all inline config comments, use the `noInlineConfig` setting in your configuration file. For example: ```json { @@ -277,7 +293,7 @@ To disable all inline config comments, use the `noInlineConfig` setting. For exa } ``` -This setting is similar to [--no-inline-config](../command-line-interface#--no-inline-config) CLI option. +You can also use the [--no-inline-config](../command-line-interface#--no-inline-config) CLI option to disable rule comments, in addition to other in-line configuration. #### Report unused `eslint-disable` comments diff --git a/docs/src/use/core-concepts.md b/docs/src/use/core-concepts.md new file mode 100644 index 00000000000..b9dfbbbfa1c --- /dev/null +++ b/docs/src/use/core-concepts.md @@ -0,0 +1,82 @@ +--- +title: Core Concepts +eleventyNavigation: + key: core concepts + title: Core Concepts + parent: use eslint + order: 2 +--- + +This page contains a high-level overview of some of the core concepts of ESLint. + +## What is ESLint? + +ESLint is a configurable JavaScript linter. It helps you find and fix problems in your JavaScript code. Problems can be anything from potential runtime bugs, to not following best practices, to styling issues. + +## Rules + +Rules are the core building block of ESLint. A rule validates if your code meets a certain expectation, and what to do if it does not meet that expectation. Rules can also contain additional configuration options specific to that rule. + +For example, the [`semi`](../rules/semi) rule lets you specify whether or not JavaScript statements should end with a semicolon (`;`). You can set the rule to either always require semicolons or require that a statement never ends with a semicolon. + +ESLint contains hundreds of built-in rules that you can use. You can also create custom rules or use rules that others have created with [plugins](#plugins). + +For more information, refer to [Rules](../rules/). + +## Configuration Files + +An ESLint configuration file is a place where you put the configuration for ESLint in your project. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. + +For more information, refer to [Configuration Files](./configure/configuration-files). + +## Shareable Configurations + +Shareable configurations are ESLint configurations that are shared via npm. + +Often shareable configurations are used to enforce style guides using ESLint's built-in rules. For example the sharable configuration [eslint-config-airbnb-base](https://www.npmjs.com/package/eslint-config-airbnb-base) implements the popular Airbnb JavaScript style guide. + +For more information, refer to [Using a shareable configuration package](./configure/configuration-files#using-a-shareable-configuration-package). + +## Plugins + +An ESLint plugin is an npm module that can contain a set of ESLint rules, configurations, processors, and environments. Often plugins include custom rules. Plugins can be used to enforce a style guide and support JavaScript extensions (like TypeScript), libraries (like React), and frameworks (Angular). + +A popular use case for plugins is to enforce best practices for a framework. For example, [@angular-eslint/eslint-plugin](https://www.npmjs.com/package/@angular-eslint/eslint-plugin) contains best practices for using the Angular framework. + +For more information, refer to [Configure Plugins](./configure/plugins). + +## Parsers + +An ESLint parser converts code into an abstract syntax tree that ESLint can evaluate. By default, ESLint uses the built-in [Espree](https://github.com/eslint/espree) parser, which is compatible with standard JavaScript runtimes and versions. + +Custom parsers let ESLint parse non-standard JavaScript syntax. Often custom parsers are included as part of shareable configurations or plugins, so you don't have to use them directly. + +For example, [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) is a custom parser included in the [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint) project that lets ESLint parse TypeScript code. + +## Custom Processors + +An ESLint processor extracts JavaScript code from other kinds of files, then lets ESLint lint the JavaScript code. Alternatively, you can use a processor to manipulate JavaScript code before parsing it with ESLint. + +For example, [eslint-plugin-markdown](https://github.com/eslint/eslint-plugin-markdown) contains a custom processor that lets you lint JavaScript code inside of Markdown code blocks. + +## Formatters + +An ESLint formatter controls the appearance of the linting results in the CLI. + +For more information, refer to [Formatters](./formatters/). + +## Integrations + +One of the things that makes ESLint such a useful tool is the ecosystem of integrations that surrounds it. For example, many code editors have ESLint extensions that show you the ESLint results of your code in the file as you work so that you don't need to use the ESLint CLI to see linting results. + +For more information, refer to [Integrations](./integrations). + +## CLI & Node.js API + +The ESLint CLI is a command line interface that lets you execute linting from the terminal. The CLI has a variety of options that you can pass to its commands. + +The ESLint Node.js API lets you use ESLint programmatically from Node.js code. The API is useful when developing plugins, integrations, and other tools related to ESLint. + +Unless you are extending ESLint in some way, you should use the CLI. + +For more information, refer to [Command Line Interface](./command-line-interface) and [Node.js API](../integrate/nodejs-api). diff --git a/docs/src/user-guide/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html similarity index 75% rename from docs/src/user-guide/formatters/html-formatter-example.html rename to docs/src/use/formatters/html-formatter-example.html index 8ee750fd02f..2bd483aa90f 100644 --- a/docs/src/user-guide/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -7,85 +7,110 @@ @@ -93,7 +118,7 @@

    ESLint Report

    - 9 problems (5 errors, 4 warnings) - Generated on Sat Aug 13 2022 21:22:40 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Tue Mar 28 2023 18:46:38 GMT-0400 (Eastern Daylight Time)
    @@ -104,7 +129,7 @@

    ESLint Report

    9 problems (5 errors, 4 warnings) - + @@ -113,7 +138,7 @@

    ESLint Report

    - + @@ -122,7 +147,7 @@

    ESLint Report

    - + @@ -131,7 +156,7 @@

    ESLint Report

    - + @@ -140,7 +165,7 @@

    ESLint Report

    - + @@ -149,7 +174,7 @@

    ESLint Report

    - + @@ -158,7 +183,7 @@

    ESLint Report

    - + @@ -167,7 +192,7 @@

    ESLint Report

    - + @@ -176,7 +201,7 @@

    ESLint Report

    - + diff --git a/docs/src/use/formatters/html-formatter-example.json b/docs/src/use/formatters/html-formatter-example.json new file mode 100644 index 00000000000..6070c859e9e --- /dev/null +++ b/docs/src/use/formatters/html-formatter-example.json @@ -0,0 +1,3 @@ +{ + "layout": false +} \ No newline at end of file diff --git a/docs/src/use/formatters/index.md b/docs/src/use/formatters/index.md new file mode 100644 index 00000000000..b5bb8be723d --- /dev/null +++ b/docs/src/use/formatters/index.md @@ -0,0 +1,296 @@ +--- +title: Formatters Reference +eleventyNavigation: + key: formatters + parent: use eslint + title: Formatters Reference + order: 6 +edit_link: https://github.com/eslint/eslint/edit/main/templates/formatter-examples.md.ejs +--- + +ESLint comes with several built-in formatters to control the appearance of the linting results, and supports third-party formatters as well. + +You can specify a formatter using the `--format` or `-f` flag in the CLI. For example, `--format json` uses the `json` formatter. + +The built-in formatter options are: + +* [checkstyle](#checkstyle) +* [compact](#compact) +* [html](#html) +* [jslint-xml](#jslint-xml) +* [json-with-metadata](#json-with-metadata) +* [json](#json) +* [junit](#junit) +* [stylish](#stylish) +* [tap](#tap) +* [unix](#unix) +* [visualstudio](#visualstudio) + +## Example Source + +Examples of each formatter were created from linting `fullOfProblems.js` using the `.eslintrc.json` configuration shown below. + +`fullOfProblems.js`: + +```js +function addOne(i) { + if (i != NaN) { + return i ++ + } else { + return + } +}; +``` + +`.eslintrc.json`: + +```json +{ + "extends": "eslint:recommended", + "rules": { + "consistent-return": 2, + "indent" : [1, 4], + "no-else-return" : 1, + "semi" : [1, "always"], + "space-unary-ops" : 2 + } +} +``` + +Tests the formatters with the CLI: + +```shell +npx eslint --format fullOfProblems.js +``` + +## Built-In Formatter Options + +### checkstyle + +Outputs results to the [Checkstyle](https://checkstyle.sourceforge.io/) format. + +Example output: + +```text + +``` + +### compact + +Human-readable output format. Mimics the default output of JSHint. + +Example output: + +```text +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 1, col 10, Error - 'addOne' is defined but never used. (no-unused-vars) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 2, col 9, Error - Use the isNaN function to compare with NaN. (use-isnan) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 3, col 16, Error - Unexpected space before unary operator '++'. (space-unary-ops) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 3, col 20, Warning - Missing semicolon. (semi) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 4, col 12, Warning - Unnecessary 'else' after 'return'. (no-else-return) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 1, Warning - Expected indentation of 8 spaces but found 6. (indent) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 7, Error - Function 'addOne' expected a return value. (consistent-return) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 13, Warning - Missing semicolon. (semi) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 7, col 2, Error - Unnecessary semicolon. (no-extra-semi) + +9 problems +``` + +### html + +Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser. + +Example output: + + + +### jslint-xml + +Outputs results to format compatible with the [JSLint Jenkins plugin](https://plugins.jenkins.io/jslint/). + +Example output: + +```text + +``` + +### json-with-metadata + +Outputs JSON-serialized results. The `json-with-metadata` provides the same linting results as the [`json`](#json) formatter with additional metadata about the rules applied. The linting results are included in the `results` property and the rules metadata is included in the `metadata` property. + +Alternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint. + +Example output: + +```text +{"results":[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}],"metadata":{"rulesMeta":{"no-else-return":{"type":"suggestion","docs":{"description":"Disallow `else` blocks after `return` statements in `if` statements","recommended":false,"url":"https://eslint.org/docs/rules/no-else-return"},"schema":[{"type":"object","properties":{"allowElseIf":{"type":"boolean","default":true}},"additionalProperties":false}],"fixable":"code","messages":{"unexpected":"Unnecessary 'else' after 'return'."}},"indent":{"type":"layout","docs":{"description":"Enforce consistent indentation","recommended":false,"url":"https://eslint.org/docs/rules/indent"},"fixable":"whitespace","schema":[{"oneOf":[{"enum":["tab"]},{"type":"integer","minimum":0}]},{"type":"object","properties":{"SwitchCase":{"type":"integer","minimum":0,"default":0},"VariableDeclarator":{"oneOf":[{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},{"type":"object","properties":{"var":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"let":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"const":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false}]},"outerIIFEBody":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"MemberExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"FunctionDeclaration":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"FunctionExpression":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"StaticBlock":{"type":"object","properties":{"body":{"type":"integer","minimum":0}},"additionalProperties":false},"CallExpression":{"type":"object","properties":{"arguments":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false},"ArrayExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ObjectExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ImportDeclaration":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"flatTernaryExpressions":{"type":"boolean","default":false},"offsetTernaryExpressions":{"type":"boolean","default":false},"ignoredNodes":{"type":"array","items":{"type":"string","not":{"pattern":":exit$"}}},"ignoreComments":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"wrongIndentation":"Expected indentation of {{expected}} but found {{actual}}."}},"space-unary-ops":{"type":"layout","docs":{"description":"Enforce consistent spacing before or after unary operators","recommended":false,"url":"https://eslint.org/docs/rules/space-unary-ops"},"fixable":"whitespace","schema":[{"type":"object","properties":{"words":{"type":"boolean","default":true},"nonwords":{"type":"boolean","default":false},"overrides":{"type":"object","additionalProperties":{"type":"boolean"}}},"additionalProperties":false}],"messages":{"unexpectedBefore":"Unexpected space before unary operator '{{operator}}'.","unexpectedAfter":"Unexpected space after unary operator '{{operator}}'.","unexpectedAfterWord":"Unexpected space after unary word operator '{{word}}'.","wordOperator":"Unary word operator '{{word}}' must be followed by whitespace.","operator":"Unary operator '{{operator}}' must be followed by whitespace.","beforeUnaryExpressions":"Space is required before unary expressions '{{token}}'."}},"semi":{"type":"layout","docs":{"description":"Require or disallow semicolons instead of ASI","recommended":false,"url":"https://eslint.org/docs/rules/semi"},"fixable":"code","schema":{"anyOf":[{"type":"array","items":[{"enum":["never"]},{"type":"object","properties":{"beforeStatementContinuationChars":{"enum":["always","any","never"]}},"additionalProperties":false}],"minItems":0,"maxItems":2},{"type":"array","items":[{"enum":["always"]},{"type":"object","properties":{"omitLastInOneLineBlock":{"type":"boolean"}},"additionalProperties":false}],"minItems":0,"maxItems":2}]},"messages":{"missingSemi":"Missing semicolon.","extraSemi":"Extra semicolon."}},"consistent-return":{"type":"suggestion","docs":{"description":"Require `return` statements to either always or never specify values","recommended":false,"url":"https://eslint.org/docs/rules/consistent-return"},"schema":[{"type":"object","properties":{"treatUndefinedAsUnspecified":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"missingReturn":"Expected to return a value at the end of {{name}}.","missingReturnValue":"{{name}} expected a return value.","unexpectedReturnValue":"{{name}} expected no return value."}}}}} +``` + +### json + +Outputs JSON-serialized results. The `json` formatter is useful when you want to programmatically work with the CLI's linting results. + +Alternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint. + +Example output: + +```text +[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}] +``` + +### junit + +Outputs results to format compatible with the [JUnit Jenkins plugin](https://plugins.jenkins.io/junit/). + +Example output: + +```text + + + + + + + + + + + + + + + +``` + +### stylish + +Human-readable output format. This is the default formatter. + +Example output: + +```text + +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js + 1:10 error 'addOne' is defined but never used no-unused-vars + 2:9 error Use the isNaN function to compare with NaN use-isnan + 3:16 error Unexpected space before unary operator '++' space-unary-ops + 3:20 warning Missing semicolon semi + 4:12 warning Unnecessary 'else' after 'return' no-else-return + 5:1 warning Expected indentation of 8 spaces but found 6 indent + 5:7 error Function 'addOne' expected a return value consistent-return + 5:13 warning Missing semicolon semi + 7:2 error Unnecessary semicolon no-extra-semi + +✖ 9 problems (5 errors, 4 warnings) + 2 errors and 4 warnings potentially fixable with the `--fix` option. + +``` + +### tap + +Outputs results to the [Test Anything Protocol (TAP)](https://testanything.org/) specification format. + +Example output: + +```text +TAP version 13 +1..1 +not ok 1 - /var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js + --- + message: '''addOne'' is defined but never used.' + severity: error + data: + line: 1 + column: 10 + ruleId: no-unused-vars + messages: + - message: Use the isNaN function to compare with NaN. + severity: error + data: + line: 2 + column: 9 + ruleId: use-isnan + - message: Unexpected space before unary operator '++'. + severity: error + data: + line: 3 + column: 16 + ruleId: space-unary-ops + - message: Missing semicolon. + severity: warning + data: + line: 3 + column: 20 + ruleId: semi + - message: Unnecessary 'else' after 'return'. + severity: warning + data: + line: 4 + column: 12 + ruleId: no-else-return + - message: Expected indentation of 8 spaces but found 6. + severity: warning + data: + line: 5 + column: 1 + ruleId: indent + - message: Function 'addOne' expected a return value. + severity: error + data: + line: 5 + column: 7 + ruleId: consistent-return + - message: Missing semicolon. + severity: warning + data: + line: 5 + column: 13 + ruleId: semi + - message: Unnecessary semicolon. + severity: error + data: + line: 7 + column: 2 + ruleId: no-extra-semi + ... + +``` + +### unix + +Outputs results to a format similar to many commands in UNIX-like systems. Parsable with tools such as [grep](https://www.gnu.org/software/grep/manual/grep.html), [sed](https://www.gnu.org/software/sed/manual/sed.html), and [awk](https://www.gnu.org/software/gawk/manual/gawk.html). + +Example output: + +```text +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:1:10: 'addOne' is defined but never used. [Error/no-unused-vars] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:2:9: Use the isNaN function to compare with NaN. [Error/use-isnan] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:3:16: Unexpected space before unary operator '++'. [Error/space-unary-ops] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:3:20: Missing semicolon. [Warning/semi] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:4:12: Unnecessary 'else' after 'return'. [Warning/no-else-return] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:1: Expected indentation of 8 spaces but found 6. [Warning/indent] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:7: Function 'addOne' expected a return value. [Error/consistent-return] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:13: Missing semicolon. [Warning/semi] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:7:2: Unnecessary semicolon. [Error/no-extra-semi] + +9 problems +``` + +### visualstudio + +Outputs results to format compatible with the integrated terminal of the [Visual Studio](https://visualstudio.microsoft.com/) IDE. When using Visual Studio, you can click on the linting results in the integrated terminal to go to the issue in the source code. + +Example output: + +```text +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(1,10): error no-unused-vars : 'addOne' is defined but never used. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(2,9): error use-isnan : Use the isNaN function to compare with NaN. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(3,16): error space-unary-ops : Unexpected space before unary operator '++'. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(3,20): warning semi : Missing semicolon. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(4,12): warning no-else-return : Unnecessary 'else' after 'return'. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,1): warning indent : Expected indentation of 8 spaces but found 6. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,7): error consistent-return : Function 'addOne' expected a return value. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,13): warning semi : Missing semicolon. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(7,2): error no-extra-semi : Unnecessary semicolon. + +9 problems +``` diff --git a/docs/src/use/getting-started.md b/docs/src/use/getting-started.md new file mode 100644 index 00000000000..623011aaf48 --- /dev/null +++ b/docs/src/use/getting-started.md @@ -0,0 +1,152 @@ +--- +title: Getting Started with ESLint +eleventyNavigation: + key: getting started + parent: use eslint + title: Getting Started + order: 1 + +--- + +ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs. + +ESLint is completely pluggable. Every single rule is a plugin and you can add more at runtime. You can also add community plugins, configurations, and parsers to extend the functionality of ESLint. + +## Prerequisites + +To use ESLint, you must have [Node.js](https://nodejs.org/en/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) installed and built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) + +## Quick start + +You can install and configure ESLint using this command: + +```shell +npm init @eslint/config +``` + +If you want to use a specific shareable config that is hosted on npm, you can use the `--config` option and specify the package name: + +```shell +# use `eslint-config-semistandard` shared config + +# npm 7+ +npm init @eslint/config -- --config semistandard + +# or (`eslint-config` prefix is optional) +npm init @eslint/config -- --config eslint-config-semistandard + +# ⚠️ npm 6.x no extra double-dash: +npm init @eslint/config --config semistandard + +``` + +The `--config` flag also supports passing in arrays: + +```shell +npm init @eslint/config -- --config semistandard,standard +# or +npm init @eslint/config -- --config semistandard --config standard +``` + +**Note:** `npm init @eslint/config` assumes you have a `package.json` file already. If you don't, make sure to run `npm init` or `yarn init` beforehand. + +After that, you can run ESLint on any file or directory like this: + +```shell +npx eslint yourfile.js + +# or + +yarn run eslint yourfile.js +``` + +## Configuration + +**Note:** If you are coming from a version before 1.0.0 please see the [migration guide](migrating-to-1.0.0). + +After running `npm init @eslint/config`, you'll have an `.eslintrc.{js,yml,json}` file in your directory. In it, you'll see some rules configured like this: + +```json +{ + "rules": { + "semi": ["error", "always"], + "quotes": ["error", "double"] + } +} +``` + +The names `"semi"` and `"quotes"` are the names of [rules](../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](configure/)). + +Your `.eslintrc.{js,yml,json}` configuration file will also include the line: + +```json +{ + "extends": "eslint:recommended" +} +``` + +Because of this line, all of the rules marked "(recommended)" on the [rules page](../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. + +## Global Install + +It is also possible to install ESLint globally, rather than locally, using `npm install eslint --global`. However, this is not recommended, and any plugins or shareable configs that you use must still be installed locally if you install ESLint globally. + +## Manual Set Up + +You can also manually set up ESLint in your project. + +Before you begin, you must already have a `package.json` file. If you don't, make sure to run `npm init` or `yarn init` to create the file beforehand. + +1. Install the ESLint package in your project: + + ```shell + npm install --save-dev eslint + ``` + +1. Add an `.eslintrc` file in one of the [supported configuration file formats](./configure/configuration-files#configuration-file-formats). + + ```shell + # Create JavaScript configuration file + touch .eslintrc.js + ``` + +1. Add configuration to the `.eslintrc` file. Refer to the [Configure ESLint documentation](configure/) to learn how to add rules, environments, custom configurations, plugins, and more. + + ```js + // .eslintrc.js example + module.exports = { + "env": { + "browser": true, + "es2021": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + } + ``` + +1. Lint code using the ESLint CLI: + + ```shell + npx eslint project-dir/ file1.js + ``` + + For more information on the available CLI options, refer to [Command Line Interface](./command-line-interface). + +--- + +## Next Steps + +* Learn about [advanced configuration](configure/) of ESLint. +* Get familiar with the [command line options](command-line-interface). +* Explore [ESLint integrations](integrations) into other tools like editors, build systems, and more. +* Can't find just the right rule? Make your own [custom rule](../extend/custom-rules). +* Make ESLint even better by [contributing](../contribute/). diff --git a/docs/src/user-guide/index.md b/docs/src/use/index.md similarity index 69% rename from docs/src/user-guide/index.md rename to docs/src/use/index.md index 558195fe9c5..6e68a087f38 100644 --- a/docs/src/user-guide/index.md +++ b/docs/src/use/index.md @@ -1,34 +1,40 @@ --- -title: User Guide -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/index.md +title: Use ESLint in Your Project eleventyNavigation: - key: user guide - title: User Guide + key: use eslint + title: Use ESLint in Your Project order: 1 --- -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/). +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 [Extend ESLint documentation](../extend/). ## [Getting Started](getting-started) Want to skip ahead and just start using ESLint? This section gives a high-level overview of installation, setup, and configuration options. -## [Rules](../rules/) +## [Core Concepts](core-concepts) -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. +Understand the main components of ESLint and how to use them in your project. -## [Configuring](configuring/) +## [Configure ESLint](configure/) 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 Reference](command-line-interface) There are a lot of command line flags for ESLint and this section explains what they do. +## [Rules Reference](../rules/) + +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. + +## [Formatters Reference](formatters) + +Control the appearance of the linting results with formatters. View all built-in formatters on this page. + ## [Integrations](integrations) -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). +Wondering if ESLint will work with your favorite editor or build system? This page has a list of integrations (submitted by their authors). ## [Rule Deprecation](rule-deprecation) @@ -45,4 +51,4 @@ If you were using a prior version of ESLint, you can get help with the transitio * [migrating-to-5.0.0](migrating-to-5.0.0) * [migrating-to-6.0.0](migrating-to-6.0.0) * [migrating-to-7.0.0](migrating-to-7.0.0) -* [migrating-to-8.0.0](migrating-to-8.0.0) +* [migrate-to-8.0.0](migrate-to-8.0.0) diff --git a/docs/src/user-guide/integrations.md b/docs/src/use/integrations.md similarity index 86% rename from docs/src/user-guide/integrations.md rename to docs/src/use/integrations.md index ff9aa0a3450..892a5080e35 100644 --- a/docs/src/user-guide/integrations.md +++ b/docs/src/use/integrations.md @@ -1,15 +1,17 @@ --- title: Integrations -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/integrations.md eleventyNavigation: key: integrations - parent: user guide + parent: use eslint title: Integrations - order: 6 + order: 7 --- +This page contains community projects that have integrated ESLint. The projects on this page are not maintained by the ESLint team. + +If you would like to recommend an integration to be added to this page, [submit a pull request](../contribute/pull-requests). + ## Editors * Sublime Text 3: @@ -28,6 +30,7 @@ eleventyNavigation: * [linter-eslint](https://atom.io/packages/linter-eslint) * [fast-eslint-8](https://atom.io/packages/fast-eslint-8) * IntelliJ IDEA, WebStorm, PhpStorm, PyCharm, RubyMine, and other JetBrains IDEs: [How to use ESLint](https://www.jetbrains.com/help/webstorm/eslint.html) +* Visual Studio: [Linting JavaScript in VS](https://learn.microsoft.com/en-us/visualstudio/javascript/linting-javascript?view=vs-2022) * Visual Studio Code: [ESLint Extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) * Brackets: Included and [Brackets ESLint](https://github.com/brackets-userland/brackets-eslint) @@ -62,15 +65,6 @@ eleventyNavigation: * Mocha.js: [mocha-eslint](https://www.npmjs.com/package/mocha-eslint) -## External ESLint rules - -* [AngularJS](https://github.com/Gillespie59/eslint-plugin-angular) -* [BackboneJS](https://github.com/ilyavolodin/eslint-plugin-backbone) -* [Jasmine](https://github.com/tlvince/eslint-plugin-jasmine) -* [React](https://github.com/yannickcr/eslint-plugin-react) - -… and many more published on npm with the [eslintplugin](https://www.npmjs.com/browse/keyword/eslintplugin) keyword. - ## Other Integration Lists You can find a curated list of other popular integrations for ESLint in the [awesome-eslint](https://github.com/dustinspecker/awesome-eslint) GitHub repository. diff --git a/docs/src/user-guide/migrating-to-8.0.0.md b/docs/src/use/migrate-to-8.0.0.md similarity index 91% rename from docs/src/user-guide/migrating-to-8.0.0.md rename to docs/src/use/migrate-to-8.0.0.md index d61955f771c..71d92c52087 100644 --- a/docs/src/user-guide/migrating-to-8.0.0.md +++ b/docs/src/use/migrate-to-8.0.0.md @@ -1,12 +1,10 @@ --- -title: Migrating to v8.0.0 -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/migrating-to-8.0.0.md +title: Migrate to v8.0.0 eleventyNavigation: - key: migrating to v8 - parent: user guide - title: Migrating to v8.x - order: 6 + key: migrate to v8 + parent: use eslint + title: Migrate to v8.x + order: 7 --- @@ -111,10 +109,10 @@ In ESLint v7.0.0, using both `--report-unused-disable-directives` and `--fix` on Four new rules have been enabled in the `eslint:recommended` preset. -* [`no-loss-of-precision`](https://eslint.org/docs/rules/no-loss-of-precision) -* [`no-nonoctal-decimal-escape`](https://eslint.org/docs/rules/no-nonoctal-decimal-escape) -* [`no-unsafe-optional-chaining`](https://eslint.org/docs/rules/no-unsafe-optional-chaining) -* [`no-useless-backreference`](https://eslint.org/docs/rules/no-useless-backreference) +* [`no-loss-of-precision`](../rules/no-loss-of-precision) +* [`no-nonoctal-decimal-escape`](../rules/no-nonoctal-decimal-escape) +* [`no-unsafe-optional-chaining`](../rules/no-unsafe-optional-chaining) +* [`no-useless-backreference`](../rules/no-useless-backreference) **To address:** Fix errors or disable these rules. @@ -122,7 +120,7 @@ Four new rules have been enabled in the `eslint:recommended` preset. ## Rules require `meta.hasSuggestions` to provide suggestions -In ESLint v7.0.0, rules that [provided suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) did not need to let ESLint know. In v8.0.0, rules providing suggestions need to set their `meta.hasSuggestions` to `true`. This informs ESLint that the rule intends to provide suggestions. Without this property, any attempt to provide a suggestion will result in an error. +In ESLint v7.0.0, rules that [provided suggestions](../extend/custom-rules#providing-suggestions) did not need to let ESLint know. In v8.0.0, rules providing suggestions need to set their `meta.hasSuggestions` to `true`. This informs ESLint that the rule intends to provide suggestions. Without this property, any attempt to provide a suggestion will result in an error. **To address:** If your rule provides suggestions, add `meta.hasSuggestions` to the object, such as: @@ -170,7 +168,7 @@ The [eslint-plugin/require-meta-fixable](https://github.com/not-an-aardvark/esli The [eslint-plugin/prefer-object-rule](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-object-rule.md) rule can automatically fix and enforce that your rules are written with the object format instead of the deprecated function format. -See the [rule documentation](https://eslint.org/docs/developer-guide/working-with-rules) for more information on writing rules. +See the [rule documentation](../extend/custom-rules) for more information on writing rules. **Related issue(s):** [#13349](https://github.com/eslint/eslint/issues/13349) @@ -180,7 +178,7 @@ Back in ESLint v4.0.0, we deprecated `SourceCode#getComments()`, but we neglecte The `SourceCode#getComments()` method will be removed in v9.0.0. -**To address:** If your rule uses `SourceCode#getComments()`, please use [`SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, or `SourceCode#getCommentsInside()`](https://eslint.org/docs/developer-guide/working-with-rules#sourcecodegetcommentsbefore-sourcecodegetcommentsafter-and-sourcecodegetcommentsinside). +**To address:** If your rule uses `SourceCode#getComments()`, please use [`SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, or `SourceCode#getCommentsInside()`](../extend/custom-rules#sourcecodegetcommentsbefore-sourcecodegetcommentsafter-and-sourcecodegetcommentsinside). **Related issue(s):** [#14744](https://github.com/eslint/eslint/issues/14744) @@ -235,7 +233,7 @@ In ESLint v8.0.0 (via Acorn v8.0.0), the key and value are now separate objects ## The `CLIEngine` class has been removed -The `CLIEngine` class has been removed and replaced by the [`ESLint` class](https://eslint.org/docs/developer-guide/nodejs-api#eslint-class). +The `CLIEngine` class has been removed and replaced by the [`ESLint` class](../integrate/nodejs-api#eslint-class). **To address:** Update your code to use the new `ESLint` class if you are currently using `CLIEngine`. The following table maps the existing `CLIEngine` methods to their `ESLint` counterparts: diff --git a/docs/src/user-guide/migrating-from-jscs.md b/docs/src/use/migrating-from-jscs.md similarity index 92% rename from docs/src/user-guide/migrating-from-jscs.md rename to docs/src/use/migrating-from-jscs.md index c25af456656..54b19683fed 100644 --- a/docs/src/user-guide/migrating-from-jscs.md +++ b/docs/src/use/migrating-from-jscs.md @@ -1,7 +1,5 @@ --- title: Migrating from JSCS -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/migrating-from-jscs.md --- @@ -12,7 +10,7 @@ In April 2016, we [announced](https://eslint.org/blog/2016/04/welcoming-jscs-to- 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](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. +* **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](../extend/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](#converting-presets) below). Additionally, the `preset` option in a configuration file is the equivalent of the ESLint `extends` option. ## Convert Configuration Files Using Polyjuice @@ -32,7 +30,7 @@ To convert your configuration file, pass in the location of your `.jscs.json` fi polyjuice --jscs .jscsrc.json > .eslintrc.json ``` -This creates a `.eslintrc.json` with the equivalent rules from `.jscsrc.json`. +This creates an `.eslintrc.json` with the equivalent rules from `.jscsrc.json`. If you have multiple `.jscsrc.json` files, you can pass them all and Polyjuice will combine them into one `.eslintrc.json` file: @@ -40,7 +38,7 @@ If you have multiple `.jscsrc.json` files, you can pass them all and Polyjuice w polyjuice --jscs .jscsrc.json ./foo/.jscsrc.json > .eslintrc.json ``` -**Note:** Polyjuice does a good job of creating a reasonable ESLint configuration from your JSCS configuration, but it may not be 100%. You may still see different warnings than you saw with JSCS, and so you may need to further modify your configuration after using Polyjuice. This is especially true if you're using inline comments to enable/disable certain rules in JSCS (you'll need to manually convert those to use ESLint-style comments instead, see "Disabling Rules Inline" later in this page). +**Note:** Polyjuice does a good job of creating a reasonable ESLint configuration from your JSCS configuration, but it may not be 100%. You may still see different warnings than you saw with JSCS, and so you may need to further modify your configuration after using Polyjuice. This is especially true if you're using inline comments to enable/disable certain rules in JSCS (you'll need to manually convert those to use ESLint-style comments instead, [see "Disabling Rules Inline"](#disabling-rules-inline) later in this page). ### Creating a New Configuration From Scratch diff --git a/docs/src/user-guide/migrating-to-1.0.0.md b/docs/src/use/migrating-to-1.0.0.md similarity index 53% rename from docs/src/user-guide/migrating-to-1.0.0.md rename to docs/src/use/migrating-to-1.0.0.md index f71b428ed7d..1ed8496359e 100644 --- a/docs/src/user-guide/migrating-to-1.0.0.md +++ b/docs/src/use/migrating-to-1.0.0.md @@ -1,7 +1,5 @@ --- title: Migrating to v1.0.0 -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/migrating-to-1.0.0.md --- @@ -25,62 +23,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](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) +* [no-alert](../rules/no-alert) +* [no-array-constructor](../rules/no-array-constructor) +* [no-caller](../rules/no-caller) +* [no-catch-shadow](../rules/no-catch-shadow) +* [no-empty-label](../rules/no-empty-label) +* [no-eval](../rules/no-eval) +* [no-extend-native](../rules/no-extend-native) +* [no-extra-bind](../rules/no-extra-bind) +* [no-extra-strict](../rules/no-extra-strict) +* [no-implied-eval](../rules/no-implied-eval) +* [no-iterator](../rules/no-iterator) +* [no-label-var](../rules/no-label-var) +* [no-labels](../rules/no-labels) +* [no-lone-blocks](../rules/no-lone-blocks) +* [no-loop-func](../rules/no-loop-func) +* [no-multi-spaces](../rules/no-multi-spaces) +* [no-multi-str](../rules/no-multi-str) +* [no-native-reassign](../rules/no-native-reassign) +* [no-new](../rules/no-new) +* [no-new-func](../rules/no-new-func) +* [no-new-object](../rules/no-new-object) +* [no-new-wrappers](../rules/no-new-wrappers) +* [no-octal-escape](../rules/no-octal-escape) +* [no-process-exit](../rules/no-process-exit) +* [no-proto](../rules/no-proto) +* [no-return-assign](../rules/no-return-assign) +* [no-script-url](../rules/no-script-url) +* [no-sequences](../rules/no-sequences) +* [no-shadow](../rules/no-shadow) +* [no-shadow-restricted-names](../rules/no-shadow-restricted-names) +* [no-spaced-func](../rules/no-spaced-func) +* [no-trailing-spaces](../rules/no-trailing-spaces) +* [no-undef-init](../rules/no-undef-init) +* [no-underscore-dangle](../rules/no-underscore-dangle) +* [no-unused-expressions](../rules/no-unused-expressions) +* [no-use-before-define](../rules/no-use-before-define) +* [no-with](../rules/no-with) +* [no-wrap-func](../rules/no-wrap-func) +* [camelcase](../rules/camelcase) +* [comma-spacing](../rules/comma-spacing) +* [consistent-return](../rules/consistent-return) +* [curly](../rules/curly) +* [dot-notation](../rules/dot-notation) +* [eol-last](../rules/eol-last) +* [eqeqeq](../rules/eqeqeq) +* [key-spacing](../rules/key-spacing) +* [new-cap](../rules/new-cap) +* [new-parens](../rules/new-parens) +* [quotes](../rules/quotes) +* [semi](../rules/semi) +* [semi-spacing](../rules/semi-spacing) +* [space-infix-ops](../rules/space-infix-ops) +* [space-return-throw-case](../rules/space-return-throw-case) +* [space-unary-ops](../rules/space-unary-ops) +* [strict](../rules/strict) +* [yoda](../rules/yoda) See also: the [full diff](https://github.com/eslint/eslint/commit/e3e9dbd9876daf4bdeb4e15f8a76a9d5e6e03e39#diff-b01a5cfd9361ca9280a460fd6bb8edbbL1) where the defaults were changed. @@ -153,19 +151,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](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) +* [generator-star](../rules/generator-star) is replaced by [generator-star-spacing](../rules/generator-star-spacing) +* [global-strict](../rules/global-strict) is replaced by [strict](../rules/strict) +* [no-comma-dangle](../rules/no-comma-dangle) is replaced by [comma-dangle](../rules/comma-dangle) +* [no-empty-class](../rules/no-empty-class) is replaced by [no-empty-character-class](../rules/no-empty-character-class) +* [no-extra-strict](../rules/no-extra-strict) is replaced by [strict](../rules/strict) +* [no-reserved-keys](../rules/no-reserved-keys) is replaced by [quote-props](../rules/quote-props) +* [no-space-before-semi](../rules/no-space-before-semi) is replaced by [semi-spacing](../rules/semi-spacing) +* [no-wrap-func](../rules/no-wrap-func) is replaced by [no-extra-parens](../rules/no-extra-parens) +* [space-after-function-name](../rules/space-after-function-name) is replaced by [space-before-function-paren](../rules/space-before-function-paren) +* [space-before-function-parentheses](../rules/space-before-function-parentheses) is replaced by [space-before-function-paren](../rules/space-before-function-paren) +* [space-in-brackets](../rules/space-in-brackets) is replaced by[object-curly-spacing](../rules/object-curly-spacing) and [array-bracket-spacing](../rules/array-bracket-spacing) +* [space-unary-word-ops](../rules/space-unary-word-ops) is replaced by [space-unary-ops](../rules/space-unary-ops) +* [spaced-line-comment](../rules/spaced-line-comment) is replaced by [spaced-comment](../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/src/user-guide/migrating-to-2.0.0.md b/docs/src/use/migrating-to-2.0.0.md similarity index 88% rename from docs/src/user-guide/migrating-to-2.0.0.md rename to docs/src/use/migrating-to-2.0.0.md index aa216aa3f49..753fed743a7 100644 --- a/docs/src/user-guide/migrating-to-2.0.0.md +++ b/docs/src/use/migrating-to-2.0.0.md @@ -1,7 +1,5 @@ --- title: Migrating to v2.0.0 -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/migrating-to-2.0.0.md --- @@ -56,11 +54,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](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). +* [no-arrow-condition](../rules/no-arrow-condition) is replaced by a combination of [no-confusing-arrow](../rules/no-confusing-arrow) and [no-constant-condition](../rules/no-constant-condition). Turn on both of these rules to get the same functionality as `no-arrow-condition`. +* [no-empty-label](../rules/no-empty-label) is replaced by [no-labels](../rules/no-labels) with `{"allowLoop": true, "allowSwitch": true}` option. +* [space-after-keywords](../rules/space-after-keywords) is replaced by [keyword-spacing](../rules/keyword-spacing). +* [space-before-keywords](../rules/space-before-keywords) is replaced by [keyword-spacing](../rules/keyword-spacing). +* [space-return-throw-case](../rules/space-return-throw-case) is replaced by [keyword-spacing](../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. @@ -212,17 +210,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](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) +* [constructor-super](../rules/constructor-super) +* [no-case-declarations](../rules/no-case-declarations) +* [no-class-assign](../rules/no-class-assign) +* [no-const-assign](../rules/no-const-assign) +* [no-dupe-class-members](../rules/no-dupe-class-members) +* [no-empty-pattern](../rules/no-empty-pattern) +* [no-new-symbol](../rules/no-new-symbol) +* [no-self-assign](../rules/no-self-assign) +* [no-this-before-super](../rules/no-this-before-super) +* [no-unexpected-multiline](../rules/no-unexpected-multiline) +* [no-unused-labels](../rules/no-unused-labels) **To address:** If you don't want to be notified by those rules, you can simply disable those rules. diff --git a/docs/src/user-guide/migrating-to-3.0.0.md b/docs/src/use/migrating-to-3.0.0.md similarity index 69% rename from docs/src/user-guide/migrating-to-3.0.0.md rename to docs/src/use/migrating-to-3.0.0.md index 8435e28ebb3..289aa145a61 100644 --- a/docs/src/user-guide/migrating-to-3.0.0.md +++ b/docs/src/use/migrating-to-3.0.0.md @@ -1,7 +1,5 @@ --- title: Migrating to v3.0.0 -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/migrating-to-3.0.0.md --- @@ -17,7 +15,7 @@ With ESLint v3.0.0, we are dropping support for Node.js versions prior to 4. Nod ESLint v3.0.0 now requires that you use a configuration to run. A configuration can be any of the following: -1. A `.eslintrc.js`, `.eslintrc.json`, `.eslintrc.yml`, `.eslintrc.yaml`, or `.eslintrc` file either in your project or home directory. +1. An `.eslintrc.js`, `.eslintrc.json`, `.eslintrc.yml`, `.eslintrc.yaml`, or `.eslintrc` file either in your project or home directory. 2. Configuration options passed on the command line using `--rule` (or to CLIEngine using `rules`). 3. A configuration file passed on the command line using `-c` (or to CLIEngine using `configFile`). 4. A base configuration is provided to CLIEngine using the `baseConfig` option. @@ -40,17 +38,17 @@ To create a new configuration, use `eslint --init`. In 3.0.0, the following rules were added to `"eslint:recommended"`: -* [`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. +* [`no-unsafe-finally`](../rules/no-unsafe-finally) helps catch `finally` clauses that may not behave as you think. +* [`no-native-reassign`](../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`](../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`](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. +* [`comma-dangle`](../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`](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. +* [`complexity`](../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/src/user-guide/migrating-to-4.0.0.md b/docs/src/use/migrating-to-4.0.0.md similarity index 87% rename from docs/src/user-guide/migrating-to-4.0.0.md rename to docs/src/use/migrating-to-4.0.0.md index cef90a3ddcc..81051a71b05 100644 --- a/docs/src/user-guide/migrating-to-4.0.0.md +++ b/docs/src/use/migrating-to-4.0.0.md @@ -1,7 +1,5 @@ --- title: Migrating to v4.0.0 -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/migrating-to-4.0.0.md --- @@ -37,10 +35,10 @@ The lists below are ordered roughly by the number of users each change is expect ## `eslint:recommended` changes -Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config: +Two new rules have been added to the [`eslint:recommended`](configure#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 +* [`no-compare-neg-zero`](../rules/no-compare-neg-zero) disallows comparisons to `-0` +* [`no-useless-escape`](../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: @@ -57,11 +55,11 @@ Two new rules have been added to the [`eslint:recommended`](https://eslint.org/d ## 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. +Previously, the [`indent`](../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 make the upgrade process easier, we've introduced the [`indent-legacy`](../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: @@ -88,13 +86,13 @@ Due to a bug, glob patterns in an `.eslintignore` file were previously resolved ## 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. +By default, the [`padded-blocks`](../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. +By default, the [`space-before-function-paren`](../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: @@ -112,7 +110,7 @@ By default, the [`space-before-function-paren`](/docs/rules/space-before-functio ## 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. +By default, the [`no-multi-spaces`](../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: diff --git a/docs/src/user-guide/migrating-to-5.0.0.md b/docs/src/use/migrating-to-5.0.0.md similarity index 91% rename from docs/src/user-guide/migrating-to-5.0.0.md rename to docs/src/use/migrating-to-5.0.0.md index 0bddbe1dcb1..5eb4ec7ad1a 100644 --- a/docs/src/user-guide/migrating-to-5.0.0.md +++ b/docs/src/use/migrating-to-5.0.0.md @@ -1,7 +1,5 @@ --- title: Migrating to v5.0.0 -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/migrating-to-5.0.0.md --- @@ -52,10 +50,10 @@ As of April 30th, 2018, Node.js 4 will be at EOL and will no longer be receiving ## `eslint:recommended` changes -Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config: +Two new rules have been added to the [`eslint:recommended`](configure/configuration-files#using-eslintrecommended) config: -* [`for-direction`](/docs/rules/for-direction) enforces that a `for` loop update clause moves the counter in the right direction. -* [`getter-return`](/docs/rules/getter-return) enforces that a `return` statement is present in property getters. +* [`for-direction`](../rules/for-direction) enforces that a `for` loop update clause moves the counter in the right direction. +* [`getter-return`](../rules/getter-return) enforces that a `return` statement is present in property getters. **To address:** To mimic the `eslint:recommended` behavior from 4.x, you can disable these rules in a config file: @@ -98,7 +96,7 @@ Note that this also enables parsing for other features from ES2018, such as [asy For compatibility, ESLint v5 will treat `ecmaFeatures: { experimentalObjectRestSpread: true }` as an alias for `ecmaVersion: 2018` when the former is found in a config file. As a result, if you use object rest/spread, your code should still parse successfully with ESLint v5. However, note that this alias will be removed in ESLint v6. -**To address:** If you use the `experimentalObjectRestSpread` option, you should be able to upgrade to ESLint v5 without any changes, but you will encounter a deprecation warning. To avoid the warning, use `ecmaVersion: 2018` in your config file rather than `ecmaFeatures: { experimentalObjectRestSpread: true }`. If you would like to disallow the use of other ES2018 features, consider using rules such as [`no-restricted-syntax`](/docs/rules/no-restricted-syntax). +**To address:** If you use the `experimentalObjectRestSpread` option, you should be able to upgrade to ESLint v5 without any changes, but you will encounter a deprecation warning. To avoid the warning, use `ecmaVersion: 2018` in your config file rather than `ecmaFeatures: { experimentalObjectRestSpread: true }`. If you would like to disallow the use of other ES2018 features, consider using rules such as [`no-restricted-syntax`](../rules/no-restricted-syntax). ## Linting nonexistent files from the command line is now a fatal error @@ -115,7 +113,7 @@ ESLint v5 will report a fatal error when either of the following conditions is m * A file provided on the command line does not exist * A glob or folder provided on the command line does not match any lintable files -Note that this also affects the [`CLIEngine.executeOnFiles()`](https://eslint.org/docs/developer-guide/nodejs-api#cliengineexecuteonfiles) API. +Note that this also affects the [`CLIEngine.executeOnFiles()`](../integrate/nodejs-api#cliengineexecuteonfiles) API. **To address:** If you encounter an error about missing files after upgrading to ESLint v5, you may want to double-check that there are no typos in the paths you provide to ESLint. To make the error go away, you can simply remove the given files or globs from the list of arguments provided to ESLint on the command line. @@ -123,8 +121,8 @@ If you use a boilerplate generator that relies on this behavior (e.g. to generat ## The default options for some rules have changed -* The default options for the [`object-curly-newline`](/docs/rules/object-curly-newline) rule have changed from `{ multiline: true }` to `{ consistent: true }`. -* The default options object for the [`no-self-assign`](/docs/rules/no-self-assign) rule has changed from `{ props: false }` to `{ props: true }`. +* The default options for the [`object-curly-newline`](../rules/object-curly-newline) rule have changed from `{ multiline: true }` to `{ consistent: true }`. +* The default options object for the [`no-self-assign`](../rules/no-self-assign) rule has changed from `{ props: false }` to `{ props: true }`. **To address:** To restore the rule behavior from ESLint v4, you can update your config file to include the previous options: @@ -139,7 +137,7 @@ If you use a boilerplate generator that relies on this behavior (e.g. to generat ## Deprecated globals have been removed from the `node`, `browser`, and `jest` environments -Some global variables have been deprecated or removed for code running in Node.js, browsers, and Jest. (For example, browsers used to expose an `SVGAltGlyphElement` global variable to JavaScript code, but this global has been removed from web standards and is no longer present in browsers.) As a result, we have removed these globals from the corresponding `eslint` environments, so use of these globals will trigger an error when using rules such as [`no-undef`](/docs/rules/no-undef). +Some global variables have been deprecated or removed for code running in Node.js, browsers, and Jest. (For example, browsers used to expose an `SVGAltGlyphElement` global variable to JavaScript code, but this global has been removed from web standards and is no longer present in browsers.) As a result, we have removed these globals from the corresponding `eslint` environments, so use of these globals will trigger an error when using rules such as [`no-undef`](../rules/no-undef). **To address:** If you use deprecated globals in the `node`, `browser`, or `jest` environments, you can add a `globals` section to your configuration to re-enable any globals you need. For example: @@ -160,7 +158,7 @@ ESLint v4 had a special behavior when linting files that only contain whitespace ESLint v5 treats whitespace-only files the same way as all other files: it parses them and runs enabled rules on them as appropriate. This could lead to additional linting problems if you use a custom rule that reports errors on empty files. -**To address:** If you have an empty file in your project and you don't want it to be linted, consider adding it to an [`.eslintignore` file](/docs/user-guide/configuring#ignoring-files-and-directories). +**To address:** If you have an empty file in your project and you don't want it to be linted, consider adding it to an [`.eslintignore` file](configure/ignore). If you have a custom rule, you should make sure it handles empty files appropriately. (In most cases, no changes should be necessary.) @@ -223,7 +221,7 @@ Previously, the `context.getScope()` method changed its behavior based on the `p Additionally, `context.getScope()` incorrectly returned the parent scope of the proper scope on `CatchClause` (in ES5), `ForStatement` (in ≧ES2015), `ForInStatement` (in ≧ES2015), `ForOfStatement`, and `WithStatement` nodes. -In ESLint v5, the `context.getScope()` method has the same behavior regardless of `parserOptions.ecmaVersion` and returns the proper scope. See [the documentation](../developer-guide/working-with-rules#contextgetscope) for more details on which scopes are returned. +In ESLint v5, the `context.getScope()` method has the same behavior regardless of `parserOptions.ecmaVersion` and returns the proper scope. See [the documentation](../extend/custom-rules#contextgetscope) for more details on which scopes are returned. **To address:** If you have written a custom rule that uses the `context.getScope()` method in node handlers, you may need to update it to account for the modified scope information. @@ -231,7 +229,7 @@ In ESLint v5, the `context.getScope()` method has the same behavior regardless o Previously, rule context objects had an undocumented `_linter` property, which was used internally within ESLint to process reports from rules. Some rules used this property to achieve functionality that was not intended to be possible for rules. For example, several plugins used the `_linter` property in a rule to monitor reports from other rules, for the purpose of checking for unused `/* eslint-disable */` directive comments. Although this functionality was useful for users, it could also cause stability problems for projects using ESLint. For example, an upgrade to a rule in one plugin could unexpectedly cause a rule in another plugin to start reporting errors. -The `_linter` property has been removed in ESLint v5.0, so it is no longer possible to implement rules with this functionality. However, the [`--report-unused-disable-directives`](/docs/user-guide/command-line-interface#--report-unused-disable-directives) CLI flag can be used to flag unused directive comments. +The `_linter` property has been removed in ESLint v5.0, so it is no longer possible to implement rules with this functionality. However, the [`--report-unused-disable-directives`](command-line-interface#--report-unused-disable-directives) CLI flag can be used to flag unused directive comments. ## `RuleTester` now uses strict equality checks in its assertions @@ -270,6 +268,6 @@ In ESLint v5, an unsuccessful linting run due to a fatal error will result in an ## The `eslint.linter` property is now non-enumerable -When using ESLint's Node.js API, the [`linter`](/docs/developer-guide/nodejs-api#linter-1) property is now non-enumerable. Note that the `linter` property was deprecated in ESLint v4 in favor of the [`Linter`](/docs/developer-guide/nodejs-api#linter) property. +When using ESLint's Node.js API, the [`linter`](../integrate/nodejs-api#linter-1) property is now non-enumerable. Note that the `linter` property was deprecated in ESLint v4 in favor of the [`Linter`](../integrate/nodejs-api#linter) property. **To address:** If you rely on enumerating all the properties of the `eslint` object, use something like `Object.getOwnPropertyNames` to ensure that non-enumerable keys are captured. diff --git a/docs/src/user-guide/migrating-to-6.0.0.md b/docs/src/use/migrating-to-6.0.0.md similarity index 83% rename from docs/src/user-guide/migrating-to-6.0.0.md rename to docs/src/use/migrating-to-6.0.0.md index 14cc7d868cf..0d2595ee062 100644 --- a/docs/src/user-guide/migrating-to-6.0.0.md +++ b/docs/src/use/migrating-to-6.0.0.md @@ -1,7 +1,5 @@ --- title: Migrating to v6.0.0 -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/migrating-to-6.0.0.md --- @@ -53,19 +51,19 @@ As of April 2019, Node.js 6 will be at EOL and will no longer be receiving secur ## `eslint:recommended` has been updated -The following rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config: +The following rules have been added to the [`eslint:recommended`](../use/configure#using-eslintrecommended) config: -* [`no-async-promise-executor`](https://eslint.org/docs/rules/no-async-promise-executor) disallows using an `async` function as the argument to the `Promise` constructor, which is usually a bug. -* [`no-misleading-character-class`](https://eslint.org/docs/rules/no-misleading-character-class) reports character classes in regular expressions that might not behave as expected. -* [`no-prototype-builtins`](https://eslint.org/docs/rules/no-prototype-builtins) reports method calls like `foo.hasOwnProperty("bar")` (which are a frequent source of bugs), and suggests that they be replaced with `Object.prototype.hasOwnProperty.call(foo, "bar")` instead. -* [`no-shadow-restricted-names`](https://eslint.org/docs/rules/no-shadow-restricted-names) disallows shadowing variables like `undefined` (e.g. with code like `let undefined = 5;`), since is likely to confuse readers. -* [`no-useless-catch`](https://eslint.org/docs/rules/no-useless-catch) reports `catch` clauses that are redundant and can be removed from the code without changing its behavior. -* [`no-with`](https://eslint.org/docs/rules/no-with) disallows use of the [`with` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with), which can make code difficult to understand and cause compatibility problems. -* [`require-atomic-updates`](https://eslint.org/docs/rules/require-atomic-updates) reports race condition bugs that can occur when reassigning variables in async functions. +* [`no-async-promise-executor`](../rules/no-async-promise-executor) disallows using an `async` function as the argument to the `Promise` constructor, which is usually a bug. +* [`no-misleading-character-class`](../rules/no-misleading-character-class) reports character classes in regular expressions that might not behave as expected. +* [`no-prototype-builtins`](../rules/no-prototype-builtins) reports method calls like `foo.hasOwnProperty("bar")` (which are a frequent source of bugs), and suggests that they be replaced with `Object.prototype.hasOwnProperty.call(foo, "bar")` instead. +* [`no-shadow-restricted-names`](../rules/no-shadow-restricted-names) disallows shadowing variables like `undefined` (e.g. with code like `let undefined = 5;`), since is likely to confuse readers. +* [`no-useless-catch`](../rules/no-useless-catch) reports `catch` clauses that are redundant and can be removed from the code without changing its behavior. +* [`no-with`](../rules/no-with) disallows use of the [`with` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with), which can make code difficult to understand and cause compatibility problems. +* [`require-atomic-updates`](../rules/require-atomic-updates) reports race condition bugs that can occur when reassigning variables in async functions. Additionally, the following rule has been *removed* from `eslint:recommended`: -* [`no-console`](https://eslint.org/docs/rules/no-console) disallows calling functions like `console.log`. While this rule is useful in many cases (e.g. to avoid inadvertently leaving debugging statements in production code), it is not as broadly applicable as the other rules in `eslint:recommended`, and it was a source of false positives in cases where `console.log` is acceptable (e.g. in CLI applications). +* [`no-console`](../rules/no-console) disallows calling functions like `console.log`. While this rule is useful in many cases (e.g. to avoid inadvertently leaving debugging statements in production code), it is not as broadly applicable as the other rules in `eslint:recommended`, and it was a source of false positives in cases where `console.log` is acceptable (e.g. in CLI applications). Finally, in ESLint v5 `eslint:recommended` would explicitly disable all core rules that were not considered "recommended". This could cause confusing behavior if `eslint:recommended` was loaded after another config, since `eslint:recommended` would have the effect of turning off some rules. In ESLint v6, `eslint:recommended` has no effect on non-recommended rules. @@ -135,7 +133,7 @@ config | ESLint v5 | ESLint v6 ## The `no-redeclare` rule is now more strict by default -The default options for the [`no-redeclare`](https://eslint.org/docs/rules/no-redeclare) rule have changed from `{ builtinGlobals: false }` to `{ builtinGlobals: true }`. Additionally, the `no-redeclare` rule will now report an error for globals enabled by comments like `/* global foo */` if those globals were already enabled through configuration anyway. +The default options for the [`no-redeclare`](../rules/no-redeclare) rule have changed from `{ builtinGlobals: false }` to `{ builtinGlobals: true }`. Additionally, the `no-redeclare` rule will now report an error for globals enabled by comments like `/* global foo */` if those globals were already enabled through configuration anyway. **To address:** @@ -155,7 +153,7 @@ Additionally, if you see new errors for `global` comments in your code, you shou ## The `comma-dangle` rule is now more strict by default -Previously, the [`comma-dangle`](https://eslint.org/docs/rules/comma-dangle) rule would ignore trailing function arguments and parameters, unless explicitly configured to check for function commas. In ESLint v6, function commas are treated the same way as other types of trailing commas. +Previously, the [`comma-dangle`](../rules/comma-dangle) rule would ignore trailing function arguments and parameters, unless explicitly configured to check for function commas. In ESLint v6, function commas are treated the same way as other types of trailing commas. **To address:** You can restore the previous default behavior of the rule with: @@ -179,7 +177,7 @@ To restore the previous behavior of a string option like `"always-multiline"`, r ## The `no-confusing-arrow` rule is now more lenient by default -The default options for the [`no-confusing-arrow`](https://eslint.org/docs/rules/no-confusing-arrow) rule have changed from `{ allowParens: false }` to `{ allowParens: true }`. +The default options for the [`no-confusing-arrow`](../rules/no-confusing-arrow) rule have changed from `{ allowParens: false }` to `{ allowParens: true }`. **To address:** You can restore the previous default behavior of the rule with: @@ -197,7 +195,7 @@ The default options for the [`no-confusing-arrow`](https://eslint.org/docs/rules Due to a bug, the glob patterns in a `files` list in an `overrides` section of a config file would never match dotfiles, making it impossible to have overrides apply to files starting with a dot. This bug has been fixed in ESLint v6. -**To address:** If you don't want dotfiles to be matched by an override, consider adding something like `excludedFiles: [".*"]` to that `overrides` section. See the [documentation](https://eslint.org/docs/user-guide/configuring#configuration-based-on-glob-patterns) for more details. +**To address:** If you don't want dotfiles to be matched by an override, consider adding something like `excludedFiles: [".*"]` to that `overrides` section. See the [documentation](../use/configure#configuration-based-on-glob-patterns) for more details. **Related issue(s):** [eslint/eslint#11201](https://github.com/eslint/eslint/issues/11201) @@ -284,7 +282,7 @@ If you're not sure which config file needs to be updated, it may be useful to ru ## User-provided regular expressions in rule options are parsed with the unicode flag -Rules like [`max-len`](/docs/rules/max-len) accept a string option which is interpreted as a regular expression. In ESLint v6.0.0, these regular expressions are interpreted with the [unicode flag](https://mathiasbynens.be/notes/es6-unicode-regex), which should exhibit more reasonable behavior when matching characters like astral symbols. Unicode regexes also validate escape sequences more strictly than non-unicode regexes. +Rules like [`max-len`](../rules/max-len) accept a string option which is interpreted as a regular expression. In ESLint v6.0.0, these regular expressions are interpreted with the [unicode flag](https://mathiasbynens.be/notes/es6-unicode-regex), which should exhibit more reasonable behavior when matching characters like astral symbols. Unicode regexes also validate escape sequences more strictly than non-unicode regexes. **To address:** If you get rule option validation errors after upgrading, ensure that any regular expressions in your rule options have no invalid escape sequences. @@ -330,6 +328,6 @@ Previously, when linting code with a parser that had not been previously defined In ESLint v6, `Linter` will no longer perform any filesystem operations, including loading parsers. -**To address:** If you're using `Linter` with a custom parser, use [`Linter#defineParser`](https://eslint.org/docs/developer-guide/nodejs-api#linterdefineparser) to explicitly define the parser before linting any code. +**To address:** If you're using `Linter` with a custom parser, use [`Linter#defineParser`](../integrate/nodejs-api#linterdefineparser) to explicitly define the parser before linting any code. **Related issue(s):** [eslint/rfcs#7](https://github.com/eslint/rfcs/pull/7) diff --git a/docs/src/user-guide/migrating-to-7.0.0.md b/docs/src/use/migrating-to-7.0.0.md similarity index 77% rename from docs/src/user-guide/migrating-to-7.0.0.md rename to docs/src/use/migrating-to-7.0.0.md index b04b143f231..898bcad11a3 100644 --- a/docs/src/user-guide/migrating-to-7.0.0.md +++ b/docs/src/use/migrating-to-7.0.0.md @@ -1,7 +1,5 @@ --- title: Migrating to v7.0.0 -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/migrating-to-7.0.0.md --- @@ -110,7 +108,7 @@ Personal config files have been deprecated since [v6.7.0](https://eslint.org/blo 1. When a project does not have a configuration file present and ESLint loads configuration from `~/.eslintrc.*`. 1. When a project has a configuration file and ESLint ignored a `~/.eslintrc.*` configuration file. This occurs when the `$HOME` directory is an ancestor directory of the project and the project's configuration files doesn't contain `root:true`. -**To address:** Remove `~/.eslintrc.*` configuration files and add a `.eslintrc.*` configuration file to your project. Alternatively, use the `--config` option to use shared config files. +**To address:** Remove `~/.eslintrc.*` configuration files and add an `.eslintrc.*` configuration file to your project. Alternatively, use the `--config` option to use shared config files. **Related issue(s):** [RFC32](https://github.com/eslint/rfcs/tree/master/designs/2019-deprecating-personal-config/README.md), [#12678](https://github.com/eslint/eslint/pull/12678) @@ -149,20 +147,20 @@ To allow for the colocation of comments that provide context with the directive, The ten Node.js/CommonJS rules in core have been deprecated and moved to the [eslint-plugin-node](https://github.com/mysticatea/eslint-plugin-node) plugin. -**To address:** As per [our deprecation policy](https://eslint.org/docs/user-guide/rule-deprecation), the deprecated rules will remain in core for the foreseeable future and are still available for use. However, we will no longer be updating or fixing any bugs in those rules. To use a supported version of the rules, we recommend using the corresponding rules in the plugin instead. +**To address:** As per [our deprecation policy](../use/rule-deprecation), the deprecated rules will remain in core for the foreseeable future and are still available for use. However, we will no longer be updating or fixing any bugs in those rules. To use a supported version of the rules, we recommend using the corresponding rules in the plugin instead. | Deprecated Rules | Replacement | | :--------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | -| [callback-return](https://eslint.org/docs/rules/callback-return) | [node/callback-return](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/callback-return.md) | -| [global-require](https://eslint.org/docs/rules/global-require) | [node/global-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/global-require.md) | -| [handle-callback-err](https://eslint.org/docs/rules/handle-callback-err) | [node/handle-callback-err](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/handle-callback-err.md) | -| [no-mixed-requires](https://eslint.org/docs/rules/no-mixed-requires) | [node/no-mixed-requires](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-mixed-requires.md) | -| [no-new-require](https://eslint.org/docs/rules/no-new-require) | [node/no-new-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-new-require.md) | -| [no-path-concat](https://eslint.org/docs/rules/no-path-concat) | [node/no-path-concat](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-path-concat.md) | -| [no-process-env](https://eslint.org/docs/rules/no-process-env) | [node/no-process-env](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-process-env.md) | -| [no-process-exit](https://eslint.org/docs/rules/no-process-exit) | [node/no-process-exit](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-process-exit.md) | -| [no-restricted-modules](https://eslint.org/docs/rules/no-restricted-modules) | [node/no-restricted-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-restricted-require.md) | -| [no-sync](https://eslint.org/docs/rules/no-sync) | [node/no-sync](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-sync.md) | +| [callback-return](../rules/callback-return) | [node/callback-return](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/callback-return.md) | +| [global-require](../rules/global-require) | [node/global-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/global-require.md) | +| [handle-callback-err](../rules/handle-callback-err) | [node/handle-callback-err](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/handle-callback-err.md) | +| [no-mixed-requires](../rules/no-mixed-requires) | [node/no-mixed-requires](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-mixed-requires.md) | +| [no-new-require](../rules/no-new-require) | [node/no-new-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-new-require.md) | +| [no-path-concat](../rules/no-path-concat) | [node/no-path-concat](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-path-concat.md) | +| [no-process-env](../rules/no-process-env) | [node/no-process-env](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-process-env.md) | +| [no-process-exit](../rules/no-process-exit) | [node/no-process-exit](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-process-exit.md) | +| [no-restricted-modules](../rules/no-restricted-modules) | [node/no-restricted-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-restricted-require.md) | +| [no-sync](../rules/no-sync) | [node/no-sync](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-sync.md) | **Related issue(s):** [#12898](https://github.com/eslint/eslint/pull/12898) @@ -170,16 +168,16 @@ The ten Node.js/CommonJS rules in core have been deprecated and moved to the [es Several rules have been enhanced and now report additional errors: -* [accessor-pairs](https://eslint.org/docs/rules/accessor-pairs) rule now recognizes class members by default. -* [array-callback-return](https://eslint.org/docs/rules/array-callback-return) rule now recognizes `flatMap` method. -* [computed-property-spacing](https://eslint.org/docs/rules/computed-property-spacing) rule now recognizes class members by default. -* [func-names](https://eslint.org/docs/rules/func-names) rule now recognizes function declarations in default exports. -* [no-extra-parens](https://eslint.org/docs/rules/no-extra-parens) rule now recognizes parentheses in assignment targets. -* [no-dupe-class-members](https://eslint.org/docs/rules/no-dupe-class-members) rule now recognizes computed keys for static class members. -* [no-magic-numbers](https://eslint.org/docs/rules/no-magic-numbers) rule now recognizes bigint literals. -* [radix](https://eslint.org/docs/rules/radix) rule now recognizes invalid numbers for the second parameter of `parseInt()`. -* [use-isnan](https://eslint.org/docs/rules/use-isnan) rule now recognizes class members by default. -* [yoda](https://eslint.org/docs/rules/yoda) rule now recognizes bigint literals. +* [accessor-pairs](../rules/accessor-pairs) rule now recognizes class members by default. +* [array-callback-return](../rules/array-callback-return) rule now recognizes `flatMap` method. +* [computed-property-spacing](../rules/computed-property-spacing) rule now recognizes class members by default. +* [func-names](../rules/func-names) rule now recognizes function declarations in default exports. +* [no-extra-parens](../rules/no-extra-parens) rule now recognizes parentheses in assignment targets. +* [no-dupe-class-members](../rules/no-dupe-class-members) rule now recognizes computed keys for static class members. +* [no-magic-numbers](../rules/no-magic-numbers) rule now recognizes bigint literals. +* [radix](../rules/radix) rule now recognizes invalid numbers for the second parameter of `parseInt()`. +* [use-isnan](../rules/use-isnan) rule now recognizes class members by default. +* [yoda](../rules/yoda) rule now recognizes bigint literals. **To address:** Fix errors or disable these rules. @@ -189,9 +187,9 @@ Several rules have been enhanced and now report additional errors: Three new rules have been enabled in the `eslint:recommended` preset. -* [no-dupe-else-if](https://eslint.org/docs/rules/no-dupe-else-if) -* [no-import-assign](https://eslint.org/docs/rules/no-import-assign) -* [no-setter-return](https://eslint.org/docs/rules/no-setter-return) +* [no-dupe-else-if](../rules/no-dupe-else-if) +* [no-import-assign](../rules/no-import-assign) +* [no-setter-return](../rules/no-setter-return) **To address:** Fix errors or disable these rules. @@ -211,7 +209,7 @@ The `RuleTester` now validates the following: ## The `CLIEngine` class has been deprecated -The [`CLIEngine` class](https://eslint.org/docs/developer-guide/nodejs-api#cliengine) has been deprecated and replaced by the new [`ESLint` class](https://eslint.org/docs/developer-guide/nodejs-api#eslint-class). +The [`CLIEngine` class](../integrate/nodejs-api#cliengine) has been deprecated and replaced by the new [`ESLint` class](../integrate/nodejs-api#eslint-class). The `CLIEngine` class provides a synchronous API that is blocking the implementation of features such as parallel linting, supporting ES modules in shareable configs/parsers/plugins/formatters, and adding the ability to visually display the progress of linting runs. The new `ESLint` class provides an asynchronous API that ESLint core will now using going forward. `CLIEngine` will remain in core for the foreseeable future but may be removed in a future major version. diff --git a/docs/src/user-guide/rule-deprecation.md b/docs/src/use/rule-deprecation.md similarity index 92% rename from docs/src/user-guide/rule-deprecation.md rename to docs/src/use/rule-deprecation.md index 2b929473187..bd15405396f 100644 --- a/docs/src/user-guide/rule-deprecation.md +++ b/docs/src/use/rule-deprecation.md @@ -1,7 +1,5 @@ --- title: Rule Deprecation -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/rule-deprecation.md --- diff --git a/docs/src/user-guide/command-line-interface.md b/docs/src/user-guide/command-line-interface.md deleted file mode 100644 index c4f65aade44..00000000000 --- a/docs/src/user-guide/command-line-interface.md +++ /dev/null @@ -1,586 +0,0 @@ ---- -title: Command Line Interface -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/command-line-interface.md -eleventyNavigation: - key: command line interface - parent: user guide - title: Command Line Interface - order: 3 - ---- - -ESLint requires Node.js for installation. Follow the instructions in the [Getting Started Guide](getting-started) to install ESLint. - -Most users use [`npx`](https://docs.npmjs.com/cli/v8/commands/npx) to run ESLint on the command line like this: - -```shell -npx eslint [options] [file|dir|glob]* -``` - -Such as: - -```shell -# Run on two files -npx eslint file1.js file2.js - -# Run on multiple files -npx eslint lib/** -``` - -Please note that when passing a glob as a parameter, it will be expanded by your shell. The results of the expansion can vary depending on your shell, and its configuration. If you want to use node `glob` syntax, you have to quote your parameter (using double quotes if you need it to run in Windows), as follows: - -```shell -npx eslint "lib/**" -``` - -**Note:** You can also use alternative package managers such as [Yarn](https://yarnpkg.com/) or [pnpm](https://pnpm.io/) to run ESLint. Please refer to your package manager's documentation for the correct syntax. - -## Options - -The command line utility has several options. You can view the options by running `npx eslint -h`. - -```text -eslint [options] file.js [file.js] [dir] - -Basic configuration: - --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 - --global [String] Define global variables - --parser String Specify the parser to be used - --parser-options Object Specify parser options - --resolve-plugins-relative-to path::String A folder where plugins should be resolved from, CWD by default - -Specifying rules and plugins: - --plugin [String] Specify plugins - --rule Object Specify rules - --rulesdir [path::String] Load additional rules from this directory. Deprecated: Use rules from plugins - -Fixing problems: - --fix Automatically fix problems - --fix-dry-run Automatically fix problems without saving the changes to the file system - --fix-type Array Specify the types of fixes to apply (directive, problem, suggestion, layout) - -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) - -Using stdin: - --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 - -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 - -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 - --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or content - default: metadata - -Miscellaneous: - --init Run config initialization wizard - default: false - --env-info Output execution environment information - default: false - --no-error-on-unmatched-pattern Prevent errors when pattern is unmatched - --exit-on-fatal-error Exit with exit code 2 in case of fatal error - 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). - -Example: - -```shell -npx eslint --ext .jsx --ext .js lib/ - -npx eslint --ext .jsx,.js lib/ -``` - -### Basic configuration - -#### `--no-eslintrc` - -Disables use of configuration from `.eslintrc.*` and `package.json` files. - -Example: - -```shell -npx 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). - -Example: - -```shell -npx eslint -c ~/my-eslint.json file.js -``` - -This example uses the configuration file at `~/my-eslint.json`. - -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 [Specifying Environments](configuring/language-options#specifying-environments) 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: - -```shell -npx eslint --env browser,node file.js -npx eslint --env browser --env node file.js -``` - -#### `--ext` - -This option allows you to specify which file extensions ESLint will use when searching for target files in the directories you specify. -By default, ESLint lints `*.js` files and the files that match the `overrides` entries of your configuration. - -Examples: - -```shell -# Use only .ts extension -npx eslint . --ext .ts - -# Use both .js and .ts -npx eslint . --ext .js --ext .ts - -# Also use both .js and .ts -npx eslint . --ext .js,.ts -``` - -**Note:** `--ext` is only used when the arguments are directories. If you use glob patterns or file names, then `--ext` is ignored. - -For example, `npx eslint lib/* --ext .js` will match all files within the `lib/` directory, regardless of extension. - -#### `--global` - -This option defines global variables so that they will not be flagged as undefined by the `no-undef` rule. Any specified global variables are assumed to be read-only by default, but appending `:true` to a variable's name ensures that `no-undef` will also allow writes. To specify multiple global variables, separate them using commas, or use the option multiple times. - -Examples: - -```shell -npx eslint --global require,exports:true file.js -npx eslint --global require --global exports:true -``` - -#### `--parser` - -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. - -Examples: - -```shell -echo '3 ** 4' | npx eslint --stdin --parser-options=ecmaVersion:6 # will fail with a parsing error -echo '3 ** 4' | npx eslint --stdin --parser-options=ecmaVersion:7 # succeeds, yay! -``` - -#### `--resolve-plugins-relative-to` - -Changes the folder where plugins are resolved from. By default, plugins are resolved from the current working directory. This option should be used when plugins were installed by someone other than the end user. It should be set to the project directory of the project that has a dependency on the necessary plugins. For example: - -* When using a config file that is located outside of the current project (with the `--config` flag), if the config uses plugins which are installed locally to itself, `--resolve-plugins-relative-to` should be set to the directory containing the config file. -* If an integration has dependencies on ESLint and a set of plugins, and the tool invokes ESLint on behalf of the user with a preset configuration, the tool should set `--resolve-plugins-relative-to` to the top-level directory of the tool. - -### Specifying rules and plugins - -#### `--plugin` - -This option specifies a plugin to load. You can omit the prefix `eslint-plugin-` from the plugin name. - -Before using the plugin, you have to install it using npm. - -Examples: - -```shell -npx eslint --plugin jquery file.js -npx eslint --plugin eslint-plugin-mocha file.js -``` - -#### `--rule` - -This option specifies rules to be used. These rules will be merged with any rules specified with configuration files. (You can use `--no-eslintrc` to change that behavior.) To define multiple rules, separate them using commas, or use the option multiple times. The [levn](https://github.com/gkz/levn#levn--) format is used for specifying the rules. - -If the rule is defined within a plugin, you have to prefix the rule ID with the plugin name and a `/`. - -Examples: - -```shell -npx eslint --rule 'quotes: [error, double]' -npx eslint --rule 'guard-for-in: error' --rule 'brace-style: [error, 1tbs]' -npx eslint --rule 'jquery/dollar-sign: error' -``` - -#### `--rulesdir` - -**Deprecated**: Use rules from plugins instead. - -This option allows you to specify another directory from which to load rules files. This allows you to dynamically load new rules at run time. This is useful when you have custom rules that aren't suitable for being bundled with ESLint. - -Example: - -```shell -npx eslint --rulesdir my-rules/ file.js -``` - -The rules in your custom rules directory must follow the same format as bundled rules to work properly. You can also specify multiple locations for custom rules by including multiple `--rulesdir` options: - -```shell -npx eslint --rulesdir my-rules/ --rulesdir my-other-rules/ file.js -``` - -Note that, as with core rules and plugin rules, you still need to enable the rules in configuration or via the `--rule` CLI option in order to actually run those rules during linting. Specifying a rules directory with `--rulesdir` does not automatically enable the rules within that directory. - -### Fixing problems - -#### `--fix` - -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 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: - -```shell -getSomeText | npx 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. - -#### `--fix-type` - -This option allows you to specify the type of fixes to apply when using either `--fix` or `--fix-dry-run`. The four types of fixes are: - -1. `problem` - fix potential errors in the code -1. `suggestion` - apply fixes to the code that improve it -1. `layout` - apply fixes that do not change the program structure (AST) -1. `directive` - apply fixes to inline directives such as `// eslint-disable` - -You can specify one or more fix type on the command line. Here are some examples: - -```shell -npx eslint --fix --fix-type suggestion . -npx eslint --fix --fix-type suggestion --fix-type problem . -npx eslint --fix --fix-type suggestion,layout . -``` - -This option is helpful if you are using another program to format your code but you would still like ESLint to apply other types of fixes. - -### Ignoring files - -#### `--ignore-path` - -This option allows you to specify the file to use as your `.eslintignore`. By default, ESLint looks in the current working directory for `.eslintignore`. You can override this behavior by providing a path to a different file. - -Example: - -```shell -npx eslint --ignore-path tmp/.eslintignore file.js -npx eslint --ignore-path .gitignore file.js -``` - -#### `--no-ignore` - -Disables excluding of files from `.eslintignore`, `--ignore-path`, `--ignore-pattern`, and `ignorePatterns` property in config files. - -Example: - -```shell -npx eslint --no-ignore file.js -``` - -#### `--ignore-pattern` - -This option allows you to specify patterns of files to ignore (in addition to those in `.eslintignore`). You can repeat the option to provide multiple patterns. The supported syntax is the same as for `.eslintignore` [files](configuring/ignoring-code#the-eslintignore-file), which use the same patterns as the `.gitignore` [specification](https://git-scm.com/docs/gitignore). You should quote your patterns in order to avoid shell interpretation of glob patterns. - -Example: - -```shell -npx eslint --ignore-pattern '/lib/' --ignore-pattern '/src/vendor/*' . -``` - -### Using stdin - -#### `--stdin` - -This option tells ESLint to read and lint source code from STDIN instead of from files. You can use this to pipe code to ESLint. - -Example: - -```shell -cat myfile.js | npx eslint --stdin -``` - -#### `--stdin-filename` - -This option allows you to specify a filename to process STDIN as. This is useful when processing files from STDIN and you have rules which depend on the filename. - -Example - -```shell -cat myfile.js | npx eslint --stdin --stdin-filename=myfile.js -``` - -### Handling warnings - -#### `--quiet` - -This option allows you to disable reporting on warnings. If you enable this option, only errors are reported by ESLint. - -Example: - -```shell -npx eslint --quiet file.js -``` - -#### `--max-warnings` - -This option allows you to specify a warning threshold, which can be used to force ESLint to exit with an error status if there are too many warning-level rule violations in your project. - -Normally, if ESLint runs and finds no errors (only warnings), it will exit with a success exit status. However, if `--max-warnings` is specified and the total warning count is greater than the specified threshold, ESLint will exit with an error status. Specifying a threshold of `-1` or omitting this option will prevent this behavior. - -Example: - -```shell -npx eslint --max-warnings 10 file.js -``` - -### Output - -#### `-o`, `--output-file` - -Enable report to be written to a file. - -Example: - -```shell -npx eslint -o ./test/test.html -``` - -When specified, the given format is output into the provided file name. - -#### `-f`, `--format` - -This option specifies the output format for the console. Possible formats are: - -* [checkstyle](formatters/#checkstyle) -* [compact](formatters/#compact) -* [html](formatters/#html) -* [jslint-xml](formatters/#jslint-xml) -* [json](formatters/#json) -* [junit](formatters/#junit) -* [stylish](formatters/#stylish) (the default) -* [tap](formatters/#tap) -* [unix](formatters/#unix) -* [visualstudio](formatters/#visualstudio) - -Example: - -```shell -npx eslint -f compact file.js -``` - -You can also use a custom formatter from the command line by specifying a path to the custom formatter file. - -Example: - -```shell -npx eslint -f ./customformat.js file.js -``` - -An npm-installed formatter is resolved with or without `eslint-formatter-` prefix. - -Example: - -```shell -npm install eslint-formatter-pretty - -npx eslint -f pretty file.js - -// equivalent: -npx 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: - -```shell -npx eslint -f compact file.js > results.txt -``` - -This saves the output into the `results.txt` file. - -#### `--color`, `--no-color` - -This option forces the enabling/disabling of colorized output. You can use this to override the default behavior, which is to enable colorized output unless no TTY is detected, such as when piping `eslint` through `cat` or `less`. - -Examples: - -```shell -npx eslint --color file.js | cat -npx eslint --no-color file.js -``` - -### Inline configuration comments - -#### `--no-inline-config` - -This option prevents inline comments like `/*eslint-disable*/` or -`/*global foo*/` from having any effect. This allows you to set an ESLint -config without files modifying it. All inline config comments are ignored, e.g.: - -* `/*eslint-disable*/` -* `/*eslint-enable*/` -* `/*global*/` -* `/*eslint*/` -* `/*eslint-env*/` -* `// eslint-disable-line` -* `// eslint-disable-next-line` - -Example: - -```shell -npx 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: - -```shell -npx eslint --report-unused-disable-directives file.js -``` - -### Caching - -#### `--cache` - -Store the info about processed files in order to only operate on the changed ones. The cache is stored in `.eslintcache` by default. Enabling this option can dramatically improve ESLint's running time by ensuring that only changed files are linted. - -**Note:** If you run ESLint with `--cache` and then run ESLint without `--cache`, the `.eslintcache` file will be deleted. This is necessary because the results of the lint might change and make `.eslintcache` invalid. If you want to control when the cache file is deleted, then use `--cache-location` to specify an alternate location for the cache file. - -**Note:** Autofixed files are not placed in the cache. Subsequent linting that does not trigger an autofix will place it in the cache. - -#### `--cache-file` - -Path to the cache file. If none specified `.eslintcache` will be used. The file will be created in the directory where the `eslint` command is executed. **Deprecated**: Use `--cache-location` instead. - -#### `--cache-location` - -Path to the cache location. Can be a file or a directory. If no location is specified, `.eslintcache` will be used. In that case, the file will be created in the directory where the `eslint` command is executed. - -If a directory is specified, a cache file will be created inside the specified folder. The name of the file will be based on the hash of the current working directory (CWD). e.g.: `.cache_hashOfCWD` - -**Important note:** If the directory for the cache does not exist make sure you add a trailing `/` on \*nix systems or `\` in windows. Otherwise the path will be assumed to be a file. - -Example: - -```shell -npx eslint "src/**/*.js" --cache --cache-location "/Users/user/.eslintcache/" -``` - -#### `--cache-strategy` - -Strategy for the cache to use for detecting changed files. Can be either `metadata` or `content`. If no strategy is specified, `metadata` will be used. - -The `content` strategy can be useful in cases where the modification time of your files change even if their contents have not. For example, this can happen during git operations like git clone because git does not track file modification time. - -Example: - -```shell -npx eslint "src/**/*.js" --cache --cache-strategy content -``` - -### Miscellaneous - -#### `--init` - -This option will run `npm init @eslint/config` to start config initialization wizard. It's designed to help new users quickly create .eslintrc file by answering a few questions, choosing a popular style guide. - -The resulting configuration file will be created in the current directory. - -#### `--env-info` - -This option outputs information about the execution environment, including the version of Node, npm, and local and global installations of ESLint. The ESLint team may ask for this information to help solve bugs. - -#### `--no-error-on-unmatched-pattern` - -This option prevents errors when a quoted glob pattern or `--ext` is unmatched. This will not prevent errors when your shell can't match a glob. - -#### `--exit-on-fatal-error` - -This option causes ESLint to exit with exit code 2 if one or more fatal parsing errors occur. Without this option, fatal parsing errors are reported as rule violations. - -#### `--debug` - -This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs. -Add this flag to an ESLint command line invocation in order to get extra debug information as the command is run (e.g. `npx eslint --debug test.js` and `npx eslint test.js --debug` are equivalent) - -#### `-h`, `--help` - -This option outputs the help menu, displaying all of the available options. All other options are ignored when this is present. - -#### `-v`, `--version` - -This option outputs the current ESLint version onto the console. All other options are ignored when this is present. - -#### `--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. - -Example: - -```shell -npx eslint --print-config file.js -``` - -## Ignoring files from linting - -ESLint supports `.eslintignore` files to exclude files from the linting process when ESLint operates on a directory. Files given as individual CLI arguments will be exempt from exclusion. The `.eslintignore` file is a plain text file containing one pattern per line. It can be located in any of the target directory's ancestors; it will affect files in its containing directory as well as all sub-directories. Here's a simple example of a `.eslintignore` file: - -```text -temp.js -**/vendor/*.js -``` - -A more detailed breakdown of supported patterns and directories ESLint ignores by default can be found in [Ignoring Code](configuring/ignoring-code). - -## Exit codes - -When linting files, ESLint will exit with one of the following exit codes: - -* `0`: Linting was successful and there are no linting errors. If the `--max-warnings` flag is set to `n`, the number of linting warnings is at most `n`. -* `1`: Linting was successful and there is at least one linting error, or there are more linting warnings than allowed by the `--max-warnings` option. -* `2`: Linting was unsuccessful due to a configuration problem or an internal error. diff --git a/docs/src/user-guide/formatters/index.md b/docs/src/user-guide/formatters/index.md deleted file mode 100644 index 074b04a9726..00000000000 --- a/docs/src/user-guide/formatters/index.md +++ /dev/null @@ -1,243 +0,0 @@ ---- -title: Formatters -layout: doc -eleventyNavigation: - key: formatters - parent: user guide - title: Formatters - order: 5 -edit_link: https://github.com/eslint/eslint/edit/main/templates/formatter-examples.md.ejs ---- - -ESLint comes with several built-in formatters to control the appearance of the linting results, and supports third-party formatters as well. - -You can specify a formatter using the `--format` or `-f` flag on the command line. For example, `--format json` uses the `json` formatter. - -The built-in formatter options are: - -* [checkstyle](#checkstyle) -* [compact](#compact) -* [html](#html) -* [jslint-xml](#jslint-xml) -* [json-with-metadata](#json-with-metadata) -* [json](#json) -* [junit](#junit) -* [stylish](#stylish) -* [tap](#tap) -* [unix](#unix) -* [visualstudio](#visualstudio) - -## Example Source - -Examples of each formatter were created from linting `fullOfProblems.js` using the `.eslintrc` configuration shown below. - -### `fullOfProblems.js` - -```js -function addOne(i) { - if (i != NaN) { - return i ++ - } else { - return - } -}; -``` - -### `.eslintrc`: - -```json -{ - "extends": "eslint:recommended", - "rules": { - "consistent-return": 2, - "indent" : [1, 4], - "no-else-return" : 1, - "semi" : [1, "always"], - "space-unary-ops" : 2 - } -} -``` - -## Output Examples - -### checkstyle - -```text -<?xml version="1.0" encoding="utf-8"?><checkstyle version="4.3"><file name="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js"><error line="1" column="10" severity="error" message="&apos;addOne&apos; is defined but never used. (no-unused-vars)" source="eslint.rules.no-unused-vars" /><error line="2" column="9" severity="error" message="Use the isNaN function to compare with NaN. (use-isnan)" source="eslint.rules.use-isnan" /><error line="3" column="16" severity="error" message="Unexpected space before unary operator &apos;++&apos;. (space-unary-ops)" source="eslint.rules.space-unary-ops" /><error line="3" column="20" severity="warning" message="Missing semicolon. (semi)" source="eslint.rules.semi" /><error line="4" column="12" severity="warning" message="Unnecessary &apos;else&apos; after &apos;return&apos;. (no-else-return)" source="eslint.rules.no-else-return" /><error line="5" column="1" severity="warning" message="Expected indentation of 8 spaces but found 6. (indent)" source="eslint.rules.indent" /><error line="5" column="7" severity="error" message="Function &apos;addOne&apos; expected a return value. (consistent-return)" source="eslint.rules.consistent-return" /><error line="5" column="13" severity="warning" message="Missing semicolon. (semi)" source="eslint.rules.semi" /><error line="7" column="2" severity="error" message="Unnecessary semicolon. (no-extra-semi)" source="eslint.rules.no-extra-semi" /></file></checkstyle> -``` - -### compact - -```text -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 1, col 10, Error - 'addOne' is defined but never used. (no-unused-vars) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 2, col 9, Error - Use the isNaN function to compare with NaN. (use-isnan) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 3, col 16, Error - Unexpected space before unary operator '++'. (space-unary-ops) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 3, col 20, Warning - Missing semicolon. (semi) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 4, col 12, Warning - Unnecessary 'else' after 'return'. (no-else-return) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 1, Warning - Expected indentation of 8 spaces but found 6. (indent) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 7, Error - Function 'addOne' expected a return value. (consistent-return) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 13, Warning - Missing semicolon. (semi) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 7, col 2, Error - Unnecessary semicolon. (no-extra-semi) - -9 problems -``` - -### html - - - -### jslint-xml - -```text -<?xml version="1.0" encoding="utf-8"?><jslint><file name="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js"><issue line="1" char="10" evidence="" reason="&apos;addOne&apos; is defined but never used. (no-unused-vars)" /><issue line="2" char="9" evidence="" reason="Use the isNaN function to compare with NaN. (use-isnan)" /><issue line="3" char="16" evidence="" reason="Unexpected space before unary operator &apos;++&apos;. (space-unary-ops)" /><issue line="3" char="20" evidence="" reason="Missing semicolon. (semi)" /><issue line="4" char="12" evidence="" reason="Unnecessary &apos;else&apos; after &apos;return&apos;. (no-else-return)" /><issue line="5" char="1" evidence="" reason="Expected indentation of 8 spaces but found 6. (indent)" /><issue line="5" char="7" evidence="" reason="Function &apos;addOne&apos; expected a return value. (consistent-return)" /><issue line="5" char="13" evidence="" reason="Missing semicolon. (semi)" /><issue line="7" char="2" evidence="" reason="Unnecessary semicolon. (no-extra-semi)" /></file></jslint> -``` - -### json-with-metadata - -```text -{"results":[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}],"metadata":{"rulesMeta":{"no-else-return":{"type":"suggestion","docs":{"description":"Disallow `else` blocks after `return` statements in `if` statements","recommended":false,"url":"https://eslint.org/docs/rules/no-else-return"},"schema":[{"type":"object","properties":{"allowElseIf":{"type":"boolean","default":true}},"additionalProperties":false}],"fixable":"code","messages":{"unexpected":"Unnecessary 'else' after 'return'."}},"indent":{"type":"layout","docs":{"description":"Enforce consistent indentation","recommended":false,"url":"https://eslint.org/docs/rules/indent"},"fixable":"whitespace","schema":[{"oneOf":[{"enum":["tab"]},{"type":"integer","minimum":0}]},{"type":"object","properties":{"SwitchCase":{"type":"integer","minimum":0,"default":0},"VariableDeclarator":{"oneOf":[{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},{"type":"object","properties":{"var":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"let":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"const":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false}]},"outerIIFEBody":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"MemberExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"FunctionDeclaration":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"FunctionExpression":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"StaticBlock":{"type":"object","properties":{"body":{"type":"integer","minimum":0}},"additionalProperties":false},"CallExpression":{"type":"object","properties":{"arguments":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false},"ArrayExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ObjectExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ImportDeclaration":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"flatTernaryExpressions":{"type":"boolean","default":false},"offsetTernaryExpressions":{"type":"boolean","default":false},"ignoredNodes":{"type":"array","items":{"type":"string","not":{"pattern":":exit$"}}},"ignoreComments":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"wrongIndentation":"Expected indentation of {{expected}} but found {{actual}}."}},"space-unary-ops":{"type":"layout","docs":{"description":"Enforce consistent spacing before or after unary operators","recommended":false,"url":"https://eslint.org/docs/rules/space-unary-ops"},"fixable":"whitespace","schema":[{"type":"object","properties":{"words":{"type":"boolean","default":true},"nonwords":{"type":"boolean","default":false},"overrides":{"type":"object","additionalProperties":{"type":"boolean"}}},"additionalProperties":false}],"messages":{"unexpectedBefore":"Unexpected space before unary operator '{{operator}}'.","unexpectedAfter":"Unexpected space after unary operator '{{operator}}'.","unexpectedAfterWord":"Unexpected space after unary word operator '{{word}}'.","wordOperator":"Unary word operator '{{word}}' must be followed by whitespace.","operator":"Unary operator '{{operator}}' must be followed by whitespace.","beforeUnaryExpressions":"Space is required before unary expressions '{{token}}'."}},"semi":{"type":"layout","docs":{"description":"Require or disallow semicolons instead of ASI","recommended":false,"url":"https://eslint.org/docs/rules/semi"},"fixable":"code","schema":{"anyOf":[{"type":"array","items":[{"enum":["never"]},{"type":"object","properties":{"beforeStatementContinuationChars":{"enum":["always","any","never"]}},"additionalProperties":false}],"minItems":0,"maxItems":2},{"type":"array","items":[{"enum":["always"]},{"type":"object","properties":{"omitLastInOneLineBlock":{"type":"boolean"}},"additionalProperties":false}],"minItems":0,"maxItems":2}]},"messages":{"missingSemi":"Missing semicolon.","extraSemi":"Extra semicolon."}},"consistent-return":{"type":"suggestion","docs":{"description":"Require `return` statements to either always or never specify values","recommended":false,"url":"https://eslint.org/docs/rules/consistent-return"},"schema":[{"type":"object","properties":{"treatUndefinedAsUnspecified":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"missingReturn":"Expected to return a value at the end of {{name}}.","missingReturnValue":"{{name}} expected a return value.","unexpectedReturnValue":"{{name}} expected no return value."}}}}} -``` - -### json - -```text -[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}] -``` - -### junit - -```text -<?xml version="1.0" encoding="utf-8"?> -<testsuites> -<testsuite package="org.eslint" time="0" tests="9" errors="9" name="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js"> -<testcase time="0" name="org.eslint.no-unused-vars" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="&apos;addOne&apos; is defined but never used."><![CDATA[line 1, col 10, Error - &apos;addOne&apos; is defined but never used. (no-unused-vars)]]></failure></testcase> -<testcase time="0" name="org.eslint.use-isnan" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Use the isNaN function to compare with NaN."><![CDATA[line 2, col 9, Error - Use the isNaN function to compare with NaN. (use-isnan)]]></failure></testcase> -<testcase time="0" name="org.eslint.space-unary-ops" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Unexpected space before unary operator &apos;++&apos;."><![CDATA[line 3, col 16, Error - Unexpected space before unary operator &apos;++&apos;. (space-unary-ops)]]></failure></testcase> -<testcase time="0" name="org.eslint.semi" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Missing semicolon."><![CDATA[line 3, col 20, Warning - Missing semicolon. (semi)]]></failure></testcase> -<testcase time="0" name="org.eslint.no-else-return" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Unnecessary &apos;else&apos; after &apos;return&apos;."><![CDATA[line 4, col 12, Warning - Unnecessary &apos;else&apos; after &apos;return&apos;. (no-else-return)]]></failure></testcase> -<testcase time="0" name="org.eslint.indent" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Expected indentation of 8 spaces but found 6."><![CDATA[line 5, col 1, Warning - Expected indentation of 8 spaces but found 6. (indent)]]></failure></testcase> -<testcase time="0" name="org.eslint.consistent-return" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Function &apos;addOne&apos; expected a return value."><![CDATA[line 5, col 7, Error - Function &apos;addOne&apos; expected a return value. (consistent-return)]]></failure></testcase> -<testcase time="0" name="org.eslint.semi" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Missing semicolon."><![CDATA[line 5, col 13, Warning - Missing semicolon. (semi)]]></failure></testcase> -<testcase time="0" name="org.eslint.no-extra-semi" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Unnecessary semicolon."><![CDATA[line 7, col 2, Error - Unnecessary semicolon. (no-extra-semi)]]></failure></testcase> -</testsuite> -</testsuites> - -``` - -### stylish - -```text - -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js - 1:10 error 'addOne' is defined but never used no-unused-vars - 2:9 error Use the isNaN function to compare with NaN use-isnan - 3:16 error Unexpected space before unary operator '++' space-unary-ops - 3:20 warning Missing semicolon semi - 4:12 warning Unnecessary 'else' after 'return' no-else-return - 5:1 warning Expected indentation of 8 spaces but found 6 indent - 5:7 error Function 'addOne' expected a return value consistent-return - 5:13 warning Missing semicolon semi - 7:2 error Unnecessary semicolon no-extra-semi - -✖ 9 problems (5 errors, 4 warnings) - 2 errors and 4 warnings potentially fixable with the `--fix` option. - -``` - -### tap - -```text -TAP version 13 -1..1 -not ok 1 - /var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js - --- - message: '''addOne'' is defined but never used.' - severity: error - data: - line: 1 - column: 10 - ruleId: no-unused-vars - messages: - - message: Use the isNaN function to compare with NaN. - severity: error - data: - line: 2 - column: 9 - ruleId: use-isnan - - message: Unexpected space before unary operator '++'. - severity: error - data: - line: 3 - column: 16 - ruleId: space-unary-ops - - message: Missing semicolon. - severity: warning - data: - line: 3 - column: 20 - ruleId: semi - - message: Unnecessary 'else' after 'return'. - severity: warning - data: - line: 4 - column: 12 - ruleId: no-else-return - - message: Expected indentation of 8 spaces but found 6. - severity: warning - data: - line: 5 - column: 1 - ruleId: indent - - message: Function 'addOne' expected a return value. - severity: error - data: - line: 5 - column: 7 - ruleId: consistent-return - - message: Missing semicolon. - severity: warning - data: - line: 5 - column: 13 - ruleId: semi - - message: Unnecessary semicolon. - severity: error - data: - line: 7 - column: 2 - ruleId: no-extra-semi - ... - -``` - -### unix - -```text -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:1:10: 'addOne' is defined but never used. [Error/no-unused-vars] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:2:9: Use the isNaN function to compare with NaN. [Error/use-isnan] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:3:16: Unexpected space before unary operator '++'. [Error/space-unary-ops] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:3:20: Missing semicolon. [Warning/semi] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:4:12: Unnecessary 'else' after 'return'. [Warning/no-else-return] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:1: Expected indentation of 8 spaces but found 6. [Warning/indent] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:7: Function 'addOne' expected a return value. [Error/consistent-return] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:13: Missing semicolon. [Warning/semi] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:7:2: Unnecessary semicolon. [Error/no-extra-semi] - -9 problems -``` - -### visualstudio - -```text -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(1,10): error no-unused-vars : 'addOne' is defined but never used. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(2,9): error use-isnan : Use the isNaN function to compare with NaN. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(3,16): error space-unary-ops : Unexpected space before unary operator '++'. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(3,20): warning semi : Missing semicolon. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(4,12): warning no-else-return : Unnecessary 'else' after 'return'. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,1): warning indent : Expected indentation of 8 spaces but found 6. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,7): error consistent-return : Function 'addOne' expected a return value. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,13): warning semi : Missing semicolon. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(7,2): error no-extra-semi : Unnecessary semicolon. - -9 problems -``` diff --git a/docs/src/user-guide/getting-started.md b/docs/src/user-guide/getting-started.md deleted file mode 100644 index 0004f40522b..00000000000 --- a/docs/src/user-guide/getting-started.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: Getting Started with ESLint -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/user-guide/getting-started.md -eleventyNavigation: - key: getting started - parent: user guide - title: Getting Started - order: 1 - ---- - -ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs. In many ways, it is similar to JSLint and JSHint with a few exceptions: - -* ESLint uses [Espree](https://github.com/eslint/espree) for JavaScript parsing. -* ESLint uses an AST to evaluate patterns in code. -* ESLint is completely pluggable, every single rule is a plugin and you can add more at runtime. - -## Installation and Usage - -Prerequisites: [Node.js](https://nodejs.org/en/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) - -You can install and configure ESLint using this command: - -```shell -npm init @eslint/config -``` - -**Note:** `npm init @eslint/config` assumes you have a `package.json` file already. If you don't, make sure to run `npm init` or `yarn init` beforehand. - -After that, you can run ESLint on any file or directory like this: - -```shell -npx eslint yourfile.js - -# or - -yarn run eslint yourfile.js -``` - -It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, this is not recommended, and any plugins or shareable configs that you use must be installed locally in either case. - -## Configuration - -**Note:** If you are coming from a version before 1.0.0 please see the [migration guide](migrating-to-1.0.0). - -After running `npm init @eslint/config`, you'll have a `.eslintrc.{js,yml,json}` file in your directory. In it, you'll see some rules configured like this: - -```json -{ - "rules": { - "semi": ["error", "always"], - "quotes": ["error", "double"] - } -} -``` - -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](configuring/)). - -Your `.eslintrc.{js,yml,json}` configuration file will also include the line: - -```json -{ - "extends": "eslint:recommended" -} -``` - -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](configuring/) of ESLint. -* Get familiar with the [command line options](command-line-interface). -* Explore [ESLint integrations](integrations) 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). -* Make ESLint even better by [contributing](/docs/developer-guide/contributing/). diff --git a/docs/tools/validate-links.js b/docs/tools/validate-links.js new file mode 100644 index 00000000000..4d88ee7526a --- /dev/null +++ b/docs/tools/validate-links.js @@ -0,0 +1,57 @@ +"use strict"; + +const path = require("path"); +const TapRender = require("@munter/tap-render"); +const spot = require("tap-spot"); +const hyperlink = require("hyperlink"); + +const tapRenderInstance = new TapRender(); + +tapRenderInstance.pipe(spot()).pipe(process.stdout); + +const skipPatterns = [ + "https://", + "fragment-redirect", + "migrating-to", + "/blog", + "/play", + "/team", + "/donate", + "/docs/latest", + 'src="null"' +]; + +/** + * Filter function to mark tests as skipped. + * Tests for which this function returns `true' are not considered failed. + * @param {Object} report hyperlink's test report for a link. + * @returns {boolean} `true` if the report contains any of `skipPatterns`. + */ +function skipFilter(report) { + return Object.values(report).some(value => + skipPatterns.some(pattern => String(value).includes(pattern))); +} + +(async () => { + try { + await hyperlink( + { + inputUrls: ["../_site/index.html"], + root: path.resolve(__dirname, "../_site"), + canonicalRoot: "https://eslint.org/docs/head/", + recursive: true, + internalOnly: true, + pretty: true, + concurrency: 25, + skipFilter + }, + tapRenderInstance + ); + } catch (err) { + console.log(err.stack); + process.exit(1); + } + const results = tapRenderInstance.close(); + + process.exit(results.fail ? 1 : 0); +})(); diff --git a/eslint.config.js b/eslint.config.js index 50f375d3316..dfed655ec36 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,13 +5,31 @@ "use strict"; +/* + * IMPORTANT! + * + * Any changes made to this file must also be made to .eslintrc.js. + * + * Internally, ESLint is using the eslint.config.js file to lint itself. + * The .eslintrc.js file is needed too, because: + * + * 1. There are tests that expect .eslintrc.js to be present to actually run. + * 2. ESLint VS Code extension expects eslintrc config files to be + * present to work correctly. + * + * Once we no longer need to support both eslintrc and flat config, we will + * remove .eslintrc.js. + */ + //----------------------------------------------------------------------------- // Requirements //----------------------------------------------------------------------------- const path = require("path"); const internalPlugin = require("eslint-plugin-internal-rules"); +const eslintPlugin = require("eslint-plugin-eslint-plugin"); const { FlatCompat } = require("@eslint/eslintrc"); +const js = require("./packages/js"); const globals = require("globals"); //----------------------------------------------------------------------------- @@ -19,12 +37,12 @@ const globals = require("globals"); //----------------------------------------------------------------------------- const compat = new FlatCompat({ - baseDirectory: __dirname + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended }); const INTERNAL_FILES = { CLI_ENGINE_PATTERN: "lib/cli-engine/**/*", - INIT_PATTERN: "lib/init/**/*", LINTER_PATTERN: "lib/linter/**/*", RULE_TESTER_PATTERN: "lib/rule-tester/**/*", RULES_PATTERN: "lib/rules/**/*", @@ -60,16 +78,29 @@ function createInternalFilesPatterns(pattern = null) { })); } - -//----------------------------------------------------------------------------- -// Config -//----------------------------------------------------------------------------- - module.exports = [ - ...compat.extends("eslint", "plugin:eslint-plugin/recommended"), + ...compat.extends("eslint"), + { + ignores: [ + "build/**", + "coverage/**", + "docs/*", + "!docs/*.js", + "!docs/tools/", + "jsdoc/**", + "templates/**", + "tests/bench/**", + "tests/fixtures/**", + "tests/performance/**", + "tmp/**", + "tools/internal-rules/node_modules/**", + "**/test.js" + ] + }, { plugins: { - "internal-rules": internalPlugin + "internal-rules": internalPlugin, + "eslint-plugin": eslintPlugin }, languageOptions: { ecmaVersion: "latest" @@ -85,29 +116,28 @@ module.exports = [ } }, rules: { - "eslint-plugin/consistent-output": "error", - "eslint-plugin/no-deprecated-context-methods": "error", - "eslint-plugin/no-only-tests": "error", - "eslint-plugin/prefer-message-ids": "error", - "eslint-plugin/prefer-output-null": "error", - "eslint-plugin/prefer-placeholders": "error", - "eslint-plugin/prefer-replace-text": "error", - "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], - "eslint-plugin/require-meta-docs-description": "error", - "eslint-plugin/require-meta-has-suggestions": "error", - "eslint-plugin/require-meta-schema": "error", - "eslint-plugin/require-meta-type": "error", - "eslint-plugin/test-case-property-ordering": "error", - "eslint-plugin/test-case-shorthand-strings": "error", "internal-rules/multiline-comment-style": "error" } - + }, + { + files: ["tools/*.js", "docs/tools/*.js"], + rules: { + "no-console": "off", + "n/no-process-exit": "off" + } }, { files: ["lib/rules/*", "tools/internal-rules/*"], - ignores: ["index.js"], + ignores: ["**/index.js"], rules: { - "eslint-plugin/prefer-object-rule": "error", + ...eslintPlugin.configs["rules-recommended"].rules, + "eslint-plugin/no-missing-message-ids": "error", + "eslint-plugin/no-unused-message-ids": "error", + "eslint-plugin/prefer-message-ids": "error", + "eslint-plugin/prefer-placeholders": "error", + "eslint-plugin/prefer-replace-text": "error", + "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], + "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow) .+[^. ]$" }], "internal-rules/no-invalid-meta": "error" } }, @@ -119,7 +149,16 @@ module.exports = [ } }, { - files: ["tests/**/*"], + files: ["tests/lib/rules/*", "tests/tools/internal-rules/*"], + rules: { + ...eslintPlugin.configs["tests-recommended"].rules, + "eslint-plugin/prefer-output-null": "error", + "eslint-plugin/test-case-property-ordering": "error", + "eslint-plugin/test-case-shorthand-strings": "error" + } + }, + { + files: ["tests/**/*.js"], languageOptions: { globals: { ...globals.mocha @@ -147,17 +186,7 @@ module.exports = [ files: [INTERNAL_FILES.CLI_ENGINE_PATTERN], rules: { "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_FILES.CLI_ENGINE_PATTERN), - resolveAbsolutePath("lib/init/index.js") - ]] - } - }, - { - files: [INTERNAL_FILES.INIT_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_FILES.INIT_PATTERN), - resolveAbsolutePath("lib/rule-tester/index.js") + ...createInternalFilesPatterns(INTERNAL_FILES.CLI_ENGINE_PATTERN) ]] } }, @@ -168,7 +197,6 @@ module.exports = [ ...createInternalFilesPatterns(INTERNAL_FILES.LINTER_PATTERN), "fs", resolveAbsolutePath("lib/cli-engine/index.js"), - resolveAbsolutePath("lib/init/index.js"), resolveAbsolutePath("lib/rule-tester/index.js") ]] } @@ -180,7 +208,6 @@ module.exports = [ ...createInternalFilesPatterns(INTERNAL_FILES.RULES_PATTERN), "fs", resolveAbsolutePath("lib/cli-engine/index.js"), - resolveAbsolutePath("lib/init/index.js"), resolveAbsolutePath("lib/linter/index.js"), resolveAbsolutePath("lib/rule-tester/index.js"), resolveAbsolutePath("lib/source-code/index.js") @@ -193,7 +220,6 @@ module.exports = [ "n/no-restricted-require": ["error", [ ...createInternalFilesPatterns(), resolveAbsolutePath("lib/cli-engine/index.js"), - resolveAbsolutePath("lib/init/index.js"), resolveAbsolutePath("lib/linter/index.js"), resolveAbsolutePath("lib/rule-tester/index.js"), resolveAbsolutePath("lib/source-code/index.js") @@ -207,7 +233,6 @@ module.exports = [ ...createInternalFilesPatterns(INTERNAL_FILES.SOURCE_CODE_PATTERN), "fs", resolveAbsolutePath("lib/cli-engine/index.js"), - resolveAbsolutePath("lib/init/index.js"), resolveAbsolutePath("lib/linter/index.js"), resolveAbsolutePath("lib/rule-tester/index.js"), resolveAbsolutePath("lib/rules/index.js") @@ -219,8 +244,7 @@ module.exports = [ rules: { "n/no-restricted-require": ["error", [ ...createInternalFilesPatterns(INTERNAL_FILES.RULE_TESTER_PATTERN), - resolveAbsolutePath("lib/cli-engine/index.js"), - resolveAbsolutePath("lib/init/index.js") + resolveAbsolutePath("lib/cli-engine/index.js") ]] } } diff --git a/lib/cli-engine/cli-engine.js b/lib/cli-engine/cli-engine.js index fdc66198809..5bca1618b94 100644 --- a/lib/cli-engine/cli-engine.js +++ b/lib/cli-engine/cli-engine.js @@ -615,8 +615,8 @@ class CLIEngine { useEslintrc: options.useEslintrc, builtInRules, loadRules, - getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"), - getEslintAllConfig: () => require("../../conf/eslint-all.js") + getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended, + getEslintAllConfig: () => require("@eslint/js").configs.all }); const fileEnumerator = new FileEnumerator({ configArrayFactory, diff --git a/lib/cli-engine/file-enumerator.js b/lib/cli-engine/file-enumerator.js index 674e83e540d..5dff8d09ccd 100644 --- a/lib/cli-engine/file-enumerator.js +++ b/lib/cli-engine/file-enumerator.js @@ -122,7 +122,8 @@ function statSafeSync(filePath) { try { return fs.statSync(filePath); } catch (error) { - /* istanbul ignore next */ + + /* c8 ignore next */ if (error.code !== "ENOENT") { throw error; } @@ -141,7 +142,8 @@ function readdirSafeSync(directoryPath) { try { return fs.readdirSync(directoryPath, { withFileTypes: true }); } catch (error) { - /* istanbul ignore next */ + + /* c8 ignore next */ if (error.code !== "ENOENT") { throw error; } @@ -215,8 +217,8 @@ class FileEnumerator { cwd = process.cwd(), configArrayFactory = new CascadingConfigArrayFactory({ cwd, - getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"), - getEslintAllConfig: () => require("../../conf/eslint-all.js") + getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended, + getEslintAllConfig: () => require("@eslint/js").configs.all }), extensions = null, globInputPaths = true, @@ -347,7 +349,7 @@ class FileEnumerator { return this._iterateFilesWithFile(absolutePath); } if (globInputPaths && isGlobPattern(pattern)) { - return this._iterateFilesWithGlob(absolutePath, isDot); + return this._iterateFilesWithGlob(pattern, isDot); } return []; @@ -396,15 +398,17 @@ class FileEnumerator { _iterateFilesWithGlob(pattern, dotfiles) { debug(`Glob: ${pattern}`); - const directoryPath = path.resolve(getGlobParent(pattern)); - const globPart = pattern.slice(directoryPath.length + 1); + const { cwd } = internalSlotsMap.get(this); + const directoryPath = path.resolve(cwd, getGlobParent(pattern)); + const absolutePath = path.resolve(cwd, pattern); + const globPart = absolutePath.slice(directoryPath.length + 1); /* * recursive if there are `**` or path separators in the glob part. * Otherwise, patterns such as `src/*.js`, it doesn't need recursive. */ const recursive = /\*\*|\/|\\/u.test(globPart); - const selector = new Minimatch(pattern, minimatchOpts); + const selector = new Minimatch(absolutePath, minimatchOpts); debug(`recursive? ${recursive}`); diff --git a/lib/cli-engine/formatters/formatters-meta.json b/lib/cli-engine/formatters/formatters-meta.json new file mode 100644 index 00000000000..bcd35e50428 --- /dev/null +++ b/lib/cli-engine/formatters/formatters-meta.json @@ -0,0 +1,46 @@ +[ + { + "name": "checkstyle", + "description": "Outputs results to the [Checkstyle](https://checkstyle.sourceforge.io/) format." + }, + { + "name": "compact", + "description": "Human-readable output format. Mimics the default output of JSHint." + }, + { + "name": "html", + "description": "Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser." + }, + { + "name": "jslint-xml", + "description": "Outputs results to format compatible with the [JSLint Jenkins plugin](https://plugins.jenkins.io/jslint/)." + }, + { + "name": "json-with-metadata", + "description": "Outputs JSON-serialized results. The `json-with-metadata` provides the same linting results as the [`json`](#json) formatter with additional metadata about the rules applied. The linting results are included in the `results` property and the rules metadata is included in the `metadata` property.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint." + }, + { + "name": "json", + "description": "Outputs JSON-serialized results. The `json` formatter is useful when you want to programmatically work with the CLI's linting results.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint." + }, + { + "name": "junit", + "description": "Outputs results to format compatible with the [JUnit Jenkins plugin](https://plugins.jenkins.io/junit/)." + }, + { + "name": "stylish", + "description": "Human-readable output format. This is the default formatter." + }, + { + "name": "tap", + "description": "Outputs results to the [Test Anything Protocol (TAP)](https://testanything.org/) specification format." + }, + { + "name": "unix", + "description": "Outputs results to a format similar to many commands in UNIX-like systems. Parsable with tools such as [grep](https://www.gnu.org/software/grep/manual/grep.html), [sed](https://www.gnu.org/software/sed/manual/sed.html), and [awk](https://www.gnu.org/software/gawk/manual/gawk.html)." + }, + { + "name": "visualstudio", + "description": "Outputs results to format compatible with the integrated terminal of the [Visual Studio](https://visualstudio.microsoft.com/) IDE. When using Visual Studio, you can click on the linting results in the integrated terminal to go to the issue in the source code." + } +] \ No newline at end of file diff --git a/lib/cli-engine/formatters/html.js b/lib/cli-engine/formatters/html.js index 6e40bbe16b7..1aa66fcefac 100644 --- a/lib/cli-engine/formatters/html.js +++ b/lib/cli-engine/formatters/html.js @@ -43,85 +43,110 @@ function pageTemplate(it) { @@ -214,7 +239,7 @@ function messageTemplate(it) { } = it; return ` - + diff --git a/lib/cli.js b/lib/cli.js index f09d143d255..afd1e65cbd1 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -6,7 +6,7 @@ "use strict"; /* - * The CLI object should *not* call process.exit() directly. It should only return + * NOTE: The CLI object should *not* call process.exit() directly. It should only return * exit codes. This allows other programs to use the CLI object and still control * when the program exits. */ @@ -19,9 +19,13 @@ const fs = require("fs"), path = require("path"), { promisify } = require("util"), { ESLint } = require("./eslint"), - CLIOptions = require("./options"), + { FlatESLint } = require("./eslint/flat-eslint"), + createCLIOptions = require("./options"), log = require("./shared/logging"), RuntimeInfo = require("./shared/runtime-info"); +const { Legacy: { naming } } = require("@eslint/eslintrc"); +const { findFlatConfigFile } = require("./eslint/flat-eslint"); +const { ModuleImporter } = require("@humanwhocodes/module-importer"); const debug = require("debug")("eslint:cli"); @@ -33,6 +37,7 @@ const debug = require("debug")("eslint:cli"); /** @typedef {import("./eslint/eslint").LintMessage} LintMessage */ /** @typedef {import("./eslint/eslint").LintResult} LintResult */ /** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */ +/** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */ //------------------------------------------------------------------------------ // Helpers @@ -54,17 +59,20 @@ function quietFixPredicate(message) { } /** - * Translates the CLI options into the options expected by the CLIEngine. + * Translates the CLI options into the options expected by the ESLint constructor. * @param {ParsedCLIOptions} cliOptions The CLI options to translate. - * @returns {ESLintOptions} The options object for the CLIEngine. + * @param {"flat"|"eslintrc"} [configType="eslintrc"] The format of the + * config to generate. + * @returns {Promise} The options object for the ESLint constructor. * @private */ -function translateOptions({ +async function translateOptions({ cache, cacheFile, cacheLocation, cacheStrategy, config, + configLookup, env, errorOnUnmatchedPattern, eslintrc, @@ -85,19 +93,60 @@ function translateOptions({ resolvePluginsRelativeTo, rule, rulesdir -}) { - return { - allowInlineConfig: inlineConfig, - cache, - cacheLocation: cacheLocation || cacheFile, - cacheStrategy, - errorOnUnmatchedPattern, - extensions: ext, - fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true), - fixTypes: fixType, - ignore, - ignorePath, - overrideConfig: { +}, configType) { + + let overrideConfig, overrideConfigFile; + const importer = new ModuleImporter(); + + if (configType === "flat") { + overrideConfigFile = (typeof config === "string") ? config : !configLookup; + if (overrideConfigFile === false) { + overrideConfigFile = void 0; + } + + let globals = {}; + + if (global) { + globals = global.reduce((obj, name) => { + if (name.endsWith(":true")) { + obj[name.slice(0, -5)] = "writable"; + } else { + obj[name] = "readonly"; + } + return obj; + }, globals); + } + + overrideConfig = [{ + languageOptions: { + globals, + parserOptions: parserOptions || {} + }, + rules: rule ? rule : {} + }]; + + if (parser) { + overrideConfig[0].languageOptions.parser = await importer.import(parser); + } + + if (plugin) { + const plugins = {}; + + for (const pluginName of plugin) { + + const shortName = naming.getShorthandName(pluginName, "eslint-plugin"); + const longName = naming.normalizePackageName(pluginName, "eslint-plugin"); + + plugins[shortName] = await importer.import(longName); + } + + overrideConfig[0].plugins = plugins; + } + + } else { + overrideConfigFile = config; + + overrideConfig = { env: env && env.reduce((obj, name) => { obj[name] = true; return obj; @@ -115,19 +164,40 @@ function translateOptions({ parserOptions, plugins: plugin, rules: rule - }, - overrideConfigFile: config, - reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0, - resolvePluginsRelativeTo, - rulePaths: rulesdir, - useEslintrc: eslintrc + }; + } + + const options = { + allowInlineConfig: inlineConfig, + cache, + cacheLocation: cacheLocation || cacheFile, + cacheStrategy, + errorOnUnmatchedPattern, + fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true), + fixTypes: fixType, + ignore, + overrideConfig, + overrideConfigFile, + reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0 }; + + if (configType === "flat") { + options.ignorePatterns = ignorePattern; + } else { + options.resolvePluginsRelativeTo = resolvePluginsRelativeTo; + options.rulePaths = rulesdir; + options.useEslintrc = eslintrc; + options.extensions = ext; + options.ignorePath = ignorePath; + } + + return options; } /** * Count error messages. * @param {LintResult[]} results The lint results. - * @returns {{errorCount:number;warningCount:number}} The number of error messages. + * @returns {{errorCount:number;fatalErrorCount:number,warningCount:number}} The number of error messages. */ function countErrors(results) { let errorCount = 0; @@ -165,10 +235,11 @@ async function isDirectory(filePath) { * @param {LintResult[]} results The results to print. * @param {string} format The name of the formatter to use or the path to the formatter. * @param {string} outputFile The path for the output file. + * @param {ResultsMeta} resultsMeta Warning count and max threshold. * @returns {Promise} True if the printing succeeds, false if not. * @private */ -async function printResults(engine, results, format, outputFile) { +async function printResults(engine, results, format, outputFile, resultsMeta) { let formatter; try { @@ -178,7 +249,7 @@ async function printResults(engine, results, format, outputFile) { return false; } - const output = await formatter.format(results); + const output = await formatter.format(results, resultsMeta); if (output) { if (outputFile) { @@ -204,6 +275,31 @@ async function printResults(engine, results, format, outputFile) { return true; } +/** + * Returns whether flat config should be used. + * @param {boolean} [allowFlatConfig] Whether or not to allow flat config. + * @returns {Promise} Where flat config should be used. + */ +async function shouldUseFlatConfig(allowFlatConfig) { + if (!allowFlatConfig) { + return false; + } + + switch (process.env.ESLINT_USE_FLAT_CONFIG) { + case "true": + return true; + case "false": + return false; + default: + + /* + * If neither explicitly enabled nor disabled, then use the presence + * of a flat config file to determine enablement. + */ + return !!(await findFlatConfigFile(process.cwd())); + } +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -218,19 +314,34 @@ const cli = { * Executes the CLI based on an array of arguments that is passed in. * @param {string|Array|Object} args The arguments to process. * @param {string} [text] The text to lint (used for TTY). + * @param {boolean} [allowFlatConfig] Whether or not to allow flat config. * @returns {Promise} The exit code for the operation. */ - async execute(args, text) { + async execute(args, text, allowFlatConfig) { if (Array.isArray(args)) { debug("CLI args: %o", args.slice(2)); } + /* + * Before doing anything, we need to see if we are using a + * flat config file. If so, then we need to change the way command + * line args are parsed. This is temporary, and when we fully + * switch to flat config we can remove this logic. + */ + + const usingFlatConfig = await shouldUseFlatConfig(allowFlatConfig); + + debug("Using flat config?", usingFlatConfig); + + const CLIOptions = createCLIOptions(usingFlatConfig); + /** @type {ParsedCLIOptions} */ let options; try { options = CLIOptions.parse(args); } catch (error) { + debug("Error parsing CLI options:", error.message); log.error(error.message); return 2; } @@ -251,6 +362,7 @@ const cli = { log.info(RuntimeInfo.environment()); return 0; } catch (err) { + debug("Error retrieving environment info"); log.error(err.message); return 2; } @@ -266,7 +378,9 @@ const cli = { return 2; } - const engine = new ESLint(translateOptions(options)); + const engine = usingFlatConfig + ? new FlatESLint(await translateOptions(options, "flat")) + : new ESLint(await translateOptions(options)); const fileConfig = await engine.calculateConfigForFile(options.printConfig); @@ -289,7 +403,9 @@ const cli = { return 2; } - const engine = new ESLint(translateOptions(options)); + const ActiveESLint = usingFlatConfig ? FlatESLint : ESLint; + + const engine = new ActiveESLint(await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc")); let results; if (useStdin) { @@ -303,27 +419,34 @@ const cli = { if (options.fix) { debug("Fix mode enabled - applying fixes"); - await ESLint.outputFixes(results); + await ActiveESLint.outputFixes(results); } let resultsToPrint = results; if (options.quiet) { debug("Quiet mode enabled - filtering out warnings"); - resultsToPrint = ESLint.getErrorResults(resultsToPrint); + resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint); } - if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) { + const resultCounts = countErrors(results); + const tooManyWarnings = options.maxWarnings >= 0 && resultCounts.warningCount > options.maxWarnings; + const resultsMeta = tooManyWarnings + ? { + maxWarningsExceeded: { + maxWarnings: options.maxWarnings, + foundWarnings: resultCounts.warningCount + } + } + : {}; - // Errors and warnings from the original unfiltered results should determine the exit code - const { errorCount, fatalErrorCount, warningCount } = countErrors(results); + if (await printResults(engine, resultsToPrint, options.format, options.outputFile, resultsMeta)) { - const tooManyWarnings = - options.maxWarnings >= 0 && warningCount > options.maxWarnings; + // Errors and warnings from the original unfiltered results should determine the exit code const shouldExitForFatalErrors = - options.exitOnFatalError && fatalErrorCount > 0; + options.exitOnFatalError && resultCounts.fatalErrorCount > 0; - if (!errorCount && tooManyWarnings) { + if (!resultCounts.errorCount && tooManyWarnings) { log.error( "ESLint found too many warnings (maximum: %s).", options.maxWarnings @@ -334,7 +457,7 @@ const cli = { return 2; } - return (errorCount || tooManyWarnings) ? 1 : 0; + return (resultCounts.errorCount || tooManyWarnings) ? 1 : 0; } return 2; diff --git a/lib/config/default-config.js b/lib/config/default-config.js index c48551a4f2a..99ea7b9f84e 100644 --- a/lib/config/default-config.js +++ b/lib/config/default-config.js @@ -19,9 +19,6 @@ exports.defaultConfig = [ { plugins: { "@": { - parsers: { - espree: require("espree") - }, /* * Because we try to delay loading rules until absolutely @@ -43,7 +40,7 @@ exports.defaultConfig = [ languageOptions: { sourceType: "module", ecmaVersion: "latest", - parser: "@/espree", + parser: require("espree"), parserOptions: {} } }, @@ -51,8 +48,8 @@ exports.defaultConfig = [ // default ignores are listed here { ignores: [ - "**/node_modules/**", - ".git/**" + "**/node_modules/*", + ".git/" ] }, diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index ad8986f516e..689dc429f50 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -13,7 +13,7 @@ const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array" const { flatConfigSchema } = require("./flat-config-schema"); const { RuleValidator } = require("./rule-validator"); const { defaultConfig } = require("./default-config"); -const recommendedConfig = require("../../conf/eslint-recommended"); +const jsPlugin = require("@eslint/js"); //----------------------------------------------------------------------------- // Helpers @@ -36,6 +36,45 @@ function splitPluginIdentifier(identifier) { }; } +/** + * Returns the name of an object in the config by reading its `meta` key. + * @param {Object} object The object to check. + * @returns {string?} The name of the object if found or `null` if there + * is no name. + */ +function getObjectId(object) { + + // first check old-style name + let name = object.name; + + if (!name) { + + if (!object.meta) { + return null; + } + + name = object.meta.name; + + if (!name) { + return null; + } + } + + // now check for old-style version + let version = object.version; + + if (!version) { + version = object.meta && object.meta.version; + } + + // if there's a version then append that + if (version) { + return `${name}@${version}`; + } + + return name; +} + const originalBaseConfig = Symbol("originalBaseConfig"); //----------------------------------------------------------------------------- @@ -70,7 +109,7 @@ class FlatConfigArray extends ConfigArray { } /** - * The baes config used to build the config array. + * The base config used to build the config array. * @type {Array} */ this[originalBaseConfig] = baseConfig; @@ -96,17 +135,23 @@ class FlatConfigArray extends ConfigArray { */ [ConfigArraySymbol.preprocessConfig](config) { if (config === "eslint:recommended") { - return recommendedConfig; + + // if we are in a Node.js environment warn the user + if (typeof process !== "undefined" && process.emitWarning) { + process.emitWarning("The 'eslint:recommended' string configuration is deprecated and will be replaced by the @eslint/js package's 'recommended' config."); + } + + return jsPlugin.configs.recommended; } if (config === "eslint:all") { - /* - * Load `eslint-all.js` here instead of at the top level to avoid loading all rule modules - * when it isn't necessary. `eslint-all.js` reads `meta` of rule objects to filter out deprecated ones, - * so requiring `eslint-all.js` module loads all rule modules as a consequence. - */ - return require("../../conf/eslint-all"); + // if we are in a Node.js environment warn the user + if (typeof process !== "undefined" && process.emitWarning) { + process.emitWarning("The 'eslint:all' string configuration is deprecated and will be replaced by the @eslint/js package's 'all' config."); + } + + return jsPlugin.configs.all; } /* @@ -145,16 +190,15 @@ class FlatConfigArray extends ConfigArray { // Check parser value if (languageOptions && languageOptions.parser) { - if (typeof languageOptions.parser === "string") { - const { pluginName, objectName: localParserName } = splitPluginIdentifier(languageOptions.parser); + const { parser } = languageOptions; - parserName = languageOptions.parser; + if (typeof parser === "object") { + parserName = getObjectId(parser); - if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[localParserName]) { - throw new TypeError(`Key "parser": Could not find "${localParserName}" in plugin "${pluginName}".`); + if (!parserName) { + invalidParser = true; } - languageOptions.parser = plugins[pluginName].parsers[localParserName]; } else { invalidParser = true; } @@ -172,6 +216,13 @@ class FlatConfigArray extends ConfigArray { } config.processor = plugins[pluginName].processors[localProcessorName]; + } else if (typeof processor === "object") { + processorName = getObjectId(processor); + + if (!processorName) { + invalidProcessor = true; + } + } else { invalidProcessor = true; } @@ -185,16 +236,25 @@ class FlatConfigArray extends ConfigArray { value: function() { if (invalidParser) { - throw new Error("Caching is not supported when parser is an object."); + throw new Error("Could not serialize parser object (missing 'meta' object)."); } if (invalidProcessor) { - throw new Error("Caching is not supported when processor is an object."); + throw new Error("Could not serialize processor object (missing 'meta' object)."); } return { ...this, - plugins: Object.keys(plugins), + plugins: Object.entries(plugins).map(([namespace, plugin]) => { + + const pluginId = getObjectId(plugin); + + if (!pluginId) { + return namespace; + } + + return `${namespace}:${pluginId}`; + }), languageOptions: { ...languageOptions, parser: parserName diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index cb8e7961add..bb6e9f899ac 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -179,18 +179,6 @@ function assertIsObject(value) { } } -/** - * Validates that a value is an object or a string. - * @param {any} value The value to check. - * @returns {void} - * @throws {TypeError} If the value isn't an object or a string. - */ -function assertIsObjectOrString(value) { - if ((!value || typeof value !== "object") && typeof value !== "string") { - throw new TypeError("Expected an object or string."); - } -} - //----------------------------------------------------------------------------- // Low-Level Schemas //----------------------------------------------------------------------------- @@ -242,15 +230,13 @@ const globalsSchema = { const parserSchema = { merge: "replace", validate(value) { - assertIsObjectOrString(value); - if (typeof value === "object" && typeof value.parse !== "function" && typeof value.parseForESLint !== "function") { - throw new TypeError("Expected object to have a parse() or parseForESLint() method."); + if (!value || typeof value !== "object" || + (typeof value.parse !== "function" && typeof value.parseForESLint !== "function") + ) { + throw new TypeError("Expected object with parse() or parseForESLint() method."); } - if (typeof value === "string") { - assertIsPluginMemberName(value); - } } }; diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js index 5818d8d1039..54b0c69d7c8 100644 --- a/lib/eslint/eslint-helpers.js +++ b/lib/eslint/eslint-helpers.js @@ -13,8 +13,31 @@ const path = require("path"); const fs = require("fs"); const fsp = fs.promises; const isGlob = require("is-glob"); -const globby = require("globby"); const hash = require("../cli-engine/hash"); +const minimatch = require("minimatch"); +const util = require("util"); +const fswalk = require("@nodelib/fs.walk"); +const globParent = require("glob-parent"); +const isPathInside = require("is-path-inside"); + +//----------------------------------------------------------------------------- +// Fixup references +//----------------------------------------------------------------------------- + +const doFsWalk = util.promisify(fswalk.walk); +const Minimatch = minimatch.Minimatch; +const MINIMATCH_OPTIONS = { dot: true }; + +//----------------------------------------------------------------------------- +// Types +//----------------------------------------------------------------------------- + +/** + * @typedef {Object} GlobSearch + * @property {Array} patterns The normalized patterns to use for a search. + * @property {Array} rawPatterns The patterns as entered by the user + * before doing any normalization. + */ //----------------------------------------------------------------------------- // Errors @@ -36,6 +59,30 @@ class NoFilesFoundError extends Error { } } +/** + * The error type when a search fails to match multiple patterns. + */ +class UnmatchedSearchPatternsError extends Error { + + /** + * @param {Object} options The options for the error. + * @param {string} options.basePath The directory that was searched. + * @param {Array} options.unmatchedPatterns The glob patterns + * which were not found. + * @param {Array} options.patterns The glob patterns that were + * searched. + * @param {Array} options.rawPatterns The raw glob patterns that + * were searched. + */ + constructor({ basePath, unmatchedPatterns, patterns, rawPatterns }) { + super(`No files matching '${rawPatterns}' in '${basePath}' were found.`); + this.basePath = basePath; + this.unmatchedPatterns = unmatchedPatterns; + this.patterns = patterns; + this.rawPatterns = rawPatterns; + } +} + /** * The error type when there are files matched by a glob, but all of them have been ignored. */ @@ -66,9 +113,9 @@ function isNonEmptyString(x) { } /** - * Check if a given value is an array of non-empty stringss or not. + * Check if a given value is an array of non-empty strings or not. * @param {any} x The value to check. - * @returns {boolean} `true` if `x` is an array of non-empty stringss. + * @returns {boolean} `true` if `x` is an array of non-empty strings. */ function isArrayOfNonEmptyString(x) { return Array.isArray(x) && x.every(isNonEmptyString); @@ -96,6 +143,317 @@ function isGlobPattern(pattern) { return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern); } + +/** + * Determines if a given glob pattern will return any results. + * Used primarily to help with useful error messages. + * @param {Object} options The options for the function. + * @param {string} options.basePath The directory to search. + * @param {string} options.pattern A glob pattern to match. + * @returns {Promise} True if there is a glob match, false if not. + */ +function globMatch({ basePath, pattern }) { + + let found = false; + const patternToUse = path.isAbsolute(pattern) + ? normalizeToPosix(path.relative(basePath, pattern)) + : pattern; + + const matcher = new Minimatch(patternToUse, MINIMATCH_OPTIONS); + + const fsWalkSettings = { + + deepFilter(entry) { + const relativePath = normalizeToPosix(path.relative(basePath, entry.path)); + + return !found && matcher.match(relativePath, true); + }, + + entryFilter(entry) { + if (found || entry.dirent.isDirectory()) { + return false; + } + + const relativePath = normalizeToPosix(path.relative(basePath, entry.path)); + + if (matcher.match(relativePath)) { + found = true; + return true; + } + + return false; + } + }; + + return new Promise(resolve => { + + // using a stream so we can exit early because we just need one match + const globStream = fswalk.walkStream(basePath, fsWalkSettings); + + globStream.on("data", () => { + globStream.destroy(); + resolve(true); + }); + + // swallow errors as they're not important here + globStream.on("error", () => { }); + + globStream.on("end", () => { + resolve(false); + }); + globStream.read(); + }); + +} + +/** + * Searches a directory looking for matching glob patterns. This uses + * the config array's logic to determine if a directory or file should + * be ignored, so it is consistent with how ignoring works throughout + * ESLint. + * @param {Object} options The options for this function. + * @param {string} options.basePath The directory to search. + * @param {Array} options.patterns An array of glob patterns + * to match. + * @param {Array} options.rawPatterns An array of glob patterns + * as the user inputted them. Used for errors. + * @param {FlatConfigArray} options.configs The config array to use for + * determining what to ignore. + * @param {boolean} options.errorOnUnmatchedPattern Determines if an error + * should be thrown when a pattern is unmatched. + * @returns {Promise>} An array of matching file paths + * or an empty array if there are no matches. + * @throws {UnmatchedSearchPatternsError} If there is a pattern that doesn't + * match any files. + */ +async function globSearch({ + basePath, + patterns, + rawPatterns, + configs, + errorOnUnmatchedPattern +}) { + + if (patterns.length === 0) { + return []; + } + + /* + * In this section we are converting the patterns into Minimatch + * instances for performance reasons. Because we are doing the same + * matches repeatedly, it's best to compile those patterns once and + * reuse them multiple times. + * + * To do that, we convert any patterns with an absolute path into a + * relative path and normalize it to Posix-style slashes. We also keep + * track of the relative patterns to map them back to the original + * patterns, which we need in order to throw an error if there are any + * unmatched patterns. + */ + const relativeToPatterns = new Map(); + const matchers = patterns.map((pattern, i) => { + const patternToUse = path.isAbsolute(pattern) + ? normalizeToPosix(path.relative(basePath, pattern)) + : pattern; + + relativeToPatterns.set(patternToUse, patterns[i]); + + return new Minimatch(patternToUse, MINIMATCH_OPTIONS); + }); + + /* + * We track unmatched patterns because we may want to throw an error when + * they occur. To start, this set is initialized with all of the patterns. + * Every time a match occurs, the pattern is removed from the set, making + * it easy to tell if we have any unmatched patterns left at the end of + * search. + */ + const unmatchedPatterns = new Set([...relativeToPatterns.keys()]); + + const filePaths = (await doFsWalk(basePath, { + + deepFilter(entry) { + const relativePath = normalizeToPosix(path.relative(basePath, entry.path)); + const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true)); + + return matchesPattern && !configs.isDirectoryIgnored(entry.path); + }, + entryFilter(entry) { + const relativePath = normalizeToPosix(path.relative(basePath, entry.path)); + + // entries may be directories or files so filter out directories + if (entry.dirent.isDirectory()) { + return false; + } + + /* + * Optimization: We need to track when patterns are left unmatched + * and so we use `unmatchedPatterns` to do that. There is a bit of + * complexity here because the same file can be matched by more than + * one pattern. So, when we start, we actually need to test every + * pattern against every file. Once we know there are no remaining + * unmatched patterns, then we can switch to just looking for the + * first matching pattern for improved speed. + */ + const matchesPattern = unmatchedPatterns.size > 0 + ? matchers.reduce((previousValue, matcher) => { + const pathMatches = matcher.match(relativePath); + + /* + * We updated the unmatched patterns set only if the path + * matches and the file isn't ignored. If the file is + * ignored, that means there wasn't a match for the + * pattern so it should not be removed. + * + * Performance note: isFileIgnored() aggressively caches + * results so there is no performance penalty for calling + * it twice with the same argument. + */ + if (pathMatches && !configs.isFileIgnored(entry.path)) { + unmatchedPatterns.delete(matcher.pattern); + } + + return pathMatches || previousValue; + }, false) + : matchers.some(matcher => matcher.match(relativePath)); + + return matchesPattern && !configs.isFileIgnored(entry.path); + } + + })).map(entry => entry.path); + + // now check to see if we have any unmatched patterns + if (errorOnUnmatchedPattern && unmatchedPatterns.size > 0) { + throw new UnmatchedSearchPatternsError({ + basePath, + unmatchedPatterns: [...unmatchedPatterns].map( + pattern => relativeToPatterns.get(pattern) + ), + patterns, + rawPatterns + }); + } + + return filePaths; +} + +/** + * Throws an error for unmatched patterns. The error will only contain information about the first one. + * Checks to see if there are any ignored results for a given search. + * @param {Object} options The options for this function. + * @param {string} options.basePath The directory to search. + * @param {Array} options.patterns An array of glob patterns + * that were used in the original search. + * @param {Array} options.rawPatterns An array of glob patterns + * as the user inputted them. Used for errors. + * @param {Array} options.unmatchedPatterns A non-empty array of glob patterns + * that were unmatched in the original search. + * @returns {void} Always throws an error. + * @throws {NoFilesFoundError} If the first unmatched pattern + * doesn't match any files even when there are no ignores. + * @throws {AllFilesIgnoredError} If the first unmatched pattern + * matches some files when there are no ignores. + */ +async function throwErrorForUnmatchedPatterns({ + basePath, + patterns, + rawPatterns, + unmatchedPatterns +}) { + + const pattern = unmatchedPatterns[0]; + const rawPattern = rawPatterns[patterns.indexOf(pattern)]; + + const patternHasMatch = await globMatch({ + basePath, + pattern + }); + + if (patternHasMatch) { + throw new AllFilesIgnoredError(rawPattern); + } + + // if we get here there are truly no matches + throw new NoFilesFoundError(rawPattern, true); +} + +/** + * Performs multiple glob searches in parallel. + * @param {Object} options The options for this function. + * @param {Map} options.searches + * An array of glob patterns to match. + * @param {FlatConfigArray} options.configs The config array to use for + * determining what to ignore. + * @param {boolean} options.errorOnUnmatchedPattern Determines if an + * unmatched glob pattern should throw an error. + * @returns {Promise>} An array of matching file paths + * or an empty array if there are no matches. + */ +async function globMultiSearch({ searches, configs, errorOnUnmatchedPattern }) { + + /* + * For convenience, we normalized the search map into an array of objects. + * Next, we filter out all searches that have no patterns. This happens + * primarily for the cwd, which is prepopulated in the searches map as an + * optimization. However, if it has no patterns, it means all patterns + * occur outside of the cwd and we can safely filter out that search. + */ + const normalizedSearches = [...searches].map( + ([basePath, { patterns, rawPatterns }]) => ({ basePath, patterns, rawPatterns }) + ).filter(({ patterns }) => patterns.length > 0); + + const results = await Promise.allSettled( + normalizedSearches.map( + ({ basePath, patterns, rawPatterns }) => globSearch({ + basePath, + patterns, + rawPatterns, + configs, + errorOnUnmatchedPattern + }) + ) + ); + + const filePaths = []; + + for (let i = 0; i < results.length; i++) { + + const result = results[i]; + const currentSearch = normalizedSearches[i]; + + if (result.status === "fulfilled") { + + // if the search was successful just add the results + if (result.value.length > 0) { + filePaths.push(...result.value); + } + + continue; + } + + // if we make it here then there was an error + const error = result.reason; + + // unexpected errors should be re-thrown + if (!error.basePath) { + throw error; + } + + if (errorOnUnmatchedPattern) { + + await throwErrorForUnmatchedPatterns({ + ...currentSearch, + unmatchedPatterns: error.unmatchedPatterns + }); + + } + + } + + return [...new Set(filePaths)]; + +} + /** * Finds all files matching the options specified. * @param {Object} args The arguments objects. @@ -104,6 +462,8 @@ function isGlobPattern(pattern) { * false to not interpret glob patterns. * @param {string} args.cwd The current working directory to find from. * @param {FlatConfigArray} args.configs The configs for the current run. + * @param {boolean} args.errorOnUnmatchedPattern Determines if an unmatched pattern + * should throw an error. * @returns {Promise>} The fully resolved file paths. * @throws {AllFilesIgnoredError} If there are no results due to an ignore pattern. * @throws {NoFilesFoundError} If no files matched the given patterns. @@ -112,25 +472,28 @@ async function findFiles({ patterns, globInputPaths, cwd, - configs + configs, + errorOnUnmatchedPattern }) { const results = []; - const globbyPatterns = []; const missingPatterns = []; + let globbyPatterns = []; + let rawPatterns = []; + const searches = new Map([[cwd, { patterns: globbyPatterns, rawPatterns: [] }]]); // check to see if we have explicit files and directories const filePaths = patterns.map(filePath => path.resolve(cwd, filePath)); const stats = await Promise.all( filePaths.map( - filePath => fsp.stat(filePath).catch(() => {}) + filePath => fsp.stat(filePath).catch(() => { }) ) ); stats.forEach((stat, index) => { const filePath = filePaths[index]; - const pattern = patterns[index]; + const pattern = normalizeToPosix(patterns[index]); if (stat) { @@ -138,101 +501,63 @@ async function findFiles({ if (stat.isFile()) { results.push({ filePath, - ignored: configs.isIgnored(filePath) + ignored: configs.isFileIgnored(filePath) }); } // directories need extensions attached if (stat.isDirectory()) { - // filePatterns are all relative to cwd - const filePatterns = configs.files - .filter(filePattern => { - - // can only do this for strings, not functions - if (typeof filePattern !== "string") { - return false; - } - - // patterns ending with * are not used for file search - if (filePattern.endsWith("*")) { - return false; - } - - // not sure how to handle negated patterns yet - if (filePattern.startsWith("!")) { - return false; - } - - // check if the pattern would be inside the cwd or not - const fullFilePattern = path.join(cwd, filePattern); - const relativeFilePattern = path.relative(configs.basePath, fullFilePattern); - - return !relativeFilePattern.startsWith(".."); - }) - .map(filePattern => { - if (filePattern.startsWith("**")) { - return path.join(pattern, filePattern); - } - - // adjust the path to be relative to the cwd - return path.relative( - cwd, - path.join(configs.basePath, filePattern) - ); - }) - .map(normalizeToPosix); - - if (filePatterns.length) { - globbyPatterns.push(...filePatterns); + // group everything in cwd together and split out others + if (isPathInside(filePath, cwd)) { + ({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd)); + } else { + if (!searches.has(filePath)) { + searches.set(filePath, { patterns: [], rawPatterns: [] }); + } + ({ patterns: globbyPatterns, rawPatterns } = searches.get(filePath)); } + globbyPatterns.push(`${normalizeToPosix(filePath)}/**`); + rawPatterns.push(pattern); } return; } // save patterns for later use based on whether globs are enabled - if (globInputPaths && isGlobPattern(filePath)) { - globbyPatterns.push(pattern); - } else { - missingPatterns.push(pattern); - } - }); + if (globInputPaths && isGlobPattern(pattern)) { - // note: globbyPatterns can be an empty array - const globbyResults = (await globby(globbyPatterns, { - cwd, - absolute: true, - ignore: configs.ignores.filter(matcher => typeof matcher === "string") - })); - - // if there are no results, tell the user why - if (!results.length && !globbyResults.length) { - - // try globby without ignoring anything - /* eslint-disable no-unreachable-loop -- We want to exit early. */ - for (const globbyPattern of globbyPatterns) { + const basePath = path.resolve(cwd, globParent(pattern)); - /* eslint-disable-next-line no-unused-vars -- Want to exit early. */ - for await (const filePath of globby.stream(globbyPattern, { cwd, absolute: true })) { - - // files were found but ignored - throw new AllFilesIgnoredError(globbyPattern); + // group in cwd if possible and split out others + if (isPathInside(basePath, cwd)) { + ({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd)); + } else { + if (!searches.has(basePath)) { + searches.set(basePath, { patterns: [], rawPatterns: [] }); + } + ({ patterns: globbyPatterns, rawPatterns } = searches.get(basePath)); } - // no files were found - throw new NoFilesFoundError(globbyPattern, globInputPaths); + globbyPatterns.push(filePath); + rawPatterns.push(pattern); + } else { + missingPatterns.push(pattern); } - /* eslint-enable no-unreachable-loop -- Go back to normal. */ - - } + }); // there were patterns that didn't match anything, tell the user - if (missingPatterns.length) { + if (errorOnUnmatchedPattern && missingPatterns.length) { throw new NoFilesFoundError(missingPatterns[0], globInputPaths); } + // now we are safe to do the search + const globbyResults = await globMultiSearch({ + searches, + configs, + errorOnUnmatchedPattern + }); return [ ...results, @@ -243,41 +568,6 @@ async function findFiles({ ]; } - -/** - * Checks whether a file exists at the given location - * @param {string} resolvedPath A path from the CWD - * @throws {Error} As thrown by `fs.statSync` or `fs.isFile`. - * @returns {boolean} `true` if a file exists - */ -function fileExists(resolvedPath) { - try { - return fs.statSync(resolvedPath).isFile(); - } catch (error) { - if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) { - return false; - } - throw error; - } -} - -/** - * Checks whether a directory exists at the given location - * @param {string} resolvedPath A path from the CWD - * @throws {Error} As thrown by `fs.statSync` or `fs.isDirectory`. - * @returns {boolean} `true` if a directory exists - */ -function directoryExists(resolvedPath) { - try { - return fs.statSync(resolvedPath).isDirectory(); - } catch (error) { - if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) { - return false; - } - throw error; - } -} - //----------------------------------------------------------------------------- // Results-related Helpers //----------------------------------------------------------------------------- @@ -322,6 +612,7 @@ function createIgnoreResult(filePath, baseDir) { message } ], + suppressedMessages: [], errorCount: 0, warningCount: 1, fatalErrorCount: 0, @@ -378,12 +669,10 @@ function processOptions({ cacheStrategy = "metadata", cwd = process.cwd(), errorOnUnmatchedPattern = true, - extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature. fix = false, fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property. globInputPaths = true, ignore = true, - ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT. ignorePatterns = null, overrideConfig = null, overrideConfigFile = null, @@ -405,12 +694,18 @@ function processOptions({ if (unknownOptionKeys.includes("envs")) { errors.push("'envs' has been removed."); } + if (unknownOptionKeys.includes("extensions")) { + errors.push("'extensions' has been removed."); + } if (unknownOptionKeys.includes("resolvePluginsRelativeTo")) { errors.push("'resolvePluginsRelativeTo' has been removed."); } if (unknownOptionKeys.includes("globals")) { errors.push("'globals' has been removed. Please use the 'overrideConfig.languageOptions.globals' option instead."); } + if (unknownOptionKeys.includes("ignorePath")) { + errors.push("'ignorePath' has been removed."); + } if (unknownOptionKeys.includes("ignorePattern")) { errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead."); } @@ -451,9 +746,6 @@ function processOptions({ if (typeof errorOnUnmatchedPattern !== "boolean") { errors.push("'errorOnUnmatchedPattern' must be a boolean."); } - if (!isArrayOfNonEmptyString(extensions) && extensions !== null) { - errors.push("'extensions' must be an array of non-empty strings or null."); - } if (typeof fix !== "boolean" && typeof fix !== "function") { errors.push("'fix' must be a boolean or a function."); } @@ -466,9 +758,6 @@ function processOptions({ if (typeof ignore !== "boolean") { errors.push("'ignore' must be a boolean."); } - if (!isNonEmptyString(ignorePath) && ignorePath !== null) { - errors.push("'ignorePath' must be a non-empty string or null."); - } if (typeof overrideConfig !== "object") { errors.push("'overrideConfig' must be an object or null."); } @@ -507,12 +796,10 @@ function processOptions({ overrideConfig, cwd, errorOnUnmatchedPattern, - extensions, fix, fixTypes, globInputPaths, ignore, - ignorePath, ignorePatterns, reportUnusedDisableDirectives }; @@ -602,8 +889,6 @@ function getCacheFile(cacheFile, cwd) { module.exports = { isGlobPattern, - directoryExists, - fileExists, findFiles, isNonEmptyString, diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 9a3bd66e487..e1d2116944e 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -36,11 +36,12 @@ const { version } = require("../../package.json"); /** @typedef {import("../shared/types").Plugin} Plugin */ /** @typedef {import("../shared/types").Rule} Rule */ /** @typedef {import("../shared/types").LintResult} LintResult */ +/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */ /** * The main formatter object. * @typedef LoadedFormatter - * @property {function(LintResult[]): string | Promise} format format function. + * @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise} format format function. */ /** @@ -625,14 +626,16 @@ class ESLint { /** * The main formatter method. * @param {LintResult[]} results The lint results to format. + * @param {ResultsMeta} resultsMeta Warning count and max threshold. * @returns {string | Promise} The formatted lint results. */ - format(results) { + format(results, resultsMeta) { let rulesMeta = null; results.sort(compareResultsByFilePath); return formatter(results, { + ...resultsMeta, get cwd() { return options.cwd; }, diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js index 6755356f17c..d88cf178233 100644 --- a/lib/eslint/flat-eslint.js +++ b/lib/eslint/flat-eslint.js @@ -16,7 +16,6 @@ const findUp = require("find-up"); const { version } = require("../../package.json"); const { Linter } = require("../linter"); const { getRuleFromConfig } = require("../config/flat-config-helpers"); -const { gitignoreToMinimatch } = require("@humanwhocodes/gitignore-to-minimatch"); const { Legacy: { ConfigOps: { @@ -28,7 +27,6 @@ const { } = require("@eslint/eslintrc"); const { - fileExists, findFiles, getCacheFile, @@ -59,6 +57,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache"); /** @typedef {import("../shared/types").LintMessage} LintMessage */ /** @typedef {import("../shared/types").ParserOptions} ParserOptions */ /** @typedef {import("../shared/types").Plugin} Plugin */ +/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */ /** @typedef {import("../shared/types").RuleConf} RuleConf */ /** @typedef {import("../shared/types").Rule} Rule */ /** @typedef {ReturnType} ExtractedConfig */ @@ -73,13 +72,11 @@ const LintResultCache = require("../cli-engine/lint-result-cache"); * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files. * @property {string} [cwd] The value to use for the current working directory. * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`. - * @property {string[]} [extensions] An array of file extensions to check. * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean. * @property {string[]} [fixTypes] Array of rule types to apply fixes for. * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file. - * @property {boolean} [ignore] False disables use of .eslintignore. - * @property {string} [ignorePath] The ignore file to use instead of .eslintignore. - * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to .eslintignore. + * @property {boolean} [ignore] False disables all ignore patterns except for the default ones. + * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores. * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy; * doesn't do any config file lookup when `true`; considered to be a config filename @@ -96,6 +93,7 @@ const FLAT_CONFIG_FILENAME = "eslint.config.js"; const debug = require("debug")("eslint:flat-eslint"); const removedFormatters = new Set(["table", "codeframe"]); const privateMembers = new WeakMap(); +const importedConfigFileModificationTime = new Map(); /** * It will calculate the error and warning count for collection of messages per file @@ -152,30 +150,6 @@ function calculateStatsPerRun(results) { }); } -/** - * Loads global ignore patterns from an ignore file (usually .eslintignore). - * @param {string} filePath The filename to load. - * @returns {ignore} A function encapsulating the ignore patterns. - * @throws {Error} If the file cannot be read. - * @private - */ -async function loadIgnoreFilePatterns(filePath) { - debug(`Loading ignore file: ${filePath}`); - - try { - const ignoreFileText = await fs.readFile(filePath, { encoding: "utf8" }); - - return ignoreFileText - .split(/\r?\n/gu) - .filter(line => line.trim() !== "" && !line.startsWith("#")); - - } catch (e) { - debug(`Error reading ignore file: ${filePath}`); - e.message = `Cannot read ignore file: ${filePath}\nError: ${e.message}`; - throw e; - } -} - /** * Create rulesMeta object. * @param {Map} rules a map of rules from which to generate the object. @@ -188,6 +162,16 @@ function createRulesMeta(rules) { }, {}); } +/** + * Return the absolute path of a file named `"__placeholder__.js"` in a given directory. + * This is used as a replacement for a missing file path. + * @param {string} cwd An absolute directory path. + * @returns {string} The absolute path of a file named `"__placeholder__.js"` in the given directory. + */ +function getPlaceholderPath(cwd) { + return path.join(cwd, "__placeholder__.js"); +} + /** @type {WeakMap} */ const usedDeprecatedRulesCache = new WeakMap(); @@ -204,7 +188,7 @@ function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) { } = privateMembers.get(eslint); const filePath = path.isAbsolute(maybeFilePath) ? maybeFilePath - : path.join(cwd, "__placeholder__.js"); + : getPlaceholderPath(cwd); const config = configs.getConfig(filePath); // Most files use the same config, so cache it. @@ -289,24 +273,51 @@ function findFlatConfigFile(cwd) { /** * Load the config array from the given filename. * @param {string} filePath The filename to load from. - * @param {Object} options Options to help load the config file. - * @param {string} options.basePath The base path for the config array. - * @param {boolean} options.shouldIgnore Whether to honor ignore patterns. - * @returns {Promise} The config array loaded from the config file. + * @returns {Promise} The config loaded from the config file. */ -async function loadFlatConfigFile(filePath, { basePath, shouldIgnore }) { +async function loadFlatConfigFile(filePath) { debug(`Loading config from ${filePath}`); const fileURL = pathToFileURL(filePath); debug(`Config file URL is ${fileURL}`); - const module = await import(fileURL); + const mtime = (await fs.stat(filePath)).mtime.getTime(); - return new FlatConfigArray(module.default, { - basePath, - shouldIgnore - }); + /* + * Append a query with the config file's modification time (`mtime`) in order + * to import the current version of the config file. Without the query, `import()` would + * cache the config file module by the pathname only, and then always return + * the same version (the one that was actual when the module was imported for the first time). + * + * This ensures that the config file module is loaded and executed again + * if it has been changed since the last time it was imported. + * If it hasn't been changed, `import()` will just return the cached version. + * + * Note that we should not overuse queries (e.g., by appending the current time + * to always reload the config file module) as that could cause memory leaks + * because entries are never removed from the import cache. + */ + fileURL.searchParams.append("mtime", mtime); + + /* + * With queries, we can bypass the import cache. However, when import-ing a CJS module, + * Node.js uses the require infrastructure under the hood. That includes the require cache, + * which caches the config file module by its file path (queries have no effect). + * Therefore, we also need to clear the require cache before importing the config file module. + * In order to get the same behavior with ESM and CJS config files, in particular - to reload + * the config file only if it has been changed, we track file modification times and clear + * the require cache only if the file has been changed. + */ + if (importedConfigFileModificationTime.get(filePath) !== mtime) { + delete require.cache[filePath]; + } + + const config = (await import(fileURL)).default; + + importedConfigFileModificationTime.set(filePath, mtime); + + return config; } /** @@ -317,12 +328,11 @@ async function loadFlatConfigFile(filePath, { basePath, shouldIgnore }) { */ async function calculateConfigArray(eslint, { cwd, + baseConfig, overrideConfig, configFile, ignore: shouldIgnore, - ignorePath, - ignorePatterns, - extensions + ignorePatterns }) { // check for cached instance @@ -350,45 +360,24 @@ async function calculateConfigArray(eslint, { basePath = path.resolve(path.dirname(configFilePath)); } - // load config array - let configs; + const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore }); + + // load config file if (configFilePath) { - configs = await loadFlatConfigFile(configFilePath, { - basePath, - shouldIgnore - }); - } else { - configs = new FlatConfigArray([], { basePath, shouldIgnore }); + const fileConfig = await loadFlatConfigFile(configFilePath); + + if (Array.isArray(fileConfig)) { + configs.push(...fileConfig); + } else { + configs.push(fileConfig); + } } // add in any configured defaults configs.push(...slots.defaultConfigs); - // if there are any extensions, create configs for them for easier matching - if (extensions && extensions.length) { - configs.push({ - files: extensions.map(ext => `**/*${ext}`) - }); - } - let allIgnorePatterns = []; - let ignoreFilePath; - - // load ignore file if necessary - if (shouldIgnore) { - if (ignorePath) { - ignoreFilePath = path.resolve(cwd, ignorePath); - allIgnorePatterns = await loadIgnoreFilePatterns(ignoreFilePath); - } else { - ignoreFilePath = path.resolve(cwd, ".eslintignore"); - - // no error if .eslintignore doesn't exist` - if (fileExists(ignoreFilePath)) { - allIgnorePatterns = await loadIgnoreFilePatterns(ignoreFilePath); - } - } - } // append command line ignore patterns if (ignorePatterns) { @@ -414,17 +403,6 @@ async function calculateConfigArray(eslint, { const negated = pattern.startsWith("!"); const basePattern = negated ? pattern.slice(1) : pattern; - /* - * Ignore patterns are considered relative to a directory - * when the pattern contains a slash in a position other - * than the last character. If that's the case, we need to - * add the relative ignore path to the current pattern to - * get the correct behavior. Otherwise, no change is needed. - */ - if (!basePattern.includes("/") || basePattern.endsWith("/")) { - return pattern; - } - return (negated ? "!" : "") + path.posix.join(relativeIgnorePath, basePattern); }); @@ -437,7 +415,7 @@ async function calculateConfigArray(eslint, { * so they can override default ignores. */ configs.push({ - ignores: allIgnorePatterns.map(gitignoreToMinimatch) + ignores: allIgnorePatterns }); } @@ -490,7 +468,7 @@ function verifyText({ * `config.extractConfig(filePath)` requires an absolute path, but `linter` * doesn't know CWD, so it gives `linter` an absolute path always. */ - const filePathToVerify = filePath === "" ? path.join(cwd, "__placeholder__.js") : filePath; + const filePathToVerify = filePath === "" ? getPlaceholderPath(cwd) : filePath; const { fixed, messages, output } = linter.verifyAndFix( text, configs, @@ -515,6 +493,7 @@ function verifyText({ const result = { filePath: filePath === "" ? filePath : path.resolve(filePath), messages, + suppressedMessages: linter.getSuppressedMessages(), ...calculateStatsPerFile(messages) }; @@ -586,6 +565,14 @@ function *iterateRuleDeprecationWarnings(configs) { } } +/** + * Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine. + * @returns {TypeError} An error object. + */ +function createExtraneousResultsError() { + return new TypeError("Results object was not created from this ESLint instance."); +} + //----------------------------------------------------------------------------- // Main API //----------------------------------------------------------------------------- @@ -689,11 +676,13 @@ class FlatESLint { results.forEach(result => { const filteredMessages = result.messages.filter(isErrorMessage); + const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage); if (filteredMessages.length > 0) { filtered.push({ ...result, messages: filteredMessages, + suppressedMessages: filteredSuppressedMessages, errorCount: filteredMessages.length, warningCount: 0, fixableErrorCount: result.fixableErrorCount, @@ -714,14 +703,16 @@ class FlatESLint { */ getRulesMetaForResults(results) { - const resultRules = new Map(); - // short-circuit simple case if (results.length === 0) { - return resultRules; + return {}; } - const { configs } = privateMembers.get(this); + const resultRules = new Map(); + const { + configs, + options: { cwd } + } = privateMembers.get(this); /* * We can only accurately return rules meta information for linting results if the @@ -730,7 +721,7 @@ class FlatESLint { * to let the user know we can't do anything here. */ if (!configs) { - throw new TypeError("Results object was not created from this ESLint instance."); + throw createExtraneousResultsError(); } for (const result of results) { @@ -739,18 +730,26 @@ class FlatESLint { * Normalize filename for . */ const filePath = result.filePath === "" - ? "__placeholder__.js" : result.filePath; + ? getPlaceholderPath(cwd) : result.filePath; + const allMessages = result.messages.concat(result.suppressedMessages); - /* - * All of the plugin and rule information is contained within the - * calculated config for the given file. - */ - const config = configs.getConfig(filePath); + for (const { ruleId } of allMessages) { + if (!ruleId) { + continue; + } - for (const { ruleId } of result.messages) { + /* + * All of the plugin and rule information is contained within the + * calculated config for the given file. + */ + const config = configs.getConfig(filePath); + + if (!config) { + throw createExtraneousResultsError(); + } const rule = getRuleFromConfig(ruleId, config); - // ensure the rule exists exists + // ensure the rule exists if (!rule) { throw new TypeError(`Could not find the rule "${ruleId}".`); } @@ -786,8 +785,8 @@ class FlatESLint { fix, fixTypes, reportUnusedDisableDirectives, - extensions, - globInputPaths + globInputPaths, + errorOnUnmatchedPattern } = eslintOptions; const startTime = Date.now(); const usedConfigs = []; @@ -812,9 +811,9 @@ class FlatESLint { const filePaths = await findFiles({ patterns: typeof patterns === "string" ? [patterns] : patterns, cwd, - extensions, globInputPaths, - configs + configs, + errorOnUnmatchedPattern }); debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`); @@ -877,7 +876,7 @@ class FlatESLint { } - // set up fixer for fixtypes if necessary + // set up fixer for fixTypes if necessary let fixer = fix; if (fix && fixTypesSet) { @@ -1053,7 +1052,7 @@ class FlatESLint { * The following values are allowed: * - `undefined` ... Load `stylish` builtin formatter. * - A builtin formatter name ... Load the builtin formatter. - * - A thirdparty formatter name: + * - A third-party formatter name: * - `foo` → `eslint-formatter-foo` * - `@foo` → `@foo/eslint-formatter` * - `@foo/bar` → `@foo/eslint-formatter-bar` @@ -1085,7 +1084,7 @@ class FlatESLint { const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter"); // TODO: This is pretty dirty...would be nice to clean up at some point. - formatterPath = ModuleResolver.resolve(npmFormat, path.join(cwd, "__placeholder__.js")); + formatterPath = ModuleResolver.resolve(npmFormat, getPlaceholderPath(cwd)); } catch { formatterPath = path.resolve(__dirname, "../", "cli-engine", "formatters", `${normalizedFormatName}.js`); } @@ -1119,14 +1118,17 @@ class FlatESLint { /** * The main formatter method. * @param {LintResults[]} results The lint results to format. + * @param {ResultsMeta} resultsMeta Warning count and max threshold. * @returns {string} The formatted lint results. */ - format(results) { + format(results, resultsMeta) { let rulesMeta = null; results.sort(compareResultsByFilePath); return formatter(results, { + ...resultsMeta, + cwd, get rulesMeta() { if (!rulesMeta) { rulesMeta = eslint.getRulesMetaForResults(results); @@ -1175,5 +1177,6 @@ class FlatESLint { //------------------------------------------------------------------------------ module.exports = { - FlatESLint + FlatESLint, + findFlatConfigFile }; diff --git a/lib/linter/code-path-analysis/code-path-segment.js b/lib/linter/code-path-analysis/code-path-segment.js index eeedd27524b..fd2726a9937 100644 --- a/lib/linter/code-path-analysis/code-path-segment.js +++ b/lib/linter/code-path-analysis/code-path-segment.js @@ -88,10 +88,10 @@ class CodePathSegment { } }); - /* istanbul ignore if */ + /* c8 ignore start */ if (debug.enabled) { this.internal.nodes = []; - } + }/* c8 ignore stop */ } /** diff --git a/lib/linter/code-path-analysis/code-path-state.js b/lib/linter/code-path-analysis/code-path-state.js index 426da8d492e..d187297d32b 100644 --- a/lib/linter/code-path-analysis/code-path-state.js +++ b/lib/linter/code-path-analysis/code-path-state.js @@ -59,7 +59,7 @@ function getContinueContext(state, label) { context = context.upper; } - /* istanbul ignore next: foolproof (syntax error) */ + /* c8 ignore next */ return null; } @@ -79,7 +79,7 @@ function getBreakContext(state, label) { context = context.upper; } - /* istanbul ignore next: foolproof (syntax error) */ + /* c8 ignore next */ return null; } @@ -433,7 +433,7 @@ class CodePathState { */ return context; - /* istanbul ignore next */ + /* c8 ignore next */ default: throw new Error("unreachable"); } @@ -1030,7 +1030,7 @@ class CodePathState { }; break; - /* istanbul ignore next */ + /* c8 ignore next */ default: throw new Error(`unknown type: "${type}"`); } @@ -1095,7 +1095,7 @@ class CodePathState { ); break; - /* istanbul ignore next */ + /* c8 ignore next */ default: throw new Error("unreachable"); } @@ -1392,11 +1392,12 @@ class CodePathState { const context = getBreakContext(this, label); - /* istanbul ignore else: foolproof (syntax error) */ + if (context) { context.brokenForkContext.add(forkContext.head); } + /* c8 ignore next */ forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); } @@ -1417,7 +1418,6 @@ class CodePathState { const context = getContinueContext(this, label); - /* istanbul ignore else: foolproof (syntax error) */ if (context) { if (context.continueDestSegments) { makeLooped(this, forkContext.head, context.continueDestSegments); diff --git a/lib/linter/code-path-analysis/debug-helpers.js b/lib/linter/code-path-analysis/debug-helpers.js index ca64862db32..e06b6cde5f1 100644 --- a/lib/linter/code-path-analysis/debug-helpers.js +++ b/lib/linter/code-path-analysis/debug-helpers.js @@ -20,7 +20,7 @@ const debug = require("debug")("eslint:code-path"); * @param {CodePathSegment} segment A segment to get. * @returns {string} Id of the segment. */ -/* istanbul ignore next */ +/* c8 ignore next */ function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring return segment.id + (segment.reachable ? "" : "!"); } @@ -67,7 +67,7 @@ module.exports = { * @param {boolean} leaving A flag whether or not it's leaving * @returns {void} */ - dumpState: !debug.enabled ? debug : /* istanbul ignore next */ function(node, state, leaving) { + dumpState: !debug.enabled ? debug : /* c8 ignore next */ function(node, state, leaving) { for (let i = 0; i < state.currentSegments.length; ++i) { const segInternal = state.currentSegments[i].internal; @@ -98,7 +98,7 @@ module.exports = { * @see http://www.graphviz.org * @see http://www.webgraphviz.com */ - dumpDot: !debug.enabled ? debug : /* istanbul ignore next */ function(codePath) { + dumpDot: !debug.enabled ? debug : /* c8 ignore next */ function(codePath) { let text = "\n" + "digraph {\n" + diff --git a/lib/linter/code-path-analysis/id-generator.js b/lib/linter/code-path-analysis/id-generator.js index 83787a4ea5a..b580104e1ac 100644 --- a/lib/linter/code-path-analysis/id-generator.js +++ b/lib/linter/code-path-analysis/id-generator.js @@ -33,10 +33,10 @@ class IdGenerator { next() { this.n = 1 + this.n | 0; - /* istanbul ignore if */ + /* c8 ignore start */ if (this.n < 0) { this.n = 1; - } + }/* c8 ignore stop */ return this.prefix + this.n; } diff --git a/lib/linter/config-comment-parser.js b/lib/linter/config-comment-parser.js index b88c5e6c850..643de8f2d31 100644 --- a/lib/linter/config-comment-parser.js +++ b/lib/linter/config-comment-parser.js @@ -131,8 +131,7 @@ module.exports = class ConfigCommentParser { const items = {}; - // Collapse whitespace around commas - string.replace(/\s*,\s*/gu, ",").split(/,+/u).forEach(name => { + string.split(",").forEach(name => { const trimmedName = name.trim(); if (trimmedName) { diff --git a/lib/linter/linter.js b/lib/linter/linter.js index a29ce923792..3b099004d2b 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -18,6 +18,9 @@ const merge = require("lodash.merge"), pkg = require("../../package.json"), astUtils = require("../shared/ast-utils"), + { + directivesPattern + } = require("../shared/directives"), { Legacy: { ConfigOps, @@ -213,6 +216,7 @@ function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, ena if (variable) { variable.eslintUsed = true; + variable.eslintExported = true; } }); @@ -376,7 +380,7 @@ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) { ast.comments.filter(token => token.type !== "Shebang").forEach(comment => { const { directivePart, justificationPart } = extractDirectiveComment(comment.value); - const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(directivePart); + const match = directivesPattern.exec(directivePart); if (!match) { return; @@ -853,47 +857,22 @@ function parse(text, languageOptions, filePath) { } } -/** - * Gets the scope for the current node - * @param {ScopeManager} scopeManager The scope manager for this AST - * @param {ASTNode} currentNode The node to get the scope of - * @returns {eslint-scope.Scope} The scope information for this node - */ -function getScope(scopeManager, currentNode) { - - // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. - const inner = currentNode.type !== "Program"; - - for (let node = currentNode; node; node = node.parent) { - const scope = scopeManager.acquire(node, inner); - - if (scope) { - if (scope.type === "function-expression-name") { - return scope.childScopes[0]; - } - return scope; - } - } - - return scopeManager.scopes[0]; -} - /** * Marks a variable as used in the current scope - * @param {ScopeManager} scopeManager The scope manager for this AST. The scope may be mutated by this function. + * @param {SourceCode} sourceCode The source code for the currently linted file. * @param {ASTNode} currentNode The node currently being traversed * @param {LanguageOptions} languageOptions The options used to parse this text * @param {string} name The name of the variable that should be marked as used. * @returns {boolean} True if the variable was found and marked as used, false if not. */ -function markVariableAsUsed(scopeManager, currentNode, languageOptions, name) { +function markVariableAsUsed(sourceCode, currentNode, languageOptions, name) { const parserOptions = languageOptions.parserOptions; const sourceType = languageOptions.sourceType; const hasGlobalReturn = (parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn) || sourceType === "commonjs"; const specialScope = hasGlobalReturn || sourceType === "module"; - const currentScope = getScope(scopeManager, currentNode); + const currentScope = sourceCode.getScope(currentNode); // Special Node.js scope means we need to start one level deeper const initialScope = currentScope.type === "global" && specialScope ? currentScope.childScopes[0] : currentScope; @@ -1022,9 +1001,9 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO getCwd: () => cwd, getFilename: () => filename, getPhysicalFilename: () => physicalFilename || filename, - getScope: () => getScope(sourceCode.scopeManager, currentNode), + getScope: () => sourceCode.getScope(currentNode), getSourceCode: () => sourceCode, - markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, languageOptions, name), + markVariableAsUsed: name => markVariableAsUsed(sourceCode, currentNode, languageOptions, name), parserOptions: { ...languageOptions.parserOptions }, @@ -1601,12 +1580,18 @@ class Linter { languageOptions.ecmaVersion ); - // add configured globals and language globals - const configuredGlobals = { - ...(getGlobalsForEcmaVersion(languageOptions.ecmaVersion)), - ...(languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0), - ...languageOptions.globals - }; + /* + * add configured globals and language globals + * + * using Object.assign instead of object spread for performance reasons + * https://github.com/eslint/eslint/issues/16302 + */ + const configuredGlobals = Object.assign( + {}, + getGlobalsForEcmaVersion(languageOptions.ecmaVersion), + languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0, + languageOptions.globals + ); // double check that there is a parser to avoid mysterious error messages if (!languageOptions.parser) { diff --git a/lib/linter/timing.js b/lib/linter/timing.js index 914cbf05591..1076ff25887 100644 --- a/lib/linter/timing.js +++ b/lib/linter/timing.js @@ -9,7 +9,7 @@ // Helpers //------------------------------------------------------------------------------ -/* istanbul ignore next */ +/* c8 ignore next */ /** * Align the string to left * @param {string} str string to evaluate @@ -22,7 +22,7 @@ function alignLeft(str, len, ch) { return str + new Array(len - str.length + 1).join(ch || " "); } -/* istanbul ignore next */ +/* c8 ignore next */ /** * Align the string to right * @param {string} str string to evaluate @@ -64,7 +64,7 @@ function getListSize() { return TIMING_ENV_VAR_AS_INTEGER > 10 ? TIMING_ENV_VAR_AS_INTEGER : MINIMUM_SIZE; } -/* istanbul ignore next */ +/* c8 ignore next */ /** * display the data * @param {Object} data Data object to be displayed @@ -119,7 +119,7 @@ function display(data) { console.log(table.join("\n")); // eslint-disable-line no-console -- Debugging function } -/* istanbul ignore next */ +/* c8 ignore next */ module.exports = (function() { const data = Object.create(null); diff --git a/lib/options.js b/lib/options.js index 6d06e3ddce1..2bc4018afb5 100644 --- a/lib/options.js +++ b/lib/options.js @@ -63,261 +63,315 @@ const optionator = require("optionator"); //------------------------------------------------------------------------------ // exports "parse(args)", "generateHelp()", and "generateHelpForOption(optionName)" -module.exports = optionator({ - prepend: "eslint [options] file.js [file.js] [dir]", - defaults: { - concatRepeatedArrays: true, - mergeRepeatedObjects: true - }, - options: [ - { - heading: "Basic configuration" - }, - { + +/** + * Creates the CLI options for ESLint. + * @param {boolean} usingFlatConfig Indicates if flat config is being used. + * @returns {Object} The optionator instance. + */ +module.exports = function(usingFlatConfig) { + + let lookupFlag; + + if (usingFlatConfig) { + lookupFlag = { + option: "config-lookup", + type: "Boolean", + default: "true", + description: "Disable look up for eslint.config.js" + }; + } else { + lookupFlag = { option: "eslintrc", type: "Boolean", default: "true", description: "Disable use of configuration from .eslintrc.*" - }, - { - option: "config", - alias: "c", - type: "path::String", - description: "Use this configuration, overriding .eslintrc.* config options if present" - }, - { + }; + } + + let envFlag; + + if (!usingFlatConfig) { + envFlag = { option: "env", type: "[String]", description: "Specify environments" - }, - { + }; + } + + let extFlag; + + if (!usingFlatConfig) { + extFlag = { option: "ext", type: "[String]", description: "Specify JavaScript file extensions" - }, - { - option: "global", - type: "[String]", - description: "Define global variables" - }, - { - option: "parser", - type: "String", - description: "Specify the parser to be used" - }, - { - option: "parser-options", - type: "Object", - description: "Specify parser options" - }, - { + }; + } + + let resolvePluginsFlag; + + if (!usingFlatConfig) { + resolvePluginsFlag = { option: "resolve-plugins-relative-to", type: "path::String", description: "A folder where plugins should be resolved from, CWD by default" - }, - { - heading: "Specifying rules and plugins" - }, - { - option: "plugin", - type: "[String]", - description: "Specify plugins" - }, - { - option: "rule", - type: "Object", - description: "Specify rules" - }, - { + }; + } + + let rulesDirFlag; + + if (!usingFlatConfig) { + rulesDirFlag = { option: "rulesdir", type: "[path::String]", description: "Load additional rules from this directory. Deprecated: Use rules from plugins" - }, - { - heading: "Fixing problems" - }, - { - option: "fix", - type: "Boolean", - default: false, - description: "Automatically fix problems" - }, - { - option: "fix-dry-run", - type: "Boolean", - default: false, - description: "Automatically fix problems without saving the changes to the file system" - }, - { - option: "fix-type", - type: "Array", - description: "Specify the types of fixes to apply (directive, problem, suggestion, layout)" - }, - { - heading: "Ignoring files" - }, - { + }; + } + + let ignorePathFlag; + + if (!usingFlatConfig) { + ignorePathFlag = { option: "ignore-path", type: "path::String", description: "Specify path of ignore file" - }, - { - option: "ignore", - type: "Boolean", - default: "true", - description: "Disable use of ignore files and patterns" - }, - { - option: "ignore-pattern", - type: "[String]", - description: "Pattern of files to ignore (in addition to those in .eslintignore)", - concatRepeatedArrays: [true, { - oneValuePerFlag: true - }] - }, - { - heading: "Using stdin" - }, - { - option: "stdin", - type: "Boolean", - default: "false", - description: "Lint code provided on " - }, - { - option: "stdin-filename", - type: "String", - description: "Specify filename to process STDIN as" - }, - { - heading: "Handling warnings" - }, - { - option: "quiet", - type: "Boolean", - default: "false", - description: "Report errors only" - }, - { - option: "max-warnings", - type: "Int", - default: "-1", - description: "Number of warnings to trigger nonzero exit code" - }, - { - heading: "Output" - }, - { - option: "output-file", - alias: "o", - type: "path::String", - description: "Specify file to write report to" - }, - { - option: "format", - alias: "f", - type: "String", - default: "stylish", - description: "Use a specific output format" - }, - { - option: "color", - type: "Boolean", - alias: "no-color", - description: "Force enabling/disabling of color" - }, - { - heading: "Inline configuration comments" - }, - { - option: "inline-config", - type: "Boolean", - default: "true", - description: "Prevent comments from changing config or rules" - }, - { - option: "report-unused-disable-directives", - type: "Boolean", - default: void 0, - description: "Adds reported errors for unused eslint-disable directives" - }, - { - heading: "Caching" - }, - { - option: "cache", - type: "Boolean", - default: "false", - description: "Only check changed files" - }, - { - option: "cache-file", - type: "path::String", - default: ".eslintcache", - description: "Path to the cache file. Deprecated: use --cache-location" - }, - { - option: "cache-location", - type: "path::String", - description: "Path to the cache file or directory" - }, - { - option: "cache-strategy", - dependsOn: ["cache"], - type: "String", - default: "metadata", - enum: ["metadata", "content"], - description: "Strategy to use for detecting changed files in the cache" - }, - { - heading: "Miscellaneous" - }, - { - option: "init", - type: "Boolean", - default: "false", - description: "Run config initialization wizard" - }, - { - option: "env-info", - type: "Boolean", - default: "false", - description: "Output execution environment information" - }, - { - option: "error-on-unmatched-pattern", - type: "Boolean", - default: "true", - description: "Prevent errors when pattern is unmatched" - }, - { - option: "exit-on-fatal-error", - type: "Boolean", - default: "false", - description: "Exit with exit code 2 in case of fatal error" - }, - { - option: "debug", - type: "Boolean", - default: false, - description: "Output debugging information" - }, - { - option: "help", - alias: "h", - type: "Boolean", - description: "Show help" - }, - { - option: "version", - alias: "v", - type: "Boolean", - description: "Output the version number" - }, - { - option: "print-config", - type: "path::String", - description: "Print the configuration for the given file" - } - ] -}); + }; + } + + return optionator({ + prepend: "eslint [options] file.js [file.js] [dir]", + defaults: { + concatRepeatedArrays: true, + mergeRepeatedObjects: true + }, + options: [ + { + heading: "Basic configuration" + }, + lookupFlag, + { + option: "config", + alias: "c", + type: "path::String", + description: usingFlatConfig + ? "Use this configuration instead of eslint.config.js" + : "Use this configuration, overriding .eslintrc.* config options if present" + }, + envFlag, + extFlag, + { + option: "global", + type: "[String]", + description: "Define global variables" + }, + { + option: "parser", + type: "String", + description: "Specify the parser to be used" + }, + { + option: "parser-options", + type: "Object", + description: "Specify parser options" + }, + resolvePluginsFlag, + { + heading: "Specify Rules and Plugins" + }, + { + option: "plugin", + type: "[String]", + description: "Specify plugins" + }, + { + option: "rule", + type: "Object", + description: "Specify rules" + }, + rulesDirFlag, + { + heading: "Fix Problems" + }, + { + option: "fix", + type: "Boolean", + default: false, + description: "Automatically fix problems" + }, + { + option: "fix-dry-run", + type: "Boolean", + default: false, + description: "Automatically fix problems without saving the changes to the file system" + }, + { + option: "fix-type", + type: "Array", + description: "Specify the types of fixes to apply (directive, problem, suggestion, layout)" + }, + { + heading: "Ignore Files" + }, + ignorePathFlag, + { + option: "ignore", + type: "Boolean", + default: "true", + description: "Disable use of ignore files and patterns" + }, + { + option: "ignore-pattern", + type: "[String]", + description: "Pattern of files to ignore (in addition to those in .eslintignore)", + concatRepeatedArrays: [true, { + oneValuePerFlag: true + }] + }, + { + heading: "Use stdin" + }, + { + option: "stdin", + type: "Boolean", + default: "false", + description: "Lint code provided on " + }, + { + option: "stdin-filename", + type: "String", + description: "Specify filename to process STDIN as" + }, + { + heading: "Handle Warnings" + }, + { + option: "quiet", + type: "Boolean", + default: "false", + description: "Report errors only" + }, + { + option: "max-warnings", + type: "Int", + default: "-1", + description: "Number of warnings to trigger nonzero exit code" + }, + { + heading: "Output" + }, + { + option: "output-file", + alias: "o", + type: "path::String", + description: "Specify file to write report to" + }, + { + option: "format", + alias: "f", + type: "String", + default: "stylish", + description: "Use a specific output format" + }, + { + option: "color", + type: "Boolean", + alias: "no-color", + description: "Force enabling/disabling of color" + }, + { + heading: "Inline configuration comments" + }, + { + option: "inline-config", + type: "Boolean", + default: "true", + description: "Prevent comments from changing config or rules" + }, + { + option: "report-unused-disable-directives", + type: "Boolean", + default: void 0, + description: "Adds reported errors for unused eslint-disable directives" + }, + { + heading: "Caching" + }, + { + option: "cache", + type: "Boolean", + default: "false", + description: "Only check changed files" + }, + { + option: "cache-file", + type: "path::String", + default: ".eslintcache", + description: "Path to the cache file. Deprecated: use --cache-location" + }, + { + option: "cache-location", + type: "path::String", + description: "Path to the cache file or directory" + }, + { + option: "cache-strategy", + dependsOn: ["cache"], + type: "String", + default: "metadata", + enum: ["metadata", "content"], + description: "Strategy to use for detecting changed files in the cache" + }, + { + heading: "Miscellaneous" + }, + { + option: "init", + type: "Boolean", + default: "false", + description: "Run config initialization wizard" + }, + { + option: "env-info", + type: "Boolean", + default: "false", + description: "Output execution environment information" + }, + { + option: "error-on-unmatched-pattern", + type: "Boolean", + default: "true", + description: "Prevent errors when pattern is unmatched" + }, + { + option: "exit-on-fatal-error", + type: "Boolean", + default: "false", + description: "Exit with exit code 2 in case of fatal error" + }, + { + option: "debug", + type: "Boolean", + default: false, + description: "Output debugging information" + }, + { + option: "help", + alias: "h", + type: "Boolean", + description: "Show help" + }, + { + option: "version", + alias: "v", + type: "Boolean", + description: "Output the version number" + }, + { + option: "print-config", + type: "path::String", + description: "Print the configuration for the given file" + } + ].filter(value => !!value) + }); +}; diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js index 19bd8e354f6..97055d104f4 100644 --- a/lib/rule-tester/flat-rule-tester.js +++ b/lib/rule-tester/flat-rule-tester.js @@ -4,7 +4,7 @@ */ "use strict"; -/* eslint-env mocha -- Mocha/Jest wrapper */ +/* globals describe, it -- Mocha globals */ //------------------------------------------------------------------------------ // Requirements @@ -345,7 +345,7 @@ class FlatRuleTester { * @returns {void} */ static setDefaultConfig(config) { - if (typeof config !== "object") { + if (typeof config !== "object" || config === null) { throw new TypeError("FlatRuleTester.setDefaultConfig: config must be an object"); } sharedDefaultConfig = config; @@ -430,7 +430,7 @@ class FlatRuleTester { if (typeof this[DESCRIBE] === "function" || typeof this[IT] === "function") { throw new Error( "Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" + - "See https://eslint.org/docs/developer-guide/nodejs-api#customizing-ruletester for more." + "See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more." ); } if (typeof it === "function") { @@ -619,15 +619,17 @@ class FlatRuleTester { plugins: { "rule-tester": { rules: { - "validate-ast"() { - return { - Program(node) { - beforeAST = cloneDeeplyExcludesParent(node); - }, - "Program:exit"(node) { - afterAST = node; - } - }; + "validate-ast": { + create() { + return { + Program(node) { + beforeAST = cloneDeeplyExcludesParent(node); + }, + "Program:exit"(node) { + afterAST = node; + } + }; + } } } } diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index fe0e468916b..8518299d0b0 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -4,7 +4,7 @@ */ "use strict"; -/* eslint-env mocha -- Mocha wrapper */ +/* globals describe, it -- Mocha globals */ /* * This is a wrapper around mocha to allow for DRY unittests for eslint @@ -314,7 +314,7 @@ function emitLegacyRuleAPIWarning(ruleName) { if (!emitLegacyRuleAPIWarning[`warned-${ruleName}`]) { emitLegacyRuleAPIWarning[`warned-${ruleName}`] = true; process.emitWarning( - `"${ruleName}" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/developer-guide/working-with-rules`, + `"${ruleName}" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules`, "DeprecationWarning" ); } @@ -329,7 +329,7 @@ function emitMissingSchemaWarning(ruleName) { if (!emitMissingSchemaWarning[`warned-${ruleName}`]) { emitMissingSchemaWarning[`warned-${ruleName}`] = true; process.emitWarning( - `"${ruleName}" rule has options but is missing the "meta.schema" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas`, + `"${ruleName}" rule has options but is missing the "meta.schema" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas`, "DeprecationWarning" ); } @@ -412,7 +412,7 @@ class RuleTester { * @returns {void} */ static setDefaultConfig(config) { - if (typeof config !== "object") { + if (typeof config !== "object" || config === null) { throw new TypeError("RuleTester.setDefaultConfig: config must be an object"); } defaultConfig = config; @@ -493,7 +493,7 @@ class RuleTester { if (typeof this[DESCRIBE] === "function" || typeof this[IT] === "function") { throw new Error( "Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" + - "See https://eslint.org/docs/developer-guide/nodejs-api#customizing-ruletester for more." + "See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more." ); } if (typeof it === "function") { @@ -632,14 +632,18 @@ class RuleTester { * The goal is to check whether or not AST was modified when * running the rule under test. */ - linter.defineRule("rule-tester/validate-ast", () => ({ - Program(node) { - beforeAST = cloneDeeplyExcludesParent(node); - }, - "Program:exit"(node) { - afterAST = node; + linter.defineRule("rule-tester/validate-ast", { + create() { + return { + Program(node) { + beforeAST = cloneDeeplyExcludesParent(node); + }, + "Program:exit"(node) { + afterAST = node; + } + }; } - })); + }); if (typeof config.parser === "string") { assert(path.isAbsolute(config.parser), "Parsers provided as strings to RuleTester must be absolute paths"); diff --git a/lib/rules/array-callback-return.js b/lib/rules/array-callback-return.js index eb5aa474b27..2b0a8559208 100644 --- a/lib/rules/array-callback-return.js +++ b/lib/rules/array-callback-return.js @@ -16,7 +16,7 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; -const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u; +const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u; /** * Checks a given code path segment is reachable. @@ -125,7 +125,7 @@ function getArrayMethodName(node) { } } - /* istanbul ignore next: unreachable */ + /* c8 ignore next */ return null; } diff --git a/lib/rules/array-element-newline.js b/lib/rules/array-element-newline.js index c762755bd83..7ac03ae56d3 100644 --- a/lib/rules/array-element-newline.js +++ b/lib/rules/array-element-newline.js @@ -47,6 +47,7 @@ module.exports = { ] } }, + type: "array", items: [ { oneOf: [ diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js index ee1b6bf598d..910e8b6e583 100644 --- a/lib/rules/camelcase.js +++ b/lib/rules/camelcase.js @@ -73,6 +73,7 @@ module.exports = { const ignoreImports = options.ignoreImports; const ignoreGlobals = options.ignoreGlobals; const allow = options.allow || []; + const sourceCode = context.getSourceCode(); //-------------------------------------------------------------------------- // Helpers @@ -245,8 +246,8 @@ module.exports = { return { // Report camelcase of global variable references ------------------ - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); if (!ignoreGlobals) { diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js index 9518da90e9e..09fecd5a448 100644 --- a/lib/rules/comma-dangle.js +++ b/lib/rules/comma-dangle.js @@ -50,7 +50,7 @@ function normalizeOptions(optionValue, ecmaVersion) { objects: optionValue, imports: optionValue, exports: optionValue, - functions: (!ecmaVersion || ecmaVersion < 8) ? "ignore" : optionValue + functions: ecmaVersion < 2017 ? "ignore" : optionValue }; } if (typeof optionValue === "object" && optionValue !== null) { @@ -134,7 +134,7 @@ module.exports = { }, create(context) { - const options = normalizeOptions(context.options[0], context.parserOptions.ecmaVersion); + const options = normalizeOptions(context.options[0], context.languageOptions.ecmaVersion); const sourceCode = context.getSourceCode(); @@ -346,7 +346,7 @@ module.exports = { "always-multiline": forceTrailingCommaIfMultiline, "only-multiline": allowTrailingCommaIfMultiline, never: forbidTrailingComma, - ignore: () => {} + ignore() {} }; return { diff --git a/lib/rules/consistent-this.js b/lib/rules/consistent-this.js index 947873b8e4a..b1fbd0ebedf 100644 --- a/lib/rules/consistent-this.js +++ b/lib/rules/consistent-this.js @@ -36,6 +36,7 @@ module.exports = { create(context) { let aliases = []; + const sourceCode = context.getSourceCode(); if (context.options.length === 0) { aliases.push("that"); @@ -115,10 +116,11 @@ module.exports = { /** * Check each alias to ensure that is was assigned to the correct value. + * @param {ASTNode} node The node that represents the scope to check. * @returns {void} */ - function ensureWasAssigned() { - const scope = context.getScope(); + function ensureWasAssigned(node) { + const scope = sourceCode.getScope(node); aliases.forEach(alias => { checkWasAssigned(alias, scope); diff --git a/lib/rules/for-direction.js b/lib/rules/for-direction.js index 7df3d7e4802..5507a9a1f32 100644 --- a/lib/rules/for-direction.js +++ b/lib/rules/for-direction.js @@ -15,7 +15,7 @@ module.exports = { type: "problem", docs: { - description: "Enforce \"for\" loop update clause moving the counter in the right direction.", + description: "Enforce \"for\" loop update clause moving the counter in the right direction", recommended: true, url: "https://eslint.org/docs/rules/for-direction" }, diff --git a/lib/rules/func-name-matching.js b/lib/rules/func-name-matching.js index 391b2a27836..69b47c286a3 100644 --- a/lib/rules/func-name-matching.js +++ b/lib/rules/func-name-matching.js @@ -44,7 +44,7 @@ function isModuleExports(pattern) { * @returns {boolean} True if the string is a valid identifier */ function isIdentifier(name, ecmaVersion) { - if (ecmaVersion >= 6) { + if (ecmaVersion >= 2015) { return esutils.keyword.isIdentifierES6(name); } return esutils.keyword.isIdentifierES5(name); @@ -104,7 +104,7 @@ module.exports = { const nameMatches = typeof context.options[0] === "string" ? context.options[0] : "always"; const considerPropertyDescriptor = options.considerPropertyDescriptor; const includeModuleExports = options.includeCommonJSModuleExports; - const ecmaVersion = context.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5; + const ecmaVersion = context.languageOptions.ecmaVersion; /** * Check whether node is a certain CallExpression. diff --git a/lib/rules/getter-return.js b/lib/rules/getter-return.js index 5209ab1504b..6d0e37bbaf6 100644 --- a/lib/rules/getter-return.js +++ b/lib/rules/getter-return.js @@ -112,18 +112,24 @@ module.exports = { } if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") { - // Object.defineProperty() - if (parent.parent.parent.type === "CallExpression" && - astUtils.getStaticPropertyName(parent.parent.parent.callee) === "defineProperty") { - return true; + // Object.defineProperty() or Reflect.defineProperty() + if (parent.parent.parent.type === "CallExpression") { + const callNode = parent.parent.parent.callee; + + if (astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperty") || + astUtils.isSpecificMemberAccess(callNode, "Reflect", "defineProperty")) { + return true; + } } - // Object.defineProperties() + // Object.defineProperties() or Object.create() if (parent.parent.parent.type === "Property" && parent.parent.parent.parent.type === "ObjectExpression" && - parent.parent.parent.parent.parent.type === "CallExpression" && - astUtils.getStaticPropertyName(parent.parent.parent.parent.parent.callee) === "defineProperties") { - return true; + parent.parent.parent.parent.parent.type === "CallExpression") { + const callNode = parent.parent.parent.parent.parent.callee; + + return astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperties") || + astUtils.isSpecificMemberAccess(callNode, "Object", "create"); } } } diff --git a/lib/rules/global-require.js b/lib/rules/global-require.js index 77ce5d10827..c8a5fc309a1 100644 --- a/lib/rules/global-require.js +++ b/lib/rules/global-require.js @@ -28,10 +28,11 @@ function findReference(scope, node) { const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] && reference.identifier.range[1] === node.range[1]); - /* istanbul ignore else: correctly returns null */ if (references.length === 1) { return references[0]; } + + /* c8 ignore next */ return null; } @@ -70,9 +71,11 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); + return { CallExpression(node) { - const currentScope = context.getScope(); + const currentScope = sourceCode.getScope(node); if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.has(parent.type)); diff --git a/lib/rules/handle-callback-err.js b/lib/rules/handle-callback-err.js index 5189564b668..300dbb0dee2 100644 --- a/lib/rules/handle-callback-err.js +++ b/lib/rules/handle-callback-err.js @@ -38,6 +38,7 @@ module.exports = { create(context) { const errorArgument = context.options[0] || "err"; + const sourceCode = context.getSourceCode(); /** * Checks if the given argument should be interpreted as a regexp pattern. @@ -79,7 +80,7 @@ module.exports = { * @returns {void} */ function checkForError(node) { - const scope = context.getScope(), + const scope = sourceCode.getScope(node), parameters = getParameters(scope), firstParameter = parameters[0]; diff --git a/lib/rules/id-blacklist.js b/lib/rules/id-blacklist.js index 5ea61e94f69..9d1efac3787 100644 --- a/lib/rules/id-blacklist.js +++ b/lib/rules/id-blacklist.js @@ -140,6 +140,7 @@ module.exports = { const denyList = new Set(context.options); const reportedNodes = new Set(); + const sourceCode = context.getSourceCode(); let globalScope; @@ -231,8 +232,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, Identifier(node) { diff --git a/lib/rules/id-denylist.js b/lib/rules/id-denylist.js index fe0a0b50bd2..0d9328137b2 100644 --- a/lib/rules/id-denylist.js +++ b/lib/rules/id-denylist.js @@ -121,6 +121,7 @@ module.exports = { const denyList = new Set(context.options); const reportedNodes = new Set(); + const sourceCode = context.getSourceCode(); let globalScope; @@ -210,8 +211,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, [[ diff --git a/lib/rules/id-length.js b/lib/rules/id-length.js index 99f833fc73b..bd269b998f5 100644 --- a/lib/rules/id-length.js +++ b/lib/rules/id-length.js @@ -6,6 +6,45 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ +const GraphemeSplitter = require("grapheme-splitter"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks if the string given as argument is ASCII or not. + * @param {string} value A string that you want to know if it is ASCII or not. + * @returns {boolean} `true` if `value` is ASCII string. + */ +function isASCII(value) { + if (typeof value !== "string") { + return false; + } + return /^[\u0020-\u007f]*$/u.test(value); +} + +/** @type {GraphemeSplitter | undefined} */ +let splitter; + +/** + * Gets the length of the string. If the string is not in ASCII, counts graphemes. + * @param {string} value A string that you want to get the length. + * @returns {number} The length of `value`. + */ +function getStringLength(value) { + if (isASCII(value)) { + return value.length; + } + if (!splitter) { + splitter = new GraphemeSplitter(); + } + return splitter.countGraphemes(value); +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -130,8 +169,10 @@ module.exports = { const name = node.name; const parent = node.parent; - const isShort = name.length < minLength; - const isLong = name.length > maxLength; + const nameLength = getStringLength(name); + + const isShort = nameLength < minLength; + const isLong = nameLength > maxLength; if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) { return; // Nothing to report diff --git a/lib/rules/id-match.js b/lib/rules/id-match.js index ec87af18d5b..73a3bd2f97e 100644 --- a/lib/rules/id-match.js +++ b/lib/rules/id-match.js @@ -67,6 +67,7 @@ module.exports = { onlyDeclarations = !!options.onlyDeclarations, ignoreDestructuring = !!options.ignoreDestructuring; + const sourceCode = context.getSourceCode(); let globalScope; //-------------------------------------------------------------------------- @@ -170,8 +171,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, Identifier(node) { diff --git a/lib/rules/indent-legacy.js b/lib/rules/indent-legacy.js index 06de8c9a754..d4793bd6360 100644 --- a/lib/rules/indent-legacy.js +++ b/lib/rules/indent-legacy.js @@ -18,8 +18,8 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ - -/* istanbul ignore next: this rule has known coverage issues, but it's deprecated and shouldn't be updated in the future anyway. */ +// this rule has known coverage issues, but it's deprecated and shouldn't be updated in the future anyway. +/* c8 ignore next */ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { @@ -212,10 +212,10 @@ module.exports = { if (context.options[0] === "tab") { indentSize = 1; indentType = "tab"; - } else /* istanbul ignore else : this will be caught by options validation */ if (typeof context.options[0] === "number") { + } else /* c8 ignore start */ if (typeof context.options[0] === "number") { indentSize = context.options[0]; indentType = "space"; - } + }/* c8 ignore stop */ if (context.options[1]) { const opts = context.options[1]; diff --git a/lib/rules/indent.js b/lib/rules/indent.js index b974a6ab150..cda02035a1d 100644 --- a/lib/rules/indent.js +++ b/lib/rules/indent.js @@ -12,7 +12,7 @@ // Requirements //------------------------------------------------------------------------------ -const createTree = require("functional-red-black-tree"); +const { OrderedMap } = require("js-sdsl"); const astUtils = require("./utils/ast-utils"); @@ -135,7 +135,8 @@ class BinarySearchTree { * Creates an empty tree */ constructor() { - this._rbTree = createTree(); + this._orderedMap = new OrderedMap(); + this._orderedMapEnd = this._orderedMap.end(); } /** @@ -145,13 +146,7 @@ class BinarySearchTree { * @returns {void} */ insert(key, value) { - const iterator = this._rbTree.find(key); - - if (iterator.valid) { - this._rbTree = iterator.update(value); - } else { - this._rbTree = this._rbTree.insert(key, value); - } + this._orderedMap.setElement(key, value); } /** @@ -160,9 +155,13 @@ class BinarySearchTree { * @returns {{key: number, value: *}|null} The found entry, or null if no such entry exists. */ findLe(key) { - const iterator = this._rbTree.le(key); + const iterator = this._orderedMap.reverseLowerBound(key); - return iterator && { key: iterator.key, value: iterator.value }; + if (iterator.equals(this._orderedMapEnd)) { + return {}; + } + + return { key: iterator.pointer[0], value: iterator.pointer[1] }; } /** @@ -177,11 +176,20 @@ class BinarySearchTree { if (start === end) { return; } - const iterator = this._rbTree.ge(start); + const iterator = this._orderedMap.lowerBound(start); - while (iterator.valid && iterator.key < end) { - this._rbTree = this._rbTree.remove(iterator.key); - iterator.next(); + if (iterator.equals(this._orderedMapEnd)) { + return; + } + + if (end > this._orderedMap.back()[0]) { + while (!iterator.equals(this._orderedMapEnd)) { + this._orderedMap.eraseElementByIterator(iterator); + } + } else { + while (iterator.pointer[0] < end) { + this._orderedMap.eraseElementByIterator(iterator); + } } } } diff --git a/lib/rules/index.js b/lib/rules/index.js index aef47f5cadc..e42639656f7 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -72,6 +72,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "lines-around-comment": () => require("./lines-around-comment"), "lines-around-directive": () => require("./lines-around-directive"), "lines-between-class-members": () => require("./lines-between-class-members"), + "logical-assignment-operators": () => require("./logical-assignment-operators"), "max-classes-per-file": () => require("./max-classes-per-file"), "max-depth": () => require("./max-depth"), "max-len": () => require("./max-len"), @@ -122,6 +123,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-empty-character-class": () => require("./no-empty-character-class"), "no-empty-function": () => require("./no-empty-function"), "no-empty-pattern": () => require("./no-empty-pattern"), + "no-empty-static-block": () => require("./no-empty-static-block"), "no-eq-null": () => require("./no-eq-null"), "no-eval": () => require("./no-eval"), "no-ex-assign": () => require("./no-ex-assign"), @@ -166,6 +168,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-nested-ternary": () => require("./no-nested-ternary"), "no-new": () => require("./no-new"), "no-new-func": () => require("./no-new-func"), + "no-new-native-nonconstructor": () => require("./no-new-native-nonconstructor"), "no-new-object": () => require("./no-new-object"), "no-new-require": () => require("./no-new-require"), "no-new-symbol": () => require("./no-new-symbol"), diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js index b764b7282ef..b0491b95fe3 100644 --- a/lib/rules/key-spacing.js +++ b/lib/rules/key-spacing.js @@ -334,6 +334,53 @@ module.exports = { const sourceCode = context.getSourceCode(); + /** + * Determines if the given property is key-value property. + * @param {ASTNode} property Property node to check. + * @returns {boolean} Whether the property is a key-value property. + */ + function isKeyValueProperty(property) { + return !( + (property.method || + property.shorthand || + property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement" + ); + } + + /** + * Starting from the given node (a property.key node here) looks forward + * until it finds the colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The colon punctuator. + */ + function getNextColon(node) { + return sourceCode.getTokenAfter(node, astUtils.isColonToken); + } + + /** + * Starting from the given node (a property.key node here) looks forward + * until it finds the last token before a colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The last token before a colon punctuator. + */ + function getLastTokenBeforeColon(node) { + const colonToken = getNextColon(node); + + return sourceCode.getTokenBefore(colonToken); + } + + /** + * Starting from the given node (a property.key node here) looks forward + * until it finds the first token after a colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The first token after a colon punctuator. + */ + function getFirstTokenAfterColon(node) { + const colonToken = getNextColon(node); + + return sourceCode.getTokenAfter(colonToken); + } + /** * Checks whether a property is a member of the property group it follows. * @param {ASTNode} lastMember The last Property known to be in the group. @@ -342,9 +389,9 @@ module.exports = { */ function continuesPropertyGroup(lastMember, candidate) { const groupEndLine = lastMember.loc.start.line, - candidateStartLine = candidate.loc.start.line; + candidateValueStartLine = (isKeyValueProperty(candidate) ? getFirstTokenAfterColon(candidate.key) : candidate).loc.start.line; - if (candidateStartLine - groupEndLine <= 1) { + if (candidateValueStartLine - groupEndLine <= 1) { return true; } @@ -358,7 +405,7 @@ module.exports = { if ( leadingComments.length && leadingComments[0].loc.start.line - groupEndLine <= 1 && - candidateStartLine - last(leadingComments).loc.end.line <= 1 + candidateValueStartLine - last(leadingComments).loc.end.line <= 1 ) { for (let i = 1; i < leadingComments.length; i++) { if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) { @@ -371,41 +418,6 @@ module.exports = { return false; } - /** - * Determines if the given property is key-value property. - * @param {ASTNode} property Property node to check. - * @returns {boolean} Whether the property is a key-value property. - */ - function isKeyValueProperty(property) { - return !( - (property.method || - property.shorthand || - property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement" - ); - } - - /** - * Starting from the given a node (a property.key node here) looks forward - * until it finds the last token before a colon punctuator and returns it. - * @param {ASTNode} node The node to start looking from. - * @returns {ASTNode} The last token before a colon punctuator. - */ - function getLastTokenBeforeColon(node) { - const colonToken = sourceCode.getTokenAfter(node, astUtils.isColonToken); - - return sourceCode.getTokenBefore(colonToken); - } - - /** - * Starting from the given a node (a property.key node here) looks forward - * until it finds the colon punctuator and returns it. - * @param {ASTNode} node The node to start looking from. - * @returns {ASTNode} The colon punctuator. - */ - function getNextColon(node) { - return sourceCode.getTokenAfter(node, astUtils.isColonToken); - } - /** * Gets an object literal property's key as the identifier name or string value. * @param {ASTNode} property Property node whose key to retrieve. diff --git a/lib/rules/lines-around-comment.js b/lib/rules/lines-around-comment.js index bd7d1cd2662..ece43b1417e 100644 --- a/lib/rules/lines-around-comment.js +++ b/lib/rules/lines-around-comment.js @@ -15,7 +15,7 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ /** - * Return an array with with any line numbers that are empty. + * Return an array with any line numbers that are empty. * @param {Array} lines An array of each line of the file. * @returns {Array} An array of line numbers. */ @@ -29,7 +29,7 @@ function getEmptyLineNums(lines) { } /** - * Return an array with with any line numbers that contain comments. + * Return an array with any line numbers that contain comments. * @param {Array} comments An array of comment tokens. * @returns {Array} An array of line numbers. */ @@ -113,6 +113,10 @@ module.exports = { }, applyDefaultIgnorePatterns: { type: "boolean" + }, + afterHashbangComment: { + type: "boolean", + default: false } }, additionalProperties: false @@ -449,6 +453,13 @@ module.exports = { before: options.beforeBlockComment }); } + } else if (token.type === "Shebang") { + if (options.afterHashbangComment) { + checkForEmptyLine(token, { + after: options.afterHashbangComment, + before: false + }); + } } }); } diff --git a/lib/rules/logical-assignment-operators.js b/lib/rules/logical-assignment-operators.js new file mode 100644 index 00000000000..f1c0119d99f --- /dev/null +++ b/lib/rules/logical-assignment-operators.js @@ -0,0 +1,474 @@ +/** + * @fileoverview Rule to replace assignment expressions with logical operator assignment + * @author Daniel Martens + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ +const astUtils = require("./utils/ast-utils.js"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const baseTypes = new Set(["Identifier", "Super", "ThisExpression"]); + +/** + * Returns true iff either "undefined" or a void expression (eg. "void 0") + * @param {ASTNode} expression Expression to check + * @param {import('eslint-scope').Scope} scope Scope of the expression + * @returns {boolean} True iff "undefined" or "void ..." + */ +function isUndefined(expression, scope) { + if (expression.type === "Identifier" && expression.name === "undefined") { + return astUtils.isReferenceToGlobalVariable(scope, expression); + } + + return expression.type === "UnaryExpression" && + expression.operator === "void" && + expression.argument.type === "Literal" && + expression.argument.value === 0; +} + +/** + * Returns true iff the reference is either an identifier or member expression + * @param {ASTNode} expression Expression to check + * @returns {boolean} True for identifiers and member expressions + */ +function isReference(expression) { + return (expression.type === "Identifier" && expression.name !== "undefined") || + expression.type === "MemberExpression"; +} + +/** + * Returns true iff the expression checks for nullish with loose equals. + * Examples: value == null, value == void 0 + * @param {ASTNode} expression Test condition + * @param {import('eslint-scope').Scope} scope Scope of the expression + * @returns {boolean} True iff implicit nullish comparison + */ +function isImplicitNullishComparison(expression, scope) { + if (expression.type !== "BinaryExpression" || expression.operator !== "==") { + return false; + } + + const reference = isReference(expression.left) ? "left" : "right"; + const nullish = reference === "left" ? "right" : "left"; + + return isReference(expression[reference]) && + (astUtils.isNullLiteral(expression[nullish]) || isUndefined(expression[nullish], scope)); +} + +/** + * Condition with two equal comparisons. + * @param {ASTNode} expression Condition + * @returns {boolean} True iff matches ? === ? || ? === ? + */ +function isDoubleComparison(expression) { + return expression.type === "LogicalExpression" && + expression.operator === "||" && + expression.left.type === "BinaryExpression" && + expression.left.operator === "===" && + expression.right.type === "BinaryExpression" && + expression.right.operator === "==="; +} + +/** + * Returns true iff the expression checks for undefined and null. + * Example: value === null || value === undefined + * @param {ASTNode} expression Test condition + * @param {import('eslint-scope').Scope} scope Scope of the expression + * @returns {boolean} True iff explicit nullish comparison + */ +function isExplicitNullishComparison(expression, scope) { + if (!isDoubleComparison(expression)) { + return false; + } + const leftReference = isReference(expression.left.left) ? "left" : "right"; + const leftNullish = leftReference === "left" ? "right" : "left"; + const rightReference = isReference(expression.right.left) ? "left" : "right"; + const rightNullish = rightReference === "left" ? "right" : "left"; + + return astUtils.isSameReference(expression.left[leftReference], expression.right[rightReference]) && + ((astUtils.isNullLiteral(expression.left[leftNullish]) && isUndefined(expression.right[rightNullish], scope)) || + (isUndefined(expression.left[leftNullish], scope) && astUtils.isNullLiteral(expression.right[rightNullish]))); +} + +/** + * Returns true for Boolean(arg) calls + * @param {ASTNode} expression Test condition + * @param {import('eslint-scope').Scope} scope Scope of the expression + * @returns {boolean} Whether the expression is a boolean cast + */ +function isBooleanCast(expression, scope) { + return expression.type === "CallExpression" && + expression.callee.name === "Boolean" && + expression.arguments.length === 1 && + astUtils.isReferenceToGlobalVariable(scope, expression.callee); +} + +/** + * Returns true for: + * truthiness checks: value, Boolean(value), !!value + * falsiness checks: !value, !Boolean(value) + * nullish checks: value == null, value === undefined || value === null + * @param {ASTNode} expression Test condition + * @param {import('eslint-scope').Scope} scope Scope of the expression + * @returns {?{ reference: ASTNode, operator: '??'|'||'|'&&'}} Null if not a known existence + */ +function getExistence(expression, scope) { + const isNegated = expression.type === "UnaryExpression" && expression.operator === "!"; + const base = isNegated ? expression.argument : expression; + + switch (true) { + case isReference(base): + return { reference: base, operator: isNegated ? "||" : "&&" }; + case base.type === "UnaryExpression" && base.operator === "!" && isReference(base.argument): + return { reference: base.argument, operator: "&&" }; + case isBooleanCast(base, scope) && isReference(base.arguments[0]): + return { reference: base.arguments[0], operator: isNegated ? "||" : "&&" }; + case isImplicitNullishComparison(expression, scope): + return { reference: isReference(expression.left) ? expression.left : expression.right, operator: "??" }; + case isExplicitNullishComparison(expression, scope): + return { reference: isReference(expression.left.left) ? expression.left.left : expression.left.right, operator: "??" }; + default: return null; + } +} + +/** + * Returns true iff the node is inside a with block + * @param {ASTNode} node Node to check + * @returns {boolean} True iff passed node is inside a with block + */ +function isInsideWithBlock(node) { + if (node.type === "Program") { + return false; + } + + return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "Require or disallow logical assignment operator shorthand", + recommended: false, + url: "https://eslint.org/docs/rules/logical-assignment-operators" + }, + + schema: { + type: "array", + oneOf: [{ + items: [ + { const: "always" }, + { + type: "object", + properties: { + enforceForIfStatements: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + minItems: 0, // 0 for allowing passing no options + maxItems: 2 + }, { + items: [{ const: "never" }], + minItems: 1, + maxItems: 1 + }] + }, + fixable: "code", + // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- Does not detect conditional suggestions + hasSuggestions: true, + messages: { + assignment: "Assignment (=) can be replaced with operator assignment ({{operator}}).", + useLogicalOperator: "Convert this assignment to use the operator {{ operator }}.", + logical: "Logical expression can be replaced with an assignment ({{ operator }}).", + convertLogical: "Replace this logical expression with an assignment with the operator {{ operator }}.", + if: "'if' statement can be replaced with a logical operator assignment with operator {{ operator }}.", + convertIf: "Replace this 'if' statement with a logical assignment with operator {{ operator }}.", + unexpected: "Unexpected logical operator assignment ({{operator}}) shorthand.", + separate: "Separate the logical assignment into an assignment with a logical operator." + } + }, + + create(context) { + const mode = context.options[0] === "never" ? "never" : "always"; + const checkIf = mode === "always" && context.options.length > 1 && context.options[1].enforceForIfStatements; + const sourceCode = context.getSourceCode(); + const isStrict = sourceCode.getScope(sourceCode.ast).isStrict; + + /** + * Returns false if the access could be a getter + * @param {ASTNode} node Assignment expression + * @returns {boolean} True iff the fix is safe + */ + function cannotBeGetter(node) { + return node.type === "Identifier" && + (isStrict || !isInsideWithBlock(node)); + } + + /** + * Check whether only a single property is accessed + * @param {ASTNode} node reference + * @returns {boolean} True iff a single property is accessed + */ + function accessesSingleProperty(node) { + if (!isStrict && isInsideWithBlock(node)) { + return node.type === "Identifier"; + } + + return node.type === "MemberExpression" && + baseTypes.has(node.object.type) && + (!node.computed || (node.property.type !== "MemberExpression" && node.property.type !== "ChainExpression")); + } + + /** + * Adds a fixer or suggestion whether on the fix is safe. + * @param {{ messageId: string, node: ASTNode }} descriptor Report descriptor without fix or suggest + * @param {{ messageId: string, fix: Function }} suggestion Adds the fix or the whole suggestion as only element in "suggest" to suggestion + * @param {boolean} shouldBeFixed Fix iff the condition is true + * @returns {Object} Descriptor with either an added fix or suggestion + */ + function createConditionalFixer(descriptor, suggestion, shouldBeFixed) { + if (shouldBeFixed) { + return { + ...descriptor, + fix: suggestion.fix + }; + } + + return { + ...descriptor, + suggest: [suggestion] + }; + } + + + /** + * Returns the operator token for assignments and binary expressions + * @param {ASTNode} node AssignmentExpression or BinaryExpression + * @returns {import('eslint').AST.Token} Operator token between the left and right expression + */ + function getOperatorToken(node) { + return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator); + } + + if (mode === "never") { + return { + + // foo ||= bar + "AssignmentExpression"(assignment) { + if (!astUtils.isLogicalAssignmentOperator(assignment.operator)) { + return; + } + + const descriptor = { + messageId: "unexpected", + node: assignment, + data: { operator: assignment.operator } + }; + const suggestion = { + messageId: "separate", + *fix(ruleFixer) { + if (sourceCode.getCommentsInside(assignment).length > 0) { + return; + } + + const operatorToken = getOperatorToken(assignment); + + // -> foo = bar + yield ruleFixer.replaceText(operatorToken, "="); + + const assignmentText = sourceCode.getText(assignment.left); + const operator = assignment.operator.slice(0, -1); + + // -> foo = foo || bar + yield ruleFixer.insertTextAfter(operatorToken, ` ${assignmentText} ${operator}`); + + const precedence = astUtils.getPrecedence(assignment.right) <= astUtils.getPrecedence({ type: "LogicalExpression", operator }); + + // ?? and || / && cannot be mixed but have same precedence + const mixed = assignment.operator === "??=" && astUtils.isLogicalExpression(assignment.right); + + if (!astUtils.isParenthesised(sourceCode, assignment.right) && (precedence || mixed)) { + + // -> foo = foo || (bar) + yield ruleFixer.insertTextBefore(assignment.right, "("); + yield ruleFixer.insertTextAfter(assignment.right, ")"); + } + } + }; + + context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left))); + } + }; + } + + return { + + // foo = foo || bar + "AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) { + if (!astUtils.isSameReference(assignment.left, assignment.right.left)) { + return; + } + + const descriptor = { + messageId: "assignment", + node: assignment, + data: { operator: `${assignment.right.operator}=` } + }; + const suggestion = { + messageId: "useLogicalOperator", + data: { operator: `${assignment.right.operator}=` }, + *fix(ruleFixer) { + if (sourceCode.getCommentsInside(assignment).length > 0) { + return; + } + + // No need for parenthesis around the assignment based on precedence as the precedence stays the same even with changed operator + const assignmentOperatorToken = getOperatorToken(assignment); + + // -> foo ||= foo || bar + yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator); + + // -> foo ||= bar + const logicalOperatorToken = getOperatorToken(assignment.right); + const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken); + + yield ruleFixer.removeRange([assignment.right.range[0], firstRightOperandToken.range[0]]); + } + }; + + context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left))); + }, + + // foo || (foo = bar) + 'LogicalExpression[right.type="AssignmentExpression"][right.operator="="]'(logical) { + + // Right side has to be parenthesized, otherwise would be parsed as (foo || foo) = bar which is illegal + if (isReference(logical.left) && astUtils.isSameReference(logical.left, logical.right.left)) { + const descriptor = { + messageId: "logical", + node: logical, + data: { operator: `${logical.operator}=` } + }; + const suggestion = { + messageId: "convertLogical", + data: { operator: `${logical.operator}=` }, + *fix(ruleFixer) { + if (sourceCode.getCommentsInside(logical).length > 0) { + return; + } + + const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && + (astUtils.getPrecedence({ type: "AssignmentExpression" }) < astUtils.getPrecedence(logical.parent)); + + if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) { + yield ruleFixer.insertTextBefore(logical, "("); + yield ruleFixer.insertTextAfter(logical, ")"); + } + + // Also removes all opening parenthesis + yield ruleFixer.removeRange([logical.range[0], logical.right.range[0]]); // -> foo = bar) + + // Also removes all ending parenthesis + yield ruleFixer.removeRange([logical.right.range[1], logical.range[1]]); // -> foo = bar + + const operatorToken = getOperatorToken(logical.right); + + yield ruleFixer.insertTextBefore(operatorToken, logical.operator); // -> foo ||= bar + } + }; + const fix = cannotBeGetter(logical.left) || accessesSingleProperty(logical.left); + + context.report(createConditionalFixer(descriptor, suggestion, fix)); + } + }, + + // if (foo) foo = bar + "IfStatement[alternate=null]"(ifNode) { + if (!checkIf) { + return; + } + + const hasBody = ifNode.consequent.type === "BlockStatement"; + + if (hasBody && ifNode.consequent.body.length !== 1) { + return; + } + + const body = hasBody ? ifNode.consequent.body[0] : ifNode.consequent; + const scope = sourceCode.getScope(ifNode); + const existence = getExistence(ifNode.test, scope); + + if ( + body.type === "ExpressionStatement" && + body.expression.type === "AssignmentExpression" && + body.expression.operator === "=" && + existence !== null && + astUtils.isSameReference(existence.reference, body.expression.left) + ) { + const descriptor = { + messageId: "if", + node: ifNode, + data: { operator: `${existence.operator}=` } + }; + const suggestion = { + messageId: "convertIf", + data: { operator: `${existence.operator}=` }, + *fix(ruleFixer) { + if (sourceCode.getCommentsInside(ifNode).length > 0) { + return; + } + + const firstBodyToken = sourceCode.getFirstToken(body); + const prevToken = sourceCode.getTokenBefore(ifNode); + + if ( + prevToken !== null && + prevToken.value !== ";" && + prevToken.value !== "{" && + firstBodyToken.type !== "Identifier" && + firstBodyToken.type !== "Keyword" + ) { + + // Do not fix if the fixed statement could be part of the previous statement (eg. fn() if (a == null) (a) = b --> fn()(a) ??= b) + return; + } + + + const operatorToken = getOperatorToken(body.expression); + + yield ruleFixer.insertTextBefore(operatorToken, existence.operator); // -> if (foo) foo ||= bar + + yield ruleFixer.removeRange([ifNode.range[0], body.range[0]]); // -> foo ||= bar + + yield ruleFixer.removeRange([body.range[1], ifNode.range[1]]); // -> foo ||= bar, only present if "if" had a body + + const nextToken = sourceCode.getTokenAfter(body.expression); + + if (hasBody && (nextToken !== null && nextToken.value !== ";")) { + yield ruleFixer.insertTextAfter(ifNode, ";"); + } + } + }; + const shouldBeFixed = cannotBeGetter(existence.reference) || + (ifNode.test.type !== "LogicalExpression" && accessesSingleProperty(existence.reference)); + + context.report(createConditionalFixer(descriptor, suggestion, shouldBeFixed)); + } + } + }; + } +}; diff --git a/lib/rules/multiline-comment-style.js b/lib/rules/multiline-comment-style.js index 68cd666532d..9cb7f3473e5 100644 --- a/lib/rules/multiline-comment-style.js +++ b/lib/rules/multiline-comment-style.js @@ -22,7 +22,37 @@ module.exports = { }, fixable: "whitespace", - schema: [{ enum: ["starred-block", "separate-lines", "bare-block"] }], + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["starred-block", "bare-block"] + } + ], + additionalItems: false + }, + { + type: "array", + items: [ + { + enum: ["separate-lines"] + }, + { + type: "object", + properties: { + checkJSDoc: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + additionalItems: false + } + ] + }, messages: { expectedBlock: "Expected a block comment instead of consecutive line comments.", expectedBareBlock: "Expected a block comment without padding stars.", @@ -37,6 +67,8 @@ module.exports = { create(context) { const sourceCode = context.getSourceCode(); const option = context.options[0] || "starred-block"; + const params = context.options[1] || {}; + const checkJSDoc = !!params.checkJSDoc; //---------------------------------------------------------------------- // Helpers @@ -333,11 +365,18 @@ module.exports = { "separate-lines"(commentGroup) { const [firstComment] = commentGroup; - if (firstComment.type !== "Block" || isJSDocComment(commentGroup)) { + const isJSDoc = isJSDocComment(commentGroup); + + if (firstComment.type !== "Block" || (!checkJSDoc && isJSDoc)) { return; } - const commentLines = getCommentLines(commentGroup); + let commentLines = getCommentLines(commentGroup); + + if (isJSDoc) { + commentLines = commentLines.slice(1, commentLines.length - 1); + } + const tokenAfter = sourceCode.getTokenAfter(firstComment, { includeComments: true }); if (tokenAfter && firstComment.loc.end.line === tokenAfter.loc.start.line) { diff --git a/lib/rules/multiline-ternary.js b/lib/rules/multiline-ternary.js index 62c84bbfed8..d8fe0b161c9 100644 --- a/lib/rules/multiline-ternary.js +++ b/lib/rules/multiline-ternary.js @@ -73,7 +73,7 @@ module.exports = { end: lastTokenOfTest.loc.end }, messageId: "unexpectedTestCons", - fix: fixer => { + fix(fixer) { if (hasComments) { return null; } @@ -101,7 +101,7 @@ module.exports = { end: lastTokenOfConsequent.loc.end }, messageId: "unexpectedConsAlt", - fix: fixer => { + fix(fixer) { if (hasComments) { return null; } diff --git a/lib/rules/new-cap.js b/lib/rules/new-cap.js index ad59fd90621..7e0b21eafb6 100644 --- a/lib/rules/new-cap.js +++ b/lib/rules/new-cap.js @@ -39,10 +39,10 @@ const CAPS_ALLOWED = [ */ function checkArray(obj, key, fallback) { - /* istanbul ignore if */ + /* c8 ignore start */ if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) { throw new TypeError(`${key}, if provided, must be an Array`); - } + }/* c8 ignore stop */ return obj[key] || fallback; } diff --git a/lib/rules/no-alert.js b/lib/rules/no-alert.js index ba0125c877b..8af188971c5 100644 --- a/lib/rules/no-alert.js +++ b/lib/rules/no-alert.js @@ -101,10 +101,12 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); + return { CallExpression(node) { const callee = skipChainExpression(node.callee), - currentScope = context.getScope(); + currentScope = sourceCode.getScope(node); // without window. if (callee.type === "Identifier") { diff --git a/lib/rules/no-catch-shadow.js b/lib/rules/no-catch-shadow.js index 49f1ba9649b..5e8b51e092d 100644 --- a/lib/rules/no-catch-shadow.js +++ b/lib/rules/no-catch-shadow.js @@ -39,6 +39,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- @@ -60,7 +62,7 @@ module.exports = { return { "CatchClause[param!=null]"(node) { - let scope = context.getScope(); + let scope = sourceCode.getScope(node); /* * When ecmaVersion >= 6, CatchClause creates its own scope diff --git a/lib/rules/no-console.js b/lib/rules/no-console.js index bad6b6f4ee8..4651282214d 100644 --- a/lib/rules/no-console.js +++ b/lib/rules/no-console.js @@ -51,6 +51,7 @@ module.exports = { create(context) { const options = context.options[0] || {}; const allowed = options.allow || []; + const sourceCode = context.getSourceCode(); /** * Checks whether the given reference is 'console' or not. @@ -109,8 +110,8 @@ module.exports = { } return { - "Program:exit"() { - const scope = context.getScope(); + "Program:exit"(node) { + const scope = sourceCode.getScope(node); const consoleVar = astUtils.getVariableByName(scope, "console"); const shadowed = consoleVar && consoleVar.defs.length > 0; diff --git a/lib/rules/no-constant-binary-expression.js b/lib/rules/no-constant-binary-expression.js index dccfa2f5826..4b2337b4771 100644 --- a/lib/rules/no-constant-binary-expression.js +++ b/lib/rules/no-constant-binary-expression.js @@ -14,6 +14,23 @@ const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|" // Helpers //------------------------------------------------------------------------------ +/** + * Checks whether or not a node is `null` or `undefined`. Similar to the one + * found in ast-utils.js, but this one correctly handles the edge case that + * `undefined` has been redefined. + * @param {Scope} scope Scope in which the expression was found. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is a `null` or `undefined`. + * @public + */ +function isNullOrUndefined(scope, node) { + return ( + isNullLiteral(node) || + (node.type === "Identifier" && node.name === "undefined" && isReferenceToGlobalVariable(scope, node)) || + (node.type === "UnaryExpression" && node.operator === "void") + ); +} + /** * Test if an AST node has a statically knowable constant nullishness. Meaning, * it will always resolve to a constant value of either: `null`, `undefined` @@ -21,9 +38,14 @@ const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|" * three states at runtime would return `false`. * @param {Scope} scope The scope in which the node was found. * @param {ASTNode} node The AST node being tested. + * @param {boolean} nonNullish if `true` then nullish values are not considered constant. * @returns {boolean} Does `node` have constant nullishness? */ -function hasConstantNullishness(scope, node) { +function hasConstantNullishness(scope, node, nonNullish) { + if (nonNullish && isNullOrUndefined(scope, node)) { + return false; + } + switch (node.type) { case "ObjectExpression": // Objects are never nullish case "ArrayExpression": // Arrays are never nullish @@ -45,9 +67,12 @@ function hasConstantNullishness(scope, node) { return (functionName === "Boolean" || functionName === "String" || functionName === "Number") && isReferenceToGlobalVariable(scope, node.callee); } + case "LogicalExpression": { + return node.operator === "??" && hasConstantNullishness(scope, node.right, true); + } case "AssignmentExpression": if (node.operator === "=") { - return hasConstantNullishness(scope, node.right); + return hasConstantNullishness(scope, node.right, nonNullish); } /* @@ -80,7 +105,7 @@ function hasConstantNullishness(scope, node) { case "SequenceExpression": { const last = node.expressions[node.expressions.length - 1]; - return hasConstantNullishness(scope, last); + return hasConstantNullishness(scope, last, nonNullish); } case "Identifier": return node.name === "undefined" && isReferenceToGlobalVariable(scope, node); @@ -348,7 +373,7 @@ function isAlwaysNew(scope, node) { * user-defined constructors could return a sentinel * object. * - * Catching these is especially useful for primitive constructures + * Catching these is especially useful for primitive constructors * which return boxed values, a surprising gotcha' in JavaScript. */ return Object.hasOwnProperty.call(globals.builtin, node.callee.name) && @@ -378,24 +403,6 @@ function isAlwaysNew(scope, node) { } } -/** - * Checks whether or not a node is `null` or `undefined`. Similar to the one - * found in ast-utils.js, but this one correctly handles the edge case that - * `undefined` has been redefined. - * @param {Scope} scope Scope in which the expression was found. - * @param {ASTNode} node A node to check. - * @returns {boolean} Whether or not the node is a `null` or `undefined`. - * @public - */ -function isNullOrUndefined(scope, node) { - return ( - isNullLiteral(node) || - (node.type === "Identifier" && node.name === "undefined" && isReferenceToGlobalVariable(scope, node)) || - (node.type === "UnaryExpression" && node.operator === "void") - ); -} - - /** * Checks if one operand will cause the result to be constant. * @param {Scope} scope Scope in which the expression was found. @@ -407,14 +414,14 @@ function isNullOrUndefined(scope, node) { function findBinaryExpressionConstantOperand(scope, a, b, operator) { if (operator === "==" || operator === "!=") { if ( - (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b)) || + (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) || (isStaticBoolean(scope, a) && hasConstantLooseBooleanComparison(scope, b)) ) { return b; } } else if (operator === "===" || operator === "!==") { if ( - (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b)) || + (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) || (isStaticBoolean(scope, a) && hasConstantStrictBooleanComparison(scope, b)) ) { return b; @@ -446,19 +453,21 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); + return { LogicalExpression(node) { const { operator, left } = node; - const scope = context.getScope(); + const scope = sourceCode.getScope(node); if ((operator === "&&" || operator === "||") && isConstant(scope, left, true)) { context.report({ node: left, messageId: "constantShortCircuit", data: { property: "truthiness", operator } }); - } else if (operator === "??" && hasConstantNullishness(scope, left)) { + } else if (operator === "??" && hasConstantNullishness(scope, left, false)) { context.report({ node: left, messageId: "constantShortCircuit", data: { property: "nullishness", operator } }); } }, BinaryExpression(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const { right, left, operator } = node; const rightConstantOperand = findBinaryExpressionConstantOperand(scope, left, right, operator); const leftConstantOperand = findBinaryExpressionConstantOperand(scope, right, left, operator); diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js index 2ef687f6dca..de548472b7d 100644 --- a/lib/rules/no-constant-condition.js +++ b/lib/rules/no-constant-condition.js @@ -48,6 +48,7 @@ module.exports = { const options = context.options[0] || {}, checkLoops = options.checkLoops !== false, loopSetStack = []; + const sourceCode = context.getSourceCode(); let loopsInCurrentScope = new Set(); @@ -62,7 +63,7 @@ module.exports = { * @private */ function trackConstantConditionLoop(node) { - if (node.test && isConstant(context.getScope(), node.test, true)) { + if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) { loopsInCurrentScope.add(node); } } @@ -87,7 +88,7 @@ module.exports = { * @private */ function reportIfConstant(node) { - if (node.test && isConstant(context.getScope(), node.test, true)) { + if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) { context.report({ node: node.test, messageId: "unexpected" }); } } diff --git a/lib/rules/no-control-regex.js b/lib/rules/no-control-regex.js index ba108437257..c42f86d1277 100644 --- a/lib/rules/no-control-regex.js +++ b/lib/rules/no-control-regex.js @@ -5,7 +5,7 @@ "use strict"; -const RegExpValidator = require("regexpp").RegExpValidator; +const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; const collector = new (class { constructor() { this._source = ""; diff --git a/lib/rules/no-else-return.js b/lib/rules/no-else-return.js index d1da3aa49cb..56234d54d3b 100644 --- a/lib/rules/no-else-return.js +++ b/lib/rules/no-else-return.js @@ -47,6 +47,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- @@ -169,25 +171,24 @@ module.exports = { /** * Display the context report if rule is violated - * @param {Node} node The 'else' node + * @param {Node} elseNode The 'else' node * @returns {void} */ - function displayReport(node) { - const currentScope = context.getScope(); + function displayReport(elseNode) { + const currentScope = sourceCode.getScope(elseNode.parent); context.report({ - node, + node: elseNode, messageId: "unexpected", - fix: fixer => { + fix(fixer) { - if (!isSafeFromNameCollisions(node, currentScope)) { + if (!isSafeFromNameCollisions(elseNode, currentScope)) { return null; } - const sourceCode = context.getSourceCode(); - const startToken = sourceCode.getFirstToken(node); + const startToken = sourceCode.getFirstToken(elseNode); const elseToken = sourceCode.getTokenBefore(startToken); - const source = sourceCode.getText(node); + const source = sourceCode.getText(elseNode); const lastIfToken = sourceCode.getTokenBefore(elseToken); let fixedSource, firstTokenOfElseBlock; @@ -203,14 +204,14 @@ module.exports = { * safe to remove the else keyword, because ASI will not add a semicolon * after the if block */ - const ifBlockMaybeUnsafe = node.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";"; + const ifBlockMaybeUnsafe = elseNode.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";"; const elseBlockUnsafe = /^[([/+`-]/u.test(firstTokenOfElseBlock.value); if (ifBlockMaybeUnsafe && elseBlockUnsafe) { return null; } - const endToken = sourceCode.getLastToken(node); + const endToken = sourceCode.getLastToken(elseNode); const lastTokenOfElseBlock = sourceCode.getTokenBefore(endToken); if (lastTokenOfElseBlock.value !== ";") { @@ -244,8 +245,8 @@ module.exports = { * Also, to avoid name collisions between two else blocks. */ return new FixTracker(fixer, sourceCode) - .retainEnclosingFunction(node) - .replaceTextRange([elseToken.range[0], node.range[1]], fixedSource); + .retainEnclosingFunction(elseNode) + .replaceTextRange([elseToken.range[0], elseNode.range[1]], fixedSource); } }); } diff --git a/lib/rules/no-empty-static-block.js b/lib/rules/no-empty-static-block.js new file mode 100644 index 00000000000..ab710628824 --- /dev/null +++ b/lib/rules/no-empty-static-block.js @@ -0,0 +1,47 @@ +/** + * @fileoverview Rule to disallow empty static blocks. + * @author Sosuke Suzuki + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "Disallow empty static blocks", + recommended: false, + url: "https://eslint.org/docs/rules/no-empty-static-block" + }, + + schema: [], + + messages: { + unexpected: "Unexpected empty static block." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + StaticBlock(node) { + if (node.body.length === 0) { + const closingBrace = sourceCode.getLastToken(node); + + if (sourceCode.getCommentsBefore(closingBrace).length === 0) { + context.report({ + node, + messageId: "unexpected" + }); + } + } + } + }; + } +}; diff --git a/lib/rules/no-empty.js b/lib/rules/no-empty.js index 459140a2e70..242e50efea4 100644 --- a/lib/rules/no-empty.js +++ b/lib/rules/no-empty.js @@ -17,6 +17,7 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + hasSuggestions: true, type: "suggestion", docs: { @@ -39,7 +40,8 @@ module.exports = { ], messages: { - unexpected: "Empty {{type}} statement." + unexpected: "Empty {{type}} statement.", + suggestComment: "Add comment inside empty {{type}} statement." } }, @@ -71,7 +73,22 @@ module.exports = { return; } - context.report({ node, messageId: "unexpected", data: { type: "block" } }); + context.report({ + node, + messageId: "unexpected", + data: { type: "block" }, + suggest: [ + { + messageId: "suggestComment", + data: { type: "block" }, + fix(fixer) { + const range = [node.range[0] + 1, node.range[1] - 1]; + + return fixer.replaceTextRange(range, " /* empty */ "); + } + } + ] + }); }, SwitchStatement(node) { diff --git a/lib/rules/no-eval.js b/lib/rules/no-eval.js index 03f7b1f691c..76d6859ab5d 100644 --- a/lib/rules/no-eval.js +++ b/lib/rules/no-eval.js @@ -72,7 +72,7 @@ module.exports = { let funcInfo = null; /** - * Pushs a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack. + * Pushes a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack. * Top-level scopes are handled separately. * * This is used in order to check whether or not `this` binding is a @@ -84,7 +84,7 @@ module.exports = { * @returns {void} */ function enterThisScope(node) { - const strict = context.getScope().isStrict; + const strict = sourceCode.getScope(node).isStrict; funcInfo = { upper: funcInfo, @@ -221,7 +221,7 @@ module.exports = { }, Program(node) { - const scope = context.getScope(), + const scope = sourceCode.getScope(node), features = context.parserOptions.ecmaFeatures || {}, strict = scope.isStrict || @@ -239,8 +239,8 @@ module.exports = { }; }, - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); exitThisScope(); reportAccessingEval(globalScope); diff --git a/lib/rules/no-extend-native.js b/lib/rules/no-extend-native.js index 52c6bd31103..b1965964394 100644 --- a/lib/rules/no-extend-native.js +++ b/lib/rules/no-extend-native.js @@ -51,6 +51,7 @@ module.exports = { create(context) { const config = context.options[0] || {}; + const sourceCode = context.getSourceCode(); const exceptions = new Set(config.exceptions || []); const modifiedBuiltins = new Set( Object.keys(globals.builtin) @@ -159,8 +160,8 @@ module.exports = { return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); modifiedBuiltins.forEach(builtin => { const builtinVar = globalScope.set.get(builtin); diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index 1c2bc4e5032..5f9411b4637 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const eslintUtils = require("eslint-utils"); +const eslintUtils = require("@eslint-community/eslint-utils"); const precedence = astUtils.getPrecedence; @@ -188,7 +188,7 @@ module.exports = { } return precedence(node) <= precedence(parent); - /* istanbul ignore next */ + /* c8 ignore next */ default: throw new Error(`Unexpected parent type: ${parent.type}`); } diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index 5ae9af8fd54..efb01a337a2 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -8,7 +8,7 @@ // Rule Definition //------------------------------------------------------------------------------ -const { isParenthesized: isParenthesizedRaw } = require("eslint-utils"); +const { isParenthesized: isParenthesizedRaw } = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils.js"); /** @type {import('../shared/types').Rule} */ @@ -52,7 +52,8 @@ module.exports = { enforceForArrowConditionals: { type: "boolean" }, enforceForSequenceExpressions: { type: "boolean" }, enforceForNewInMemberExpressions: { type: "boolean" }, - enforceForFunctionPrototypeMethods: { type: "boolean" } + enforceForFunctionPrototypeMethods: { type: "boolean" }, + allowParensAfterCommentPattern: { type: "string" } }, additionalProperties: false } @@ -86,6 +87,7 @@ module.exports = { context.options[1].enforceForNewInMemberExpressions === false; const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] && context.options[1].enforceForFunctionPrototypeMethods === false; + const ALLOW_PARENS_AFTER_COMMENT_PATTERN = ALL_NODES && context.options[1] && context.options[1].allowParensAfterCommentPattern; const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" }); const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" }); @@ -402,6 +404,19 @@ module.exports = { if (isIIFE(node) && !isParenthesised(node.callee)) { return; } + + if (ALLOW_PARENS_AFTER_COMMENT_PATTERN) { + const commentsBeforeLeftParenToken = sourceCode.getCommentsBefore(leftParenToken); + const totalCommentsBeforeLeftParenTokenCount = commentsBeforeLeftParenToken.length; + const ignorePattern = new RegExp(ALLOW_PARENS_AFTER_COMMENT_PATTERN, "u"); + + if ( + totalCommentsBeforeLeftParenTokenCount > 0 && + ignorePattern.test(commentsBeforeLeftParenToken[totalCommentsBeforeLeftParenTokenCount - 1].value) + ) { + return; + } + } } /** @@ -634,10 +649,10 @@ module.exports = { currentNode = currentNode.parent; - /* istanbul ignore if */ + /* c8 ignore start */ if (currentNode === null) { throw new Error("Nodes are not in the ancestor-descendant relationship."); - } + }/* c8 ignore stop */ path.push(currentNode); } @@ -751,6 +766,38 @@ module.exports = { return false; } + /** + * Checks if the left-hand side of an assignment is an identifier, the operator is one of + * `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous class or function. + * + * As per https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation, an + * assignment involving one of the operators `=`, `&&=`, `||=` or `??=` where the right-hand + * side is an anonymous class or function and the left-hand side is an *unparenthesized* + * identifier has different semantics than other assignments. + * Specifically, when an expression like `foo = function () {}` is evaluated, `foo.name` + * will be set to the string "foo", i.e. the identifier name. The same thing does not happen + * when evaluating `(foo) = function () {}`. + * Since the parenthesizing of the identifier in the left-hand side is significant in this + * special case, the parentheses, if present, should not be flagged as unnecessary. + * @param {ASTNode} node an AssignmentExpression node. + * @returns {boolean} `true` if the left-hand side of the assignment is an identifier, the + * operator is one of `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous + * class or function; otherwise, `false`. + */ + function isAnonymousFunctionAssignmentException({ left, operator, right }) { + if (left.type === "Identifier" && ["=", "&&=", "||=", "??="].includes(operator)) { + const rhsType = right.type; + + if (rhsType === "ArrowFunctionExpression") { + return true; + } + if ((rhsType === "FunctionExpression" || rhsType === "ClassExpression") && !right.id) { + return true; + } + } + return false; + } + return { ArrayExpression(node) { node.elements @@ -789,7 +836,8 @@ module.exports = { }, AssignmentExpression(node) { - if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left)) { + if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left) && + (!isAnonymousFunctionAssignmentException(node) || isParenthesisedTwice(node.left))) { report(node.left); } diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js index b51faa87bae..4c911eaf409 100644 --- a/lib/rules/no-fallthrough.js +++ b/lib/rules/no-fallthrough.js @@ -4,12 +4,28 @@ */ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { directivesPattern } = require("../shared/directives"); + //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu; +/** + * Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive. + * @param {string} comment The comment string to check. + * @param {RegExp} fallthroughCommentPattern The regular expression used for checking for fallthrough comments. + * @returns {boolean} `true` if the comment string is truly a fallthrough comment. + */ +function isFallThroughComment(comment, fallthroughCommentPattern) { + return fallthroughCommentPattern.test(comment) && !directivesPattern.test(comment.trim()); +} + /** * Checks whether or not a given case has a fallthrough comment. * @param {ASTNode} caseWhichFallsThrough SwitchCase node which falls through. @@ -25,14 +41,14 @@ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f const trailingCloseBrace = sourceCode.getLastToken(caseWhichFallsThrough.consequent[0]); const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop(); - if (commentInBlock && fallthroughCommentPattern.test(commentInBlock.value)) { + if (commentInBlock && isFallThroughComment(commentInBlock.value, fallthroughCommentPattern)) { return true; } } const comment = sourceCode.getCommentsBefore(subsequentCase).pop(); - return Boolean(comment && fallthroughCommentPattern.test(comment.value)); + return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern)); } /** @@ -76,6 +92,10 @@ module.exports = { commentPattern: { type: "string", default: "" + }, + allowEmptyCase: { + type: "boolean", + default: false } }, additionalProperties: false @@ -91,6 +111,7 @@ module.exports = { const options = context.options[0] || {}; let currentCodePath = null; const sourceCode = context.getSourceCode(); + const allowEmptyCase = options.allowEmptyCase || false; /* * We need to use leading comments of the next SwitchCase node because @@ -104,7 +125,6 @@ module.exports = { } else { fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT; } - return { onCodePathStart(codePath) { currentCodePath = codePath; @@ -119,7 +139,8 @@ module.exports = { * Checks whether or not there is a fallthrough comment. * And reports the previous fallthrough node if that does not exist. */ - if (fallthroughCase && !hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern)) { + + if (fallthroughCase && (!hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern))) { context.report({ messageId: node.test ? "case" : "default", node @@ -137,7 +158,7 @@ module.exports = { * And allows empty cases and the last case. */ if (currentCodePath.currentSegments.some(isReachable) && - (node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) && + (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) && node.parent.cases[node.parent.cases.length - 1] !== node) { fallthroughCase = node; } diff --git a/lib/rules/no-global-assign.js b/lib/rules/no-global-assign.js index 9f2f0ee3642..4659dcc94c1 100644 --- a/lib/rules/no-global-assign.js +++ b/lib/rules/no-global-assign.js @@ -41,6 +41,7 @@ module.exports = { create(context) { const config = context.options[0]; + const sourceCode = context.getSourceCode(); const exceptions = (config && config.exceptions) || []; /** @@ -84,8 +85,8 @@ module.exports = { } return { - Program() { - const globalScope = context.getScope(); + Program(node) { + const globalScope = sourceCode.getScope(node); globalScope.variables.forEach(checkVariable); } diff --git a/lib/rules/no-implicit-coercion.js b/lib/rules/no-implicit-coercion.js index c2367715d9d..d4126112597 100644 --- a/lib/rules/no-implicit-coercion.js +++ b/lib/rules/no-implicit-coercion.js @@ -71,6 +71,24 @@ function isMultiplyByOne(node) { ); } +/** + * Checks whether the given node logically represents multiplication by a fraction of `1`. + * For example, `a * 1` in `a * 1 / b` is technically multiplication by `1`, but the + * whole expression can be logically interpreted as `a * (1 / b)` rather than `(a * 1) / b`. + * @param {BinaryExpression} node A BinaryExpression node to check. + * @param {SourceCode} sourceCode The source code object. + * @returns {boolean} Whether or not the node is a multiplying by a fraction of `1`. + */ +function isMultiplyByFractionOfOne(node, sourceCode) { + return node.type === "BinaryExpression" && + node.operator === "*" && + (node.right.type === "Literal" && node.right.value === 1) && + node.parent.type === "BinaryExpression" && + node.parent.operator === "/" && + node.parent.left === node && + !astUtils.isParenthesised(sourceCode, node); +} + /** * Checks whether the result of a node is numeric or not * @param {ASTNode} node The node to test @@ -290,7 +308,8 @@ module.exports = { // 1 * foo operatorAllowed = options.allow.includes("*"); - const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && getNonNumericOperand(node); + const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && !isMultiplyByFractionOfOne(node, sourceCode) && + getNonNumericOperand(node); if (nonNumericOperand) { const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`; diff --git a/lib/rules/no-implicit-globals.js b/lib/rules/no-implicit-globals.js index 934630ea070..de9c4c274d4 100644 --- a/lib/rules/no-implicit-globals.js +++ b/lib/rules/no-implicit-globals.js @@ -43,6 +43,7 @@ module.exports = { create(context) { const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true; + const sourceCode = context.getSourceCode(); /** * Reports the node. @@ -62,8 +63,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); scope.variables.forEach(variable => { @@ -77,6 +78,11 @@ module.exports = { return; } + // Variables exported by "exported" block comments + if (variable.eslintExported) { + return; + } + variable.defs.forEach(def => { const defNode = def.node; diff --git a/lib/rules/no-implied-eval.js b/lib/rules/no-implied-eval.js index 44f146171aa..c99af2d3968 100644 --- a/lib/rules/no-implied-eval.js +++ b/lib/rules/no-implied-eval.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { getStaticValue } = require("eslint-utils"); +const { getStaticValue } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Rule Definition @@ -37,6 +37,7 @@ module.exports = { create(context) { const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]); const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u; + const sourceCode = context.getSourceCode(); /** * Checks whether a node is evaluated as a string or not. @@ -66,7 +67,7 @@ module.exports = { if (firstArgument) { - const staticValue = getStaticValue(firstArgument, context.getScope()); + const staticValue = getStaticValue(firstArgument, sourceCode.getScope(node)); const isStaticString = staticValue && typeof staticValue.value === "string"; const isString = isStaticString || isEvaluatedString(firstArgument); @@ -117,8 +118,8 @@ module.exports = { reportImpliedEvalCallExpression(node); } }, - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); GLOBAL_CANDIDATES .map(candidate => astUtils.getVariableByName(globalScope, candidate)) diff --git a/lib/rules/no-import-assign.js b/lib/rules/no-import-assign.js index fc104fe6c46..dcfebaf631b 100644 --- a/lib/rules/no-import-assign.js +++ b/lib/rules/no-import-assign.js @@ -9,7 +9,7 @@ // Helpers //------------------------------------------------------------------------------ -const { findVariable } = require("eslint-utils"); +const { findVariable } = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils"); const WellKnownMutationFunctions = { @@ -194,9 +194,11 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); + return { ImportDeclaration(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); for (const variable of context.getDeclaredVariables(node)) { const shouldCheckMembers = variable.defs.some( diff --git a/lib/rules/no-invalid-regexp.js b/lib/rules/no-invalid-regexp.js index 0f1d9c7bedc..b2e11a00821 100644 --- a/lib/rules/no-invalid-regexp.js +++ b/lib/rules/no-invalid-regexp.js @@ -8,7 +8,7 @@ // Requirements //------------------------------------------------------------------------------ -const RegExpValidator = require("regexpp").RegExpValidator; +const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; const validator = new RegExpValidator(); const validFlags = /[dgimsuy]/gu; const undefined1 = void 0; @@ -59,6 +59,20 @@ module.exports = { } } + /** + * Reports error with the provided message. + * @param {ASTNode} node The node holding the invalid RegExp + * @param {string} message The message to report. + * @returns {void} + */ + function report(node, message) { + context.report({ + node, + messageId: "regexMessage", + data: { message } + }); + } + /** * Check if node is a string * @param {ASTNode} node node to evaluate @@ -108,10 +122,13 @@ module.exports = { /** * Check syntax error in a given flags. - * @param {string} flags The RegExp flags to validate. + * @param {string|null} flags The RegExp flags to validate. * @returns {string|null} The syntax error. */ function validateRegExpFlags(flags) { + if (!flags) { + return null; + } try { validator.validateFlags(flags); return null; @@ -122,34 +139,39 @@ module.exports = { return { "CallExpression, NewExpression"(node) { - if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp" || !isString(node.arguments[0])) { + if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp") { return; } - const pattern = node.arguments[0].value; + let flags = getFlags(node); if (flags && allowedFlags) { flags = flags.replace(allowedFlags, ""); } - const message = - ( - flags && validateRegExpFlags(flags) - ) || - ( + let message = validateRegExpFlags(flags); + + if (message) { + report(node, message); + return; + } + + if (!isString(node.arguments[0])) { + return; + } + + const pattern = node.arguments[0].value; + + message = ( - // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag - flags === null - ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false) - : validateRegExpPattern(pattern, flags.includes("u")) - ); + // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag + flags === null + ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false) + : validateRegExpPattern(pattern, flags.includes("u")) + ); if (message) { - context.report({ - node, - messageId: "regexMessage", - data: { message } - }); + report(node, message); } } }; diff --git a/lib/rules/no-invalid-this.js b/lib/rules/no-invalid-this.js index b9cb43af5d7..1048f22861a 100644 --- a/lib/rules/no-invalid-this.js +++ b/lib/rules/no-invalid-this.js @@ -95,7 +95,7 @@ module.exports = { } if (codePath.origin === "program") { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const features = context.parserOptions.ecmaFeatures || {}; // `this` at the top level of scripts always refers to the global object @@ -120,7 +120,7 @@ module.exports = { * always valid, so we can set `init: true` right away. */ stack.push({ - init: !context.getScope().isStrict, + init: !sourceCode.getScope(node).isStrict, node, valid: true }); diff --git a/lib/rules/no-label-var.js b/lib/rules/no-label-var.js index a07d283f522..440d09d149d 100644 --- a/lib/rules/no-label-var.js +++ b/lib/rules/no-label-var.js @@ -34,6 +34,7 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); //-------------------------------------------------------------------------- // Helpers @@ -59,7 +60,7 @@ module.exports = { LabeledStatement(node) { // Fetch the innermost scope. - const scope = context.getScope(); + const scope = sourceCode.getScope(node); /* * Recursively find the identifier walking up the scope, starting diff --git a/lib/rules/no-labels.js b/lib/rules/no-labels.js index 6112d04affb..7257307f0cd 100644 --- a/lib/rules/no-labels.js +++ b/lib/rules/no-labels.js @@ -98,7 +98,7 @@ module.exports = { info = info.upper; } - /* istanbul ignore next: syntax error */ + /* c8 ignore next */ return "other"; } diff --git a/lib/rules/no-lone-blocks.js b/lib/rules/no-lone-blocks.js index 486a76ffdc9..23f581d3fc6 100644 --- a/lib/rules/no-lone-blocks.js +++ b/lib/rules/no-lone-blocks.js @@ -33,6 +33,7 @@ module.exports = { // A stack of lone blocks to be checked for block-level bindings const loneBlocks = []; let ruleDef; + const sourceCode = context.getSourceCode(); /** * Reports a node as invalid. @@ -91,7 +92,7 @@ module.exports = { }; // ES6: report blocks without block-level bindings, or that's only child of another block - if (context.parserOptions.ecmaVersion >= 6) { + if (context.languageOptions.ecmaVersion >= 2015) { ruleDef = { BlockStatement(node) { if (isLoneBlock(node)) { @@ -120,8 +121,8 @@ module.exports = { } }; - ruleDef.FunctionDeclaration = function() { - if (context.getScope().isStrict) { + ruleDef.FunctionDeclaration = function(node) { + if (sourceCode.getScope(node).isStrict) { markLoneBlock(); } }; diff --git a/lib/rules/no-loop-func.js b/lib/rules/no-loop-func.js index f81a7133680..ff066817226 100644 --- a/lib/rules/no-loop-func.js +++ b/lib/rules/no-loop-func.js @@ -168,6 +168,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Reports functions which match the following condition: * @@ -183,7 +185,7 @@ module.exports = { return; } - const references = context.getScope().through; + const references = sourceCode.getScope(node).through; const unsafeRefs = references.filter(r => !isSafe(loopNode, r)).map(r => r.identifier.name); if (unsafeRefs.length > 0) { diff --git a/lib/rules/no-loss-of-precision.js b/lib/rules/no-loss-of-precision.js index 6dc6d864dcd..3b2ab9c2f45 100644 --- a/lib/rules/no-loss-of-precision.js +++ b/lib/rules/no-loss-of-precision.js @@ -105,7 +105,7 @@ module.exports = { } /** - * Converts an integer to to an object containing the integer's coefficient and order of magnitude + * Converts an integer to an object containing the integer's coefficient and order of magnitude * @param {string} stringInteger the string representation of the integer being converted * @returns {Object} the object containing the integer's coefficient and order of magnitude */ @@ -120,7 +120,7 @@ module.exports = { /** * - * Converts a float to to an object containing the floats's coefficient and order of magnitude + * Converts a float to an object containing the floats's coefficient and order of magnitude * @param {string} stringFloat the string representation of the float being converted * @returns {Object} the object containing the integer's coefficient and order of magnitude */ diff --git a/lib/rules/no-magic-numbers.js b/lib/rules/no-magic-numbers.js index 9b085881556..786e595220a 100644 --- a/lib/rules/no-magic-numbers.js +++ b/lib/rules/no-magic-numbers.js @@ -65,6 +65,10 @@ module.exports = { ignoreDefaultValues: { type: "boolean", default: false + }, + ignoreClassFieldInitialValues: { + type: "boolean", + default: false } }, additionalProperties: false @@ -82,7 +86,8 @@ module.exports = { enforceConst = !!config.enforceConst, ignore = new Set((config.ignore || []).map(normalizeIgnoreValue)), ignoreArrayIndexes = !!config.ignoreArrayIndexes, - ignoreDefaultValues = !!config.ignoreDefaultValues; + ignoreDefaultValues = !!config.ignoreDefaultValues, + ignoreClassFieldInitialValues = !!config.ignoreClassFieldInitialValues; const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"]; @@ -106,6 +111,17 @@ module.exports = { return parent.type === "AssignmentPattern" && parent.right === fullNumberNode; } + /** + * Returns whether the number is the initial value of a class field. + * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node + * @returns {boolean} true if the number is the initial value of a class field. + */ + function isClassFieldInitialValue(fullNumberNode) { + const parent = fullNumberNode.parent; + + return parent.type === "PropertyDefinition" && parent.value === fullNumberNode; + } + /** * Returns whether the given node is used as a radix within parseInt() or Number.parseInt() * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node @@ -194,6 +210,7 @@ module.exports = { if ( isIgnoredValue(value) || (ignoreDefaultValues && isDefaultValue(fullNumberNode)) || + (ignoreClassFieldInitialValues && isClassFieldInitialValue(fullNumberNode)) || isParseIntRadix(fullNumberNode) || isJSXNumber(fullNumberNode) || (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value)) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 667d066e81c..ddbcaefd549 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -3,17 +3,16 @@ */ "use strict"; -const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); -const { RegExpValidator, RegExpParser, visitRegExpAST } = require("regexpp"); +const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); +const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); const astUtils = require("./utils/ast-utils.js"); +const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const REGEXPP_LATEST_ECMA_VERSION = 2022; - /** * Iterate character sequences of a given nodes. * @@ -185,46 +184,18 @@ module.exports = { } } - /** - * Checks if the given regular expression pattern would be valid with the `u` flag. - * @param {string} pattern The regular expression pattern to verify. - * @returns {boolean} `true` if the pattern would be valid with the `u` flag. - * `false` if the pattern would be invalid with the `u` flag or the configured - * ecmaVersion doesn't support the `u` flag. - */ - function isValidWithUnicodeFlag(pattern) { - const { ecmaVersion } = context.parserOptions; - - // ecmaVersion is unknown or it doesn't support the 'u' flag - if (typeof ecmaVersion !== "number" || ecmaVersion <= 5) { - return false; - } - - const validator = new RegExpValidator({ - ecmaVersion: Math.min(ecmaVersion + 2009, REGEXPP_LATEST_ECMA_VERSION) - }); - - try { - validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); - } catch { - return false; - } - - return true; - } - return { "Literal[regex]"(node) { verify(node, node.regex.pattern, node.regex.flags, fixer => { - if (!isValidWithUnicodeFlag(node.regex.pattern)) { + if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) { return null; } return fixer.insertTextAfter(node, "u"); }); }, - "Program"() { - const scope = context.getScope(); + "Program"(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); /* @@ -232,22 +203,22 @@ module.exports = { * E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`, * `const {RegExp: a} = window; new a()`, etc... */ - for (const { node } of tracker.iterateGlobalReferences({ + for (const { node: refNode } of tracker.iterateGlobalReferences({ RegExp: { [CALL]: true, [CONSTRUCT]: true } })) { - const [patternNode, flagsNode] = node.arguments; + const [patternNode, flagsNode] = refNode.arguments; const pattern = getStringIfConstant(patternNode, scope); const flags = getStringIfConstant(flagsNode, scope); if (typeof pattern === "string") { - verify(node, pattern, flags || "", fixer => { + verify(refNode, pattern, flags || "", fixer => { - if (!isValidWithUnicodeFlag(pattern)) { + if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) { return null; } - if (node.arguments.length === 1) { - const penultimateToken = sourceCode.getLastToken(node, { skip: 1 }); // skip closing parenthesis + if (refNode.arguments.length === 1) { + const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis return fixer.insertTextAfter( penultimateToken, diff --git a/lib/rules/no-native-reassign.js b/lib/rules/no-native-reassign.js index 634fea93308..27fd38ab86a 100644 --- a/lib/rules/no-native-reassign.js +++ b/lib/rules/no-native-reassign.js @@ -47,6 +47,7 @@ module.exports = { create(context) { const config = context.options[0]; const exceptions = (config && config.exceptions) || []; + const sourceCode = context.getSourceCode(); /** * Reports write references. @@ -87,8 +88,8 @@ module.exports = { } return { - Program() { - const globalScope = context.getScope(); + Program(node) { + const globalScope = sourceCode.getScope(node); globalScope.variables.forEach(checkVariable); } diff --git a/lib/rules/no-new-func.js b/lib/rules/no-new-func.js index 4759f380b29..4680ae5d7ca 100644 --- a/lib/rules/no-new-func.js +++ b/lib/rules/no-new-func.js @@ -40,27 +40,28 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const variable = globalScope.set.get("Function"); if (variable && variable.defs.length === 0) { variable.references.forEach(ref => { - const node = ref.identifier; - const { parent } = node; + const idNode = ref.identifier; + const { parent } = idNode; let evalNode; if (parent) { - if (node === parent.callee && ( + if (idNode === parent.callee && ( parent.type === "NewExpression" || parent.type === "CallExpression" )) { evalNode = parent; } else if ( parent.type === "MemberExpression" && - node === parent.object && + idNode === parent.object && callMethods.has(astUtils.getStaticPropertyName(parent)) ) { const maybeCallee = parent.parent.type === "ChainExpression" ? parent.parent : parent; diff --git a/lib/rules/no-new-native-nonconstructor.js b/lib/rules/no-new-native-nonconstructor.js new file mode 100644 index 00000000000..05171c92b32 --- /dev/null +++ b/lib/rules/no-new-native-nonconstructor.js @@ -0,0 +1,66 @@ +/** + * @fileoverview Rule to disallow use of the new operator with global non-constructor functions + * @author Sosuke Suzuki + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const nonConstructorGlobalFunctionNames = ["Symbol", "BigInt"]; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "problem", + + docs: { + description: "Disallow `new` operators with global non-constructor functions", + recommended: false, + url: "https://eslint.org/docs/rules/no-new-native-nonconstructor" + }, + + schema: [], + + messages: { + noNewNonconstructor: "`{{name}}` cannot be called as a constructor." + } + }, + + create(context) { + + const sourceCode = context.getSourceCode(); + + return { + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); + + for (const nonConstructorName of nonConstructorGlobalFunctionNames) { + const variable = globalScope.set.get(nonConstructorName); + + if (variable && variable.defs.length === 0) { + variable.references.forEach(ref => { + const idNode = ref.identifier; + const parent = idNode.parent; + + if (parent && parent.type === "NewExpression" && parent.callee === idNode) { + context.report({ + node: idNode, + messageId: "noNewNonconstructor", + data: { name: nonConstructorName } + }); + } + }); + } + } + } + }; + + } +}; diff --git a/lib/rules/no-new-object.js b/lib/rules/no-new-object.js index 4dbe8db7365..6351a279fa1 100644 --- a/lib/rules/no-new-object.js +++ b/lib/rules/no-new-object.js @@ -34,10 +34,13 @@ module.exports = { }, create(context) { + + const sourceCode = context.getSourceCode(); + return { NewExpression(node) { const variable = astUtils.getVariableByName( - context.getScope(), + sourceCode.getScope(node), node.callee.name ); diff --git a/lib/rules/no-new-symbol.js b/lib/rules/no-new-symbol.js index 534201c0ba6..551f4a9a414 100644 --- a/lib/rules/no-new-symbol.js +++ b/lib/rules/no-new-symbol.js @@ -29,19 +29,21 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const variable = globalScope.set.get("Symbol"); if (variable && variable.defs.length === 0) { variable.references.forEach(ref => { - const node = ref.identifier; - const parent = node.parent; + const idNode = ref.identifier; + const parent = idNode.parent; - if (parent && parent.type === "NewExpression" && parent.callee === node) { + if (parent && parent.type === "NewExpression" && parent.callee === idNode) { context.report({ - node, + node: idNode, messageId: "noNewSymbol" }); } diff --git a/lib/rules/no-obj-calls.js b/lib/rules/no-obj-calls.js index 86355d85d36..40df43e1501 100644 --- a/lib/rules/no-obj-calls.js +++ b/lib/rules/no-obj-calls.js @@ -9,14 +9,14 @@ // Requirements //------------------------------------------------------------------------------ -const { CALL, CONSTRUCT, ReferenceTracker } = require("eslint-utils"); +const { CALL, CONSTRUCT, ReferenceTracker } = require("@eslint-community/eslint-utils"); const getPropertyName = require("./utils/ast-utils").getStaticPropertyName; //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"]; +const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect", "Intl"]; /** * Returns the name of the node to report @@ -58,9 +58,11 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const traceMap = {}; @@ -71,12 +73,12 @@ module.exports = { }; } - for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) { - const name = getReportNodeName(node.callee); + for (const { node: refNode, path } of tracker.iterateGlobalReferences(traceMap)) { + const name = getReportNodeName(refNode.callee); const ref = path[0]; const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall"; - context.report({ node, messageId, data: { name, ref } }); + context.report({ node: refNode, messageId, data: { name, ref } }); } } }; diff --git a/lib/rules/no-promise-executor-return.js b/lib/rules/no-promise-executor-return.js index caa195ffa07..2a99c6fe812 100644 --- a/lib/rules/no-promise-executor-return.js +++ b/lib/rules/no-promise-executor-return.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const { findVariable } = require("eslint-utils"); +const { findVariable } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Helpers @@ -84,6 +84,7 @@ module.exports = { create(context) { let funcInfo = null; + const sourceCode = context.getSourceCode(); /** * Reports the given node. @@ -99,7 +100,7 @@ module.exports = { onCodePathStart(_, node) { funcInfo = { upper: funcInfo, - shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, context.getScope()) + shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, sourceCode.getScope(node)) }; if (funcInfo.shouldCheck && node.type === "ArrowFunctionExpression" && node.expression) { diff --git a/lib/rules/no-redeclare.js b/lib/rules/no-redeclare.js index 59749cb6643..c16c030f9a1 100644 --- a/lib/rules/no-redeclare.js +++ b/lib/rules/no-redeclare.js @@ -129,7 +129,7 @@ module.exports = { * @private */ function checkForBlock(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); /* * In ES5, some node type such as `BlockStatement` doesn't have that scope. @@ -141,8 +141,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); findVariablesInScope(scope); diff --git a/lib/rules/no-regex-spaces.js b/lib/rules/no-regex-spaces.js index 6d74aabe263..48ee8c3d024 100644 --- a/lib/rules/no-regex-spaces.js +++ b/lib/rules/no-regex-spaces.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const regexpp = require("regexpp"); +const regexpp = require("@eslint-community/regexpp"); //------------------------------------------------------------------------------ // Helpers @@ -54,6 +54,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Validate regular expression * @param {ASTNode} nodeToReport Node to report. @@ -149,7 +151,7 @@ module.exports = { * @private */ function checkFunction(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const regExpVar = astUtils.getVariableByName(scope, "RegExp"); const shadowed = regExpVar && regExpVar.defs.length > 0; const patternNode = node.arguments[0]; diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index d99e8928209..d201e3b03a6 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -27,27 +27,78 @@ module.exports = { }, schema: [{ - type: "object", - properties: { - restrictedNamedExports: { - type: "array", - items: { - type: "string" + anyOf: [ + { + type: "object", + properties: { + restrictedNamedExports: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } }, - uniqueItems: true + additionalProperties: false + }, + { + type: "object", + properties: { + restrictedNamedExports: { + type: "array", + items: { + type: "string", + pattern: "^(?!default$)" + }, + uniqueItems: true + }, + restrictDefaultExports: { + type: "object", + properties: { + + // Allow/Disallow `export default foo; export default 42; export default function foo() {}` format + direct: { + type: "boolean" + }, + + // Allow/Disallow `export { foo as default };` declarations + named: { + type: "boolean" + }, + + // Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` declarations + defaultFrom: { + type: "boolean" + }, + + // Allow/Disallow `export { foo as default } from "mod";` declarations + namedFrom: { + type: "boolean" + }, + + // Allow/Disallow `export * as default from "mod"`; declarations + namespaceFrom: { + type: "boolean" + } + }, + additionalProperties: false + } + }, + additionalProperties: false } - }, - additionalProperties: false + ] }], messages: { - restrictedNamed: "'{{name}}' is restricted from being used as an exported name." + restrictedNamed: "'{{name}}' is restricted from being used as an exported name.", + restrictedDefault: "Exporting 'default' is restricted." } }, create(context) { const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); + const restrictDefaultExports = context.options[0] && context.options[0].restrictDefaultExports; /** * Checks and reports given exported name. @@ -63,6 +114,42 @@ module.exports = { messageId: "restrictedNamed", data: { name } }); + return; + } + + if (name === "default") { + if (node.parent.type === "ExportAllDeclaration") { + if (restrictDefaultExports && restrictDefaultExports.namespaceFrom) { + context.report({ + node, + messageId: "restrictedDefault" + }); + } + + } else { // ExportSpecifier + const isSourceSpecified = !!node.parent.parent.source; + const specifierLocalName = astUtils.getModuleExportName(node.parent.local); + + if (!isSourceSpecified && restrictDefaultExports && restrictDefaultExports.named) { + context.report({ + node, + messageId: "restrictedDefault" + }); + return; + } + + if (isSourceSpecified && restrictDefaultExports) { + if ( + (specifierLocalName === "default" && restrictDefaultExports.defaultFrom) || + (specifierLocalName !== "default" && restrictDefaultExports.namedFrom) + ) { + context.report({ + node, + messageId: "restrictedDefault" + }); + } + } + } } } @@ -73,6 +160,15 @@ module.exports = { } }, + ExportDefaultDeclaration(node) { + if (restrictDefaultExports && restrictDefaultExports.direct) { + context.report({ + node, + messageId: "restrictedDefault" + }); + } + }, + ExportNamedDeclaration(node) { const declaration = node.declaration; diff --git a/lib/rules/no-restricted-globals.js b/lib/rules/no-restricted-globals.js index b666238382d..ffc39c801f0 100644 --- a/lib/rules/no-restricted-globals.js +++ b/lib/rules/no-restricted-globals.js @@ -50,6 +50,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + // If no globals are restricted, we don't need to do anything if (context.options.length === 0) { return {}; @@ -99,8 +101,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); // Report variables declared elsewhere (ex: variables defined as "global" by eslint) scope.variables.forEach(variable => { diff --git a/lib/rules/no-return-await.js b/lib/rules/no-return-await.js index 3007c8c877d..ecfd6697a13 100644 --- a/lib/rules/no-return-await.js +++ b/lib/rules/no-return-await.js @@ -13,6 +13,7 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + hasSuggestions: true, type: "suggestion", docs: { @@ -29,6 +30,7 @@ module.exports = { ], messages: { + removeAwait: "Remove redundant `await`.", redundantUseOfAwait: "Redundant use of `await` on a return value." } }, @@ -44,7 +46,32 @@ module.exports = { context.report({ node: context.getSourceCode().getFirstToken(node), loc: node.loc, - messageId: "redundantUseOfAwait" + messageId: "redundantUseOfAwait", + suggest: [ + { + messageId: "removeAwait", + fix(fixer) { + const sourceCode = context.getSourceCode(); + const [awaitToken, tokenAfterAwait] = sourceCode.getFirstTokens(node, 2); + + const areAwaitAndAwaitedExpressionOnTheSameLine = awaitToken.loc.start.line === tokenAfterAwait.loc.start.line; + + if (!areAwaitAndAwaitedExpressionOnTheSameLine) { + return null; + } + + const [startOfAwait, endOfAwait] = awaitToken.range; + + const characterAfterAwait = sourceCode.text[endOfAwait]; + const trimLength = characterAfterAwait === " " ? 1 : 0; + + const range = [startOfAwait, endOfAwait + trimLength]; + + return fixer.removeRange(range); + } + } + ] + }); } diff --git a/lib/rules/no-setter-return.js b/lib/rules/no-setter-return.js index 25e8f1428b2..46969d6dd70 100644 --- a/lib/rules/no-setter-return.js +++ b/lib/rules/no-setter-return.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { findVariable } = require("eslint-utils"); +const { findVariable } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Helpers @@ -156,6 +156,7 @@ module.exports = { create(context) { let funcInfo = null; + const sourceCode = context.getSourceCode(); /** * Creates and pushes to the stack a function info object for the given function node. @@ -163,7 +164,7 @@ module.exports = { * @returns {void} */ function enterFunction(node) { - const outerScope = getOuterScope(context.getScope()); + const outerScope = getOuterScope(sourceCode.getScope(node)); funcInfo = { upper: funcInfo, diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 3af9354ebd7..dda9f5fd7be 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -67,6 +67,7 @@ module.exports = { allow: (context.options[0] && context.options[0].allow) || [], ignoreOnInitialization: context.options[0] && context.options[0].ignoreOnInitialization }; + const sourceCode = context.getSourceCode(); /** * Checks whether or not a given location is inside of the range of a given node. @@ -318,8 +319,8 @@ module.exports = { } return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const stack = globalScope.childScopes.slice(); while (stack.length) { diff --git a/lib/rules/no-undef-init.js b/lib/rules/no-undef-init.js index 2cb1c3f3710..6e8a1fad72a 100644 --- a/lib/rules/no-undef-init.js +++ b/lib/rules/no-undef-init.js @@ -39,7 +39,7 @@ module.exports = { VariableDeclarator(node) { const name = sourceCode.getText(node.id), init = node.init && node.init.name, - scope = context.getScope(), + scope = sourceCode.getScope(node), undefinedVar = astUtils.getVariableByName(scope, "undefined"), shadowed = undefinedVar && undefinedVar.defs.length > 0, lastToken = sourceCode.getLastToken(node); diff --git a/lib/rules/no-undef.js b/lib/rules/no-undef.js index e920ce6c288..4cd3fa9b679 100644 --- a/lib/rules/no-undef.js +++ b/lib/rules/no-undef.js @@ -54,10 +54,11 @@ module.exports = { create(context) { const options = context.options[0]; const considerTypeOf = options && options.typeof === true || false; + const sourceCode = context.getSourceCode(); return { - "Program:exit"(/* node */) { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); globalScope.through.forEach(ref => { const identifier = ref.identifier; diff --git a/lib/rules/no-undefined.js b/lib/rules/no-undefined.js index e006320b522..7203894c397 100644 --- a/lib/rules/no-undefined.js +++ b/lib/rules/no-undefined.js @@ -28,6 +28,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Report an invalid "undefined" identifier node. * @param {ASTNode} node The node to report. @@ -66,8 +68,8 @@ module.exports = { } return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const stack = [globalScope]; diff --git a/lib/rules/no-underscore-dangle.js b/lib/rules/no-underscore-dangle.js index eb3e404a66d..692637920b9 100644 --- a/lib/rules/no-underscore-dangle.js +++ b/lib/rules/no-underscore-dangle.js @@ -53,6 +53,14 @@ module.exports = { enforceInClassFields: { type: "boolean", default: false + }, + allowInArrayDestructuring: { + type: "boolean", + default: true + }, + allowInObjectDestructuring: { + type: "boolean", + default: true } }, additionalProperties: false @@ -74,6 +82,8 @@ module.exports = { const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false; const enforceInClassFields = typeof options.enforceInClassFields !== "undefined" ? options.enforceInClassFields : false; const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true; + const allowInArrayDestructuring = typeof options.allowInArrayDestructuring !== "undefined" ? options.allowInArrayDestructuring : true; + const allowInObjectDestructuring = typeof options.allowInObjectDestructuring !== "undefined" ? options.allowInObjectDestructuring : true; //------------------------------------------------------------------------- // Helpers @@ -195,6 +205,7 @@ module.exports = { checkForDanglingUnderscoreInFunctionParameters(node); } + /** * Check if variable expression has a dangling underscore * @param {ASTNode} node node to evaluate @@ -202,18 +213,32 @@ module.exports = { * @private */ function checkForDanglingUnderscoreInVariableExpression(node) { - const identifier = node.id.name; + context.getDeclaredVariables(node).forEach(variable => { + const definition = variable.defs.find(def => def.node === node); + const identifierNode = definition.name; + const identifier = identifierNode.name; + let parent = identifierNode.parent; + + while (!["VariableDeclarator", "ArrayPattern", "ObjectPattern"].includes(parent.type)) { + parent = parent.parent; + } - if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && - !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) { - context.report({ - node, - messageId: "unexpectedUnderscore", - data: { - identifier - } - }); - } + if ( + hasDanglingUnderscore(identifier) && + !isSpecialCaseIdentifierInVariableExpression(identifier) && + !isAllowed(identifier) && + !(allowInArrayDestructuring && parent.type === "ArrayPattern") && + !(allowInObjectDestructuring && parent.type === "ObjectPattern") + ) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier + } + }); + } + }); } /** diff --git a/lib/rules/no-unmodified-loop-condition.js b/lib/rules/no-unmodified-loop-condition.js index 12f61e98e6a..3df0a7d87df 100644 --- a/lib/rules/no-unmodified-loop-condition.js +++ b/lib/rules/no-unmodified-loop-condition.js @@ -340,8 +340,8 @@ module.exports = { } return { - "Program:exit"() { - const queue = [context.getScope()]; + "Program:exit"(node) { + const queue = [sourceCode.getScope(node)]; groupMap = new Map(); diff --git a/lib/rules/no-unneeded-ternary.js b/lib/rules/no-unneeded-ternary.js index c193282fa70..80b83ac9ded 100644 --- a/lib/rules/no-unneeded-ternary.js +++ b/lib/rules/no-unneeded-ternary.js @@ -144,7 +144,7 @@ module.exports = { context.report({ node, messageId: "unnecessaryConditionalAssignment", - fix: fixer => { + fix(fixer) { const shouldParenthesizeAlternate = ( astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE || diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index 778889a7676..79f972f4b04 100644 --- a/lib/rules/no-unused-vars.js +++ b/lib/rules/no-unused-vars.js @@ -673,7 +673,7 @@ module.exports = { return { "Program:exit"(programNode) { - const unusedVars = collectUnusedVariables(context.getScope(), []); + const unusedVars = collectUnusedVariables(sourceCode.getScope(programNode), []); for (let i = 0, l = unusedVars.length; i < l; ++i) { const unusedVar = unusedVars[i]; diff --git a/lib/rules/no-use-before-define.js b/lib/rules/no-use-before-define.js index 592c083589c..60cb905b0d6 100644 --- a/lib/rules/no-use-before-define.js +++ b/lib/rules/no-use-before-define.js @@ -68,7 +68,7 @@ function isInClassStaticInitializerRange(node, location) { } /** - * Checks whether a given scope is the scope of a a class static initializer. + * Checks whether a given scope is the scope of a class static initializer. * Static initializers are static blocks and initializers of static fields. * @param {eslint-scope.Scope} scope A scope to check. * @returns {boolean} `true` if the scope is a class static initializer scope. @@ -258,6 +258,7 @@ module.exports = { create(context) { const options = parseOptions(context.options[0]); + const sourceCode = context.getSourceCode(); /** * Determines whether a given reference should be checked. @@ -339,8 +340,8 @@ module.exports = { } return { - Program() { - checkReferencesInScope(context.getScope()); + Program(node) { + checkReferencesInScope(sourceCode.getScope(node)); } }; } diff --git a/lib/rules/no-useless-backreference.js b/lib/rules/no-useless-backreference.js index f23535bc359..bef1bee11f9 100644 --- a/lib/rules/no-useless-backreference.js +++ b/lib/rules/no-useless-backreference.js @@ -9,8 +9,8 @@ // Requirements //------------------------------------------------------------------------------ -const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); -const { RegExpParser, visitRegExpAST } = require("regexpp"); +const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); +const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); //------------------------------------------------------------------------------ // Helpers @@ -82,6 +82,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Checks and reports useless backreferences in the given regular expression. * @param {ASTNode} node Node that represents regular expression. A regex literal or RegExp constructor call. @@ -167,8 +169,8 @@ module.exports = { checkRegex(node, pattern, flags); }, - Program() { - const scope = context.getScope(), + Program(node) { + const scope = sourceCode.getScope(node), tracker = new ReferenceTracker(scope), traceMap = { RegExp: { @@ -177,13 +179,13 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(traceMap)) { - const [patternNode, flagsNode] = node.arguments, + for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { + const [patternNode, flagsNode] = refNode.arguments, pattern = getStringIfConstant(patternNode, scope), flags = getStringIfConstant(flagsNode, scope); if (typeof pattern === "string") { - checkRegex(node, pattern, flags || ""); + checkRegex(refNode, pattern, flags || ""); } } } diff --git a/lib/rules/no-useless-computed-key.js b/lib/rules/no-useless-computed-key.js index e7a3dc1db6d..f7f12179b7b 100644 --- a/lib/rules/no-useless-computed-key.js +++ b/lib/rules/no-useless-computed-key.js @@ -74,7 +74,7 @@ function hasUselessComputedKey(node) { return value !== "constructor"; - /* istanbul ignore next */ + /* c8 ignore next */ default: throw new Error(`Unexpected node type: ${node.type}`); } diff --git a/lib/rules/no-useless-return.js b/lib/rules/no-useless-return.js index be8d4dfd3a5..55e34b3ab0f 100644 --- a/lib/rules/no-useless-return.js +++ b/lib/rules/no-useless-return.js @@ -197,7 +197,7 @@ module.exports = { return { - // Makes and pushs a new scope information. + // Makes and pushes a new scope information. onCodePathStart(codePath) { scopeInfo = { upper: scopeInfo, diff --git a/lib/rules/no-var.js b/lib/rules/no-var.js index 2185610ea10..80ff8f1a027 100644 --- a/lib/rules/no-var.js +++ b/lib/rules/no-var.js @@ -90,7 +90,7 @@ function getScopeNode(node) { } } - /* istanbul ignore next : unreachable */ + /* c8 ignore next */ return null; } @@ -159,7 +159,7 @@ function hasReferenceInTDZ(node) { return !reference.init && ( start < idStart || (defaultValue !== null && start >= defaultStart && end <= defaultEnd) || - (start >= initStart && end <= initEnd) + (!astUtils.isFunction(node) && start >= initStart && end <= initEnd) ); }); }; diff --git a/lib/rules/no-warning-comments.js b/lib/rules/no-warning-comments.js index 4f867cbb707..9754f50880b 100644 --- a/lib/rules/no-warning-comments.js +++ b/lib/rules/no-warning-comments.js @@ -37,6 +37,15 @@ module.exports = { }, location: { enum: ["start", "anywhere"] + }, + decoration: { + type: "array", + items: { + type: "string", + pattern: "^\\S$" + }, + minItems: 1, + uniqueItems: true } }, additionalProperties: false @@ -53,6 +62,7 @@ module.exports = { configuration = context.options[0] || {}, warningTerms = configuration.terms || ["todo", "fixme", "xxx"], location = configuration.location || "start", + decoration = [...configuration.decoration || []].join(""), selfConfigRegEx = /\bno-warning-comments\b/u; /** @@ -64,6 +74,7 @@ module.exports = { */ function convertToRegExp(term) { const escaped = escapeRegExp(term); + const escapedDecoration = escapeRegExp(decoration); /* * When matching at the start, ignore leading whitespace, and @@ -74,18 +85,23 @@ module.exports = { * e.g. terms ["TODO"] matches `//TODO something` * $ handles any terms at the end of a comment * e.g. terms ["TODO"] matches `// something TODO` - * \s* handles optional leading spaces (for "start" location only) - * e.g. terms ["TODO"] matches `// TODO something` * \b handles terms preceded/followed by word boundary * e.g. terms: ["!FIX", "FIX!"] matches `// FIX!something` or `// something!FIX` * terms: ["FIX"] matches `// FIX!` or `// !FIX`, but not `// fixed or affix` + * + * For location start: + * [\s]* handles optional leading spaces + * e.g. terms ["TODO"] matches `// TODO something` + * [\s\*]* (where "\*" is the escaped string of decoration) + * handles optional leading spaces or decoration characters (for "start" location only) + * e.g. terms ["TODO"] matches `/**** TODO something ... ` */ const wordBoundary = "\\b"; let prefix = ""; if (location === "start") { - prefix = "^\\s*"; + prefix = `^[\\s${escapedDecoration}]*`; } else if (/^\w/u.test(term)) { prefix = wordBoundary; } @@ -95,12 +111,15 @@ module.exports = { /* * For location "start", the typical regex is: - * /^\s*ESCAPED_TERM\b/iu. + * /^[\s]*ESCAPED_TERM\b/iu. + * Or if decoration characters are specified (e.g. "*"), then any of + * those characters may appear in any order at the start: + * /^[\s\*]*ESCAPED_TERM\b/iu. * * For location "anywhere" the typical regex is * /\bESCAPED_TERM\b/iu * - * If it starts or ends with non-word character, the prefix and suffix empty, respectively. + * If it starts or ends with non-word character, the prefix and suffix are empty, respectively. */ return new RegExp(`${prefix}${escaped}${suffix}`, flags); } diff --git a/lib/rules/object-shorthand.js b/lib/rules/object-shorthand.js index b755aea3f48..64a506ba6cc 100644 --- a/lib/rules/object-shorthand.js +++ b/lib/rules/object-shorthand.js @@ -354,11 +354,12 @@ module.exports = { /** * Enters a function. This creates a new lexical identifier scope, so a new Set of arrow functions is pushed onto the stack. * Also, this marks all `arguments` identifiers so that they can be detected later. + * @param {ASTNode} node The node representing the function. * @returns {void} */ - function enterFunction() { + function enterFunction(node) { lexicalScopeStack.unshift(new Set()); - context.getScope().variables.filter(variable => variable.name === "arguments").forEach(variable => { + sourceCode.getScope(node).variables.filter(variable => variable.name === "arguments").forEach(variable => { variable.references.map(ref => ref.identifier).forEach(identifier => argumentsIdentifiers.add(identifier)); }); } diff --git a/lib/rules/padded-blocks.js b/lib/rules/padded-blocks.js index 5faf4343874..bc194283dea 100644 --- a/lib/rules/padded-blocks.js +++ b/lib/rules/padded-blocks.js @@ -186,7 +186,7 @@ module.exports = { case "ClassBody": return options.classes; - /* istanbul ignore next */ + /* c8 ignore next */ default: throw new Error("unreachable"); } diff --git a/lib/rules/prefer-arrow-callback.js b/lib/rules/prefer-arrow-callback.js index 9667139a88d..68c630e0a98 100644 --- a/lib/rules/prefer-arrow-callback.js +++ b/lib/rules/prefer-arrow-callback.js @@ -53,7 +53,7 @@ function getVariableOfArguments(scope) { } } - /* istanbul ignore next */ + /* c8 ignore next */ return null; } @@ -126,7 +126,7 @@ function getCallbackInfo(node) { parent = parent.parent; } - /* istanbul ignore next */ + /* c8 ignore next */ throw new Error("unreachable"); } @@ -270,7 +270,7 @@ module.exports = { } // Skip if it's using arguments. - const variable = getVariableOfArguments(context.getScope()); + const variable = getVariableOfArguments(sourceCode.getScope(node)); if (variable && variable.references.length > 0) { return; @@ -335,6 +335,7 @@ module.exports = { // Convert the function expression to an arrow function. const functionToken = sourceCode.getFirstToken(node, node.async ? 1 : 0); const leftParenToken = sourceCode.getTokenAfter(functionToken, astUtils.isOpeningParenToken); + const tokenBeforeBody = sourceCode.getTokenBefore(node.body); if (sourceCode.commentsExistBetween(functionToken, leftParenToken)) { @@ -348,7 +349,7 @@ module.exports = { // Remove extra tokens and spaces. yield fixer.removeRange([functionToken.range[0], leftParenToken.range[0]]); } - yield fixer.insertTextBefore(node.body, "=> "); + yield fixer.insertTextAfter(tokenBeforeBody, " =>"); // Get the node that will become the new arrow function. let replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node; diff --git a/lib/rules/prefer-const.js b/lib/rules/prefer-const.js index 08f4492aaea..e3d2db7aeb9 100644 --- a/lib/rules/prefer-const.js +++ b/lib/rules/prefer-const.js @@ -446,7 +446,19 @@ module.exports = { reportCount += nodesToReport.length; - shouldFix = shouldFix && (reportCount === varDeclParent.declarations.length); + let totalDeclarationsCount = 0; + + varDeclParent.declarations.forEach(declaration => { + if (declaration.id.type === "ObjectPattern") { + totalDeclarationsCount += declaration.id.properties.length; + } else if (declaration.id.type === "ArrayPattern") { + totalDeclarationsCount += declaration.id.elements.length; + } else { + totalDeclarationsCount += 1; + } + }); + + shouldFix = shouldFix && (reportCount === totalDeclarationsCount); } } diff --git a/lib/rules/prefer-exponentiation-operator.js b/lib/rules/prefer-exponentiation-operator.js index fec5319723e..a0eac79be10 100644 --- a/lib/rules/prefer-exponentiation-operator.js +++ b/lib/rules/prefer-exponentiation-operator.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { CALL, ReferenceTracker } = require("eslint-utils"); +const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Helpers @@ -172,8 +172,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const trackMap = { Math: { @@ -181,8 +181,8 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(trackMap)) { - report(node); + for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { + report(refNode); } } }; diff --git a/lib/rules/prefer-named-capture-group.js b/lib/rules/prefer-named-capture-group.js index 1a13ffa8582..4fbf6886f27 100644 --- a/lib/rules/prefer-named-capture-group.js +++ b/lib/rules/prefer-named-capture-group.js @@ -14,8 +14,8 @@ const { CONSTRUCT, ReferenceTracker, getStringIfConstant -} = require("eslint-utils"); -const regexpp = require("regexpp"); +} = require("@eslint-community/eslint-utils"); +const regexpp = require("@eslint-community/regexpp"); //------------------------------------------------------------------------------ // Helpers @@ -23,6 +23,61 @@ const regexpp = require("regexpp"); const parser = new regexpp.RegExpParser(); +/** + * Creates fixer suggestions for the regex, if statically determinable. + * @param {number} groupStart Starting index of the regex group. + * @param {string} pattern The regular expression pattern to be checked. + * @param {string} rawText Source text of the regexNode. + * @param {ASTNode} regexNode AST node which contains the regular expression. + * @returns {Array} Fixer suggestions for the regex, if statically determinable. + */ +function suggestIfPossible(groupStart, pattern, rawText, regexNode) { + switch (regexNode.type) { + case "Literal": + if (typeof regexNode.value === "string" && rawText.includes("\\")) { + return null; + } + break; + case "TemplateLiteral": + if (regexNode.expressions.length || rawText.slice(1, -1) !== pattern) { + return null; + } + break; + default: + return null; + } + + const start = regexNode.range[0] + groupStart + 2; + + return [ + { + fix(fixer) { + const existingTemps = pattern.match(/temp\d+/gu) || []; + const highestTempCount = existingTemps.reduce( + (previous, next) => + Math.max(previous, Number(next.slice("temp".length))), + 0 + ); + + return fixer.insertTextBeforeRange( + [start, start], + `?` + ); + }, + messageId: "addGroupName" + }, + { + fix(fixer) { + return fixer.insertTextBeforeRange( + [start, start], + "?:" + ); + }, + messageId: "addNonCapture" + } + ]; +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -38,23 +93,29 @@ module.exports = { url: "https://eslint.org/docs/rules/prefer-named-capture-group" }, + hasSuggestions: true, + schema: [], messages: { + addGroupName: "Add name to capture group.", + addNonCapture: "Convert group to non-capturing.", required: "Capture group '{{group}}' should be converted to a named or non-capturing group." } }, create(context) { + const sourceCode = context.getSourceCode(); /** * Function to check regular expression. - * @param {string} pattern The regular expression pattern to be check. - * @param {ASTNode} node AST node which contains regular expression. + * @param {string} pattern The regular expression pattern to be checked. + * @param {ASTNode} node AST node which contains the regular expression or a call/new expression. + * @param {ASTNode} regexNode AST node which contains the regular expression. * @param {boolean} uFlag Flag indicates whether unicode mode is enabled or not. * @returns {void} */ - function checkRegex(pattern, node, uFlag) { + function checkRegex(pattern, node, regexNode, uFlag) { let ast; try { @@ -68,12 +129,16 @@ module.exports = { regexpp.visitRegExpAST(ast, { onCapturingGroupEnter(group) { if (!group.name) { + const rawText = sourceCode.getText(regexNode); + const suggest = suggestIfPossible(group.start, pattern, rawText, regexNode); + context.report({ node, messageId: "required", data: { group: group.raw - } + }, + suggest }); } } @@ -83,11 +148,11 @@ module.exports = { return { Literal(node) { if (node.regex) { - checkRegex(node.regex.pattern, node, node.regex.flags.includes("u")); + checkRegex(node.regex.pattern, node, node, node.regex.flags.includes("u")); } }, - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const traceMap = { RegExp: { @@ -96,12 +161,12 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(traceMap)) { - const regex = getStringIfConstant(node.arguments[0]); - const flags = getStringIfConstant(node.arguments[1]); + for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { + const regex = getStringIfConstant(refNode.arguments[0]); + const flags = getStringIfConstant(refNode.arguments[1]); if (regex) { - checkRegex(regex, node, flags && flags.includes("u")); + checkRegex(regex, refNode, refNode.arguments[0], flags && flags.includes("u")); } } } diff --git a/lib/rules/prefer-object-has-own.js b/lib/rules/prefer-object-has-own.js index 023d0a64f4c..55eba59d6ac 100644 --- a/lib/rules/prefer-object-has-own.js +++ b/lib/rules/prefer-object-has-own.js @@ -61,6 +61,9 @@ module.exports = { fixable: "code" }, create(context) { + + const sourceCode = context.getSourceCode(); + return { CallExpression(node) { if (!(node.callee.type === "MemberExpression" && node.callee.object.type === "MemberExpression")) { @@ -72,7 +75,7 @@ module.exports = { const isObject = hasLeftHandObject(node.callee.object); // check `Object` scope - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const variable = astUtils.getVariableByName(scope, "Object"); if ( @@ -85,7 +88,6 @@ module.exports = { node, messageId: "useHasOwn", fix(fixer) { - const sourceCode = context.getSourceCode(); if (sourceCode.getCommentsInside(node.callee).length > 0) { return null; diff --git a/lib/rules/prefer-object-spread.js b/lib/rules/prefer-object-spread.js index 08192001a2b..f574d2aadc8 100644 --- a/lib/rules/prefer-object-spread.js +++ b/lib/rules/prefer-object-spread.js @@ -6,7 +6,7 @@ "use strict"; -const { CALL, ReferenceTracker } = require("eslint-utils"); +const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils"); const { isCommaToken, isOpeningParenToken, @@ -247,7 +247,7 @@ module.exports = { docs: { description: - "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.", + "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", recommended: false, url: "https://eslint.org/docs/rules/prefer-object-spread" }, @@ -265,8 +265,8 @@ module.exports = { const sourceCode = context.getSourceCode(); return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const trackMap = { Object: { @@ -275,22 +275,22 @@ module.exports = { }; // Iterate all calls of `Object.assign` (only of the global variable `Object`). - for (const { node } of tracker.iterateGlobalReferences(trackMap)) { + for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { if ( - node.arguments.length >= 1 && - node.arguments[0].type === "ObjectExpression" && - !hasArraySpread(node) && + refNode.arguments.length >= 1 && + refNode.arguments[0].type === "ObjectExpression" && + !hasArraySpread(refNode) && !( - node.arguments.length > 1 && - hasArgumentsWithAccessors(node) + refNode.arguments.length > 1 && + hasArgumentsWithAccessors(refNode) ) ) { - const messageId = node.arguments.length === 1 + const messageId = refNode.arguments.length === 1 ? "useLiteralMessage" : "useSpreadMessage"; - const fix = defineFixer(node, sourceCode); + const fix = defineFixer(refNode, sourceCode); - context.report({ node, messageId, fix }); + context.report({ node: refNode, messageId, fix }); } } } diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index f30eddbf8c5..94b52155ee2 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -10,16 +10,15 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils"); -const { RegExpValidator, visitRegExpAST, RegExpParser } = require("regexpp"); +const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("@eslint-community/eslint-utils"); +const { RegExpValidator, visitRegExpAST, RegExpParser } = require("@eslint-community/regexpp"); const { canTokensBeAdjacent } = require("./utils/ast-utils"); +const { REGEXPP_LATEST_ECMA_VERSION } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const REGEXPP_LATEST_ECMA_VERSION = 2022; - /** * Determines whether the given node is a string literal. * @param {ASTNode} node Node to check. @@ -146,6 +145,8 @@ module.exports = { messages: { unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.", replaceWithLiteral: "Replace with an equivalent regular expression literal.", + replaceWithLiteralAndFlags: "Replace with an equivalent regular expression literal with flags '{{ flags }}'.", + replaceWithIntendedLiteralAndFlags: "Replace with a regular expression literal with flags '{{ flags }}'.", unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.", unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor." } @@ -161,7 +162,7 @@ module.exports = { * @returns {boolean} True if the identifier is a reference to a global variable. */ function isGlobalReference(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const variable = findVariable(scope, node); return variable !== null && variable.scope.type === "global" && variable.defs.length === 0; @@ -248,16 +249,18 @@ module.exports = { /** * Returns a ecmaVersion compatible for regexpp. - * @param {any} ecmaVersion The ecmaVersion to convert. + * @param {number} ecmaVersion The ecmaVersion to convert. * @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp. */ function getRegexppEcmaVersion(ecmaVersion) { - if (typeof ecmaVersion !== "number" || ecmaVersion <= 5) { + if (ecmaVersion <= 5) { return 5; } - return Math.min(ecmaVersion + 2009, REGEXPP_LATEST_ECMA_VERSION); + return Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION); } + const regexppEcmaVersion = getRegexppEcmaVersion(context.languageOptions.ecmaVersion); + /** * Makes a character escaped or else returns null. * @param {string} character The character to escape. @@ -293,9 +296,86 @@ module.exports = { } } + /** + * Checks whether the given regex and flags are valid for the ecma version or not. + * @param {string} pattern The regex pattern to check. + * @param {string | undefined} flags The regex flags to check. + * @returns {boolean} True if the given regex pattern and flags are valid for the ecma version. + */ + function isValidRegexForEcmaVersion(pattern, flags) { + const validator = new RegExpValidator({ ecmaVersion: regexppEcmaVersion }); + + try { + validator.validatePattern(pattern, 0, pattern.length, flags ? flags.includes("u") : false); + if (flags) { + validator.validateFlags(flags); + } + return true; + } catch { + return false; + } + } + + /** + * Checks whether two given regex flags contain the same flags or not. + * @param {string} flagsA The regex flags. + * @param {string} flagsB The regex flags. + * @returns {boolean} True if two regex flags contain same flags. + */ + function areFlagsEqual(flagsA, flagsB) { + return [...flagsA].sort().join("") === [...flagsB].sort().join(""); + } + + + /** + * Merges two regex flags. + * @param {string} flagsA The regex flags. + * @param {string} flagsB The regex flags. + * @returns {string} The merged regex flags. + */ + function mergeRegexFlags(flagsA, flagsB) { + const flagsSet = new Set([ + ...flagsA, + ...flagsB + ]); + + return [...flagsSet].join(""); + } + + /** + * Checks whether a give node can be fixed to the given regex pattern and flags. + * @param {ASTNode} node The node to check. + * @param {string} pattern The regex pattern to check. + * @param {string} flags The regex flags + * @returns {boolean} True if a node can be fixed to the given regex pattern and flags. + */ + function canFixTo(node, pattern, flags) { + const tokenBefore = sourceCode.getTokenBefore(node); + + return sourceCode.getCommentsInside(node).length === 0 && + (!tokenBefore || validPrecedingTokens.has(tokenBefore.value)) && + isValidRegexForEcmaVersion(pattern, flags); + } + + /** + * Returns a safe output code considering the before and after tokens. + * @param {ASTNode} node The regex node. + * @param {string} newRegExpValue The new regex expression value. + * @returns {string} The output code. + */ + function getSafeOutput(node, newRegExpValue) { + const tokenBefore = sourceCode.getTokenBefore(node); + const tokenAfter = sourceCode.getTokenAfter(node); + + return (tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") + + newRegExpValue + + (tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : ""); + + } + return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const traceMap = { RegExp: { @@ -304,45 +384,86 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(traceMap)) { - if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) { - if (node.arguments.length === 2) { - context.report({ node, messageId: "unexpectedRedundantRegExpWithFlags" }); - } else { - context.report({ node, messageId: "unexpectedRedundantRegExp" }); - } - } else if (hasOnlyStaticStringArguments(node)) { - let regexContent = getStringValue(node.arguments[0]); - let noFix = false; - let flags; + for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { + if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(refNode)) { + const regexNode = refNode.arguments[0]; - if (node.arguments[1]) { - flags = getStringValue(node.arguments[1]); - } + if (refNode.arguments.length === 2) { + const suggests = []; - const regexppEcmaVersion = getRegexppEcmaVersion(context.parserOptions.ecmaVersion); - const RegExpValidatorInstance = new RegExpValidator({ ecmaVersion: regexppEcmaVersion }); + const argFlags = getStringValue(refNode.arguments[1]) || ""; - try { - RegExpValidatorInstance.validatePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false); - if (flags) { - RegExpValidatorInstance.validateFlags(flags); + if (canFixTo(refNode, regexNode.regex.pattern, argFlags)) { + suggests.push({ + messageId: "replaceWithLiteralAndFlags", + pattern: regexNode.regex.pattern, + flags: argFlags + }); } - } catch { - noFix = true; - } - const tokenBefore = sourceCode.getTokenBefore(node); + const literalFlags = regexNode.regex.flags || ""; + const mergedFlags = mergeRegexFlags(literalFlags, argFlags); + + if ( + !areFlagsEqual(mergedFlags, argFlags) && + canFixTo(refNode, regexNode.regex.pattern, mergedFlags) + ) { + suggests.push({ + messageId: "replaceWithIntendedLiteralAndFlags", + pattern: regexNode.regex.pattern, + flags: mergedFlags + }); + } - if (tokenBefore && !validPrecedingTokens.has(tokenBefore.value)) { - noFix = true; + context.report({ + node: refNode, + messageId: "unexpectedRedundantRegExpWithFlags", + suggest: suggests.map(({ flags, pattern, messageId }) => ({ + messageId, + data: { + flags + }, + fix(fixer) { + return fixer.replaceText(refNode, getSafeOutput(refNode, `/${pattern}/${flags}`)); + } + })) + }); + } else { + const outputs = []; + + if (canFixTo(refNode, regexNode.regex.pattern, regexNode.regex.flags)) { + outputs.push(sourceCode.getText(regexNode)); + } + + + context.report({ + node: refNode, + messageId: "unexpectedRedundantRegExp", + suggest: outputs.map(output => ({ + messageId: "replaceWithLiteral", + fix(fixer) { + return fixer.replaceText( + refNode, + getSafeOutput(refNode, output) + ); + } + })) + }); } + } else if (hasOnlyStaticStringArguments(refNode)) { + let regexContent = getStringValue(refNode.arguments[0]); + let noFix = false; + let flags; - if (!/^[-a-zA-Z0-9\\[\](){} \t\r\n\v\f!@#$%^&*+^_=/~`.> 0) { + if (!/^[-a-zA-Z0-9\\[\](){} \t\r\n\v\f!@#$%^&*+^_=/~`.> { - const node = reference.identifier; + const idNode = reference.identifier; - if (astUtils.isCallee(node)) { - checkArguments(node.parent); + if (astUtils.isCallee(idNode)) { + checkArguments(idNode.parent); } }); } @@ -182,12 +182,12 @@ module.exports = { variable = astUtils.getVariableByName(scope, "Number"); if (variable && !isShadowed(variable)) { variable.references.forEach(reference => { - const node = reference.identifier.parent; - const maybeCallee = node.parent.type === "ChainExpression" - ? node.parent - : node; + const parentNode = reference.identifier.parent; + const maybeCallee = parentNode.parent.type === "ChainExpression" + ? parentNode.parent + : parentNode; - if (isParseIntMethod(node) && astUtils.isCallee(maybeCallee)) { + if (isParseIntMethod(parentNode) && astUtils.isCallee(maybeCallee)) { checkArguments(maybeCallee.parent); } }); diff --git a/lib/rules/require-atomic-updates.js b/lib/rules/require-atomic-updates.js index 7a5f822ab28..4ed256a4a51 100644 --- a/lib/rules/require-atomic-updates.js +++ b/lib/rules/require-atomic-updates.js @@ -204,8 +204,8 @@ module.exports = { let stack = null; return { - onCodePathStart(codePath) { - const scope = context.getScope(); + onCodePathStart(codePath, node) { + const scope = sourceCode.getScope(node); const shouldVerify = scope.type === "function" && (scope.block.async || scope.block.generator); diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index 4236af6db47..2fe1539cfcc 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -14,7 +14,9 @@ const { CONSTRUCT, ReferenceTracker, getStringIfConstant -} = require("eslint-utils"); +} = require("@eslint-community/eslint-utils"); +const astUtils = require("./utils/ast-utils.js"); +const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Rule Definition @@ -31,7 +33,10 @@ module.exports = { url: "https://eslint.org/docs/rules/require-unicode-regexp" }, + hasSuggestions: true, + messages: { + addUFlag: "Add the 'u' flag.", requireUFlag: "Use the 'u' flag." }, @@ -39,28 +44,79 @@ module.exports = { }, create(context) { + + const sourceCode = context.getSourceCode(); + return { "Literal[regex]"(node) { const flags = node.regex.flags || ""; if (!flags.includes("u")) { - context.report({ node, messageId: "requireUFlag" }); + context.report({ + messageId: "requireUFlag", + node, + suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern) + ? [ + { + fix(fixer) { + return fixer.insertTextAfter(node, "u"); + }, + messageId: "addUFlag" + } + ] + : null + }); } }, - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const trackMap = { RegExp: { [CALL]: true, [CONSTRUCT]: true } }; - for (const { node } of tracker.iterateGlobalReferences(trackMap)) { - const flagsNode = node.arguments[1]; + for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { + const [patternNode, flagsNode] = refNode.arguments; + const pattern = getStringIfConstant(patternNode, scope); const flags = getStringIfConstant(flagsNode, scope); if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) { - context.report({ node, messageId: "requireUFlag" }); + context.report({ + messageId: "requireUFlag", + node: refNode, + suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern) + ? [ + { + fix(fixer) { + if (flagsNode) { + if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") { + const flagsNodeText = sourceCode.getText(flagsNode); + + return fixer.replaceText(flagsNode, [ + flagsNodeText.slice(0, flagsNodeText.length - 1), + flagsNodeText.slice(flagsNodeText.length - 1) + ].join("u")); + } + + // We intentionally don't suggest concatenating + "u" to non-literals + return null; + } + + const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis + + return fixer.insertTextAfter( + penultimateToken, + astUtils.isCommaToken(penultimateToken) + ? ' "u",' + : ', "u"' + ); + }, + messageId: "addUFlag" + } + ] + : null + }); } } } diff --git a/lib/rules/require-yield.js b/lib/rules/require-yield.js index ffb2229790d..b3f1341136a 100644 --- a/lib/rules/require-yield.js +++ b/lib/rules/require-yield.js @@ -68,7 +68,6 @@ module.exports = { // Increases the count of `yield` keyword. YieldExpression() { - /* istanbul ignore else */ if (stack.length > 0) { stack[stack.length - 1] += 1; } diff --git a/lib/rules/strict.js b/lib/rules/strict.js index e677c95e717..75bb561253e 100644 --- a/lib/rules/strict.js +++ b/lib/rules/strict.js @@ -105,7 +105,7 @@ module.exports = { if (ecmaFeatures.impliedStrict) { mode = "implied"; } else if (mode === "safe") { - mode = ecmaFeatures.globalReturn ? "global" : "function"; + mode = ecmaFeatures.globalReturn || context.languageOptions.sourceType === "commonjs" ? "global" : "function"; } /** diff --git a/lib/rules/symbol-description.js b/lib/rules/symbol-description.js index 1c8a364986c..e96a428ce1d 100644 --- a/lib/rules/symbol-description.js +++ b/lib/rules/symbol-description.js @@ -35,6 +35,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Reports if node does not conform the rule in case rule is set to * report missing description @@ -51,16 +53,16 @@ module.exports = { } return { - "Program:exit"() { - const scope = context.getScope(); + "Program:exit"(node) { + const scope = sourceCode.getScope(node); const variable = astUtils.getVariableByName(scope, "Symbol"); if (variable && variable.defs.length === 0) { variable.references.forEach(reference => { - const node = reference.identifier; + const idNode = reference.identifier; - if (astUtils.isCallee(node)) { - checkArgument(node.parent); + if (astUtils.isCallee(idNode)) { + checkArgument(idNode.parent); } }); } diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index f919f5a26c8..f4a18cff783 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -1350,7 +1350,7 @@ module.exports = { } } - /* istanbul ignore next */ + /* c8 ignore next */ return true; }, @@ -1978,7 +1978,7 @@ module.exports = { if (comments.length) { const lastComment = comments[comments.length - 1]; - if (lastComment.range[0] > leftToken.range[0]) { + if (!leftToken || lastComment.range[0] > leftToken.range[0]) { leftToken = lastComment; } } @@ -1986,7 +1986,13 @@ module.exports = { leftToken = leftValue; } - if (leftToken.type === "Shebang") { + /* + * If a hashbang comment was passed as a token object from SourceCode, + * its type will be "Shebang" because of the way ESLint itself handles hashbangs. + * If a hashbang comment was passed in a string and then tokenized in this function, + * its type will be "Hashbang" because of the way Espree tokenizes hashbangs. + */ + if (leftToken.type === "Shebang" || leftToken.type === "Hashbang") { return false; } @@ -2007,7 +2013,7 @@ module.exports = { if (comments.length) { const firstComment = comments[0]; - if (firstComment.range[0] < rightToken.range[0]) { + if (!rightToken || firstComment.range[0] < rightToken.range[0]) { rightToken = firstComment; } } diff --git a/lib/rules/utils/regular-expressions.js b/lib/rules/utils/regular-expressions.js new file mode 100644 index 00000000000..234a1cb8b11 --- /dev/null +++ b/lib/rules/utils/regular-expressions.js @@ -0,0 +1,42 @@ +/** + * @fileoverview Common utils for regular expressions. + * @author Josh Goldberg + * @author Toru Nagashima + */ + +"use strict"; + +const { RegExpValidator } = require("@eslint-community/regexpp"); + +const REGEXPP_LATEST_ECMA_VERSION = 2022; + +/** + * Checks if the given regular expression pattern would be valid with the `u` flag. + * @param {number} ecmaVersion ECMAScript version to parse in. + * @param {string} pattern The regular expression pattern to verify. + * @returns {boolean} `true` if the pattern would be valid with the `u` flag. + * `false` if the pattern would be invalid with the `u` flag or the configured + * ecmaVersion doesn't support the `u` flag. + */ +function isValidWithUnicodeFlag(ecmaVersion, pattern) { + if (ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag + return false; + } + + const validator = new RegExpValidator({ + ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION) + }); + + try { + validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); + } catch { + return false; + } + + return true; +} + +module.exports = { + isValidWithUnicodeFlag, + REGEXPP_LATEST_ECMA_VERSION +}; diff --git a/lib/rules/valid-typeof.js b/lib/rules/valid-typeof.js index 908d5725e22..6ba5d466118 100644 --- a/lib/rules/valid-typeof.js +++ b/lib/rules/valid-typeof.js @@ -44,7 +44,7 @@ module.exports = { const VALID_TYPES = new Set(["symbol", "undefined", "object", "boolean", "number", "string", "function", "bigint"]), OPERATORS = new Set(["==", "===", "!=", "!=="]); - + const sourceCode = context.getSourceCode(); const requireStringLiterals = context.options[0] && context.options[0].requireStringLiterals; let globalScope; @@ -77,8 +77,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, UnaryExpression(node) { diff --git a/lib/rules/wrap-iife.js b/lib/rules/wrap-iife.js index 4c2c9275d87..66ea3f8fa46 100644 --- a/lib/rules/wrap-iife.js +++ b/lib/rules/wrap-iife.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const eslintUtils = require("eslint-utils"); +const eslintUtils = require("@eslint-community/eslint-utils"); //---------------------------------------------------------------------- // Helpers diff --git a/lib/shared/directives.js b/lib/shared/directives.js new file mode 100644 index 00000000000..ff67b00a553 --- /dev/null +++ b/lib/shared/directives.js @@ -0,0 +1,15 @@ +/** + * @fileoverview Common utils for directives. + * + * This file contains only shared items for directives. + * If you make a utility for rules, please see `../rules/utils/ast-utils.js`. + * + * @author gfyoung + */ +"use strict"; + +const directivesPattern = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u; + +module.exports = { + directivesPattern +}; diff --git a/lib/shared/logging.js b/lib/shared/logging.js index 7f06a74e142..fd5e8a648e1 100644 --- a/lib/shared/logging.js +++ b/lib/shared/logging.js @@ -7,7 +7,7 @@ /* eslint no-console: "off" -- Logging util */ -/* istanbul ignore next */ +/* c8 ignore next */ module.exports = { /** diff --git a/lib/shared/runtime-info.js b/lib/shared/runtime-info.js index 56c0898be54..b99ad1038f3 100644 --- a/lib/shared/runtime-info.js +++ b/lib/shared/runtime-info.js @@ -97,7 +97,7 @@ function environment() { */ function getNpmPackageVersion(pkg, { global = false } = {}) { const npmBinArgs = ["bin", "-g"]; - const npmLsArgs = ["ls", "--depth=0", "--json", "eslint"]; + const npmLsArgs = ["ls", "--depth=0", "--json", pkg]; if (global) { npmLsArgs.push("-g"); diff --git a/lib/shared/traverser.js b/lib/shared/traverser.js index be0ed59a4f9..38b4e215132 100644 --- a/lib/shared/traverser.js +++ b/lib/shared/traverser.js @@ -74,7 +74,7 @@ class Traverser { } /** - * Gives a a copy of the ancestor nodes. + * Gives a copy of the ancestor nodes. * @returns {ASTNode[]} The ancestor nodes. */ parents() { diff --git a/lib/shared/types.js b/lib/shared/types.js index 5d9a4e6cf26..20335f68a73 100644 --- a/lib/shared/types.js +++ b/lib/shared/types.js @@ -21,7 +21,7 @@ module.exports = {}; /** * @typedef {Object} ParserOptions * @property {EcmaFeatures} [ecmaFeatures] The optional features. - * @property {3|5|6|7|8|9|10|11|12|13|2015|2016|2017|2018|2019|2020|2021|2022} [ecmaVersion] The ECMAScript version (or revision number). + * @property {3|5|6|7|8|9|10|11|12|13|14|2015|2016|2017|2018|2019|2020|2021|2022|2023} [ecmaVersion] The ECMAScript version (or revision number). * @property {"script"|"module"} [sourceType] The source code type. * @property {boolean} [allowReserved] Allowing the use of reserved words as identifiers in ES3. */ @@ -190,10 +190,23 @@ module.exports = {}; * @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules. */ +/** + * Information provided when the maximum warning threshold is exceeded. + * @typedef {Object} MaxWarningsExceeded + * @property {number} maxWarnings Number of warnings to trigger nonzero exit code. + * @property {number} foundWarnings Number of warnings found while linting. + */ + +/** + * Metadata about results for formatters. + * @typedef {Object} ResultsMeta + * @property {MaxWarningsExceeded} [maxWarningsExceeded] Present if the maxWarnings threshold was exceeded. + */ + /** * A formatter function. * @callback FormatterFunction * @param {LintResult[]} results The list of linting results. - * @param {{cwd: string, rulesMeta: Record}} [context] A context object. + * @param {{cwd: string, maxWarningsExceeded?: MaxWarningsExceeded, rulesMeta: Record}} [context] A context object. * @returns {string | Promise} Formatted text. */ diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js index 49b2820a846..7e1630cc73b 100644 --- a/lib/source-code/source-code.js +++ b/lib/source-code/source-code.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const - { isCommentToken } = require("eslint-utils"), + { isCommentToken } = require("@eslint-community/eslint-utils"), TokenStore = require("./token-store"), astUtils = require("../shared/ast-utils"), Traverser = require("../shared/traverser"); @@ -143,6 +143,8 @@ function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) { // Public Interface //------------------------------------------------------------------------------ +const caches = Symbol("caches"); + /** * Represents parsed source code. */ @@ -175,6 +177,13 @@ class SourceCode extends TokenStore { validate(ast); super(ast.tokens, ast.comments); + /** + * General purpose caching for the class. + */ + this[caches] = new Map([ + ["scopes", new WeakMap()] + ]); + /** * The flag to indicate that the source code has Unicode BOM. * @type {boolean} @@ -588,6 +597,48 @@ class SourceCode extends TokenStore { return positionIndex; } + + /** + * Gets the scope for the given node + * @param {ASTNode} currentNode The node to get the scope of + * @returns {eslint-scope.Scope} The scope information for this node + * @throws {TypeError} If the `currentNode` argument is missing. + */ + getScope(currentNode) { + + if (!currentNode) { + throw new TypeError("Missing required argument: node."); + } + + // check cache first + const cache = this[caches].get("scopes"); + const cachedScope = cache.get(currentNode); + + if (cachedScope) { + return cachedScope; + } + + // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. + const inner = currentNode.type !== "Program"; + + for (let node = currentNode; node; node = node.parent) { + const scope = this.scopeManager.acquire(node, inner); + + if (scope) { + if (scope.type === "function-expression-name") { + cache.set(currentNode, scope.childScopes[0]); + return scope.childScopes[0]; + } + + cache.set(currentNode, scope); + return scope; + } + } + + cache.set(currentNode, this.scopeManager.scopes[0]); + return this.scopeManager.scopes[0]; + } + } module.exports = SourceCode; diff --git a/lib/source-code/token-store/cursor.js b/lib/source-code/token-store/cursor.js index 494762acdfa..0b726006e8e 100644 --- a/lib/source-code/token-store/cursor.js +++ b/lib/source-code/token-store/cursor.js @@ -69,7 +69,7 @@ module.exports = class Cursor { * @returns {boolean} `true` if the next token exists. * @abstract */ - /* istanbul ignore next */ + /* c8 ignore next */ moveNext() { // eslint-disable-line class-methods-use-this -- Unused throw new Error("Not implemented."); } diff --git a/lib/source-code/token-store/index.js b/lib/source-code/token-store/index.js index 25db8a4f4db..46a96b2f4b1 100644 --- a/lib/source-code/token-store/index.js +++ b/lib/source-code/token-store/index.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const assert = require("assert"); -const { isCommentToken } = require("eslint-utils"); +const { isCommentToken } = require("@eslint-community/eslint-utils"); const cursors = require("./cursors"); const ForwardTokenCursor = require("./forward-token-cursor"); const PaddedTokenCursor = require("./padded-token-cursor"); diff --git a/lib/source-code/token-store/utils.js b/lib/source-code/token-store/utils.js index a2bd77de71a..859831916ea 100644 --- a/lib/source-code/token-store/utils.js +++ b/lib/source-code/token-store/utils.js @@ -49,13 +49,18 @@ exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) { } if ((startLoc - 1) in indexMap) { const index = indexMap[startLoc - 1]; - const token = (index >= 0 && index < tokens.length) ? tokens[index] : null; + const token = tokens[index]; + + // If the mapped index is out of bounds, the returned cursor index will point after the end of the tokens array. + if (!token) { + return tokens.length; + } /* * For the map of "comment's location -> token's index", it points the next token of a comment. * In that case, +1 is unnecessary. */ - if (token && token.range[0] >= startLoc) { + if (token.range[0] >= startLoc) { return index; } return index + 1; @@ -77,13 +82,18 @@ exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) { } if ((endLoc - 1) in indexMap) { const index = indexMap[endLoc - 1]; - const token = (index >= 0 && index < tokens.length) ? tokens[index] : null; + const token = tokens[index]; + + // If the mapped index is out of bounds, the returned cursor index will point before the end of the tokens array. + if (!token) { + return tokens.length - 1; + } /* * For the map of "comment's location -> token's index", it points the next token of a comment. * In that case, -1 is necessary. */ - if (token && token.range[1] > endLoc) { + if (token.range[1] > endLoc) { return index - 1; } return index; diff --git a/messages/no-config-found.js b/messages/no-config-found.js index 9860410a602..21cf549ebc8 100644 --- a/messages/no-config-found.js +++ b/messages/no-config-found.js @@ -10,6 +10,6 @@ ESLint couldn't find a configuration file. To set up a configuration file for th ESLint looked for configuration files in ${directoryPath} and its ancestors. If it found none, it then looked in your home directory. -If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://eslint.org/chat/help +If you think you already have a configuration file or if you need more help, please stop by the ESLint Discord server: https://eslint.org/chat `.trimStart(); }; diff --git a/messages/print-config-with-directory-path.js b/messages/print-config-with-directory-path.js index f65bdaaf770..4559c8d6de4 100644 --- a/messages/print-config-with-directory-path.js +++ b/messages/print-config-with-directory-path.js @@ -3,6 +3,6 @@ module.exports = function() { return ` The '--print-config' CLI option requires a path to a source code file rather than a directory. -See also: https://eslint.org/docs/user-guide/command-line-interface#--print-config +See also: https://eslint.org/docs/latest/use/command-line-interface#--print-config `.trimStart(); }; diff --git a/package.json b/package.json index d5e2c392d4f..e593df9971c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.22.0", + "version": "8.37.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { @@ -13,22 +13,23 @@ "./use-at-your-own-risk": "./lib/unsupported-api.js" }, "scripts": { + "build:docs:update-links": "node tools/fetch-docs-links.js", + "build:site": "node Makefile.js gensite", + "build:webpack": "node Makefile.js webpack", + "build:readme": "node tools/update-readme.js", + "lint": "node Makefile.js lint", + "lint:docs:js": "node Makefile.js lintDocsJS", + "lint:fix": "node Makefile.js lint -- fix", + "lint:fix:docs:js": "node Makefile.js lintDocsJS -- fix", + "release:generate:alpha": "node Makefile.js generatePrerelease -- alpha", + "release:generate:beta": "node Makefile.js generatePrerelease -- beta", + "release:generate:latest": "node Makefile.js generateRelease", + "release:generate:rc": "node Makefile.js generatePrerelease -- rc", + "release:publish": "node Makefile.js publishRelease", "test": "node Makefile.js test", "test:cli": "mocha", - "lint": "node Makefile.js lint", - "lint:docsjs": "node Makefile.js lintDocsJS", - "fix": "node Makefile.js lint -- fix", - "fix:docsjs": "node Makefile.js lintDocsJS -- fix", - "fuzz": "node Makefile.js fuzz", - "generate-release": "node Makefile.js generateRelease", - "generate-alpharelease": "node Makefile.js generatePrerelease -- alpha", - "generate-betarelease": "node Makefile.js generatePrerelease -- beta", - "generate-rcrelease": "node Makefile.js generatePrerelease -- rc", - "publish-release": "node Makefile.js publishRelease", - "gensite": "node Makefile.js gensite", - "webpack": "node Makefile.js webpack", - "perf": "node Makefile.js perf", - "docs:update-links": "node tools/fetch-docs-links.js" + "test:fuzz": "node Makefile.js fuzz", + "test:performance": "node Makefile.js perf" }, "gitHooks": { "pre-commit": "lint-staged" @@ -36,6 +37,10 @@ "lint-staged": { "*.js": "eslint --fix", "*.md": "markdownlint --fix", + "lib/rules/*.js": [ + "node tools/update-eslint-all.js", + "git add packages/js/src/configs/eslint-all.js" + ], "docs/src/rules/*.md": [ "node tools/fetch-docs-links.js", "git add docs/src/_data/further_reading_links.json" @@ -55,9 +60,13 @@ "homepage": "https://eslint.org", "bugs": "https://github.com/eslint/eslint/issues/", "dependencies": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.37.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -65,23 +74,22 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.3", - "esquery": "^1.4.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -89,16 +97,15 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "devDependencies": { "@babel/core": "^7.4.3", "@babel/preset-env": "^7.4.3", "babel-loader": "^8.0.5", + "c8": "^7.12.0", "chai": "^4.0.1", "cheerio": "^0.22.0", "common-tags": "^1.8.0", @@ -120,7 +127,6 @@ "glob": "^7.1.6", "got": "^11.8.3", "gray-matter": "^4.0.3", - "jsdoc": "^3.5.5", "karma": "^6.1.1", "karma-chrome-launcher": "^3.1.0", "karma-mocha": "^2.0.1", @@ -142,7 +148,6 @@ "mocha-junit-reporter": "^2.0.0", "node-polyfill-webpack-plugin": "^1.0.3", "npm-license": "^0.3.3", - "nyc": "^15.0.1", "pirates": "^4.0.5", "progress": "^2.0.3", "proxyquire": "^2.0.1", diff --git a/packages/eslint-config-eslint/README.md b/packages/eslint-config-eslint/README.md index ea144120fc4..c12625d58d3 100644 --- a/packages/eslint-config-eslint/README.md +++ b/packages/eslint-config-eslint/README.md @@ -2,7 +2,7 @@ # ESLint Configuration -[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) | [Twitter](https://twitter.com/geteslint) | [Mailing List](https://groups.google.com/group/eslint) | [Chatroom](https://eslint.org/chat) +[Website](https://eslint.org) | [Configure ESLint](https://eslint.org/docs/latest/use/configure) | [Rules](https://eslint.org/docs/rules/) | [Contributing](https://eslint.org/docs/latest/contribute) | [Twitter](https://twitter.com/geteslint) | [Discord](https://eslint.org/chat) | [Mastodon](https://fosstodon.org/@eslint) Contains the ESLint configuration used for projects maintained by the ESLint team. @@ -32,7 +32,7 @@ In your `.eslintrc` file, add: ### Where to ask for help? -Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://eslint.org/chat) +Open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat) instead of filing an issue. [npm-image]: https://img.shields.io/npm/v/eslint-config-eslint.svg?style=flat-square [npm-url]: https://www.npmjs.com/package/eslint-config-eslint diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml index e233e8b01cc..afaabe4f7ab 100644 --- a/packages/eslint-config-eslint/default.yml +++ b/packages/eslint-config-eslint/default.yml @@ -275,7 +275,7 @@ rules: object-curly-newline: ["error", { "consistent": true, "multiline": true }] object-curly-spacing: ["error", "always"] object-property-newline: ["error", { "allowAllPropertiesOnSameLine": true }] - object-shorthand: "error" + object-shorthand: ["error", "always", { "avoidExplicitReturnArrows": true }] one-var-declaration-per-line: "error" operator-assignment: "error" operator-linebreak: "error" diff --git a/packages/js/LICENSE b/packages/js/LICENSE new file mode 100644 index 00000000000..b607bb36e96 --- /dev/null +++ b/packages/js/LICENSE @@ -0,0 +1,19 @@ +Copyright OpenJS Foundation and other contributors, + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/js/README.md b/packages/js/README.md new file mode 100644 index 00000000000..a8121c3a77b --- /dev/null +++ b/packages/js/README.md @@ -0,0 +1,57 @@ +[![npm version](https://img.shields.io/npm/v/@eslint/js.svg)](https://www.npmjs.com/package/@eslint/js) + +# ESLint JavaScript Plugin + +[Website](https://eslint.org) | [Configure ESLint](https://eslint.org/docs/latest/use/configure) | [Rules](https://eslint.org/docs/rules/) | [Contributing](https://eslint.org/docs/latest/contribute) | [Twitter](https://twitter.com/geteslint) | [Chatroom](https://eslint.org/chat) + +The beginnings of separating out JavaScript-specific functionality from ESLint. + +Right now, this plugin contains two configurations: + +* `recommended` - enables the rules recommended by the ESLint team (the replacement for `"eslint:recommended"`) +* `all` - enables all ESLint rules (the replacement for `"eslint:all"`) + +## Installation + +```shell +npm install @eslint/js -D +``` + +## Usage + +Use in your `eslint.config.js` file anytime you want to extend one of the configs: + +```js +import js from "@eslint/js"; + +export default [ + + // apply recommended rules to JS files + { + files: ["**/*.js"], + rules: js.configs.recommended.rules + }, + + // apply recommended rules to JS files with an override + { + files: ["**/*.js"], + rules: { + ...js.configs.recommended.rules, + "no-unused-vars": "warn" + } + }, + + // apply all rules to JS files + { + files: ["**/*.js"], + rules: { + ...js.configs.all.rules, + "no-unused-vars": "warn" + } + } +] +``` + +## License + +MIT diff --git a/packages/js/package.json b/packages/js/package.json new file mode 100644 index 00000000000..27e4f0056a1 --- /dev/null +++ b/packages/js/package.json @@ -0,0 +1,31 @@ +{ + "name": "@eslint/js", + "version": "8.37.0", + "description": "ESLint JavaScript language implementation", + "main": "./src/index.js", + "scripts": {}, + "files": [ + "LICENSE", + "README.md", + "src" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/eslint/eslint.git", + "directory": "packages/js" + }, + "homepage": "https://eslint.org", + "bugs": "https://github.com/eslint/eslint/issues/", + "keywords": [ + "javascript", + "eslint-plugin", + "eslint" + ], + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } +} diff --git a/packages/js/src/configs/eslint-all.js b/packages/js/src/configs/eslint-all.js new file mode 100644 index 00000000000..f3a415cce5c --- /dev/null +++ b/packages/js/src/configs/eslint-all.js @@ -0,0 +1,279 @@ +/* + * WARNING: This file is autogenerated using the tools/update-eslint-all.js + * script. Do not edit manually. + */ +"use strict"; + +/* eslint quote-props: off -- autogenerated so don't lint */ + +module.exports = Object.freeze({ + "rules": { + "accessor-pairs": "error", + "array-bracket-newline": "error", + "array-bracket-spacing": "error", + "array-callback-return": "error", + "array-element-newline": "error", + "arrow-body-style": "error", + "arrow-parens": "error", + "arrow-spacing": "error", + "block-scoped-var": "error", + "block-spacing": "error", + "brace-style": "error", + "camelcase": "error", + "capitalized-comments": "error", + "class-methods-use-this": "error", + "comma-dangle": "error", + "comma-spacing": "error", + "comma-style": "error", + "complexity": "error", + "computed-property-spacing": "error", + "consistent-return": "error", + "consistent-this": "error", + "constructor-super": "error", + "curly": "error", + "default-case": "error", + "default-case-last": "error", + "default-param-last": "error", + "dot-location": "error", + "dot-notation": "error", + "eol-last": "error", + "eqeqeq": "error", + "for-direction": "error", + "func-call-spacing": "error", + "func-name-matching": "error", + "func-names": "error", + "func-style": "error", + "function-call-argument-newline": "error", + "function-paren-newline": "error", + "generator-star-spacing": "error", + "getter-return": "error", + "grouped-accessor-pairs": "error", + "guard-for-in": "error", + "id-denylist": "error", + "id-length": "error", + "id-match": "error", + "implicit-arrow-linebreak": "error", + "indent": "error", + "init-declarations": "error", + "jsx-quotes": "error", + "key-spacing": "error", + "keyword-spacing": "error", + "line-comment-position": "error", + "linebreak-style": "error", + "lines-around-comment": "error", + "lines-between-class-members": "error", + "logical-assignment-operators": "error", + "max-classes-per-file": "error", + "max-depth": "error", + "max-len": "error", + "max-lines": "error", + "max-lines-per-function": "error", + "max-nested-callbacks": "error", + "max-params": "error", + "max-statements": "error", + "max-statements-per-line": "error", + "multiline-comment-style": "error", + "multiline-ternary": "error", + "new-cap": "error", + "new-parens": "error", + "newline-per-chained-call": "error", + "no-alert": "error", + "no-array-constructor": "error", + "no-async-promise-executor": "error", + "no-await-in-loop": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-case-declarations": "error", + "no-class-assign": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": "error", + "no-confusing-arrow": "error", + "no-console": "error", + "no-const-assign": "error", + "no-constant-binary-expression": "error", + "no-constant-condition": "error", + "no-constructor-return": "error", + "no-continue": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-div-regex": "error", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-else-if": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-duplicate-imports": "error", + "no-else-return": "error", + "no-empty": "error", + "no-empty-character-class": "error", + "no-empty-function": "error", + "no-empty-pattern": "error", + "no-empty-static-block": "error", + "no-eq-null": "error", + "no-eval": "error", + "no-ex-assign": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-boolean-cast": "error", + "no-extra-label": "error", + "no-extra-parens": "error", + "no-extra-semi": "error", + "no-fallthrough": "error", + "no-floating-decimal": "error", + "no-func-assign": "error", + "no-global-assign": "error", + "no-implicit-coercion": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-import-assign": "error", + "no-inline-comments": "error", + "no-inner-declarations": "error", + "no-invalid-regexp": "error", + "no-invalid-this": "error", + "no-irregular-whitespace": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-loss-of-precision": "error", + "no-magic-numbers": "error", + "no-misleading-character-class": "error", + "no-mixed-operators": "error", + "no-mixed-spaces-and-tabs": "error", + "no-multi-assign": "error", + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-multiple-empty-lines": "error", + "no-negated-condition": "error", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-native-nonconstructor": "error", + "no-new-object": "error", + "no-new-symbol": "error", + "no-new-wrappers": "error", + "no-nonoctal-decimal-escape": "error", + "no-obj-calls": "error", + "no-octal": "error", + "no-octal-escape": "error", + "no-param-reassign": "error", + "no-plusplus": "error", + "no-promise-executor-return": "error", + "no-proto": "error", + "no-prototype-builtins": "error", + "no-redeclare": "error", + "no-regex-spaces": "error", + "no-restricted-exports": "error", + "no-restricted-globals": "error", + "no-restricted-imports": "error", + "no-restricted-properties": "error", + "no-restricted-syntax": "error", + "no-return-assign": "error", + "no-return-await": "error", + "no-script-url": "error", + "no-self-assign": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-setter-return": "error", + "no-shadow": "error", + "no-shadow-restricted-names": "error", + "no-sparse-arrays": "error", + "no-tabs": "error", + "no-template-curly-in-string": "error", + "no-ternary": "error", + "no-this-before-super": "error", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-undef": "error", + "no-undef-init": "error", + "no-undefined": "error", + "no-underscore-dangle": "error", + "no-unexpected-multiline": "error", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unreachable": "error", + "no-unreachable-loop": "error", + "no-unsafe-finally": "error", + "no-unsafe-negation": "error", + "no-unsafe-optional-chaining": "error", + "no-unused-expressions": "error", + "no-unused-labels": "error", + "no-unused-private-class-members": "error", + "no-unused-vars": "error", + "no-use-before-define": "error", + "no-useless-backreference": "error", + "no-useless-call": "error", + "no-useless-catch": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-escape": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "error", + "no-void": "error", + "no-warning-comments": "error", + "no-whitespace-before-property": "error", + "no-with": "error", + "nonblock-statement-body-position": "error", + "object-curly-newline": "error", + "object-curly-spacing": "error", + "object-property-newline": "error", + "object-shorthand": "error", + "one-var": "error", + "one-var-declaration-per-line": "error", + "operator-assignment": "error", + "operator-linebreak": "error", + "padded-blocks": "error", + "padding-line-between-statements": "error", + "prefer-arrow-callback": "error", + "prefer-const": "error", + "prefer-destructuring": "error", + "prefer-exponentiation-operator": "error", + "prefer-named-capture-group": "error", + "prefer-numeric-literals": "error", + "prefer-object-has-own": "error", + "prefer-object-spread": "error", + "prefer-promise-reject-errors": "error", + "prefer-regex-literals": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + "quote-props": "error", + "quotes": "error", + "radix": "error", + "require-atomic-updates": "error", + "require-await": "error", + "require-unicode-regexp": "error", + "require-yield": "error", + "rest-spread-spacing": "error", + "semi": "error", + "semi-spacing": "error", + "semi-style": "error", + "sort-imports": "error", + "sort-keys": "error", + "sort-vars": "error", + "space-before-blocks": "error", + "space-before-function-paren": "error", + "space-in-parens": "error", + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": "error", + "strict": "error", + "switch-colon-spacing": "error", + "symbol-description": "error", + "template-curly-spacing": "error", + "template-tag-spacing": "error", + "unicode-bom": "error", + "use-isnan": "error", + "valid-typeof": "error", + "vars-on-top": "error", + "wrap-iife": "error", + "wrap-regex": "error", + "yield-star-spacing": "error", + "yoda": "error" + } +}); diff --git a/conf/eslint-recommended.js b/packages/js/src/configs/eslint-recommended.js similarity index 97% rename from conf/eslint-recommended.js rename to packages/js/src/configs/eslint-recommended.js index 6f639855a96..248c613caed 100644 --- a/conf/eslint-recommended.js +++ b/packages/js/src/configs/eslint-recommended.js @@ -9,8 +9,8 @@ /* eslint sort-keys: ["error", "asc"] -- Long, so make more readable */ /** @type {import("../lib/shared/types").ConfigData} */ -module.exports = { - rules: { +module.exports = Object.freeze({ + rules: Object.freeze({ "constructor-super": "error", "for-direction": "error", "getter-return": "error", @@ -72,5 +72,5 @@ module.exports = { "require-yield": "error", "use-isnan": "error", "valid-typeof": "error" - } -}; + }) +}); diff --git a/packages/js/src/index.js b/packages/js/src/index.js new file mode 100644 index 00000000000..0d4be486a9b --- /dev/null +++ b/packages/js/src/index.js @@ -0,0 +1,17 @@ +/** + * @fileoverview Main package entrypoint. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + configs: { + all: require("./configs/eslint-all"), + recommended: require("./configs/eslint-recommended") + } +}; diff --git a/templates/blogpost.md.ejs b/templates/blogpost.md.ejs index 2d73ee127fe..efb1161ed47 100644 --- a/templates/blogpost.md.ejs +++ b/templates/blogpost.md.ejs @@ -50,7 +50,7 @@ npm i eslint@<%- version %> --save-dev ### Migration Guide -As there are a lot of changes, we've created a [migration guide](/docs/<%- prereleaseMajorVersion %>/user-guide/migrating-to-<%- prereleaseMajorVersion %>) describing the changes in great detail along with the steps you should take to address them. We expect that most users should be able to upgrade without any build changes, but the migration guide should be a useful resource if you encounter problems. +As there are a lot of changes, we've created a [migration guide](/docs/<%- prereleaseMajorVersion %>/use/migrating-to-<%- prereleaseMajorVersion %>) describing the changes in great detail along with the steps you should take to address them. We expect that most users should be able to upgrade without any build changes, but the migration guide should be a useful resource if you encounter problems. <% } %> diff --git a/templates/bug-report.md b/templates/bug-report.md index c8c552d306d..d7a7a404907 100644 --- a/templates/bug-report.md +++ b/templates/bug-report.md @@ -1,10 +1,19 @@ -**Tell us about your environment:** - -* **ESLint Version:** -* **Node Version:** -* **npm Version:** - -**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?** +**Tell us about your environment (`npx eslint --env-info`):** + +* **Node version:** +* **npm version:** +* **Local ESLint version:** +* **Global ESLint version:** +* **Operating System:** + +**What parser are you using (place an "X" next to just one item)?** + +[ ] `Default (Espree)` +[ ] `@typescript-eslint/parser` +[ ] `@babel/eslint-parser` +[ ] `vue-eslint-parser` +[ ] `@angular-eslint/template-parser` +[ ] `Other` **Please show your full configuration:** diff --git a/templates/formatter-examples.md.ejs b/templates/formatter-examples.md.ejs index 6dae9d79002..20f46f2d5aa 100644 --- a/templates/formatter-examples.md.ejs +++ b/templates/formatter-examples.md.ejs @@ -1,17 +1,16 @@ --- -title: Formatters -layout: doc +title: Formatters Reference eleventyNavigation: key: formatters - parent: user guide - title: Formatters - order: 5 + parent: use eslint + title: Formatters Reference + order: 6 edit_link: https://github.com/eslint/eslint/edit/main/templates/formatter-examples.md.ejs --- ESLint comes with several built-in formatters to control the appearance of the linting results, and supports third-party formatters as well. -You can specify a formatter using the `--format` or `-f` flag on the command line. For example, `--format json` uses the `json` formatter. +You can specify a formatter using the `--format` or `-f` flag in the CLI. For example, `--format json` uses the `json` formatter. The built-in formatter options are: @@ -21,9 +20,9 @@ The built-in formatter options are: ## Example Source -Examples of each formatter were created from linting `fullOfProblems.js` using the `.eslintrc` configuration shown below. +Examples of each formatter were created from linting `fullOfProblems.js` using the `.eslintrc.json` configuration shown below. -### `fullOfProblems.js` +`fullOfProblems.js`: ```js function addOne(i) { @@ -35,7 +34,7 @@ function addOne(i) { }; ``` -### `.eslintrc`: +`.eslintrc.json`: ```json { @@ -50,17 +49,26 @@ function addOne(i) { } ``` -## Output Examples +Tests the formatters with the CLI: + +```shell +npx eslint --format fullOfProblems.js +``` + +## Built-In Formatter Options <% Object.keys(formatterResults).forEach(function(formatterName) { -%> ### <%= formatterName %> -<% if (formatterName !== "html") { -%> +<%= formatterResults[formatterName].description %> + +Example output: + +<% if (formatterName !== "html") { -%> ```text -<%= formatterResults[formatterName].result %> +<%- formatterResults[formatterName].result %> ``` <% } else {-%> - <% } -%> <% }) -%> diff --git a/templates/rule-change-proposal.md b/templates/rule-change-proposal.md index a4a8e17cbd5..a4d4acb2ad4 100644 --- a/templates/rule-change-proposal.md +++ b/templates/rule-change-proposal.md @@ -1,8 +1,17 @@ **What rule do you want to change?** -**Does this change cause the rule to produce more or fewer warnings?** +**What change do you want to make (place an "X" next to just one item)?** -**How will the change be implemented? (New option, new default behavior, etc.)?** +[ ] Generate more warnings +[ ] Generate fewer warnings +[ ] Implement autofix +[ ] Implement suggestions + +**How will the change be implemented (place an "X" next to just one item)?** + +[ ] A new option +[ ] A new default behavior +[ ] Other **Please provide some example code that this change will affect:** diff --git a/templates/rule-proposal.md b/templates/rule-proposal.md index 2adf9317613..cc1b011361a 100644 --- a/templates/rule-proposal.md +++ b/templates/rule-proposal.md @@ -1,13 +1,14 @@ -**Please describe what the rule should do:** +**What should the new rule do?** + +**What new ECMAScript feature does this rule relate to? Note that we only accept new core rules related to new ECMAScript features.** **What category of rule is this (place an "X" next to just one item)?** -[ ] Enforces code style -[ ] Warns about a potential error +[ ] Warns about a potential problem [ ] Suggests an alternate way of doing something -[ ] Other (please specify:) +[ ] Enforces a formatting/stylistic preference -**Provide 2-3 code examples that this rule will warn about:** +**Please provide some example JavaScript code that this rule will warn about:** ```js diff --git a/tests/bench/large.js b/tests/bench/large.js index a3db1ac6c8d..5361f0a0324 100644 --- a/tests/bench/large.js +++ b/tests/bench/large.js @@ -50947,7 +50947,7 @@ exports.debuglog = function(set) { /** - * Echos the value of a value. Trys to print the value out + * Echos the value of a value. Tries to print the value out * in the best way possible given the different types. * * @param {Object} obj The object to print out. diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js index 4a929a62c11..848dba14671 100644 --- a/tests/bin/eslint.js +++ b/tests/bin/eslint.js @@ -82,7 +82,7 @@ describe("bin/eslint.js", () => { describe("reading from stdin", () => { it("has exit code 0 if no linting errors are reported", () => { - const child = runESLint(["--stdin", "--no-eslintrc"]); + const child = runESLint(["--stdin", "--no-config-lookup"]); child.stdin.write("var foo = bar;\n"); child.stdin.end(); @@ -92,7 +92,7 @@ describe("bin/eslint.js", () => { it("has exit code 0 if no linting errors are reported", () => { const child = runESLint([ "--stdin", - "--no-eslintrc", + "--no-config-lookup", "--rule", "{'no-extra-semi': 2}", "--fix-dry-run", @@ -128,7 +128,7 @@ describe("bin/eslint.js", () => { }); it("has exit code 1 if a syntax error is thrown", () => { - const child = runESLint(["--stdin", "--no-eslintrc"]); + const child = runESLint(["--stdin", "--no-config-lookup"]); child.stdin.write("This is not valid JS syntax.\n"); child.stdin.end(); @@ -136,7 +136,7 @@ describe("bin/eslint.js", () => { }); it("has exit code 2 if a syntax error is thrown when exit-on-fatal-error is true", () => { - const child = runESLint(["--stdin", "--no-eslintrc", "--exit-on-fatal-error"]); + const child = runESLint(["--stdin", "--no-config-lookup", "--exit-on-fatal-error"]); child.stdin.write("This is not valid JS syntax.\n"); child.stdin.end(); @@ -144,7 +144,7 @@ describe("bin/eslint.js", () => { }); it("has exit code 1 if a linting error occurs", () => { - const child = runESLint(["--stdin", "--no-eslintrc", "--rule", "semi:2"]); + const child = runESLint(["--stdin", "--no-config-lookup", "--rule", "semi:2"]); child.stdin.write("var foo = bar // <-- no semicolon\n"); child.stdin.end(); @@ -182,7 +182,7 @@ describe("bin/eslint.js", () => { ); it("successfully reads from an asynchronous pipe", () => { - const child = runESLint(["--stdin", "--no-eslintrc"]); + const child = runESLint(["--stdin", "--no-config-lookup"]); child.stdin.write("var foo = bar;\n"); return new Promise(resolve => setTimeout(resolve, 300)).then(() => { @@ -194,7 +194,7 @@ describe("bin/eslint.js", () => { }); it("successfully handles more than 4k data via stdin", () => { - const child = runESLint(["--stdin", "--no-eslintrc"]); + const child = runESLint(["--stdin", "--no-config-lookup"]); const large = fs.createReadStream(path.join(__dirname, "../bench/large.js"), "utf8"); large.pipe(child.stdin); @@ -205,9 +205,9 @@ describe("bin/eslint.js", () => { describe("running on files", () => { it("has exit code 0 if no linting errors occur", () => assertExitCode(runESLint(["bin/eslint.js"]), 0)); - it("has exit code 0 if a linting warning is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--env", "es2021", "--no-eslintrc", "--rule", "semi: [1, never]"]), 0)); - it("has exit code 1 if a linting error is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--env", "es2021", "--no-eslintrc", "--rule", "semi: [2, never]"]), 1)); - it("has exit code 1 if a syntax error is thrown", () => assertExitCode(runESLint(["README.md"]), 1)); + it("has exit code 0 if a linting warning is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--no-config-lookup", "--rule", "semi: [1, never]"]), 0)); + it("has exit code 1 if a linting error is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--no-config-lookup", "--rule", "semi: [2, never]"]), 1)); + it("has exit code 1 if a syntax error is thrown", () => assertExitCode(runESLint(["tests/fixtures/exit-on-fatal-error/fatal-error.js", "--no-ignore"]), 1)); }); describe("automatically fixing files", () => { @@ -222,7 +222,7 @@ describe("bin/eslint.js", () => { }); it("has exit code 0 and fixes a file if all rules can be fixed", () => { - const child = runESLint(["--fix", "--no-eslintrc", "--no-ignore", tempFilePath]); + const child = runESLint(["--fix", "--no-config-lookup", "--no-ignore", tempFilePath]); const exitCodeAssertion = assertExitCode(child, 0); const outputFileAssertion = awaitExit(child).then(() => { assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedText); @@ -232,7 +232,7 @@ describe("bin/eslint.js", () => { }); it("has exit code 0, fixes errors in a file, and does not report or fix warnings if --quiet and --fix are used", () => { - const child = runESLint(["--fix", "--quiet", "--no-eslintrc", "--no-ignore", tempFilePath]); + const child = runESLint(["--fix", "--quiet", "--no-config-lookup", "--no-ignore", tempFilePath]); const exitCodeAssertion = assertExitCode(child, 0); const stdoutAssertion = getOutput(child).then(output => assert.strictEqual(output.stdout, "")); const outputFileAssertion = awaitExit(child).then(() => { @@ -243,7 +243,7 @@ describe("bin/eslint.js", () => { }); it("has exit code 1 and fixes a file if not all rules can be fixed", () => { - const child = runESLint(["--fix", "--no-eslintrc", "--no-ignore", "--rule", "max-len: [2, 10]", tempFilePath]); + const child = runESLint(["--fix", "--no-config-lookup", "--no-ignore", "--rule", "max-len: [2, 10]", tempFilePath]); const exitCodeAssertion = assertExitCode(child, 1); const outputFileAssertion = awaitExit(child).then(() => { assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedText); @@ -260,7 +260,7 @@ describe("bin/eslint.js", () => { describe("cache files", () => { const CACHE_PATH = ".temp-eslintcache"; const SOURCE_PATH = "tests/fixtures/cache/src/test-file.js"; - const ARGS_WITHOUT_CACHE = ["--no-eslintrc", "--no-ignore", SOURCE_PATH, "--cache-location", CACHE_PATH]; + const ARGS_WITHOUT_CACHE = ["--no-config-lookup", "--no-ignore", SOURCE_PATH, "--cache-location", CACHE_PATH]; const ARGS_WITH_CACHE = ARGS_WITHOUT_CACHE.concat("--cache"); describe("when no cache file exists", () => { @@ -388,40 +388,10 @@ describe("bin/eslint.js", () => { }); it("prints the error message pointing to line of code", () => { - const invalidConfig = path.join(__dirname, "../fixtures/bin/.eslintrc.yml"); - const child = runESLint(["--no-ignore", invalidConfig]); - const exitCodeAssertion = assertExitCode(child, 2); - const outputAssertion = getOutput(child).then(output => { - assert.strictEqual(output.stdout, ""); - assert.match( - output.stderr, - /: bad indentation of a mapping entry \(\d+:\d+\)/u // a part of the error message from `js-yaml` dependency - ); - }); - - return Promise.all([exitCodeAssertion, outputAssertion]); - }); - }); - + const invalidConfig = path.join(__dirname, "../fixtures/bin/eslint.config.js"); + const child = runESLint(["--no-ignore", "-c", invalidConfig]); - describe("emitting a warning for ecmaFeatures", () => { - it("does not emit a warning when it does not find an ecmaFeatures option", () => { - const child = runESLint(["Makefile.js"]); - - const exitCodePromise = assertExitCode(child, 0); - const outputPromise = getOutput(child).then(output => assert.strictEqual(output.stderr, "")); - - return Promise.all([exitCodePromise, outputPromise]); - }); - it("emits a warning when it finds an ecmaFeatures option", () => { - const child = runESLint(["-c", "tests/fixtures/config-file/ecma-features/.eslintrc.yml", "Makefile.js"]); - - const exitCodePromise = assertExitCode(child, 0); - const outputPromise = getOutput(child).then(output => { - assert.include(output.stderr, "The 'ecmaFeatures' config file property is deprecated and has no effect."); - }); - - return Promise.all([exitCodePromise, outputPromise]); + return assertExitCode(child, 2); }); }); diff --git a/tests/conf/eslint-all.js b/tests/conf/eslint-all.js index 472ac3b25d7..abee0c69b16 100644 --- a/tests/conf/eslint-all.js +++ b/tests/conf/eslint-all.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert; -const eslintAll = require("../../conf/eslint-all"); +const eslintAll = require("../../packages/js").configs.all; const rules = eslintAll.rules; //------------------------------------------------------------------------------ diff --git a/tests/conf/eslint-recommended.js b/tests/conf/eslint-recommended.js index a3068e14140..59803dce6cf 100644 --- a/tests/conf/eslint-recommended.js +++ b/tests/conf/eslint-recommended.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert; -const eslintRecommended = require("../../conf/eslint-recommended"); +const eslintRecommended = require("../../packages/js").configs.recommended; const rules = eslintRecommended.rules; //------------------------------------------------------------------------------ diff --git a/tests/fixtures/cli-engine/empty/.keep b/tests/fixtures/.eslintignore_empty similarity index 100% rename from tests/fixtures/cli-engine/empty/.keep rename to tests/fixtures/.eslintignore_empty diff --git a/tests/fixtures/bin/eslint.config.js b/tests/fixtures/bin/eslint.config.js new file mode 100644 index 00000000000..bbaa7590f3f --- /dev/null +++ b/tests/fixtures/bin/eslint.config.js @@ -0,0 +1,5 @@ +// intentionally invalid JavaScript +rules: + semi: error +yoda: error + quotes: error diff --git a/tests/fixtures/cli-engine/eslint.config_with_ignores2.js b/tests/fixtures/cli-engine/eslint.config_with_ignores2.js new file mode 100644 index 00000000000..0450eb699f2 --- /dev/null +++ b/tests/fixtures/cli-engine/eslint.config_with_ignores2.js @@ -0,0 +1,3 @@ +module.exports = [{ + ignores: ["**/fixtures/**"] +}]; diff --git a/tests/fixtures/cli/ignore-pattern-relative/.eslintrc.js b/tests/fixtures/cli/ignore-pattern-relative/.eslintrc.js new file mode 100644 index 00000000000..cfa7c2db15b --- /dev/null +++ b/tests/fixtures/cli/ignore-pattern-relative/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + root: true +}; diff --git a/tests/fixtures/cli/ignore-pattern-relative/eslint.config.js b/tests/fixtures/cli/ignore-pattern-relative/eslint.config.js new file mode 100644 index 00000000000..e0a30c5dfa3 --- /dev/null +++ b/tests/fixtures/cli/ignore-pattern-relative/eslint.config.js @@ -0,0 +1 @@ +module.exports = []; diff --git a/tests/fixtures/cli/ignore-pattern-relative/subdir/subsubdir/a.js b/tests/fixtures/cli/ignore-pattern-relative/subdir/subsubdir/a.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/configurations/es6.js b/tests/fixtures/configurations/es6.js new file mode 100644 index 00000000000..975beb20920 --- /dev/null +++ b/tests/fixtures/configurations/es6.js @@ -0,0 +1,7 @@ +module.exports = { + languageOptions: { + parserOptions: { + ecmaVersion: 6 + } + } +}; diff --git a/tests/fixtures/dot-files/.a.js b/tests/fixtures/dot-files/.a.js new file mode 100644 index 00000000000..e5f581a97a9 --- /dev/null +++ b/tests/fixtures/dot-files/.a.js @@ -0,0 +1 @@ +console.log("Running"); diff --git a/tests/fixtures/dot-files/.c.js b/tests/fixtures/dot-files/.c.js new file mode 100644 index 00000000000..e5f581a97a9 --- /dev/null +++ b/tests/fixtures/dot-files/.c.js @@ -0,0 +1 @@ +console.log("Running"); diff --git a/tests/fixtures/dot-files/b.js b/tests/fixtures/dot-files/b.js new file mode 100644 index 00000000000..e5f581a97a9 --- /dev/null +++ b/tests/fixtures/dot-files/b.js @@ -0,0 +1 @@ +console.log("Running"); diff --git a/tests/fixtures/dot-files/eslint.config.js b/tests/fixtures/dot-files/eslint.config.js new file mode 100644 index 00000000000..e0b84a703fd --- /dev/null +++ b/tests/fixtures/dot-files/eslint.config.js @@ -0,0 +1,8 @@ +module.exports = [ + { + files: ["*.js"] + }, + { + ignores: ["eslint.config.js"] + } +]; diff --git a/tests/fixtures/dots-in-files/a..b.js b/tests/fixtures/dots-in-files/a..b.js new file mode 100644 index 00000000000..e5f581a97a9 --- /dev/null +++ b/tests/fixtures/dots-in-files/a..b.js @@ -0,0 +1 @@ +console.log("Running"); diff --git a/tests/fixtures/dots-in-files/eslint.config.js b/tests/fixtures/dots-in-files/eslint.config.js new file mode 100644 index 00000000000..8c1da94e458 --- /dev/null +++ b/tests/fixtures/dots-in-files/eslint.config.js @@ -0,0 +1,8 @@ +module.exports = [ + { + files: ["a..b.js"] + }, + { + ignores: ["eslint.config.js"] + } +]; diff --git a/tests/fixtures/eslint.config_with_ignores.js b/tests/fixtures/eslint.config_with_ignores.js new file mode 100644 index 00000000000..4640a3bf3e9 --- /dev/null +++ b/tests/fixtures/eslint.config_with_ignores.js @@ -0,0 +1,8 @@ +const eslintConfig = require("./eslint.config.js"); + +module.exports = [ + eslintConfig, + { + ignores: ["**/*.json", "**/*.js"] + } +]; diff --git a/tests/fixtures/eslint.config_with_ignores2.js b/tests/fixtures/eslint.config_with_ignores2.js new file mode 100644 index 00000000000..ec15bf421bc --- /dev/null +++ b/tests/fixtures/eslint.config_with_ignores2.js @@ -0,0 +1,8 @@ +const eslintConfig = require("./eslint.config.js"); + +module.exports = [ + eslintConfig, + { + ignores: ["**/undef.js", "undef2.js", "**/undef3.js"] + } +]; diff --git a/tests/fixtures/eslint.config_with_rules.js b/tests/fixtures/eslint.config_with_rules.js new file mode 100644 index 00000000000..6817217d965 --- /dev/null +++ b/tests/fixtures/eslint.config_with_rules.js @@ -0,0 +1,5 @@ +module.exports = [{ + rules: { + quotes: ["error", "single"] + } +}]; diff --git a/tests/fixtures/example-app/app.js b/tests/fixtures/example-app/app.js new file mode 100644 index 00000000000..e5f581a97a9 --- /dev/null +++ b/tests/fixtures/example-app/app.js @@ -0,0 +1 @@ +console.log("Running"); diff --git a/tests/fixtures/example-app/eslint.config.js b/tests/fixtures/example-app/eslint.config.js new file mode 100644 index 00000000000..e0a30c5dfa3 --- /dev/null +++ b/tests/fixtures/example-app/eslint.config.js @@ -0,0 +1 @@ +module.exports = []; diff --git a/tests/fixtures/example-app/subdir/util.js b/tests/fixtures/example-app/subdir/util.js new file mode 100644 index 00000000000..f053ebf7976 --- /dev/null +++ b/tests/fixtures/example-app/subdir/util.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/tests/fixtures/example-app2/eslint.config.js b/tests/fixtures/example-app2/eslint.config.js new file mode 100644 index 00000000000..cfef56877df --- /dev/null +++ b/tests/fixtures/example-app2/eslint.config.js @@ -0,0 +1,3 @@ +module.exports = [{ + files: ["subdir*/**/*.js"] +}]; diff --git a/tests/fixtures/example-app2/subdir1/a.js b/tests/fixtures/example-app2/subdir1/a.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/example-app2/subdir2/b.js b/tests/fixtures/example-app2/subdir2/b.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/ignores-directory/eslint.config.js b/tests/fixtures/ignores-directory/eslint.config.js new file mode 100644 index 00000000000..25d0cef4bcb --- /dev/null +++ b/tests/fixtures/ignores-directory/eslint.config.js @@ -0,0 +1,3 @@ +module.exports = { + ignores: ["subdir/subsubdir"] +}; diff --git a/tests/fixtures/ignores-directory/subdir/subsubdir/a.js b/tests/fixtures/ignores-directory/subdir/subsubdir/a.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/ignores-relative/a.js b/tests/fixtures/ignores-relative/a.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/ignores-relative/eslint.config.js b/tests/fixtures/ignores-relative/eslint.config.js new file mode 100644 index 00000000000..41032a96ad1 --- /dev/null +++ b/tests/fixtures/ignores-relative/eslint.config.js @@ -0,0 +1,5 @@ +module.exports = [ + { + ignores: ["a.js"] + } +]; diff --git a/tests/fixtures/ignores-relative/subdir/a.js b/tests/fixtures/ignores-relative/subdir/a.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/ignores-self/eslint.config.js b/tests/fixtures/ignores-self/eslint.config.js new file mode 100644 index 00000000000..35f93ee5df3 --- /dev/null +++ b/tests/fixtures/ignores-self/eslint.config.js @@ -0,0 +1,3 @@ +module.exports = { + ignores: ["**/ignores-self/**"] +}; diff --git a/tests/fixtures/ignores-subdirectory/eslint.config.js b/tests/fixtures/ignores-subdirectory/eslint.config.js new file mode 100644 index 00000000000..2e1db61e7fe --- /dev/null +++ b/tests/fixtures/ignores-subdirectory/eslint.config.js @@ -0,0 +1,3 @@ +module.exports = { + ignores: ["subdir"] +}; diff --git a/tests/fixtures/ignores-subdirectory/subdir/subsubdir/a.js b/tests/fixtures/ignores-subdirectory/subdir/subsubdir/a.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/max-warnings/eslint.config.js b/tests/fixtures/max-warnings/eslint.config.js new file mode 100644 index 00000000000..8ad137c75fa --- /dev/null +++ b/tests/fixtures/max-warnings/eslint.config.js @@ -0,0 +1,5 @@ +module.exports = { + rules: { + quotes: [1, "single"] + } +}; diff --git a/tests/fixtures/parsers/all-comments-parser.js b/tests/fixtures/parsers/all-comments-parser.js new file mode 100644 index 00000000000..595392a0970 --- /dev/null +++ b/tests/fixtures/parsers/all-comments-parser.js @@ -0,0 +1,28 @@ +// Similar to the default parser, but considers leading and trailing comments to be part of the root node. +// Some custom parsers like @typescript-eslint/parser behave in this way. + +const espree = require("espree"); +exports.parse = function(code, options) { + const ast = espree.parse(code, options); + + if (ast.range && ast.comments && ast.comments.length > 0) { + const firstComment = ast.comments[0]; + const lastComment = ast.comments[ast.comments.length - 1]; + + if (ast.range[0] > firstComment.range[0]) { + ast.range[0] = firstComment.range[0]; + ast.start = firstComment.start; + if (ast.loc) { + ast.loc.start = firstComment.loc.start; + } + } + if (ast.range[1] < lastComment.range[1]) { + ast.range[1] = lastComment.range[1]; + ast.end = lastComment.end; + if (ast.loc) { + ast.loc.end = lastComment.loc.end; + } + } + } + return ast; +}; diff --git a/tests/fixtures/rules/custom-rule.js b/tests/fixtures/rules/custom-rule.js index 6d8b662ba39..8b1cce10dbc 100644 --- a/tests/fixtures/rules/custom-rule.js +++ b/tests/fixtures/rules/custom-rule.js @@ -1,15 +1,17 @@ -module.exports = function(context) { +module.exports = { + meta: { + schema: [] + }, + create(context) { - "use strict"; + "use strict"; - return { - "Identifier": function(node) { - if (node.name === "foo") { - context.report(node, "Identifier cannot be named 'foo'."); + return { + "Identifier": function(node) { + if (node.name === "foo") { + context.report(node, "Identifier cannot be named 'foo'."); + } } - } - }; - + }; + } }; - -module.exports.schema = []; diff --git a/tests/fixtures/shallow-glob/eslint.config.js b/tests/fixtures/shallow-glob/eslint.config.js new file mode 100644 index 00000000000..211719ddb90 --- /dev/null +++ b/tests/fixtures/shallow-glob/eslint.config.js @@ -0,0 +1,5 @@ +module.exports = [ + { + files: ["subdir/*.js"] + } +]; diff --git a/tests/fixtures/shallow-glob/subdir/broken.js b/tests/fixtures/shallow-glob/subdir/broken.js new file mode 100644 index 00000000000..d280fee2fdc --- /dev/null +++ b/tests/fixtures/shallow-glob/subdir/broken.js @@ -0,0 +1 @@ +module.exports = /* intentional syntax error */ diff --git a/tests/fixtures/shallow-glob/subdir/subsubdir/broken.js b/tests/fixtures/shallow-glob/subdir/subsubdir/broken.js new file mode 100644 index 00000000000..2e1fe2e76a2 --- /dev/null +++ b/tests/fixtures/shallow-glob/subdir/subsubdir/broken.js @@ -0,0 +1 @@ +function( {} // intentional syntax error diff --git a/tests/fixtures/shallow-glob/subdir/subsubdir/plain.jsx b/tests/fixtures/shallow-glob/subdir/subsubdir/plain.jsx new file mode 100644 index 00000000000..e901f01b487 --- /dev/null +++ b/tests/fixtures/shallow-glob/subdir/subsubdir/plain.jsx @@ -0,0 +1 @@ +foo; diff --git a/tests/fixtures/shallow-glob/target-dir/passing.js b/tests/fixtures/shallow-glob/target-dir/passing.js new file mode 100644 index 00000000000..ec01c2c1416 --- /dev/null +++ b/tests/fixtures/shallow-glob/target-dir/passing.js @@ -0,0 +1 @@ +module.exports = true; diff --git a/tests/fixtures/testers/rule-tester/no-test-global.js b/tests/fixtures/testers/rule-tester/no-test-global.js index 6703cc62100..96c4c0790be 100644 --- a/tests/fixtures/testers/rule-tester/no-test-global.js +++ b/tests/fixtures/testers/rule-tester/no-test-global.js @@ -15,9 +15,12 @@ module.exports = { schema: [], }, create(context) { + + const sourceCode = context.getSourceCode(); + return { - "Program": function(node) { - var globals = context.getScope().variables.map(function (variable) { + "Program"(node) { + var globals = sourceCode.getScope(node).variables.map(function (variable) { return variable.name; }); diff --git a/tests/fixtures/{curly-path}/client/eslint.config.js b/tests/fixtures/{curly-path}/client/eslint.config.js new file mode 100644 index 00000000000..cab59b0a888 --- /dev/null +++ b/tests/fixtures/{curly-path}/client/eslint.config.js @@ -0,0 +1 @@ +module.exports = { rules: { "no-console": "off" } }; diff --git a/tests/fixtures/{curly-path}/client/src/one.js b/tests/fixtures/{curly-path}/client/src/one.js new file mode 100644 index 00000000000..0dab8b6bcb0 --- /dev/null +++ b/tests/fixtures/{curly-path}/client/src/one.js @@ -0,0 +1 @@ +console.log("one"); diff --git a/tests/fixtures/{curly-path}/server/eslint.config.js b/tests/fixtures/{curly-path}/server/eslint.config.js new file mode 100644 index 00000000000..7e8b0562ad8 --- /dev/null +++ b/tests/fixtures/{curly-path}/server/eslint.config.js @@ -0,0 +1 @@ +module.exports = { rules: { "no-console": "warn" } }; diff --git a/tests/fixtures/{curly-path}/server/src/two.js b/tests/fixtures/{curly-path}/server/src/two.js new file mode 100644 index 00000000000..ef0e38f5b55 --- /dev/null +++ b/tests/fixtures/{curly-path}/server/src/two.js @@ -0,0 +1 @@ +console.log("two"); diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js index 9cd5593c8b3..03dff9bb3d7 100644 --- a/tests/lib/cli-engine/cli-engine.js +++ b/tests/lib/cli-engine/cli-engine.js @@ -4433,7 +4433,7 @@ describe("CLIEngine", () => { const cwd = getFixturePath("ignored-paths", "configurations"); const engine = new CLIEngine({ cwd }); - // a .eslintignore in parent directories includes `*.js`, but don't load it. + // an .eslintignore in parent directories includes `*.js`, but don't load it. assert(!engine.isPathIgnored("foo.js")); assert(engine.isPathIgnored("node_modules/foo.js")); }); @@ -4593,7 +4593,7 @@ describe("CLIEngine", () => { const cwd = getFixturePath("ignored-paths", "no-ignore-file"); const engine = new CLIEngine({ ignorePath: false, cwd }); - // a .eslintignore in parent directories includes `*.js`, but don't load it. + // an .eslintignore in parent directories includes `*.js`, but don't load it. assert(!engine.isPathIgnored("foo.js")); assert(engine.isPathIgnored("node_modules/foo.js")); }); @@ -5018,7 +5018,7 @@ describe("CLIEngine", () => { it("should call fs.writeFileSync() for each result with output", () => { const fakeFS = { - writeFileSync: () => {} + writeFileSync() {} }, localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", { fs: fakeFS @@ -5048,7 +5048,7 @@ describe("CLIEngine", () => { it("should call fs.writeFileSync() for each result with output and not at all for a result without output", () => { const fakeFS = { - writeFileSync: () => {} + writeFileSync() {} }, localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", { fs: fakeFS diff --git a/tests/lib/cli-engine/file-enumerator.js b/tests/lib/cli-engine/file-enumerator.js index 2ccf15e73d1..3a96cf5eb84 100644 --- a/tests/lib/cli-engine/file-enumerator.js +++ b/tests/lib/cli-engine/file-enumerator.js @@ -13,6 +13,7 @@ const path = require("path"); const os = require("os"); const { assert } = require("chai"); const sh = require("shelljs"); +const sinon = require("sinon"); const { Legacy: { CascadingConfigArrayFactory @@ -182,6 +183,73 @@ describe("FileEnumerator", () => { }); }); + // https://github.com/eslint/eslint/issues/14742 + describe("with 5 directories ('{lib}', '{lib}/client', '{lib}/client/src', '{lib}/server', '{lib}/server/src') that contains two files '{lib}/client/src/one.js' and '{lib}/server/src/two.js'", () => { + const root = path.join(os.tmpdir(), "eslint/file-enumerator"); + const files = { + "{lib}/client/src/one.js": "console.log('one.js');", + "{lib}/server/src/two.js": "console.log('two.js');", + "{lib}/client/.eslintrc.json": JSON.stringify({ + rules: { + "no-console": "error" + }, + env: { + mocha: true + } + }), + "{lib}/server/.eslintrc.json": JSON.stringify({ + rules: { + "no-console": "off" + }, + env: { + mocha: true + } + }) + }; + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files + }); + + /** @type {FileEnumerator} */ + let enumerator; + + beforeEach(async () => { + await prepare(); + enumerator = new FileEnumerator({ + cwd: path.resolve(getPath("{lib}/server")) + }); + }); + + afterEach(cleanup); + + describe("when running eslint in the server directory", () => { + it("should use the config '{lib}/server/.eslintrc.json' for '{lib}/server/src/two.js'.", () => { + const spy = sinon.spy(fs, "readdirSync"); + + const list = [ + ...enumerator.iterateFiles(["src/**/*.{js,json}"]) + ]; + + // should enter the directory '{lib}/server/src' directly + assert.strictEqual(spy.getCall(0).firstArg, path.join(root, "{lib}/server/src")); + assert.strictEqual(list.length, 1); + assert.strictEqual(list[0].config.length, 2); + assert.strictEqual(list[0].config[0].name, "DefaultIgnorePattern"); + assert.strictEqual(list[0].config[1].filePath, getPath("{lib}/server/.eslintrc.json")); + assert.deepStrictEqual( + list.map(entry => entry.filePath), + [ + path.join(root, "{lib}/server/src/two.js") + ] + ); + + // destroy the spy + sinon.restore(); + }); + }); + }); + // This group moved from 'tests/lib/util/glob-utils.js' when refactoring to keep the cumulated test cases. describe("with 'tests/fixtures/glob-utils' files", () => { let fixtureDir; diff --git a/tests/lib/cli.js b/tests/lib/cli.js index d1725c05b72..cef1ab478df 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -49,9 +49,10 @@ describe("cli", () => { * Verify that ESLint class receives correct opts via await cli.execute(). * @param {string} cmd CLI command. * @param {Object} opts Options hash that should match that received by ESLint class. + * @param {string} configType The config type to work with. * @returns {void} */ - async function verifyESLintOpts(cmd, opts) { + async function verifyESLintOpts(cmd, opts, configType) { // create a fake ESLint class to test with const fakeESLint = sinon.mock().withExactArgs(sinon.match(opts)); @@ -62,10 +63,11 @@ describe("cli", () => { const localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, + "./flat-eslint": { FlatESLint: fakeESLint, findFlatConfigFile: () => null }, "./shared/logging": log }); - await localCLI.execute(cmd); + await localCLI.execute(cmd, null, configType === "flat"); sinon.verifyAndRestore(); } @@ -105,1123 +107,1378 @@ describe("cli", () => { sh.rm("-r", fixtureDir); }); - describe("execute()", () => { - it("should return error when text with incorrect quotes is passed as argument", async () => { - const configFile = getFixturePath("configurations", "quotes-error.json"); - const result = await cli.execute(`-c ${configFile}`, "var foo = 'bar';"); + ["eslintrc", "flat"].forEach(configType => { - assert.strictEqual(result, 1); - }); + const useFlatConfig = configType === "flat"; - it("should not print debug info when passed the empty string as text", async () => { - const result = await cli.execute(["--stdin", "--no-eslintrc"], ""); + describe("execute()", () => { - assert.strictEqual(result, 0); - assert.isTrue(log.info.notCalled); - }); + it(`should return error when text with incorrect quotes is passed as argument with configType:${configType}`, async () => { + const configFile = getFixturePath("configurations", "quotes-error.js"); + const result = await cli.execute(`-c ${configFile} --stdin --stdin-filename foo.js`, "var foo = 'bar';", useFlatConfig); - it("should return no error when --ext .js2 is specified", async () => { - const filePath = getFixturePath("files"); - const result = await cli.execute(`--ext .js2 ${filePath}`); + assert.strictEqual(result, 1); + }); - assert.strictEqual(result, 0); - }); + it(`should not print debug info when passed the empty string as text with configType:${configType}`, async () => { + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + const result = await cli.execute(["--stdin", flag, "--stdin-filename", "foo.js"], "", useFlatConfig); - it("should exit with console error when passed unsupported arguments", async () => { - const filePath = getFixturePath("files"); - const result = await cli.execute(`--blah --another ${filePath}`); + assert.strictEqual(result, 0); + assert.isTrue(log.info.notCalled); + }); + + it(`should exit with console error when passed unsupported arguments with configType:${configType}`, async () => { + const filePath = getFixturePath("files"); + const result = await cli.execute(`--blah --another ${filePath}`, null, useFlatConfig); + + assert.strictEqual(result, 2); + }); - assert.strictEqual(result, 2); }); - }); + describe("flat config", () => { + const originalEnv = process.env; + const originalCwd = process.cwd; - describe("when given a config file", () => { - it("should load the specified config file", async () => { - const configPath = getFixturePath(".eslintrc"); - const filePath = getFixturePath("passing.js"); + beforeEach(() => { + process.env = { ...originalEnv }; + }); - await cli.execute(`--config ${configPath} ${filePath}`); - }); - }); + afterEach(() => { + process.env = originalEnv; + process.cwd = originalCwd; + }); - describe("when there is a local config file", () => { - const code = "lib/cli.js"; + it(`should use it when an eslint.config.js is present and useFlatConfig is true:${configType}`, async () => { + process.cwd = getFixturePath; - it("should load the local config file", async () => { + const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); - // Mock CWD - process.eslintCwd = getFixturePath("configurations", "single-quotes"); + // When flat config is used, we get an exit code of 2 because the --ext option is unrecognized. + assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); + }); - await cli.execute(code); + it(`should not use it when ESLINT_USE_FLAT_CONFIG=false even if an eslint.config.js is present:${configType}`, async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "false"; + process.cwd = getFixturePath; - process.eslintCwd = null; - }); - }); + const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); + + assert.strictEqual(exitCode, 0); + }); - describe("when given a config with rules with options and severity level set to error", () => { - it("should exit with an error status (1)", async () => { - const configPath = getFixturePath("configurations", "quotes-error.json"); - const filePath = getFixturePath("single-quoted.js"); - const code = `--no-ignore --config ${configPath} ${filePath}`; + it(`should use it when ESLINT_USE_FLAT_CONFIG=true and useFlatConfig is true even if an eslint.config.js is not present:${configType}`, async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "true"; - const exitStatus = await cli.execute(code); + // Set the CWD to outside the fixtures/ directory so that no eslint.config.js is found + process.cwd = () => getFixturePath(".."); - assert.strictEqual(exitStatus, 1); + const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); + + // When flat config is used, we get an exit code of 2 because the --ext option is unrecognized. + assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); + }); }); - }); - describe("when given a config file and a directory of files", () => { - it("should load and execute without error", async () => { - const configPath = getFixturePath("configurations", "semi-error.json"); - const filePath = getFixturePath("formatters"); - const code = `--config ${configPath} ${filePath}`; + describe("when given a config with rules with options and severity level set to error", () => { - const exitStatus = await cli.execute(code); + const originalCwd = process.cwd; - assert.strictEqual(exitStatus, 0); - }); - }); + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - describe("when given a config with environment set to browser", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-browser.json"); - const filePath = getFixturePath("globals-browser.js"); - const code = `--config ${configPath} ${filePath}`; + afterEach(() => { + process.cwd = originalCwd; + }); - const exit = await cli.execute(code); + it(`should exit with an error status (1) with configType:${configType}`, async () => { + const configPath = getFixturePath("configurations", "quotes-error.js"); + const filePath = getFixturePath("single-quoted.js"); + const code = `--no-ignore --config ${configPath} ${filePath}`; - assert.strictEqual(exit, 0); + const exitStatus = await cli.execute(code, null, useFlatConfig); + + assert.strictEqual(exitStatus, 1); + }); }); - }); - describe("when given a config with environment set to Node.js", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-node.json"); - const filePath = getFixturePath("globals-node.js"); - const code = `--config ${configPath} ${filePath}`; + describe("when there is a local config file", () => { + + it(`should load the local config file with configType:${configType}`, async () => { + await cli.execute("lib/cli.js", null, useFlatConfig); + }); - const exit = await cli.execute(code); + if (useFlatConfig) { + it(`should load the local config file with glob pattern and configType:${configType}`, async () => { + await cli.execute("lib/cli*.js", null, useFlatConfig); + }); + } - assert.strictEqual(exit, 0); + // only works on Windows + if (os.platform() === "win32") { + it(`should load the local config file with Windows slashes glob pattern and configType:${configType}`, async () => { + await cli.execute("lib\\cli*.js", null, useFlatConfig); + }); + } }); - }); - describe("when given a config with environment set to Nashorn", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-nashorn.json"); - const filePath = getFixturePath("globals-nashorn.js"); - const code = `--config ${configPath} ${filePath}`; + describe("Formatters", () => { - const exit = await cli.execute(code); + describe("when given a valid built-in formatter name", () => { + it(`should execute without any errors with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + const exit = await cli.execute(`${flag} -f checkstyle ${filePath}`, null, useFlatConfig); - assert.strictEqual(exit, 0); - }); - }); + assert.strictEqual(exit, 0); + }); + }); - describe("when given a config with environment set to WebExtensions", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-webextensions.json"); - const filePath = getFixturePath("globals-webextensions.js"); - const code = `--config ${configPath} ${filePath}`; + describe("when given a valid built-in formatter name that uses rules meta.", () => { + + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should execute without any errors with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + const exit = await cli.execute(`--no-ignore -f json-with-metadata ${filePath} ${flag}`, null, useFlatConfig); + + assert.strictEqual(exit, 0); + + /* + * Note: There is a behavior difference between eslintrc and flat config + * when using formatters. For eslintrc, rulesMeta always contains every + * rule that was loaded during the last run; for flat config, rulesMeta + * only contains meta data for the rules that triggered messages in the + * results. (Flat config uses ESLint#getRulesMetaForResults().) + */ + + // Check metadata. + const { metadata } = JSON.parse(log.info.args[0][0]); + const expectedMetadata = { + cwd: process.cwd(), + rulesMeta: useFlatConfig ? {} : Array.from(BuiltinRules).reduce((obj, [ruleId, rule]) => { + obj[ruleId] = rule.meta; + return obj; + }, {}) + }; + + assert.deepStrictEqual(metadata, expectedMetadata); + }); + }); - const exit = await cli.execute(code); + describe("when the --max-warnings option is passed", () => { + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - assert.strictEqual(exit, 0); - }); - }); + describe("and there are too many warnings", () => { + it(`should provide \`maxWarningsExceeded\` metadata to the formatter with configType:${configType}`, async () => { + const exit = await cli.execute( + `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, + "'hello' + 'world';", + useFlatConfig + ); - describe("when given a valid built-in formatter name", () => { - it("should execute without any errors", async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f checkstyle ${filePath}`); + assert.strictEqual(exit, 1); - assert.strictEqual(exit, 0); - }); - }); + const { metadata } = JSON.parse(log.info.args[0][0]); - describe("when given a valid built-in formatter name that uses rules meta.", () => { - it("should execute without any errors", async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f json-with-metadata ${filePath} --no-eslintrc`); + assert.deepStrictEqual( + metadata.maxWarningsExceeded, + { maxWarnings: 1, foundWarnings: 2 } + ); + }); + }); - assert.strictEqual(exit, 0); + describe("and warnings do not exceed the limit", () => { + it(`should omit \`maxWarningsExceeded\` metadata from the formatter with configType:${configType}`, async () => { + const exit = await cli.execute( + `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, + "'hello world';", + useFlatConfig + ); - // Check metadata. - const { metadata } = JSON.parse(log.info.args[0][0]); - const expectedMetadata = { - cwd: process.cwd(), - rulesMeta: Array.from(BuiltinRules).reduce((obj, [ruleId, rule]) => { - obj[ruleId] = rule.meta; - return obj; - }, {}) - }; + assert.strictEqual(exit, 0); - assert.deepStrictEqual(metadata, expectedMetadata); - }); - }); + const { metadata } = JSON.parse(log.info.args[0][0]); - describe("when given an invalid built-in formatter name", () => { - it("should execute with error", async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f fakeformatter ${filePath}`); + assert.notProperty(metadata, "maxWarningsExceeded"); + }); + }); + }); - assert.strictEqual(exit, 2); - }); - }); + describe("when given an invalid built-in formatter name", () => { + it(`should execute with error: with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`-f fakeformatter ${filePath}`); - describe("when given a valid formatter path", () => { - it("should execute without any errors", async () => { - const formatterPath = getFixturePath("formatters", "simple.js"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f ${formatterPath} ${filePath}`); + assert.strictEqual(exit, 2); + }); + }); - assert.strictEqual(exit, 0); - }); - }); + describe("when given a valid formatter path", () => { + it(`should execute without any errors with configType:${configType}`, async () => { + const formatterPath = getFixturePath("formatters", "simple.js"); + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`-f ${formatterPath} ${filePath}`); - describe("when given an invalid formatter path", () => { - it("should execute with error", async () => { - const formatterPath = getFixturePath("formatters", "file-does-not-exist.js"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f ${formatterPath} ${filePath}`); + assert.strictEqual(exit, 0); + }); + }); - assert.strictEqual(exit, 2); - }); - }); + describe("when given an invalid formatter path", () => { + it(`should execute with error with configType:${configType}`, async () => { + const formatterPath = getFixturePath("formatters", "file-does-not-exist.js"); + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`-f ${formatterPath} ${filePath}`, null, useFlatConfig); - describe("when given an async formatter path", () => { - it("should execute without any errors", async () => { - const formatterPath = getFixturePath("formatters", "async.js"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f ${formatterPath} ${filePath}`); + assert.strictEqual(exit, 2); + }); + }); + + describe("when given an async formatter path", () => { + it(`should execute without any errors with configType:${configType}`, async () => { + const formatterPath = getFixturePath("formatters", "async.js"); + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`-f ${formatterPath} ${filePath}`); - assert.strictEqual(log.info.getCall(0).args[0], "from async formatter"); - assert.strictEqual(exit, 0); + assert.strictEqual(log.info.getCall(0).args[0], "from async formatter"); + assert.strictEqual(exit, 0); + }); + }); }); - }); - describe("when executing a file with a lint error", () => { - it("should exit with error", async () => { - const filePath = getFixturePath("undef.js"); - const code = `--no-ignore --rule no-undef:2 ${filePath}`; + describe("Exit Codes", () => { - const exit = await cli.execute(code); + const originalCwd = process.cwd; - assert.strictEqual(exit, 1); - }); - }); + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - describe("when using --fix-type without --fix or --fix-dry-run", () => { - it("should exit with error", async () => { - const filePath = getFixturePath("passing.js"); - const code = `--fix-type suggestion ${filePath}`; + afterEach(() => { + process.cwd = originalCwd; + }); - const exit = await cli.execute(code); + describe("when executing a file with a lint error", () => { - assert.strictEqual(exit, 2); - }); - }); + it(`should exit with error with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const code = `--no-ignore --rule no-undef:2 ${filePath}`; - describe("when executing a file with a syntax error", () => { - it("should exit with error", async () => { - const filePath = getFixturePath("syntax-error.js"); - const exit = await cli.execute(`--no-ignore ${filePath}`); + const exit = await cli.execute(code, null, useFlatConfig); - assert.strictEqual(exit, 1); - }); - }); + assert.strictEqual(exit, 1); + }); + }); - describe("when calling execute more than once", () => { - it("should not print the results from previous execution", async () => { - const filePath = getFixturePath("missing-semicolon.js"); - const passingPath = getFixturePath("passing.js"); + describe("when using --fix-type without --fix or --fix-dry-run", () => { + it(`should exit with error with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const code = `--fix-type suggestion ${filePath}`; - await cli.execute(`--no-ignore --rule semi:2 ${filePath}`); + const exit = await cli.execute(code, null, useFlatConfig); - assert.isTrue(log.info.called, "Log should have been called."); + assert.strictEqual(exit, 2); + }); + }); - log.info.resetHistory(); + describe("when executing a file with a syntax error", () => { + it(`should exit with error with configType:${configType}`, async () => { + const filePath = getFixturePath("syntax-error.js"); + const exit = await cli.execute(`--no-ignore ${filePath}`, null, useFlatConfig); - await cli.execute(`--no-ignore --rule semi:2 ${passingPath}`); - assert.isTrue(log.info.notCalled); + assert.strictEqual(exit, 1); + }); + }); }); - }); - describe("when executing with version flag", () => { - it("should print out current version", async () => { - assert.strictEqual(await cli.execute("-v"), 0); - assert.strictEqual(log.info.callCount, 1); - }); - }); + describe("when calling execute more than once", () => { - describe("when executing with env-info flag", () => { - it("should print out environment information", async () => { - assert.strictEqual(await cli.execute("--env-info"), 0); - assert.strictEqual(log.info.callCount, 1); - }); + const originalCwd = process.cwd; - it("should print error message and return error code", async () => { - RuntimeInfo.environment.throws("There was an error!"); + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - assert.strictEqual(await cli.execute("--env-info"), 2); - assert.strictEqual(log.error.callCount, 1); - }); - }); + afterEach(() => { + process.cwd = originalCwd; + }); - describe("when executing without no-error-on-unmatched-pattern flag", () => { - it("should throw an error on unmatched glob pattern", async () => { - const filePath = getFixturePath("unmatched-patterns"); - const globPattern = "*.js3"; + it(`should not print the results from previous execution with configType:${configType}`, async () => { + const filePath = getFixturePath("missing-semicolon.js"); + const passingPath = getFixturePath("passing.js"); - await stdAssert.rejects(async () => { - await cli.execute(`"${filePath}/${globPattern}"`); - }, new Error(`No files matching '${filePath}/${globPattern}' were found.`)); - }); + await cli.execute(`--no-ignore --rule semi:2 ${filePath}`, null, useFlatConfig); - it("should throw an error on unmatched --ext", async () => { - const filePath = getFixturePath("unmatched-patterns"); - const extension = ".js3"; + assert.isTrue(log.info.called, "Log should have been called."); - await stdAssert.rejects(async () => { - await cli.execute(`--ext ${extension} ${filePath}`); - }, `No files matching '${filePath}' were found`); - }); - }); + log.info.resetHistory(); - describe("when executing with no-error-on-unmatched-pattern flag", () => { - it("should not throw an error on unmatched node glob syntax patterns", async () => { - const filePath = getFixturePath("unmatched-patterns"); - const exit = await cli.execute(`--no-error-on-unmatched-pattern "${filePath}/*.js3"`); + await cli.execute(`--no-ignore --rule semi:2 ${passingPath}`, null, useFlatConfig); + assert.isTrue(log.info.notCalled); - assert.strictEqual(exit, 0); + }); }); - it("should not throw an error on unmatched --ext", async () => { - const filePath = getFixturePath("unmatched-patterns"); - const exit = await cli.execute(`--no-error-on-unmatched-pattern --ext .js3 ${filePath}`); - - assert.strictEqual(exit, 0); + describe("when executing with version flag", () => { + it(`should print out current version with configType:${configType}`, async () => { + assert.strictEqual(await cli.execute("-v", null, useFlatConfig), 0); + assert.strictEqual(log.info.callCount, 1); + }); }); - }); - describe("when executing with no-error-on-unmatched-pattern flag and multiple patterns", () => { - it("should not throw an error on multiple unmatched node glob syntax patterns", async () => { - const filePath = getFixturePath("unmatched-patterns"); - const exit = await cli.execute(`--no-error-on-unmatched-pattern ${filePath}/*.js3 ${filePath}/*.js4`); + describe("when executing with env-info flag", () => { - assert.strictEqual(exit, 0); - }); + it(`should print out environment information with configType:${configType}`, async () => { + assert.strictEqual(await cli.execute("--env-info", null, useFlatConfig), 0); + assert.strictEqual(log.info.callCount, 1); + }); - it("should still throw an error on when a matched pattern has lint errors", async () => { - const filePath = getFixturePath("unmatched-patterns"); - const exit = await cli.execute(`--no-error-on-unmatched-pattern ${filePath}/*.js3 ${filePath}/*.js`); + describe("With error condition", () => { - assert.strictEqual(exit, 1); - }); - }); + beforeEach(() => { + RuntimeInfo.environment = sinon.stub().throws("There was an error!"); + }); - describe("when executing with no-error-on-unmatched-pattern flag and multiple --ext arguments", () => { - it("should not throw an error on multiple unmatched --ext arguments", async () => { - const filePath = getFixturePath("unmatched-patterns"); - const exit = await cli.execute(`--no-error-on-unmatched-pattern --ext .js3 --ext .js4 ${filePath}`); + afterEach(() => { + RuntimeInfo.environment = sinon.stub(); + }); - assert.strictEqual(exit, 0); - }); + it(`should print error message and return error code with configType:${configType}`, async () => { - it("should still throw an error on when a matched pattern has lint errors", async () => { - const filePath = getFixturePath("unmatched-patterns"); - const exit = await cli.execute(`--no-error-on-unmatched-pattern --ext .js3 --ext .js ${filePath}`); + assert.strictEqual(await cli.execute("--env-info", null, useFlatConfig), 2); + assert.strictEqual(log.error.callCount, 1); + }); + }); - assert.strictEqual(exit, 1); }); - }); - describe("when executing with help flag", () => { - it("should print out help", async () => { - assert.strictEqual(await cli.execute("-h"), 0); - assert.strictEqual(log.info.callCount, 1); + describe("when executing with help flag", () => { + it(`should print out help with configType:${configType}`, async () => { + assert.strictEqual(await cli.execute("-h", null, useFlatConfig), 0); + assert.strictEqual(log.info.callCount, 1); + }); }); - }); - describe("when given a directory with eslint excluded files in the directory", () => { - it("should throw an error and not process any files", async () => { - const ignorePath = getFixturePath(".eslintignore"); - const filePath = getFixturePath("cli"); + describe("when executing a file with a shebang", () => { + it(`should execute without error with configType:${configType}`, async () => { + const filePath = getFixturePath("shebang.js"); + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + const exit = await cli.execute(`${flag} --no-ignore ${filePath}`, null, useFlatConfig); - await stdAssert.rejects(async () => { - await cli.execute(`--ignore-path ${ignorePath} ${filePath}`); - }, new Error(`All files matched by '${filePath}' are ignored.`)); + assert.strictEqual(exit, 0); + }); }); - }); - describe("when given a file in excluded files list", () => { - it("should not process the file", async () => { - const ignorePath = getFixturePath(".eslintignore"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--ignore-path ${ignorePath} ${filePath}`); + describe("FixtureDir Dependent Tests", () => { - // a warning about the ignored file - assert.isTrue(log.info.called); - assert.strictEqual(exit, 0); - }); + const originalCwd = process.cwd; - it("should process the file when forced", async () => { - const ignorePath = getFixturePath(".eslintignore"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--ignore-path ${ignorePath} --no-ignore ${filePath}`); + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - // no warnings - assert.isFalse(log.info.called); - assert.strictEqual(exit, 0); - }); - }); + afterEach(() => { + process.cwd = originalCwd; + }); - describe("when given a pattern to ignore", () => { - it("should not process any files", async () => { - const ignoredFile = getFixturePath("cli/syntax-error.js"); - const filePath = getFixturePath("cli/passing.js"); - const exit = await cli.execute(`--ignore-pattern cli/ ${ignoredFile} ${filePath}`); + describe("when given a config file and a directory of files", () => { + it(`should load and execute without error with configType:${configType}`, async () => { + const configPath = getFixturePath("configurations", "semi-error.js"); + const filePath = getFixturePath("formatters"); + const code = `--no-ignore --config ${configPath} ${filePath}`; + const exitStatus = await cli.execute(code, null, useFlatConfig); - // warnings about the ignored files - assert.isTrue(log.info.called); - assert.strictEqual(exit, 0); - }); - }); + assert.strictEqual(exitStatus, 0); + }); + }); - describe("when given patterns to ignore", () => { - it("should not process any matching files", async () => { - const ignorePaths = ["a", "b"]; + describe("when executing with global flag", () => { - const cmd = ignorePaths.map(ignorePath => `--ignore-pattern ${ignorePath}`).concat(".").join(" "); + it(`should default defined variables to read-only with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const exit = await cli.execute(`--global baz,bat --no-ignore --rule no-global-assign:2 ${filePath}`, null, useFlatConfig); - const opts = { - overrideConfig: { - ignorePatterns: ignorePaths - } - }; + assert.isTrue(log.info.calledOnce); + assert.strictEqual(exit, 1); + }); - await verifyESLintOpts(cmd, opts); - }); - }); + it(`should allow defining writable global variables with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const exit = await cli.execute(`--global baz:false,bat:true --no-ignore ${filePath}`, null, useFlatConfig); - describe("when executing a file with a shebang", () => { - it("should execute without error", async () => { - const filePath = getFixturePath("shebang.js"); - const exit = await cli.execute(`--no-ignore ${filePath}`); + assert.isTrue(log.info.notCalled); + assert.strictEqual(exit, 0); + }); - assert.strictEqual(exit, 0); - }); - }); + it(`should allow defining variables with multiple flags with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const exit = await cli.execute(`--global baz --global bat:true --no-ignore ${filePath}`); - describe("when loading a custom rule", () => { - it("should return an error when rule isn't found", async () => { - const rulesPath = getFixturePath("rules", "wrong"); - const configPath = getFixturePath("rules", "eslint.json"); - const filePath = getFixturePath("rules", "test", "test-custom-rule.js"); - const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; + assert.isTrue(log.info.notCalled); + assert.strictEqual(exit, 0); + }); + }); - await stdAssert.rejects(async () => { - const exit = await cli.execute(code); - assert.strictEqual(exit, 2); - }, /Error while loading rule 'custom-rule': Boom!/u); - }); + describe("when supplied with rule flag and severity level set to error", () => { - it("should return a warning when rule is matched", async () => { - const rulesPath = getFixturePath("rules"); - const configPath = getFixturePath("rules", "eslint.json"); - const filePath = getFixturePath("rules", "test", "test-custom-rule.js"); - const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; - await cli.execute(code); + it(`should exit with an error status (2) with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `--no-ignore --rule 'quotes: [2, double]' ${filePath}`; + const exitStatus = await cli.execute(code, null, useFlatConfig); - assert.isTrue(log.info.calledOnce); - assert.isTrue(log.info.neverCalledWith("")); - }); + assert.strictEqual(exitStatus, 1); + }); + }); - it("should return warnings from multiple rules in different directories", async () => { - const rulesPath = getFixturePath("rules", "dir1"); - const rulesPath2 = getFixturePath("rules", "dir2"); - const configPath = getFixturePath("rules", "multi-rulesdirs.json"); - const filePath = getFixturePath("rules", "test-multi-rulesdirs.js"); - const code = `--rulesdir ${rulesPath} --rulesdir ${rulesPath2} --config ${configPath} --no-ignore ${filePath}`; - const exit = await cli.execute(code); - - const call = log.info.getCall(0); - - assert.isTrue(log.info.calledOnce); - assert.isTrue(call.args[0].includes("String!")); - assert.isTrue(call.args[0].includes("Literal!")); - assert.isTrue(call.args[0].includes("2 problems")); - assert.isTrue(log.info.neverCalledWith("")); - assert.strictEqual(exit, 1); - }); + describe("when the quiet option is enabled", () => { + it(`should only print error with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const cliArgs = `--no-ignore --quiet -f compact --rule 'quotes: [2, double]' --rule 'no-unused-vars: 1' ${filePath}`; - }); + await cli.execute(cliArgs, null, useFlatConfig); - describe("when executing with no-eslintrc flag", () => { - it("should ignore a local config file", async () => { - const filePath = getFixturePath("eslintrc", "quotes.js"); - const exit = await cli.execute(`--no-eslintrc --no-ignore ${filePath}`); + sinon.assert.calledOnce(log.info); - assert.isTrue(log.info.notCalled); - assert.strictEqual(exit, 0); - }); - }); + const formattedOutput = log.info.firstCall.args[0]; - describe("when executing without no-eslintrc flag", () => { - it("should load a local config file", async () => { - const filePath = getFixturePath("eslintrc", "quotes.js"); - const exit = await cli.execute(`--no-ignore ${filePath}`); + assert.include(formattedOutput, "Error"); + assert.notInclude(formattedOutput, "Warning"); + }); - assert.isTrue(log.info.calledOnce); - assert.strictEqual(exit, 1); - }); - }); + it(`should print nothing if there are no errors with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const cliArgs = `--quiet -f compact --rule 'quotes: [1, double]' --rule 'no-unused-vars: 1' ${filePath}`; - describe("when executing without env flag", () => { - it("should not define environment-specific globals", async () => { - const files = [ - getFixturePath("globals-browser.js"), - getFixturePath("globals-node.js") - ]; + await cli.execute(cliArgs, null, useFlatConfig); - await cli.execute(`--no-eslintrc --config ./conf/eslint-recommended.js --no-ignore ${files.join(" ")}`); + sinon.assert.notCalled(log.info); + }); + }); - assert.strictEqual(log.info.args[0][0].split("\n").length, 10); - }); - }); - describe("when executing with global flag", () => { - it("should default defined variables to read-only", async () => { - const filePath = getFixturePath("undef.js"); - const exit = await cli.execute(`--global baz,bat --no-ignore --rule no-global-assign:2 ${filePath}`); + describe("no-error-on-unmatched-pattern flag", () => { - assert.isTrue(log.info.calledOnce); - assert.strictEqual(exit, 1); - }); + describe("when executing without no-error-on-unmatched-pattern flag", () => { + it(`should throw an error on unmatched glob pattern with configType:${configType}`, async () => { + let filePath = getFixturePath("unmatched-patterns"); + const globPattern = "unmatched*.js"; - it("should allow defining writable global variables", async () => { - const filePath = getFixturePath("undef.js"); - const exit = await cli.execute(`--global baz:false,bat:true --no-ignore ${filePath}`); + if (useFlatConfig) { + filePath = filePath.replace(/\\/gu, "/"); + } - assert.isTrue(log.info.notCalled); - assert.strictEqual(exit, 0); - }); + await stdAssert.rejects(async () => { + await cli.execute(`"${filePath}/${globPattern}"`, null, useFlatConfig); + }, new Error(`No files matching '${filePath}/${globPattern}' were found.`)); + }); - it("should allow defining variables with multiple flags", async () => { - const filePath = getFixturePath("undef.js"); - const exit = await cli.execute(`--global baz --global bat:true --no-ignore ${filePath}`); + }); - assert.isTrue(log.info.notCalled); - assert.strictEqual(exit, 0); - }); - }); + describe("when executing with no-error-on-unmatched-pattern flag", () => { + it(`should not throw an error on unmatched node glob syntax patterns with configType:${configType}`, async () => { + const filePath = getFixturePath("unmatched-patterns"); + const exit = await cli.execute(`--no-error-on-unmatched-pattern "${filePath}/unmatched*.js"`, null, useFlatConfig); - describe("when supplied with rule flag and severity level set to error", () => { - it("should exit with an error status (2)", async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `--no-ignore --rule 'quotes: [2, double]' ${filePath}`; - const exitStatus = await cli.execute(code); + assert.strictEqual(exit, 0); + }); + }); - assert.strictEqual(exitStatus, 1); - }); - }); + describe("when executing with no-error-on-unmatched-pattern flag and multiple patterns", () => { + it(`should not throw an error on multiple unmatched node glob syntax patterns with configType:${configType}`, async () => { + const filePath = getFixturePath("unmatched-patterns/js3"); + const exit = await cli.execute(`--no-error-on-unmatched-pattern ${filePath}/unmatched1*.js ${filePath}/unmatched2*.js`, null, useFlatConfig); - describe("when the quiet option is enabled", () => { + assert.strictEqual(exit, 0); + }); - it("should only print error", async () => { - const filePath = getFixturePath("single-quoted.js"); - const cliArgs = `--no-ignore --quiet -f compact --rule 'quotes: [2, double]' --rule 'no-unused-vars: 1' ${filePath}`; + it(`should still throw an error on when a matched pattern has lint errors with configType:${configType}`, async () => { + const filePath = getFixturePath("unmatched-patterns"); + const exit = await cli.execute(`--no-ignore --no-error-on-unmatched-pattern ${filePath}/unmatched1*.js ${filePath}/failing.js`, null, useFlatConfig); - await cli.execute(cliArgs); + assert.strictEqual(exit, 1); + }); + }); - sinon.assert.calledOnce(log.info); + }); - const formattedOutput = log.info.firstCall.args[0]; + describe("Parser Options", () => { - assert.include(formattedOutput, "Error"); - assert.notInclude(formattedOutput, "Warning"); - }); + describe("when given parser options", () => { + it(`should exit with error if parser options are invalid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`--no-ignore --parser-options test111 ${filePath}`, null, useFlatConfig); - it("should print nothing if there are no errors", async () => { - const filePath = getFixturePath("single-quoted.js"); - const cliArgs = `--quiet -f compact --rule 'quotes: [1, double]' --rule 'no-unused-vars: 1' ${filePath}`; + assert.strictEqual(exit, 2); + }); - await cli.execute(cliArgs); + it(`should exit with no error if parser is valid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`, null, useFlatConfig); - sinon.assert.notCalled(log.info); - }); - }); + assert.strictEqual(exit, 0); + }); - describe("when supplied with report output file path", () => { - afterEach(() => { - sh.rm("-rf", "tests/output"); - }); + it(`should exit with an error on ecmaVersion 7 feature in ecmaVersion 6 with configType:${configType}`, async () => { + const filePath = getFixturePath("passing-es7.js"); + const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`, null, useFlatConfig); - it("should write the file and create dirs if they don't exist", async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `--no-ignore --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; + assert.strictEqual(exit, 1); + }); - await cli.execute(code); + it(`should exit with no error on ecmaVersion 7 feature in ecmaVersion 7 with configType:${configType}`, async () => { + const filePath = getFixturePath("passing-es7.js"); + const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:7 ${filePath}`, null, useFlatConfig); - assert.include(fs.readFileSync("tests/output/eslint-output.txt", "utf8"), filePath); - assert.isTrue(log.info.notCalled); - }); + assert.strictEqual(exit, 0); + }); - it("should return an error if the path is a directory", async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `--no-ignore --rule 'quotes: [1, double]' --o tests/output ${filePath}`; + it(`should exit with no error on ecmaVersion 7 feature with config ecmaVersion 6 and command line ecmaVersion 7 with configType:${configType}`, async () => { + const configPath = useFlatConfig + ? getFixturePath("configurations", "es6.js") + : getFixturePath("configurations", "es6.json"); + const filePath = getFixturePath("passing-es7.js"); + const exit = await cli.execute(`--no-ignore --config ${configPath} --parser-options=ecmaVersion:7 ${filePath}`, null, useFlatConfig); - fs.mkdirSync("tests/output"); + assert.strictEqual(exit, 0); + }); + }); + }); - const exit = await cli.execute(code); + describe("when given the max-warnings flag", () => { - assert.strictEqual(exit, 2); - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - }); + let filePath, configFilePath; - it("should return an error if the path could not be written to", async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `--no-ignore --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; + before(() => { + filePath = getFixturePath("max-warnings/six-warnings.js"); + configFilePath = getFixturePath(useFlatConfig ? "max-warnings/eslint.config.js" : "max-warnings/.eslintrc"); + }); - fs.writeFileSync("tests/output", "foo"); + it(`should not change exit code if warning count under threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute(`--no-ignore --max-warnings 10 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - const exit = await cli.execute(code); + assert.strictEqual(exitCode, 0); + }); - assert.strictEqual(exit, 2); - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - }); - }); + it(`should exit with exit code 1 if warning count exceeds threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute(`--no-ignore --max-warnings 5 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - describe("when supplied with a plugin", () => { - it("should pass plugins to ESLint", async () => { - const examplePluginName = "eslint-plugin-example"; + assert.strictEqual(exitCode, 1); + assert.ok(log.error.calledOnce); + assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings"); + }); - await verifyESLintOpts(`--no-ignore --plugin ${examplePluginName} foo.js`, { - overrideConfig: { - plugins: [examplePluginName] - } - }); - }); + it(`should exit with exit code 1 without printing warnings if the quiet option is enabled and warning count exceeds threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute(`--no-ignore --quiet --max-warnings 5 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - }); + assert.strictEqual(exitCode, 1); + assert.ok(log.error.calledOnce); + assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings"); + assert.ok(log.info.notCalled); // didn't print warnings + }); + + it(`should not change exit code if warning count equals threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute(`--no-ignore --max-warnings 6 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - describe("when supplied with a plugin-loading path", () => { - it("should pass the option to ESLint", async () => { - const examplePluginDirPath = "foo/bar"; + assert.strictEqual(exitCode, 0); + }); - await verifyESLintOpts(`--resolve-plugins-relative-to ${examplePluginDirPath} foo.js`, { - resolvePluginsRelativeTo: examplePluginDirPath + it(`should not change exit code if flag is not specified and there are warnings with configType:${configType}`, async () => { + const exitCode = await cli.execute(`-c ${configFilePath} ${filePath}`, null, useFlatConfig); + + assert.strictEqual(exitCode, 0); + }); }); - }); - }); - describe("when given an parser name", () => { - it("should exit with a fatal error if parser is invalid", async () => { - const filePath = getFixturePath("passing.js"); + describe("when given the exit-on-fatal-error flag", () => { + it(`should not change exit code if no fatal errors are reported with configType:${configType}`, async () => { + const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error.js"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - await stdAssert.rejects(async () => await cli.execute(`--no-ignore --parser test111 ${filePath}`), "Cannot find module 'test111'"); - }); + assert.strictEqual(exitCode, 0); + }); - it("should exit with no error if parser is valid", async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--no-ignore --parser espree ${filePath}`); + it(`should exit with exit code 1 if no fatal errors are found, but rule violations are found with configType:${configType}`, async () => { + const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error-rule-violation.js"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - assert.strictEqual(exit, 0); - }); - }); + assert.strictEqual(exitCode, 1); + }); - describe("when given parser options", () => { - it("should exit with error if parser options are invalid", async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--no-ignore --parser-options test111 ${filePath}`); + it(`should exit with exit code 2 if fatal error is found with configType:${configType}`, async () => { + const filePath = getFixturePath("exit-on-fatal-error", "fatal-error.js"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - assert.strictEqual(exit, 2); - }); + assert.strictEqual(exitCode, 2); + }); - it("should exit with no error if parser is valid", async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`); + it(`should exit with exit code 2 if fatal error is found in any file with configType:${configType}`, async () => { + const filePath = getFixturePath("exit-on-fatal-error"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - assert.strictEqual(exit, 0); - }); + assert.strictEqual(exitCode, 2); + }); - it("should exit with an error on ecmaVersion 7 feature in ecmaVersion 6", async () => { - const filePath = getFixturePath("passing-es7.js"); - const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`); - assert.strictEqual(exit, 1); - }); + }); - it("should exit with no error on ecmaVersion 7 feature in ecmaVersion 7", async () => { - const filePath = getFixturePath("passing-es7.js"); - const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:7 ${filePath}`); - assert.strictEqual(exit, 0); - }); + describe("Ignores", () => { + + describe("when given a directory with eslint excluded files in the directory", () => { + it(`should throw an error and not process any files with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("cli"); + const expectedMessage = useFlatConfig + ? `All files matched by '${filePath.replace(/\\/gu, "/")}' are ignored.` + : `All files matched by '${filePath}' are ignored.`; + + await stdAssert.rejects(async () => { + await cli.execute(`${options} ${filePath}`, null, useFlatConfig); + }, new Error(expectedMessage)); + }); + }); + + describe("when given a file in excluded files list", () => { + it(`should not process the file with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`${options} ${filePath}`, null, useFlatConfig); + + // a warning about the ignored file + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + + it(`should process the file when forced with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`${options} --no-ignore ${filePath}`, null, useFlatConfig); + + // no warnings + assert.isFalse(log.info.called); + assert.strictEqual(exit, 0); + }); + }); + + describe("when given a pattern to ignore", () => { + it(`should not process any files with configType:${configType}`, async () => { + const ignoredFile = getFixturePath("cli/syntax-error.js"); + const ignorePathOption = useFlatConfig + ? "" + : "--ignore-path .eslintignore_empty"; + const filePath = getFixturePath("cli/passing.js"); + const ignorePattern = useFlatConfig ? "cli/**" : "cli/"; + const exit = await cli.execute( + `--ignore-pattern ${ignorePattern} ${ignorePathOption} ${ignoredFile} ${filePath}`, null, useFlatConfig + ); + + // warnings about the ignored files + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + + it(`should interpret pattern that contains a slash as relative to cwd with configType:${configType}`, async () => { + process.cwd = () => getFixturePath("cli/ignore-pattern-relative/subdir"); + + /* + * The config file is in `cli/ignore-pattern-relative`, so this would fail + * if `subdir/**` ignore pattern is interpreted as relative to the config base path. + */ + const exit = await cli.execute("**/*.js --ignore-pattern subdir/**", null, useFlatConfig); + + assert.strictEqual(exit, 0); + + await stdAssert.rejects( + async () => await cli.execute("**/*.js --ignore-pattern subsubdir/*.js", null, useFlatConfig), + /All files matched by '\*\*\/\*\.js' are ignored/u + ); + }); + + it(`should interpret pattern that doesn't contain a slash as relative to cwd with configType:${configType}`, async () => { + process.cwd = () => getFixturePath("cli/ignore-pattern-relative/subdir/subsubdir"); + + await stdAssert.rejects( + async () => await cli.execute("**/*.js --ignore-pattern *.js", null, useFlatConfig), + /All files matched by '\*\*\/\*\.js' are ignored/u + ); + }); + + if (useFlatConfig) { + it("should ignore files if the pattern is a path to a directory (with trailing slash)", async () => { + const filePath = getFixturePath("cli/syntax-error.js"); + const exit = await cli.execute(`--ignore-pattern cli/ ${filePath}`, null, true); + + // parsing error causes exit code 1 + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + + it("should ignore files if the pattern is a path to a directory (without trailing slash)", async () => { + const filePath = getFixturePath("cli/syntax-error.js"); + const exit = await cli.execute(`--ignore-pattern cli ${filePath}`, null, true); + + // parsing error causes exit code 1 + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + } + }); - it("should exit with no error on ecmaVersion 7 feature with config ecmaVersion 6 and command line ecmaVersion 7", async () => { - const configPath = getFixturePath("configurations", "es6.json"); - const filePath = getFixturePath("passing-es7.js"); - const exit = await cli.execute(`--no-ignore --config ${configPath} --parser-options=ecmaVersion:7 ${filePath}`); + }); - assert.strictEqual(exit, 0); }); - }); - describe("when given the max-warnings flag", () => { - it("should not change exit code if warning count under threshold", async () => { - const filePath = getFixturePath("max-warnings"); - const exitCode = await cli.execute(`--no-ignore --max-warnings 10 ${filePath}`); - assert.strictEqual(exitCode, 0); - }); + describe("when given a parser name", () => { - it("should exit with exit code 1 if warning count exceeds threshold", async () => { - const filePath = getFixturePath("max-warnings"); - const exitCode = await cli.execute(`--no-ignore --max-warnings 5 ${filePath}`); + it(`should exit with a fatal error if parser is invalid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); - assert.strictEqual(exitCode, 1); - assert.ok(log.error.calledOnce); - assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings"); - }); + await stdAssert.rejects(async () => await cli.execute(`--no-ignore --parser test111 ${filePath}`, null, useFlatConfig), "Cannot find module 'test111'"); + }); - it("should exit with exit code 1 without printing warnings if the quiet option is enabled and warning count exceeds threshold", async () => { - const filePath = getFixturePath("max-warnings"); - const exitCode = await cli.execute(`--no-ignore --quiet --max-warnings 5 ${filePath}`); + it(`should exit with no error if parser is valid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + const exit = await cli.execute(`${flag} --no-ignore --parser espree ${filePath}`, null, useFlatConfig); + + assert.strictEqual(exit, 0); + }); - assert.strictEqual(exitCode, 1); - assert.ok(log.error.calledOnce); - assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings"); - assert.ok(log.info.notCalled); // didn't print warnings }); - it("should not change exit code if warning count equals threshold", async () => { - const filePath = getFixturePath("max-warnings"); - const exitCode = await cli.execute(`--no-ignore --max-warnings 6 ${filePath}`); + describe("when supplied with report output file path", () => { + afterEach(() => { + sh.rm("-rf", "tests/output"); + }); - assert.strictEqual(exitCode, 0); - }); + it(`should write the file and create dirs if they don't exist with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `--no-ignore --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; - it("should not change exit code if flag is not specified and there are warnings", async () => { - const filePath = getFixturePath("max-warnings"); - const exitCode = await cli.execute(filePath); + await cli.execute(code, null, useFlatConfig); - assert.strictEqual(exitCode, 0); - }); - }); + assert.include(fs.readFileSync("tests/output/eslint-output.txt", "utf8"), filePath); + assert.isTrue(log.info.notCalled); + }); - describe("when given the exit-on-fatal-error flag", () => { - it("should not change exit code if no fatal errors are reported", async () => { - const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error.js"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`); + it(`should return an error if the path is a directory with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `--no-ignore --rule 'quotes: [1, double]' --o tests/output ${filePath}`; - assert.strictEqual(exitCode, 0); - }); + fs.mkdirSync("tests/output"); - it("should exit with exit code 1 if no fatal errors are found, but rule violations are found", async () => { - const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error-rule-violation.js"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`); + const exit = await cli.execute(code, null, useFlatConfig); - assert.strictEqual(exitCode, 1); - }); + assert.strictEqual(exit, 2); + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + }); - it("should exit with exit code 2 if fatal error is found", async () => { - const filePath = getFixturePath("exit-on-fatal-error", "fatal-error.js"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`); + it(`should return an error if the path could not be written to with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `--no-ignore --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; - assert.strictEqual(exitCode, 2); - }); + fs.writeFileSync("tests/output", "foo"); - it("should exit with exit code 2 if fatal error is found in any file", async () => { - const filePath = getFixturePath("exit-on-fatal-error"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`); + const exit = await cli.execute(code, null, useFlatConfig); - assert.strictEqual(exitCode, 2); + assert.strictEqual(exit, 2); + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + }); }); + describe("when passed --no-inline-config", () => { + let localCLI; - }); + afterEach(() => { + sinon.verifyAndRestore(); + }); - describe("when passed --no-inline-config", () => { - let localCLI; + it(`should pass allowInlineConfig:false to ESLint when --no-inline-config is used with configType:${configType}`, async () => { + + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: false })); + + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message" + } + ], + errorCount: 1, + warningCount: 0 + }]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.stub(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./shared/logging": log + }); + + await localCLI.execute("--no-inline-config .", null, useFlatConfig); + }); - afterEach(() => { - sinon.verifyAndRestore(); - }); + it(`should not error and allowInlineConfig should be true by default with configType:${configType}`, async () => { - it("should pass allowInlineConfig:false to ESLint when --no-inline-config is used", async () => { + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: true })); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: false })); + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.stub(); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.stub(); + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./shared/logging": log + }); + + const exitCode = await localCLI.execute(".", null, useFlatConfig); + + assert.strictEqual(exitCode, 0); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log }); - await localCLI.execute("--no-inline-config ."); }); - it("should not error and allowInlineConfig should be true by default", async () => { + describe("when passed --fix", () => { + let localCLI; + + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it(`should pass fix:true to ESLint when executing on files with configType:${configType}`, async () => { - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: true })); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.stub(); + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().once(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./shared/logging": log + }); + + const exitCode = await localCLI.execute("--fix .", null, useFlatConfig); + + assert.strictEqual(exitCode, 0); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log }); - const exitCode = await localCLI.execute("."); - assert.strictEqual(exitCode, 0); + it(`should rewrite files when in fix mode with configType:${configType}`, async () => { - }); + const report = [{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message" + } + ], + errorCount: 1, + warningCount: 0 + }]; - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - describe("when passed --fix", () => { - let localCLI; + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().withExactArgs(report); - afterEach(() => { - sinon.verifyAndRestore(); - }); + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, - it("should pass fix:true to ESLint when executing on files", async () => { + "./shared/logging": log + }); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); + const exitCode = await localCLI.execute("--fix .", null, useFlatConfig); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().once(); + assert.strictEqual(exitCode, 1); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix ."); + it(`should provide fix predicate and rewrite files when in fix mode and quiet mode with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 0); + const report = [{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 1, + message: "Fake message" + } + ], + errorCount: 0, + warningCount: 1 + }]; - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func })); + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.getErrorResults = sinon.stub().returns([]); + fakeESLint.outputFixes = sinon.mock().withExactArgs(report); - it("should rewrite files when in fix mode", async () => { + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]; + "./shared/logging": log + }); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); + const exitCode = await localCLI.execute("--fix --quiet .", null, useFlatConfig); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().withExactArgs(report); + assert.strictEqual(exitCode, 0); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix ."); + it(`should not call ESLint and return 2 when executing on text with configType:${configType}`, async () => { + + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().never(); - assert.strictEqual(exitCode, 1); + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + + "./shared/logging": log + }); + + const exitCode = await localCLI.execute("--fix .", "foo = bar;", null, useFlatConfig); + + assert.strictEqual(exitCode, 2); + }); }); - it("should provide fix predicate and rewrite files when in fix mode and quiet mode", async () => { + describe("when passed --fix-dry-run", () => { + let localCLI; - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 1, - message: "Fake message" - } - ], - errorCount: 0, - warningCount: 1 - }]; + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it(`should pass fix:true to ESLint when executing on files with configType:${configType}`, async () => { + + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func })); + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().never(); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.getErrorResults = sinon.stub().returns([]); - fakeESLint.outputFixes = sinon.mock().withExactArgs(report); + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + + "./shared/logging": log + }); + + const exitCode = await localCLI.execute("--fix-dry-run .", null, useFlatConfig); + + assert.strictEqual(exitCode, 0); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix --quiet ."); + it(`should pass fixTypes to ESLint when --fix-type is passed with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 0); + const expectedESLintOptions = { + fix: true, + fixTypes: ["suggestion"] + }; - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match(expectedESLintOptions)); + + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.stub(); - it("should not call ESLint and return 2 when executing on text", async () => { + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().never(); + "./shared/logging": log + }); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log + const exitCode = await localCLI.execute("--fix-dry-run --fix-type suggestion .", null, useFlatConfig); + + assert.strictEqual(exitCode, 0); }); - const exitCode = await localCLI.execute("--fix .", "foo = bar;"); + it(`should not rewrite files when in fix-dry-run mode with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 2); - }); + const report = [{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message" + } + ], + errorCount: 1, + warningCount: 0 + }]; - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - describe("when passed --fix-dry-run", () => { - let localCLI; + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().never(); - afterEach(() => { - sinon.verifyAndRestore(); - }); + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, - it("should pass fix:true to ESLint when executing on files", async () => { + "./shared/logging": log + }); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); + const exitCode = await localCLI.execute("--fix-dry-run .", null, useFlatConfig); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().never(); + assert.strictEqual(exitCode, 1); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix-dry-run ."); + it(`should provide fix predicate when in fix-dry-run mode and quiet mode with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 0); + const report = [{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 1, + message: "Fake message" + } + ], + errorCount: 0, + warningCount: 1 + }]; - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func })); + + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.getErrorResults = sinon.stub().returns([]); + fakeESLint.outputFixes = sinon.mock().never(); - it("should pass fixTypes to ESLint when --fix-type is passed", async () => { + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, - const expectedESLintOptions = { - fix: true, - fixTypes: ["suggestion"] - }; + "./shared/logging": log + }); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match(expectedESLintOptions)); + const exitCode = await localCLI.execute("--fix-dry-run --quiet .", null, useFlatConfig); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.stub(); + assert.strictEqual(exitCode, 0); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix-dry-run --fix-type suggestion ."); + it(`should allow executing on text with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 0); - }); + const report = [{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message" + } + ], + errorCount: 1, + warningCount: 0 + }]; - it("should not rewrite files when in fix-dry-run mode", async () => { + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]; + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintText").returns(report); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().never(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); + "./shared/logging": log + }); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().never(); + const exitCode = await localCLI.execute("--fix-dry-run .", "foo = bar;", useFlatConfig); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log + assert.strictEqual(exitCode, 1); }); - const exitCode = await localCLI.execute("--fix-dry-run ."); + it(`should not call ESLint and return 2 when used with --fix with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 1); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().never(); + localCLI = proxyquire("../../lib/cli", { + "./eslint": { ESLint: fakeESLint }, + "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + + "./shared/logging": log + }); + + const exitCode = await localCLI.execute("--fix --fix-dry-run .", "foo = bar;", useFlatConfig); + + assert.strictEqual(exitCode, 2); + }); }); - it("should provide fix predicate when in fix-dry-run mode and quiet mode", async () => { + describe("when passing --print-config", () => { + it(`should print out the configuration with configType:${configType}`, async () => { + const filePath = getFixturePath("xxxx"); - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 1, - message: "Fake message" - } - ], - errorCount: 0, - warningCount: 1 - }]; + const exitCode = await cli.execute(`--print-config ${filePath}`, null, useFlatConfig); + + assert.isTrue(log.info.calledOnce); + assert.strictEqual(exitCode, 0); + }); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func })); + it(`should error if any positional file arguments are passed with configType:${configType}`, async () => { + const filePath1 = getFixturePath("files", "bar.js"); + const filePath2 = getFixturePath("files", "foo.js"); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.getErrorResults = sinon.stub().returns([]); - fakeESLint.outputFixes = sinon.mock().never(); + const exitCode = await cli.execute(`--print-config ${filePath1} ${filePath2}`, null, useFlatConfig); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + assert.strictEqual(exitCode, 2); }); - const exitCode = await localCLI.execute("--fix-dry-run --quiet ."); + it(`should error out when executing on text with configType:${configType}`, async () => { + const exitCode = await cli.execute("--print-config=myFile.js", "foo = bar;", useFlatConfig); + + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + assert.strictEqual(exitCode, 2); + }); + }); + + // --------- + }); + - assert.strictEqual(exitCode, 0); + describe("when given a config file", () => { + it("should load the specified config file", async () => { + const configPath = getFixturePath(".eslintrc"); + const filePath = getFixturePath("passing.js"); + await cli.execute(`--config ${configPath} ${filePath}`); }); + }); - it("should allow executing on text", async () => { - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]; + describe("eslintrc Only", () => { - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); + describe("Environments", () => { - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintText").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().never(); + describe("when given a config with environment set to browser", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath("configurations", "env-browser.json"); + const filePath = getFixturePath("globals-browser.js"); + const code = `--config ${configPath} ${filePath}`; - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log + const exit = await cli.execute(code); + + assert.strictEqual(exit, 0); + }); }); - const exitCode = await localCLI.execute("--fix-dry-run .", "foo = bar;"); + describe("when given a config with environment set to Node.js", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath("configurations", "env-node.json"); + const filePath = getFixturePath("globals-node.js"); + const code = `--config ${configPath} ${filePath}`; + + const exit = await cli.execute(code); - assert.strictEqual(exitCode, 1); + assert.strictEqual(exit, 0); + }); + }); + + describe("when given a config with environment set to Nashorn", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath("configurations", "env-nashorn.json"); + const filePath = getFixturePath("globals-nashorn.js"); + const code = `--config ${configPath} ${filePath}`; + + const exit = await cli.execute(code); + + assert.strictEqual(exit, 0); + }); + }); + + describe("when given a config with environment set to WebExtensions", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath("configurations", "env-webextensions.json"); + const filePath = getFixturePath("globals-webextensions.js"); + const code = `--config ${configPath} ${filePath}`; + + const exit = await cli.execute(code); + + assert.strictEqual(exit, 0); + }); + }); }); - it("should not call ESLint and return 2 when used with --fix", async () => { + describe("when loading a custom rule", () => { + it("should return an error when rule isn't found", async () => { + const rulesPath = getFixturePath("rules", "wrong"); + const configPath = getFixturePath("rules", "eslint.json"); + const filePath = getFixturePath("rules", "test", "test-custom-rule.js"); + const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; + + await stdAssert.rejects(async () => { + const exit = await cli.execute(code); + + assert.strictEqual(exit, 2); + }, /Error while loading rule 'custom-rule': Boom!/u); + }); + + it("should return a warning when rule is matched", async () => { + const rulesPath = getFixturePath("rules"); + const configPath = getFixturePath("rules", "eslint.json"); + const filePath = getFixturePath("rules", "test", "test-custom-rule.js"); + const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; + + await cli.execute(code); + + assert.isTrue(log.info.calledOnce); + assert.isTrue(log.info.neverCalledWith("")); + }); + + it("should return warnings from multiple rules in different directories", async () => { + const rulesPath = getFixturePath("rules", "dir1"); + const rulesPath2 = getFixturePath("rules", "dir2"); + const configPath = getFixturePath("rules", "multi-rulesdirs.json"); + const filePath = getFixturePath("rules", "test-multi-rulesdirs.js"); + const code = `--rulesdir ${rulesPath} --rulesdir ${rulesPath2} --config ${configPath} --no-ignore ${filePath}`; + const exit = await cli.execute(code); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().never(); + const call = log.info.getCall(0); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./shared/logging": log + assert.isTrue(log.info.calledOnce); + assert.isTrue(call.args[0].includes("String!")); + assert.isTrue(call.args[0].includes("Literal!")); + assert.isTrue(call.args[0].includes("2 problems")); + assert.isTrue(log.info.neverCalledWith("")); + assert.strictEqual(exit, 1); }); - const exitCode = await localCLI.execute("--fix --fix-dry-run .", "foo = bar;"); - assert.strictEqual(exitCode, 2); }); - }); - describe("when passing --print-config", () => { - it("should print out the configuration", async () => { - const filePath = getFixturePath("xxxx"); + describe("when executing with no-eslintrc flag", () => { + it("should ignore a local config file", async () => { + const filePath = getFixturePath("eslintrc", "quotes.js"); + const exit = await cli.execute(`--no-eslintrc --no-ignore ${filePath}`); - const exitCode = await cli.execute(`--print-config ${filePath}`); + assert.isTrue(log.info.notCalled); + assert.strictEqual(exit, 0); + }); + }); - assert.isTrue(log.info.calledOnce); - assert.strictEqual(exitCode, 0); + describe("when executing without no-eslintrc flag", () => { + it("should load a local config file", async () => { + const filePath = getFixturePath("eslintrc", "quotes.js"); + const exit = await cli.execute(`--no-ignore ${filePath}`); + + assert.isTrue(log.info.calledOnce); + assert.strictEqual(exit, 1); + }); }); - it("should error if any positional file arguments are passed", async () => { - const filePath1 = getFixturePath("files", "bar.js"); - const filePath2 = getFixturePath("files", "foo.js"); + describe("when executing without env flag", () => { + it("should not define environment-specific globals", async () => { + const files = [ + getFixturePath("globals-browser.js"), + getFixturePath("globals-node.js") + ]; - const exitCode = await cli.execute(`--print-config ${filePath1} ${filePath2}`); + await cli.execute(`--no-eslintrc --config ./packages/js/src/configs/eslint-recommended.js --no-ignore ${files.join(" ")}`); - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - assert.strictEqual(exitCode, 2); + assert.strictEqual(log.info.args[0][0].split("\n").length, 10); + }); }); - it("should error out when executing on text", async () => { - const exitCode = await cli.execute("--print-config=myFile.js", "foo = bar;"); - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - assert.strictEqual(exitCode, 2); + describe("when supplied with a plugin", () => { + it("should pass plugins to ESLint", async () => { + const examplePluginName = "eslint-plugin-example"; + + await verifyESLintOpts(`--no-ignore --plugin ${examplePluginName} foo.js`, { + overrideConfig: { + plugins: [examplePluginName] + } + }); + }); + + }); + + describe("when supplied with a plugin-loading path", () => { + it("should pass the option to ESLint", async () => { + const examplePluginDirPath = "foo/bar"; + + await verifyESLintOpts(`--resolve-plugins-relative-to ${examplePluginDirPath} foo.js`, { + resolvePluginsRelativeTo: examplePluginDirPath + }); + }); }); + + }); + }); diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index 675257ee8ec..7ca78a388f2 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -11,9 +11,12 @@ const { FlatConfigArray } = require("../../../lib/config/flat-config-array"); const assert = require("chai").assert; -const allConfig = require("../../../conf/eslint-all"); -const recommendedConfig = require("../../../conf/eslint-recommended"); +const { + all: allConfig, + recommended: recommendedConfig +} = require("@eslint/js").configs; const stringify = require("json-stable-stringify-without-jsonify"); +const espree = require("espree"); //----------------------------------------------------------------------------- // Helpers @@ -25,15 +28,17 @@ const baseConfig = { "@": { rules: { foo: { - schema: { - type: "array", - items: [ - { - enum: ["always", "never"] - } - ], - minItems: 0, - maxItems: 1 + meta: { + schema: { + type: "array", + items: [ + { + enum: ["always", "never"] + } + ], + minItems: 0, + maxItems: 1 + } } }, @@ -48,13 +53,15 @@ const baseConfig = { boom() {}, foo2: { - schema: { - type: "array", - items: { - type: "string" - }, - uniqueItems: true, - minItems: 1 + meta: { + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true, + minItems: 1 + } } } } @@ -184,6 +191,7 @@ describe("FlatConfigArray", () => { }); describe("Serialization of configs", () => { + it("should convert config into normalized JSON object", () => { const configs = new FlatConfigArray([{ @@ -201,7 +209,39 @@ describe("FlatConfigArray", () => { languageOptions: { ecmaVersion: "latest", sourceType: "module", - parser: "@/espree", + parser: `espree@${espree.version}`, + parserOptions: {} + }, + processor: void 0 + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + + it("should convert config with plugin name/version into normalized JSON object", () => { + + const configs = new FlatConfigArray([{ + plugins: { + a: {}, + b: { + name: "b-plugin", + version: "2.3.1" + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + const expected = { + plugins: ["@", "a", "b:b-plugin@2.3.1"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + parser: `espree@${espree.version}`, parserOptions: {} }, processor: void 0 @@ -213,11 +253,89 @@ describe("FlatConfigArray", () => { assert.strictEqual(stringify(actual), stringify(expected)); }); - it("should throw an error when config with parser object is normalized", () => { + it("should convert config with plugin meta into normalized JSON object", () => { + + const configs = new FlatConfigArray([{ + plugins: { + a: {}, + b: { + meta: { + name: "b-plugin", + version: "2.3.1" + } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + const expected = { + plugins: ["@", "a", "b:b-plugin@2.3.1"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + parser: `espree@${espree.version}`, + parserOptions: {} + }, + processor: void 0 + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + + it("should throw an error when config with unnamed parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize parser/u); + + }); + + it("should throw an error when config with unnamed parser object with empty meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: {}, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize parser/u); + + }); + + it("should throw an error when config with unnamed parser object with only meta version is normalized", () => { const configs = new FlatConfigArray([{ languageOptions: { parser: { + meta: { + version: "0.1.1" + }, parse() { /* empty */ } } } @@ -229,11 +347,132 @@ describe("FlatConfigArray", () => { assert.throws(() => { config.toJSON(); - }, /Caching is not supported/u); + }, /Could not serialize parser/u); }); - it("should throw an error when config with processor object is normalized", () => { + it("should not throw an error when config with named parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with named and versioned parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser", + version: "0.1.0" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with meta-named and versioned parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser" + }, + version: "0.1.0", + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with named and versioned parser object outside of meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + name: "custom-parser", + version: "0.1.0", + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should throw an error when config with unnamed processor object is normalized", () => { const configs = new FlatConfigArray([{ processor: { @@ -248,10 +487,146 @@ describe("FlatConfigArray", () => { assert.throws(() => { config.toJSON(); - }, /Caching is not supported/u); + }, /Could not serialize processor/u); }); + it("should throw an error when config with processor object with empty meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: {}, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize processor/u); + + }); + + + it("should not throw an error when config with named processor object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: { + name: "custom-processor" + }, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: `espree@${espree.version}`, + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor" + }); + + }); + + it("should not throw an error when config with named processor object without meta is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + name: "custom-processor", + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: `espree@${espree.version}`, + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor" + }); + + }); + + it("should not throw an error when config with named and versioned processor object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: { + name: "custom-processor", + version: "1.2.3" + }, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: `espree@${espree.version}`, + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor@1.2.3" + }); + + }); + + it("should not throw an error when config with named and versioned processor object without meta is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + name: "custom-processor", + version: "1.2.3", + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: `espree@${espree.version}`, + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor@1.2.3" + }); + + }); }); @@ -1033,21 +1408,21 @@ describe("FlatConfigArray", () => { parser: true } } - ], "Expected an object or string."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); - it("should error when an unexpected value is found", async () => { + it("should error when a null is found", async () => { await assertInvalidConfig([ { languageOptions: { - parser: "true" + parser: null } } - ], /Expected string in the form "pluginName\/objectName"/u); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); - it("should error when a plugin parser can't be found", async () => { + it("should error when a parser is a string", async () => { await assertInvalidConfig([ { @@ -1055,7 +1430,7 @@ describe("FlatConfigArray", () => { parser: "foo/bar" } } - ], "Key \"parser\": Could not find \"bar\" in plugin \"foo\"."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); it("should error when a value doesn't have a parse() method", async () => { @@ -1066,7 +1441,7 @@ describe("FlatConfigArray", () => { parser: {} } } - ], "Expected object to have a parse() or parseForESLint() method."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); it("should merge two objects when second object has overrides", () => { @@ -1081,24 +1456,12 @@ describe("FlatConfigArray", () => { } }, { - plugins: { - "@foo/baz": { - parsers: { - bar: stubParser - } - } - }, languageOptions: { - parser: "@foo/baz/bar" + parser: stubParser } } ], { plugins: { - "@foo/baz": { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, languageOptions: { @@ -1113,27 +1476,14 @@ describe("FlatConfigArray", () => { return assertMergedResult([ { - plugins: { - foo: { - parsers: { - bar: stubParser - } - } - }, - languageOptions: { - parser: "foo/bar" + parser: stubParser } }, { } ], { plugins: { - foo: { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, @@ -1153,25 +1503,12 @@ describe("FlatConfigArray", () => { { }, { - plugins: { - foo: { - parsers: { - bar: stubParser - } - } - }, - languageOptions: { - parser: "foo/bar" + parser: stubParser } } ], { plugins: { - foo: { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, @@ -1505,20 +1842,20 @@ describe("FlatConfigArray", () => { { rules: { foo: 1, - bar: "error" + foo2: "error" } }, { rules: { foo: ["error", "never"], - bar: ["warn", "foo"] + foo2: ["warn", "foo"] } } ], { plugins: baseConfig.plugins, rules: { foo: [2, "never"], - bar: [1, "foo"] + foo2: [1, "foo"] } })); diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 74f26cfa896..aa30047edc2 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -4400,7 +4400,7 @@ describe("ESLint", () => { const cwd = getFixturePath("ignored-paths", "configurations"); const engine = new ESLint({ cwd }); - // a .eslintignore in parent directories includes `*.js`, but don't load it. + // an .eslintignore in parent directories includes `*.js`, but don't load it. assert(!await engine.isPathIgnored("foo.js")); assert(await engine.isPathIgnored("node_modules/foo.js")); }); @@ -4971,7 +4971,7 @@ describe("ESLint", () => { const rulesMeta = engine.getRulesMetaForResults([]); - assert.strictEqual(Object.keys(rulesMeta).length, 0); + assert.deepStrictEqual(rulesMeta, {}); }); it("should return one rule meta when there is a linting error", async () => { @@ -4987,6 +4987,7 @@ describe("ESLint", () => { const results = await engine.lintText("a"); const rulesMeta = engine.getRulesMetaForResults(results); + assert.strictEqual(Object.keys(rulesMeta).length, 1); assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); }); @@ -5003,6 +5004,7 @@ describe("ESLint", () => { const results = await engine.lintText("a // eslint-disable-line semi"); const rulesMeta = engine.getRulesMetaForResults(results); + assert.strictEqual(Object.keys(rulesMeta).length, 1); assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); }); @@ -5051,6 +5053,51 @@ describe("ESLint", () => { nodePlugin.rules["no-new-require"].meta ); }); + + it("should ignore messages not related to a rule", async () => { + const engine = new ESLint({ + useEslintrc: false, + overrideConfig: { + ignorePatterns: "ignored.js", + rules: { + "no-var": "warn" + } + }, + reportUnusedDisableDirectives: "warn" + }); + + { + const results = await engine.lintText("syntax error"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("// eslint-disable-line no-var"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("", { filePath: "ignored.js", warnIgnored: true }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + }); + + it("should return a non-empty value if some of the messages are related to a rule", async () => { + const engine = new ESLint({ + useEslintrc: false, + overrideConfig: { rules: { "no-var": "warn" } }, + reportUnusedDisableDirectives: "warn" + }); + + const results = await engine.lintText("// eslint-disable-line no-var\nvar foo;"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); + }); }); describe("outputFixes()", () => { diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index d06ab526772..d79e4f92d4b 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -11,6 +11,7 @@ //------------------------------------------------------------------------------ const assert = require("assert"); +const util = require("util"); const fs = require("fs"); const fsp = fs.promises; const os = require("os"); @@ -24,6 +25,32 @@ const hash = require("../../../lib/cli-engine/hash"); const { unIndent, createCustomTeardown } = require("../../_utils"); const coreRules = require("../../../lib/rules"); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Creates a directory if it doesn't already exist. + * @param {string} dirPath The path to the directory that should exist. + * @returns {void} + */ +function ensureDirectoryExists(dirPath) { + try { + fs.statSync(dirPath); + } catch { + fs.mkdirSync(dirPath); + } +} + +/** + * Does nothing for a given time. + * @param {number} time Time in ms. + * @returns {void} + */ +async function sleep(time) { + await util.promisify(setTimeout)(time); +} + //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ @@ -128,6 +155,7 @@ describe("FlatESLint", () => { configFile: "", envs: [], globals: [], + ignorePath: ".gitignore", ignorePattern: [], parser: "", parserOptions: {}, @@ -136,7 +164,7 @@ describe("FlatESLint", () => { }), new RegExp(escapeStringRegExp([ "Invalid Options:", - "- Unknown options: cacheFile, configFile, envs, globals, ignorePattern, parser, parserOptions, rules" + "- Unknown options: cacheFile, configFile, envs, globals, ignorePath, ignorePattern, parser, parserOptions, rules" ].join("\n")), "u") ); }); @@ -150,12 +178,10 @@ describe("FlatESLint", () => { cacheLocation: "", cwd: "foo", errorOnUnmatchedPattern: "", - extensions: "", fix: "", fixTypes: ["xyz"], globInputPaths: "", ignore: "", - ignorePath: "", overrideConfig: "", overrideConfigFile: "", plugins: "", @@ -169,12 +195,10 @@ describe("FlatESLint", () => { "- 'cacheLocation' must be a non-empty string.", "- 'cwd' must be an absolute path.", "- 'errorOnUnmatchedPattern' must be a boolean.", - "- 'extensions' must be an array of non-empty strings or null.", "- 'fix' must be a boolean or a function.", "- 'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".", "- 'globInputPaths' must be a boolean.", "- 'ignore' must be a boolean.", - "- 'ignorePath' must be a non-empty string or null.", "- 'overrideConfig' must be an object or null.", "- 'overrideConfigFile' must be a non-empty string, null, or true.", "- 'plugins' must be an object or null.", @@ -219,6 +243,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].fixableErrorCount, 3); assert.strictEqual(results[0].fixableWarningCount, 0); assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report the total and per file warnings when using local cwd .eslintrc", async () => { @@ -244,6 +269,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].fixableErrorCount, 0); assert.strictEqual(results[0].fixableWarningCount, 3); assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report one message when using specific config file", async () => { @@ -262,6 +288,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].warningCount, 0); assert.strictEqual(results[0].fatalErrorCount, 0); assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report the filename when passed in", async () => { @@ -277,9 +304,8 @@ describe("FlatESLint", () => { it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config.js" + overrideConfigFile: "fixtures/eslint.config_with_ignores.js" }); const options = { filePath: "fixtures/passing.js", warnIgnored: true }; @@ -296,13 +322,13 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].fixableErrorCount, 0); assert.strictEqual(results[0].fixableWarningCount, 0); assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config.js" + overrideConfigFile: "fixtures/eslint.config_with_ignores.js" }); const options = { filePath: "fixtures/passing.js", @@ -318,9 +344,8 @@ describe("FlatESLint", () => { it("should suppress excluded file warnings by default", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config.js" + overrideConfigFile: "fixtures/eslint.config_with_ignores.js" }); const options = { filePath: "fixtures/passing.js" }; const results = await eslint.lintText("var bar = foo;", options); @@ -331,10 +356,9 @@ describe("FlatESLint", () => { it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { eslint = new FlatESLint({ - ignorePath: "fixtures/.eslintignore", cwd: getFixturePath(".."), ignore: false, - overrideConfigFile: true, + overrideConfigFile: "fixtures/eslint.config_with_ignores.js", overrideConfig: { rules: { "no-undef": 2 @@ -349,6 +373,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); assert.strictEqual(results[0].messages[0].severity, 2); assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should return a message and fixed text when in fix mode", async () => { @@ -370,6 +395,7 @@ describe("FlatESLint", () => { { filePath: getFixturePath("passing.js"), messages: [], + suppressedMessages: [], errorCount: 0, warningCount: 0, fatalErrorCount: 0, @@ -412,6 +438,7 @@ describe("FlatESLint", () => { nodeType: "Identifier" } ], + suppressedMessages: [], errorCount: 1, warningCount: 0, fatalErrorCount: 0, @@ -451,6 +478,7 @@ describe("FlatESLint", () => { column: 19 } ], + suppressedMessages: [], errorCount: 1, warningCount: 0, fatalErrorCount: 1, @@ -490,6 +518,7 @@ describe("FlatESLint", () => { column: 10 } ], + suppressedMessages: [], errorCount: 1, warningCount: 0, fatalErrorCount: 1, @@ -537,6 +566,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[0].source, void 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should not return a `source` property when fixes are applied", async () => { @@ -578,6 +608,7 @@ describe("FlatESLint", () => { column: 19 } ], + suppressedMessages: [], errorCount: 1, warningCount: 0, fatalErrorCount: 1, @@ -601,6 +632,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].filePath, getFixturePath("node_modules/passing.js")); assert.strictEqual(results[0].messages[0].message, expectedMsg); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should warn when deprecated rules are found in a config", async () => { @@ -666,6 +698,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 1); assert.strictEqual(results[0].messages[0].message, "Parsing error: Boom!"); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report zero messages when given a config file and a valid file", async () => { @@ -678,6 +711,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should handle multiple patterns with overlapping files", async () => { @@ -690,6 +724,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report zero messages when given a config file and a valid file and espree as parser", async () => { @@ -708,6 +743,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report zero messages when given a config file and a valid file and esprima as parser", async () => { @@ -724,6 +760,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should throw an error when given a config file and a valid file and invalid parser", async () => { @@ -736,96 +773,379 @@ describe("FlatESLint", () => { overrideConfigFile: true }); - await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected string in the form "pluginName\/objectName" but found "test11"/u); + await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected object with parse\(\) or parseForESLint\(\) method/u); }); it("should report zero messages when given a directory with a .js2 file", async () => { eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), - extensions: [".js2"], - overrideConfigFile: getFixturePath("eslint.config.js") + overrideConfigFile: getFixturePath("eslint.config.js"), + overrideConfig: { + files: ["**/*.js2"] + } }); const results = await eslint.lintFiles([getFixturePath("files/foo.js2")]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - it("should fall back to defaults when extensions is set to an empty array", async () => { + it("should report zero messages when given a directory with a .js and a .js2 file", async () => { eslint = new FlatESLint({ - cwd: getFixturePath(), - overrideConfigFile: true, ignore: false, - overrideConfig: { - rules: { - quotes: ["error", "double"] - } - }, - extensions: [] + cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: getFixturePath("eslint.config.js") }); + const results = await eslint.lintFiles(["fixtures/files/"]); - const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - it("should report zero messages when given a directory with a .js and a .js2 file", async () => { + // https://github.com/eslint/eslint/issues/16413 + it("should find files and report zero messages when given a parent directory with a .js", async () => { eslint = new FlatESLint({ - extensions: [".js", ".js2"], ignore: false, - cwd: getFixturePath(".."), - overrideConfigFile: getFixturePath("eslint.config.js") + cwd: getFixturePath("example-app/subdir") }); - const results = await eslint.lintFiles(["fixtures/files/"]); + const results = await eslint.lintFiles(["../*.js"]); assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/16038 + it("should allow files patterns with '..' inside", async () => { + eslint = new FlatESLint({ + ignore: false, + cwd: getFixturePath("dots-in-files") + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dots-in-files/a..b.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + + // https://github.com/eslint/eslint/issues/16299 + it("should only find files in the subdir1 directory when given a directory name", async () => { + eslint = new FlatESLint({ + ignore: false, + cwd: getFixturePath("example-app2") + }); + const results = await eslint.lintFiles(["subdir1"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("example-app2/subdir1/a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/14742 + it("should run", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("{curly-path}", "server") + }); + const results = await eslint.lintFiles(["src/**/*.{js,json}"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual( + results[0].filePath, + getFixturePath("{curly-path}/server/src/two.js") + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/16265 + describe("Dot files in searches", () => { + + it("should find dot files in current directory when a . pattern is used", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("dot-files") + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].filePath, getFixturePath("dot-files/.c.js")); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].filePath, getFixturePath("dot-files/b.js")); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should find dot files in current directory when a *.js pattern is used", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("dot-files") + }); + const results = await eslint.lintFiles(["*.js"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].filePath, getFixturePath("dot-files/.c.js")); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].filePath, getFixturePath("dot-files/b.js")); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should find dot files in current directory when a .a.js pattern is used", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("dot-files") + }); + const results = await eslint.lintFiles([".a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + // https://github.com/eslint/eslint/issues/16275 + describe("Glob patterns without matches", () => { + + it("should throw an error for a missing pattern when combined with a found pattern", async () => { + eslint = new FlatESLint({ + ignore: false, + cwd: getFixturePath("example-app2") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1", "doesnotexist/*.js"]); + }, /No files matching 'doesnotexist\/\*\.js' were found/u); + }); + + it("should throw an error for an ignored directory pattern when combined with a found pattern", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2"] + } + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); + }, /All files matched by 'subdir2\/\*\.js' are ignored/u); + }); + + it("should throw an error for an ignored file pattern when combined with a found pattern", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2/*.js"] + } + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); + }, /All files matched by 'subdir2\/\*\.js' are ignored/u); + }); + + it("should always throw an error for the first unmatched file pattern", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir1/*.js", "subdir2/*.js"] + } + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["doesnotexist1/*.js", "doesnotexist2/*.js"]); + }, /No files matching 'doesnotexist1\/\*\.js' were found/u); + + await assert.rejects(async () => { + await eslint.lintFiles(["doesnotexist1/*.js", "subdir1/*.js"]); + }, /No files matching 'doesnotexist1\/\*\.js' were found/u); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "doesnotexist1/*.js"]); + }, /All files matched by 'subdir1\/\*\.js' are ignored/u); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); + }, /All files matched by 'subdir1\/\*\.js' are ignored/u); + }); + + it("should not throw an error for an ignored file pattern when errorOnUnmatchedPattern is false", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2/*.js"] + }, + errorOnUnmatchedPattern: false + }); + + const results = await eslint.lintFiles(["subdir2/*.js"]); + + assert.strictEqual(results.length, 0); + }); + + it("should not throw an error for a non-existing file pattern when errorOnUnmatchedPattern is false", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("example-app2"), + errorOnUnmatchedPattern: false + }); + + const results = await eslint.lintFiles(["doesexist/*.js"]); + + assert.strictEqual(results.length, 0); + }); + }); + + // https://github.com/eslint/eslint/issues/16260 + describe("Globbing based on configs", () => { + it("should report zero messages when given a directory with a .js and config file specifying a subdirectory", async () => { + eslint = new FlatESLint({ + ignore: false, + cwd: getFixturePath("shallow-glob") + }); + const results = await eslint.lintFiles(["target-dir"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should glob for .jsx file in a subdirectory of the passed-in directory and not glob for any other patterns", async () => { + eslint = new FlatESLint({ + ignore: false, + overrideConfigFile: true, + overrideConfig: { + files: ["subdir/**/*.jsx", "target-dir/*.js"], + languageOptions: { + parserOptions: { + jsx: true + } + } + }, + cwd: getFixturePath("shallow-glob") + }); + const results = await eslint.lintFiles(["subdir/subsubdir"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("shallow-glob/subdir/subsubdir/broken.js")); + assert(results[0].messages[0].fatal, "Fatal error expected."); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].filePath, getFixturePath("shallow-glob/subdir/subsubdir/plain.jsx")); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + + it("should glob for all files in subdir when passed-in on the command line with a partial matching glob", async () => { + eslint = new FlatESLint({ + ignore: false, + overrideConfigFile: true, + overrideConfig: { + files: ["s*/subsubdir/*.jsx", "target-dir/*.js"], + languageOptions: { + parserOptions: { + jsx: true + } + } + }, + cwd: getFixturePath("shallow-glob") + }); + const results = await eslint.lintFiles(["subdir"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 1); + assert(results[0].messages[0].fatal, "Fatal error expected."); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 1); + assert(results[0].messages[0].fatal, "Fatal error expected."); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); }); it("should report zero messages when given a '**' pattern with a .js and a .js2 file", async () => { eslint = new FlatESLint({ - extensions: [".js", ".js2"], ignore: false, cwd: path.join(fixtureDir, ".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, overrideConfigFile: getFixturePath("eslint.config.js") }); const results = await eslint.lintFiles(["fixtures/files/*"]); - assert.strictEqual(results.length, 2); + assert.strictEqual(results.length, 3); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); it("should resolve globs when 'globInputPaths' option is true", async () => { eslint = new FlatESLint({ - extensions: [".js", ".js2"], ignore: false, cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, overrideConfigFile: getFixturePath("eslint.config.js") }); const results = await eslint.lintFiles(["fixtures/files/*"]); - assert.strictEqual(results.length, 2); + assert.strictEqual(results.length, 3); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); + // only works on a Windows machine + if (os.platform() === "win32") { + + it("should resolve globs with Windows slashes when 'globInputPaths' option is true", async () => { + eslint = new FlatESLint({ + ignore: false, + cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: getFixturePath("eslint.config.js") + + }); + const results = await eslint.lintFiles(["fixtures\\files\\*"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + } + + it("should not resolve globs when 'globInputPaths' option is false", async () => { eslint = new FlatESLint({ - extensions: [".js", ".js2"], ignore: false, cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, overrideConfigFile: true, globInputPaths: false }); @@ -851,6 +1171,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].fixableErrorCount, 0); assert.strictEqual(results[0].fixableWarningCount, 0); assert.strictEqual(results[0].messages[0].message, expectedMsg); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report on globs with explicit inclusion of dotfiles", async () => { @@ -881,7 +1202,7 @@ describe("FlatESLint", () => { await assert.rejects(async () => { await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules\/\*\*\/\*.js' are ignored\./u); + }, /All files matched by 'node_modules' are ignored\./u); }); // https://github.com/eslint/eslint/issues/5547 @@ -893,43 +1214,32 @@ describe("FlatESLint", () => { await assert.rejects(async () => { await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules\/\*\*\/\*\.js' are ignored\./u); - }); - - it("should throw an error when given a directory with all eslint excluded files in the directory", async () => { - eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore") - }); - - await assert.rejects(async () => { - await eslint.lintFiles([getFixturePath("./cli-engine/")]); - }, /All files matched by '.*?cli-engine[\\/]\*\*[\\/]\*\.js' are ignored/u); + }, /All files matched by 'node_modules' are ignored\./u); }); it("should throw an error when all given files are ignored", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore") + overrideConfigFile: getFixturePath("eslint.config_with_ignores.js") }); await assert.rejects(async () => { await eslint.lintFiles(["tests/fixtures/cli-engine/"]); - }, /All files matched by 'tests\/fixtures\/cli-engine\/\*\*\/\*\.js' are ignored\./u); + }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); }); it("should throw an error when all given files are ignored even with a `./` prefix", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore") + overrideConfigFile: getFixturePath("eslint.config_with_ignores.js") }); await assert.rejects(async () => { await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by 'tests\/fixtures\/cli-engine\/\*\*\/\*\.js' are ignored\./u); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); }); // https://github.com/eslint/eslint/issues/3788 - it("should ignore one-level down node_modules when ignore file has 'node_modules/' in it", async () => { + it("should ignore one-level down node_modules by default", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath("cli-engine", "nested_node_modules", ".eslintignore"), overrideConfigFile: true, overrideConfig: { rules: { @@ -949,10 +1259,9 @@ describe("FlatESLint", () => { }); // https://github.com/eslint/eslint/issues/3812 - it("should ignore all files and throw an error when fixtures/ is in ignore file", async () => { + it("should ignore all files and throw an error when **/fixtures/** is in `ignores` in the config file", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath("cli-engine/.eslintignore2"), - overrideConfigFile: true, + overrideConfigFile: getFixturePath("cli-engine/eslint.config_with_ignores2.js"), overrideConfig: { rules: { quotes: [2, "double"] @@ -962,7 +1271,7 @@ describe("FlatESLint", () => { await assert.rejects(async () => { await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by 'tests\/fixtures\/cli-engine\/\*\*\/\*\.js' are ignored\./u); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); }); it("should throw an error when all given files are ignored via ignore-pattern", async () => { @@ -979,7 +1288,7 @@ describe("FlatESLint", () => { it("should return a warning when an explicitly given file is ignored", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore"), + overrideConfigFile: "eslint.config_with_ignores.js", cwd: getFixturePath() }); const filePath = getFixturePath("passing.js"); @@ -994,14 +1303,14 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].fatalErrorCount, 0); assert.strictEqual(results[0].fixableErrorCount, 0); assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should return two messages when given a file in excluded files list while ignore is off", async () => { eslint = new FlatESLint({ cwd: getFixturePath(), - ignorePath: getFixturePath(".eslintignore"), ignore: false, - overrideConfigFile: true, + overrideConfigFile: getFixturePath("eslint.config_with_ignores.js"), overrideConfig: { rules: { "no-undef": 2 @@ -1017,62 +1326,188 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].messages[0].severity, 2); assert.strictEqual(results[0].messages[1].ruleId, "no-undef"); assert.strictEqual(results[0].messages[1].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - }); + // https://github.com/eslint/eslint/issues/16300 + it("should process ignore patterns relative to basePath not cwd", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-relative/subdir") + }); + const results = await eslint.lintFiles(["**/*.js"]); - it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { - eslint = new FlatESLint({ - extensions: [".js", ".js2"], - ignore: false, - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-relative/subdir/a.js")); }); - const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - }); + // https://github.com/eslint/eslint/issues/16354 + it("should skip subdirectory files when ignore pattern matches deep subdirectory", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-directory") + }); - it("should return one error message when given a config with rules with options and severity level set to error", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - overrideConfigFile: true, - overrideConfig: { - rules: { - quotes: ["error", "double"] - } - }, - ignore: false - }); - const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/**"]); + }, /All files matched by 'subdir\/\*\*' are ignored\./u); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/subsubdir/**"]); + }, /All files matched by 'subdir\/subsubdir\/\*\*' are ignored\./u); + + const results = await eslint.lintFiles(["subdir/subsubdir/a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-directory/subdir/subsubdir/a.js")); + assert.strictEqual(results[0].warningCount, 1); + assert(results[0].messages[0].message.startsWith("File ignored"), "Should contain file ignored warning"); - it("should return 5 results when given a config and a directory of 5 valid files", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 1, - strict: 0 - } - } }); - const formattersDir = getFixturePath("formatters"); - const results = await eslint.lintFiles([formattersDir]); + // https://github.com/eslint/eslint/issues/16414 + it("should skip subdirectory files when ignore pattern matches subdirectory", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-subdirectory") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/**/*.js"]); + }, /All files matched by 'subdir\/\*\*\/\*\.js' are ignored\./u); + + const results = await eslint.lintFiles(["subdir/subsubdir/a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-subdirectory/subdir/subsubdir/a.js")); + assert.strictEqual(results[0].warningCount, 1); + assert(results[0].messages[0].message.startsWith("File ignored"), "Should contain file ignored warning"); + + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-subdirectory/subdir") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subsubdir/**/*.js"]); + }, /All files matched by 'subsubdir\/\*\*\/\*\.js' are ignored\./u); + + + }); + + // https://github.com/eslint/eslint/issues/16340 + it("should lint files even when cwd directory name matches ignores pattern", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-self") + }); + + const results = await eslint.lintFiles(["*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-self/eslint.config.js")); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + + }); + + // https://github.com/eslint/eslint/issues/16416 + it("should allow reignoring of previously ignored files", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-relative"), + overrideConfigFile: true, + overrideConfig: { + ignores: [ + "*.js", + "!a*.js", + "a.js" + ] + } + }); + const results = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-relative/a.js")); + }); + + // https://github.com/eslint/eslint/issues/16415 + it("should allow directories to be unignored", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-directory"), + overrideConfigFile: true, + overrideConfig: { + ignores: [ + "subdir/*", + "!subdir/subsubdir" + ] + } + }); + const results = await eslint.lintFiles(["subdir/**/*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-directory/subdir/subsubdir/a.js")); + }); + + + }); + + + it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { + eslint = new FlatESLint({ + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + ignore: false, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true + }); + const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should return one error message when given a config with rules with options and severity level set to error", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath(), + overrideConfigFile: true, + overrideConfig: { + rules: { + quotes: ["error", "double"] + } + }, + ignore: false + }); + const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return 5 results when given a config and a directory of 5 valid files", async () => { + eslint = new FlatESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 1, + strict: 0 + } + } + }); + + const formattersDir = getFixturePath("formatters"); + const results = await eslint.lintFiles([formattersDir]); assert.strictEqual(results.length, 5); assert.strictEqual(path.relative(formattersDir, results[0].filePath), "async.js"); @@ -1082,6 +1517,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].fixableErrorCount, 0); assert.strictEqual(results[0].fixableWarningCount, 0); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(path.relative(formattersDir, results[1].filePath), "broken.js"); assert.strictEqual(results[1].errorCount, 0); assert.strictEqual(results[1].warningCount, 0); @@ -1089,6 +1525,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[1].fixableErrorCount, 0); assert.strictEqual(results[1].fixableWarningCount, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); assert.strictEqual(path.relative(formattersDir, results[2].filePath), "cwd.js"); assert.strictEqual(results[2].errorCount, 0); assert.strictEqual(results[2].warningCount, 0); @@ -1096,6 +1533,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[2].fixableErrorCount, 0); assert.strictEqual(results[2].fixableWarningCount, 0); assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); assert.strictEqual(path.relative(formattersDir, results[3].filePath), "simple.js"); assert.strictEqual(results[3].errorCount, 0); assert.strictEqual(results[3].warningCount, 0); @@ -1103,6 +1541,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[3].fixableErrorCount, 0); assert.strictEqual(results[3].fixableWarningCount, 0); assert.strictEqual(results[3].messages.length, 0); + assert.strictEqual(results[3].suppressedMessages.length, 0); assert.strictEqual(path.relative(formattersDir, results[4].filePath), path.join("test", "simple.js")); assert.strictEqual(results[4].errorCount, 0); assert.strictEqual(results[4].warningCount, 0); @@ -1110,6 +1549,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[4].fixableErrorCount, 0); assert.strictEqual(results[4].fixableWarningCount, 0); assert.strictEqual(results[4].messages.length, 0); + assert.strictEqual(results[4].suppressedMessages.length, 0); }); it("should return zero messages when given a config with browser globals", async () => { @@ -1121,6 +1561,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0, "Should have no messages."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should return zero messages when given an option to add browser globals", async () => { @@ -1143,6 +1584,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should return zero messages when given a config with sourceType set to commonjs and Node.js globals", async () => { @@ -1154,6 +1596,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0, "Should have no messages."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should not return results from previous call when calling more than once", async () => { @@ -1176,12 +1619,14 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].filePath, failFilePath); assert.strictEqual(results[0].messages.length, 1); assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[0].messages[0].severity, 2); results = await eslint.lintFiles([passFilePath]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].filePath, passFilePath); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should return zero messages when executing a file with a shebang", async () => { @@ -1194,6 +1639,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0, "Should have lint messages."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should return zero messages when executing without a config file", async () => { @@ -1208,6 +1654,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].filePath, filePath); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); // working @@ -1328,6 +1775,7 @@ describe("FlatESLint", () => { { filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), messages: [], + suppressedMessages: [], errorCount: 0, warningCount: 0, fatalErrorCount: 0, @@ -1339,6 +1787,7 @@ describe("FlatESLint", () => { { filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), messages: [], + suppressedMessages: [], errorCount: 0, warningCount: 0, fatalErrorCount: 0, @@ -1361,6 +1810,7 @@ describe("FlatESLint", () => { severity: 2 } ], + suppressedMessages: [], errorCount: 1, warningCount: 0, fatalErrorCount: 0, @@ -1384,6 +1834,7 @@ describe("FlatESLint", () => { severity: 2 } ], + suppressedMessages: [], errorCount: 1, warningCount: 0, fatalErrorCount: 0, @@ -1434,6 +1885,8 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 2, "Expected two messages."); assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); it("should return two messages when executing with cli option that specifies a plugin", async () => { @@ -1449,6 +1902,8 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 2); assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); it("should return two messages when executing with cli option that specifies preloaded plugin", async () => { @@ -1467,6 +1922,8 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 2); assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); }); @@ -1547,7 +2004,6 @@ describe("FlatESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); const file = getFixturePath("cache/src", "test-file.js"); @@ -1575,7 +2031,6 @@ describe("FlatESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); const file = getFixturePath("cache/src", "test-file.js"); @@ -1599,7 +2054,6 @@ describe("FlatESLint", () => { "no-console": 0 } }, - extensions: ["js"], ignore: false }); const file = getFixturePath("cli-engine", "console.js"); @@ -1627,7 +2081,6 @@ describe("FlatESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); @@ -1660,7 +2113,6 @@ describe("FlatESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); @@ -1691,7 +2143,6 @@ describe("FlatESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); @@ -1721,7 +2172,6 @@ describe("FlatESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); @@ -1750,7 +2200,6 @@ describe("FlatESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], cwd: path.join(fixtureDir, "..") }; @@ -1791,8 +2240,7 @@ describe("FlatESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -1826,8 +2274,7 @@ describe("FlatESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -1870,8 +2317,7 @@ describe("FlatESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -1909,8 +2355,7 @@ describe("FlatESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); assert(shell.test("-f", cacheLocation), "the cache for eslint exists"); @@ -1932,8 +2377,7 @@ describe("FlatESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); assert(shell.test("-f", cacheLocation), "the cache for eslint exists"); @@ -1956,8 +2400,7 @@ describe("FlatESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); const file = getFixturePath("cli-engine", "console.js"); @@ -1980,8 +2423,8 @@ describe("FlatESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } + }); const file = getFixturePath("cli-engine", "console.js"); @@ -2012,7 +2455,7 @@ describe("FlatESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], + cwd: path.join(fixtureDir, "..") }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); @@ -2051,8 +2494,8 @@ describe("FlatESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } + }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -2090,8 +2533,8 @@ describe("FlatESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } + }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -2131,8 +2574,8 @@ describe("FlatESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } + }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -2185,17 +2628,17 @@ describe("FlatESLint", () => { } }, { - files: ["**/*.txt/*.txt"] + files: ["**/*.txt", "**/*.txt/*.txt"] } ], - - extensions: ["js", "txt"], cwd: path.join(fixtureDir, "..") }); const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); it("should run processors when calling lintFiles with config file that specifies preloaded processor", async () => { @@ -2225,16 +2668,17 @@ describe("FlatESLint", () => { } }, { - files: ["**/*.txt/*.txt"] + files: ["**/*.txt", "**/*.txt/*.txt"] } ], - extensions: ["js", "txt"], cwd: path.join(fixtureDir, "..") }); const results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); it("should run processors when calling lintText with config file that specifies preloaded processor", async () => { @@ -2264,16 +2708,16 @@ describe("FlatESLint", () => { } }, { - files: ["**/*.txt/*.txt"] + files: ["**/*.txt", "**/*.txt/*.txt"] } ], - extensions: ["js", "txt"], ignore: false }); const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should run processors when calling lintText with processor resolves same extension but different content correctly", async () => { @@ -2314,9 +2758,11 @@ describe("FlatESLint", () => { "no-console": 2, "no-unused-vars": 2 } + }, + { + files: ["**/*.txt"] } ], - extensions: ["txt"], ignore: false }); const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); @@ -2324,6 +2770,8 @@ describe("FlatESLint", () => { assert.strictEqual(count, 2); assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); describe("autofixing with processors", () => { @@ -2349,23 +2797,28 @@ describe("FlatESLint", () => { it("should run in autofix mode when using a processor that supports autofixing", async () => { eslint = new FlatESLint({ overrideConfigFile: true, - overrideConfig: { - files: ["**/*.html"], - plugins: { - test: { processors: { html: Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) } } + overrideConfig: [ + { + files: ["**/*.html"], + plugins: { + test: { processors: { html: Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) } } + }, + processor: "test/html", + rules: { + semi: 2 + } }, - processor: "test/html", - rules: { - semi: 2 + { + files: ["**/*.txt"] } - }, - extensions: ["js", "txt"], + ], ignore: false, fix: true }); const results = await eslint.lintText("", { filePath: "foo.html" }); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[0].output, ""); }); @@ -2388,28 +2841,34 @@ describe("FlatESLint", () => { const results = await eslint.lintText("", { filePath: "foo.html" }); assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); }); it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", async () => { eslint = new FlatESLint({ overrideConfigFile: true, - overrideConfig: { - files: ["**/*.html"], - plugins: { - test: { processors: { html: Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) } } + overrideConfig: [ + { + files: ["**/*.html"], + plugins: { + test: { processors: { html: Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) } } + }, + processor: "test/html", + rules: { + semi: 2 + } }, - processor: "test/html", - rules: { - semi: 2 + { + files: ["**/*.txt"] } - }, - extensions: ["js", "txt"], + ], ignore: false }); const results = await eslint.lintText("", { filePath: "foo.html" }); assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); }); }); @@ -2430,9 +2889,10 @@ describe("FlatESLint", () => { }); it("should throw if the directory exists and is empty", async () => { + ensureDirectoryExists(getFixturePath("cli-engine/empty")); await assert.rejects(async () => { await eslint.lintFiles(["empty"]); - }, /No files matching 'empty\/\*\*\/\*\.js' were found\./u); + }, /No files matching 'empty' were found\./u); }); it("one glob pattern", async () => { @@ -2452,6 +2912,13 @@ describe("FlatESLint", () => { await eslint.lintFiles(["console.js", "non-exist.js"]); }, /No files matching 'non-exist\.js' were found\./u); }); + + // https://github.com/eslint/eslint/issues/16275 + it("a mix of an existing glob pattern and a non-existing glob pattern", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["*.js", "non-exist/*.js"]); + }, /No files matching 'non-exist\/\*\.js' were found\./u); + }); }); describe("multiple processors", () => { @@ -2499,9 +2966,19 @@ describe("FlatESLint", () => { let id; beforeEach(() => (id = Date.now().toString())); - afterEach(async () => fsp.rmdir(root, { recursive: true, force: true })); - it("should lint only JavaScript blocks if '--ext' was not given.", async () => { + /* + * `fs.rmdir(path, { recursive: true })` is deprecated and will be removed. + * Use `fs.rm(path, { recursive: true })` instead. + * When supporting Node.js 14.14.0+, the compatibility condition can be removed for `fs.rmdir`. + */ + if (typeof fsp.rm === "function") { + afterEach(async () => fsp.rm(root, { recursive: true, force: true })); + } else { + afterEach(async () => fsp.rmdir(root, { recursive: true, force: true })); + } + + it("should lint only JavaScript blocks.", async () => { const teardown = createCustomTeardown({ cwd: path.join(root, id), files: { @@ -2533,51 +3010,8 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].messages.length, 1, "Should have one message."); assert.strictEqual(results[0].messages[0].ruleId, "semi"); assert.strictEqual(results[0].messages[0].line, 2, "Message should be on line 2."); - }); - - it("should fix only JavaScript blocks if '--ext' was not given.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown") - } - }, - { - files: ["**/*.js"], - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - } - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), fix: true }); - const results = await eslint.lintFiles(["test.md"]); + assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].output, unIndent` - \`\`\`js - console.log("hello");${/* ← fixed */""} - \`\`\` - \`\`\`html -
    Hello
    - - - \`\`\` - `); }); it("should lint HTML blocks as well with multiple processors if represented in config.", async () => { @@ -2609,7 +3043,7 @@ describe("FlatESLint", () => { }); await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); + eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); const results = await eslint.lintFiles(["test.md"]); assert.strictEqual(results.length, 1, "Should have one result."); @@ -2618,6 +3052,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].messages[0].line, 2, "First error should be on line 2"); assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block assert.strictEqual(results[0].messages[1].line, 7, "Second error should be on line 7."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should fix HTML blocks as well with multiple processors if represented in config.", async () => { @@ -2649,11 +3084,12 @@ describe("FlatESLint", () => { }); await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true }); + eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] }, fix: true }); const results = await eslint.lintFiles(["test.md"]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[0].output, unIndent` \`\`\`js console.log("hello");${/* ← fixed */""} @@ -2708,7 +3144,7 @@ describe("FlatESLint", () => { }); await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); + eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); const results = await eslint.lintFiles(["test.md"]); assert.strictEqual(results.length, 1); @@ -2717,6 +3153,8 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].messages[0].line, 2); assert.strictEqual(results[0].messages[1].ruleId, "no-console"); assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { @@ -2757,7 +3195,7 @@ describe("FlatESLint", () => { }); await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); + eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); const results = await eslint.lintFiles(["test.md"]); assert.strictEqual(results.length, 1); @@ -2768,6 +3206,7 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].messages[1].line, 7); assert.strictEqual(results[0].messages[2].ruleId, "no-console"); assert.strictEqual(results[0].messages[2].line, 10); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should throw an error if invalid processor was specified.", async () => { @@ -2920,6 +3359,7 @@ describe("FlatESLint", () => { assert.strictEqual(messages.length, 1); assert.strictEqual(messages[0].severity, 1); assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); describe("the runtime option overrides config files.", () => { @@ -2969,6 +3409,7 @@ describe("FlatESLint", () => { assert.strictEqual(messages.length, 1); assert.strictEqual(messages[0].severity, 2); assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); }); }); @@ -3058,7 +3499,7 @@ describe("FlatESLint", () => { describe("isPathIgnored", () => { it("should check if the given path is ignored", async () => { const engine = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore2"), + overrideConfigFile: getFixturePath("eslint.config_with_ignores2.js"), cwd: getFixturePath() }); @@ -3069,7 +3510,7 @@ describe("FlatESLint", () => { it("should return false if ignoring is disabled", async () => { const engine = new FlatESLint({ ignore: false, - ignorePath: getFixturePath(".eslintignore2"), + overrideConfigFile: getFixturePath("eslint.config_with_ignores2.js"), cwd: getFixturePath() }); @@ -3108,7 +3549,7 @@ describe("FlatESLint", () => { const engine = new FlatESLint({ cwd, overrideConfigFile: true, - ignorePatterns: "!/node_modules/package" + ignorePatterns: "!node_modules/package/**" }); const result = await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js")); @@ -3121,7 +3562,9 @@ describe("FlatESLint", () => { const engine = new FlatESLint({ cwd, overrideConfigFile: true, - ignorePath: getFixturePath("ignored-paths", ".eslintignoreWithUnignoredDefaults") + overrideConfig: { + ignores: ["!node_modules/package/**"] + } }); assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); @@ -3148,53 +3591,21 @@ describe("FlatESLint", () => { assert(!await engine.isPathIgnored(`${getFixturePath("ignored-paths", "foo")}/../unignored.js`)); }); - it("should ignore /node_modules/ relative to .eslintignore when loaded", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePath: getFixturePath("ignored-paths", ".eslintignore"), cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "existing.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo", "node_modules", "existing.js"))); - }); - - it("should ignore /node_modules/ relative to cwd without an .eslintignore", async () => { + it("should ignore /node_modules/ relative to cwd without any configured ignore patterns", async () => { const cwd = getFixturePath("ignored-paths", "no-ignore-file"); const engine = new FlatESLint({ cwd }); assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "node_modules", "existing.js"))); assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "foo", "node_modules", "existing.js"))); }); - }); - - describe("with no .eslintignore file", () => { - it("should not travel to parent directories to find .eslintignore when it's missing and cwd is provided", async () => { - const cwd = getFixturePath("ignored-paths", "configurations"); - const engine = new FlatESLint({ cwd }); - - // a .eslintignore in parent directories includes `*.js`, but don't load it. - assert(!await engine.isPathIgnored("foo.js")); - assert(await engine.isPathIgnored("node_modules/foo.js")); - }); - - it("should return false for files outside of the cwd (with no ignore file provided)", async () => { - // Default ignore patterns should not inadvertently ignore files in parent directories + it("should not inadvertently ignore all files in parent directories", async () => { const engine = new FlatESLint({ cwd: getFixturePath("ignored-paths", "no-ignore-file") }); assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); }); - describe("with .eslintignore file or package.json file", () => { - it("should load .eslintignore from cwd when explicitly passed", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ cwd }); - - // `${cwd}/.eslintignore` includes `sampleignorepattern`. - assert(await engine.isPathIgnored("sampleignorepattern")); - }); - - }); - describe("with ignorePatterns option", () => { it("should accept a string for options.ignorePatterns", async () => { const cwd = getFixturePath("ignored-paths", "ignore-pattern"); @@ -3229,16 +3640,20 @@ describe("FlatESLint", () => { it("should return true for file matching an ignore pattern exactly", async () => { const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePatterns: ["undef.js"], cwd }); + const engine = new FlatESLint({ + ignorePatterns: ["undef.js"], + cwd, + overrideConfigFile: true + }); assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); - it("should return false for file in subfolder of cwd matching an ignore pattern with leading '/'", async () => { + it("should return false for file in subfolder of cwd matching an ignore pattern with a base filename", async () => { const cwd = getFixturePath("ignored-paths"); const filePath = getFixturePath("ignored-paths", "subdir", "undef.js"); const engine = new FlatESLint({ - ignorePatterns: ["/undef.js"], + ignorePatterns: ["undef.js"], overrideConfigFile: true, cwd }); @@ -3253,11 +3668,11 @@ describe("FlatESLint", () => { assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "ignore-me.txt"))); }); - it("should return true for file matching a grandchild of an ignore pattern", async () => { + it("should return true for file matching a grandchild of a directory when the pattern is directory/**", async () => { const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePatterns: ["ignore-pattern"], cwd }); + const engine = new FlatESLint({ ignorePatterns: ["ignore-pattern/**"], cwd }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.txt"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.js"))); }); it("should return false for file not matching any ignore pattern", async () => { @@ -3284,165 +3699,30 @@ describe("FlatESLint", () => { }); }); - describe("with ignorePath option", () => { - it("initialization with ignorePath should work when cwd is a parent directory", async () => { + describe("with config ignores ignorePatterns option", () => { + it("should return false for ignored file when unignored with ignore pattern", async () => { const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new FlatESLint({ ignorePath, cwd }); + const engine = new FlatESLint({ + overrideConfigFile: getFixturePath("eslint.config_with_ignores2.js"), + ignorePatterns: ["!undef.js"], + cwd + }); - assert(await engine.isPathIgnored("custom-name/foo.js")); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); + }); - it("initialization with ignorePath should work when the file is in the cwd", async () => { - const cwd = getFixturePath("ignored-paths", "custom-name"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new FlatESLint({ ignorePath, cwd }); + it("should throw if non-string value is given to 'filePath' parameter", async () => { + const eslint = new FlatESLint(); - assert(await engine.isPathIgnored("foo.js")); - }); + await assert.rejects(() => eslint.isPathIgnored(null), /'filePath' must be a non-empty string/u); + }); + }); - it("initialization with ignorePath should work when cwd is a subdirectory", async () => { - const cwd = getFixturePath("ignored-paths", "custom-name", "subdirectory"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("../custom-name/foo.js")); - }); - - it("missing ignore file should throw error", done => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "not-a-directory", ".foobaz"); - const engine = new FlatESLint({ ignorePath, cwd }); - - engine.isPathIgnored("foo.js").then(() => { - assert.fail("missing file should not succeed"); - }).catch(error => { - assert(/Cannot read ignore file/u.test(error)); - done(); - }); - }); - - it("should return false for files outside of ignorePath's directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should resolve relative paths from CWD", async () => { - const cwd = getFixturePath("ignored-paths", "subdir"); - - // /undef.js in ignore file - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); - const engine = new FlatESLint({ ignorePath, cwd, overrideConfigFile: true }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js")), "subdir/undef.js should be ignored"); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/subdir/undef.js")), "subdir/subdir/undef.js should not be ignored"); - }); - - it("should resolve relative paths from CWD when it's in a child directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/foo.js"))); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/bar.js"))); - }); - - it("should resolve relative paths from CWD when it contains negated globs", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new FlatESLint({ - ignorePath, - cwd, - overrideConfig: { - files: ["**/*.txt"] - } - }); - - assert(await engine.isPathIgnored("subdir/blah.txt"), "subdir/blah.txt should be ignore"); - assert(await engine.isPathIgnored("blah.txt"), "blah.txt should be ignored"); - assert(await engine.isPathIgnored("subdir/bar.txt"), "subdir/bar.txt should be ignored"); - assert(!await engine.isPathIgnored("bar.txt"), "bar.txt should not be ignored"); - assert(!await engine.isPathIgnored("baz.txt"), "baz.txt should not be ignored"); - assert(!await engine.isPathIgnored("subdir/baz.txt"), "subdir/baz.txt should not be ignored"); - }); - - it("should resolve default ignore patterns from the CWD even when the ignorePath is in a subdirectory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("node_modules/blah.js")); - }); - - it("should resolve default ignore patterns from the CWD even when the ignorePath is in a parent directory", async () => { - const cwd = getFixturePath("ignored-paths", "subdir"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("node_modules/blah.js")); - }); - - it("should handle .eslintignore which contains CRLF correctly.", async () => { - const ignoreFileContent = fs.readFileSync(getFixturePath("ignored-paths", "crlf/.eslintignore"), "utf8"); - - assert(ignoreFileContent.includes("\r"), "crlf/.eslintignore should contains CR.", "Ignore file must have CRLF for test to pass."); - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "crlf/.eslintignore"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide1/a.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide2/a.js"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide3/a.js"))); - }); - - it("should ignore a non-negated pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "ignore.js"))); - }); - - it("should not ignore a negated pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "unignore.js"))); - }); - }); - - describe("with ignorePath option and ignorePatterns option", () => { - it("should return false for ignored file when unignored with ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - ignorePath: getFixturePath("ignored-paths", ".eslintignoreForNegationTest"), - ignorePatterns: ["!undef.js"], - cwd - }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - }); - - it("should throw if non-string value is given to 'filePath' parameter", async () => { - const eslint = new FlatESLint(); - - await assert.rejects(() => eslint.isPathIgnored(null), /'filePath' must be a non-empty string/u); - }); - }); - - describe("loadFormatter()", () => { - it("should return a formatter object when a bundled formatter is requested", async () => { - const engine = new FlatESLint(); - const formatter = await engine.loadFormatter("compact"); + describe("loadFormatter()", () => { + it("should return a formatter object when a bundled formatter is requested", async () => { + const engine = new FlatESLint(); + const formatter = await engine.loadFormatter("compact"); assert.strictEqual(typeof formatter, "object"); assert.strictEqual(typeof formatter.format, "function"); @@ -3623,8 +3903,7 @@ describe("FlatESLint", () => { it("should return 0 error or warning messages even when the file has warnings", async () => { const engine = new FlatESLint({ - overrideConfigFile: true, - ignorePath: path.join(fixtureDir, ".eslintignore"), + overrideConfigFile: getFixturePath("eslint.config_with_ignores.js"), cwd: path.join(fixtureDir, "..") }); const options = { @@ -3677,7 +3956,7 @@ describe("FlatESLint", () => { describe("getRulesMetaForResults()", () => { - it("should throw an error when results were not created from this instance", async () => { + it("should throw an error when this instance did not lint any files", async () => { const engine = new FlatESLint({ overrideConfigFile: true }); @@ -3704,6 +3983,7 @@ describe("FlatESLint", () => { nodeType: "CallExpression" } ], + suppressedMessages: [], errorCount: 2, warningCount: 0, fatalErrorCount: 0, @@ -3713,7 +3993,99 @@ describe("FlatESLint", () => { "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n" } ]); - }, /Results object was not created from this ESLint instance/u); + }, { + constructor: TypeError, + message: "Results object was not created from this ESLint instance." + }); + }); + + it("should throw an error when results were created from a different instance", async () => { + const engine1 = new FlatESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo"), + overrideConfig: { + rules: { + semi: 2 + } + } + }); + const engine2 = new FlatESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "bar"), + overrideConfig: { + rules: { + semi: 2 + } + } + }); + + const results1 = await engine1.lintText("1", { filePath: "file.js" }); + const results2 = await engine2.lintText("2", { filePath: "file.js" }); + + engine1.getRulesMetaForResults(results1); // should not throw an error + assert.throws(() => { + engine1.getRulesMetaForResults(results2); + }, { + constructor: TypeError, + message: "Results object was not created from this ESLint instance." + }); + }); + + it("should treat a result without `filePath` as if the file was located in `cwd`", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo", "bar"), + ignorePatterns: "*/**", // ignore all subdirectories of `cwd` + overrideConfig: { + rules: { + eqeqeq: "warn" + } + } + }); + + const results = await engine.lintText("a==b"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta.eqeqeq, coreRules.get("eqeqeq").meta); + }); + + it("should not throw an error if a result without `filePath` contains an ignored file warning", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo", "bar"), + ignorePatterns: "**" + }); + + const results = await engine.lintText("", { warnIgnored: true }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + }); + + it("should not throw an error if results contain linted files and one ignored file", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + cwd: getFixturePath(), + ignorePatterns: "passing*", + overrideConfig: { + rules: { + "no-undef": 2, + semi: 1 + } + } + }); + + const results = await engine.lintFiles(["missing-semicolon.js", "passing.js", "undef.js"]); + + assert( + results.some(({ messages }) => messages.some(({ message, ruleId }) => !ruleId && message.startsWith("File ignored"))), + "At least one file should be ignored but none is." + ); + + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta["no-undef"], coreRules.get("no-undef").meta); + assert.deepStrictEqual(rulesMeta.semi, coreRules.get("semi").meta); }); it("should return empty object when there are no linting errors", async () => { @@ -3723,7 +4095,7 @@ describe("FlatESLint", () => { const rulesMeta = engine.getRulesMetaForResults([]); - assert.strictEqual(Object.keys(rulesMeta).length, 0); + assert.deepStrictEqual(rulesMeta, {}); }); it("should return one rule meta when there is a linting error", async () => { @@ -3739,6 +4111,24 @@ describe("FlatESLint", () => { const results = await engine.lintText("a", { filePath: "foo.js" }); const rulesMeta = engine.getRulesMetaForResults(results); + assert.strictEqual(Object.keys(rulesMeta).length, 1); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return one rule meta when there is a suppressed linting error", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 2 + } + } + }); + + const results = await engine.lintText("a // eslint-disable-line semi"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 1); assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); }); @@ -3786,6 +4176,51 @@ describe("FlatESLint", () => { nodePlugin.rules["no-new-require"].meta ); }); + + it("should ignore messages not related to a rule", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + ignorePatterns: "ignored.js", + overrideConfig: { + rules: { + "no-var": "warn" + } + }, + reportUnusedDisableDirectives: "warn" + }); + + { + const results = await engine.lintText("syntax error"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("// eslint-disable-line no-var"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("", { filePath: "ignored.js", warnIgnored: true }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + }); + + it("should return a non-empty value if some of the messages are related to a rule", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + overrideConfig: { rules: { "no-var": "warn" } }, + reportUnusedDisableDirectives: "warn" + }); + + const results = await engine.lintText("// eslint-disable-line no-var\nvar foo;"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); + }); }); describe("outputFixes()", () => { @@ -3884,6 +4319,7 @@ describe("FlatESLint", () => { assert.strictEqual(messages.length, 1); assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should not report a violation by default", async () => { @@ -3909,6 +4345,8 @@ describe("FlatESLint", () => { const messages = results[0].messages; assert.strictEqual(messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 1); + assert.strictEqual(results[0].suppressedMessages[0].ruleId, "no-alert"); }); }); @@ -3935,6 +4373,7 @@ describe("FlatESLint", () => { nodeType: null } ], + suppressedMessages: [], errorCount: 1, warningCount: 0, fatalErrorCount: 0, @@ -4090,19 +4529,13 @@ describe("FlatESLint", () => { }); - /* - * These tests fail due to a bug in fast-flob that doesn't allow - * negated patterns inside of ignores. These tests won't work until - * this bug is fixed: - * https://github.com/mrmlnc/fast-glob/issues/356 - */ - xdescribe("ignorePatterns can unignore '/node_modules/foo'.", () => { + describe("ignores can unignore '/node_modules/foo'.", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}-unignores`, files: { "eslint.config.js": `module.exports = { - ignores: ["!**/node_modules/foo/**"] + ignores: ["!**/node_modules/foo"] };`, "node_modules/foo/index.js": "", "node_modules/foo/.dot.js": "", @@ -4139,21 +4572,74 @@ describe("FlatESLint", () => { .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "eslint.config.js"), - path.join(root, "foo.js"), - path.join(root, "node_modules/foo/index.js") + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo.js"), + path.join(getPath(), "node_modules/foo/.dot.js"), + path.join(getPath(), "node_modules/foo/index.js") ]); }); }); - xdescribe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => { + describe("ignores can unignore '/node_modules/foo/**'.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}-unignores`, + files: { + "eslint.config.js": `module.exports = { + ignores: ["!**/node_modules/foo/**"] + };`, + "node_modules/foo/index.js": "", + "node_modules/foo/.dot.js": "", + "node_modules/bar/index.js": "", + "foo.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { + const engine = new FlatESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); + }); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/.dot.js'.", async () => { + const engine = new FlatESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { + const engine = new FlatESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); + }); + + it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { + const engine = new FlatESLint({ cwd: getPath() }); + const result = (await engine.lintFiles("**/*.js")); + + const filePaths = result + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo.js"), + path.join(getPath(), "node_modules/foo/.dot.js"), + path.join(getPath(), "node_modules/foo/index.js") + ]); + }); + }); + + describe("ignore pattern can re-ignore files that are unignored by a previous pattern.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}-reignore`, files: { "eslint.config.js": `module.exports = ${JSON.stringify({ - ignores: ["!.*"] + ignores: ["!.*", ".foo*"] })}`, - ".eslintignore": ".foo*", ".foo.js": "", ".bar.js": "" } @@ -4181,20 +4667,19 @@ describe("FlatESLint", () => { .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, ".bar.js"), - path.join(root, "eslint.config.js") + path.join(getPath(), ".bar.js"), + path.join(getPath(), "eslint.config.js") ]); }); }); - xdescribe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => { + describe("ignore pattern can unignore files that are ignored by a previous pattern.", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}-dignore`, files: { "eslint.config.js": `module.exports = ${JSON.stringify({ - ignores: ["**/*.js"] + ignores: ["**/*.js", "!foo.js"] })}`, - ".eslintignore": "!foo.js", "foo.js": "", "bar.js": "" } @@ -4222,7 +4707,7 @@ describe("FlatESLint", () => { .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js") + path.join(getPath(), "foo.js") ]); }); }); @@ -4483,6 +4968,7 @@ describe("FlatESLint", () => { // Expected to be an 'eqeqeq' error because the file matches to `$CWD/foo/*.js`. assert.deepStrictEqual(results, [ { + suppressedMessages: [], errorCount: 1, filePath: path.join(getPath(), "foo/test.js"), fixableErrorCount: 0, @@ -4519,6 +5005,7 @@ describe("FlatESLint", () => { // Expected to be no errors because the file doesn't match to `$CWD/foo/*.js`. assert.deepStrictEqual(results, [ { + suppressedMessages: [], errorCount: 0, filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"), fixableErrorCount: 0, @@ -4570,6 +5057,7 @@ describe("FlatESLint", () => { // Expected to be no errors because the file matches to `$CWD/foo/*.js`. assert.deepStrictEqual(results, [ { + suppressedMessages: [], errorCount: 0, filePath: path.join(getPath(), "foo/test.js"), fixableErrorCount: 0, @@ -4593,6 +5081,7 @@ describe("FlatESLint", () => { // Expected to be an 'eqeqeq' error because the file doesn't match to `$CWD/foo/*.js`. assert.deepStrictEqual(results, [ { + suppressedMessages: [], errorCount: 1, filePath: path.join(getPath(), "bar/myconf/foo/test.js"), fixableErrorCount: 0, @@ -4619,17 +5108,17 @@ describe("FlatESLint", () => { }); }); - // dependent on https://github.com/mrmlnc/fast-glob/issues/86 - xdescribe("if { ignores: 'foo/*.js', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { + describe("if { ignores: 'foo/*.js', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: `${root}a3`, files: { - "node_modules/myconf/eslint.config.js": `module.exports = { - ignores: ["**/eslint.config.js", "!node_modules/myconf", "foo/*.js"], + "node_modules/myconf/eslint.config.js": `module.exports = [{ + ignores: ["!node_modules/myconf", "foo/*.js"], + }, { rules: { eqeqeq: "error" } - }`, + }]`, "node_modules/myconf/foo/test.js": "a == b", "foo/test.js": "a == b" } @@ -4638,7 +5127,7 @@ describe("FlatESLint", () => { beforeEach(prepare); afterEach(cleanup); - it("'lintFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { + it("'lintFiles()' with '**/*.js' should lint 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { const engine = new FlatESLint({ overrideConfigFile: "node_modules/myconf/eslint.config.js", cwd: getPath() @@ -4648,10 +5137,269 @@ describe("FlatESLint", () => { .sort(); assert.deepStrictEqual(files, [ + path.join(getPath(), "node_modules/myconf/eslint.config.js"), path.join(getPath(), "node_modules/myconf/foo/test.js") ]); }); }); }); + describe("baseConfig", () => { + it("can be an object", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: { + rules: { + semi: 2 + } + } + }); + + const [{ messages }] = await eslint.lintText("foo"); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + }); + + it("can be an array", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: [ + { + rules: { + "no-var": 2 + } + }, + { + rules: { + semi: 2 + } + } + ] + }); + + const [{ messages }] = await eslint.lintText("var foo"); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-var"); + assert.strictEqual(messages[1].ruleId, "semi"); + }); + + it("should be inserted after default configs", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: { + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + } + } + }); + + const [{ messages }] = await eslint.lintText("let x"); + + /* + * if baseConfig was inserted before default configs, + * `ecmaVersion: "latest"` from default configs would overwrite + * `ecmaVersion: 5` from baseConfig, so this wouldn't be a parsing error. + */ + + assert.strictEqual(messages.length, 1); + assert(messages[0].fatal, "Fatal error expected."); + }); + + it("should be inserted before configs from the config file", async () => { + const eslint = new FlatESLint({ + cwd: getFixturePath(), + baseConfig: { + rules: { + strict: ["error", "global"] + }, + languageOptions: { + sourceType: "script" + } + } + }); + + const [{ messages }] = await eslint.lintText("foo"); + + /* + * if baseConfig was inserted after configs from the config file, + * `strict: 0` from eslint.config.js wouldn't overwrite `strict: ["error", "global"]` + * from baseConfig, so there would be an error message from the `strict` rule. + */ + + assert.strictEqual(messages.length, 0); + }); + + it("should be inserted before overrideConfig", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: { + rules: { + semi: 2 + } + }, + overrideConfig: { + rules: { + semi: 1 + } + } + }); + + const [{ messages }] = await eslint.lintText("foo"); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].severity, 1); + }); + + it("should be inserted before configs from the config file and overrideConfig", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: getFixturePath("eslint.config_with_rules.js"), + baseConfig: { + rules: { + quotes: ["error", "double"], + semi: "error" + } + }, + overrideConfig: { + rules: { + quotes: "warn" + } + } + }); + + const [{ messages }] = await eslint.lintText('const foo = "bar"'); + + /* + * baseConfig: { quotes: ["error", "double"], semi: "error" } + * eslint.config_with_rules.js: { quotes: ["error", "single"] } + * overrideConfig: { quotes: "warn" } + * + * Merged config: { quotes: ["warn", "single"], semi: "error" } + */ + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "quotes"); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[1].ruleId, "semi"); + assert.strictEqual(messages[1].severity, 2); + }); + + it("when it has 'files' they should be interpreted as relative to the config file", async () => { + + /* + * `fixtures/plugins` directory does not have a config file. + * It's parent directory `fixtures` does have a config file, so + * the base path will be `fixtures`, cwd will be `fixtures/plugins` + */ + const eslint = new FlatESLint({ + cwd: getFixturePath("plugins"), + baseConfig: { + files: ["plugins/a.js"], + rules: { + semi: 2 + } + } + }); + + const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js") }); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + }); + + it("when it has 'ignores' they should be interpreted as relative to the config file", async () => { + + /* + * `fixtures/plugins` directory does not have a config file. + * It's parent directory `fixtures` does have a config file, so + * the base path will be `fixtures`, cwd will be `fixtures/plugins` + */ + const eslint = new FlatESLint({ + cwd: getFixturePath("plugins"), + baseConfig: { + ignores: ["plugins/a.js"] + } + }); + + const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js"), warnIgnored: true }); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.match(messages[0].message, /ignored/u); + }); + }); + + describe("config file", () => { + + it("new instance of FlatESLint should use the latest version of the config file (ESM)", async () => { + const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`); + const configFileContent = "export default [{ rules: { semi: ['error', 'always'] } }];"; + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": '{ "type": "module" }', + "eslint.config.js": configFileContent, + "a.js": "foo\nbar;" + } + }); + + await teardown.prepare(); + + let eslint = new FlatESLint({ cwd }); + let [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "missingSemi"); + assert.strictEqual(messages[0].line, 1); + + await sleep(100); + await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never")); + + eslint = new FlatESLint({ cwd }); + [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "extraSemi"); + assert.strictEqual(messages[0].line, 2); + }); + + it("new instance of FlatESLint should use the latest version of the config file (CJS)", async () => { + const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`); + const configFileContent = "module.exports = [{ rules: { semi: ['error', 'always'] } }];"; + const teardown = createCustomTeardown({ + cwd, + files: { + "eslint.config.js": configFileContent, + "a.js": "foo\nbar;" + } + }); + + await teardown.prepare(); + + let eslint = new FlatESLint({ cwd }); + let [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "missingSemi"); + assert.strictEqual(messages[0].line, 1); + + await sleep(100); + await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never")); + + eslint = new FlatESLint({ cwd }); + [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "extraSemi"); + assert.strictEqual(messages[0].line, 2); + }); + }); + }); diff --git a/tests/lib/linter/code-path-analysis/code-path-analyzer.js b/tests/lib/linter/code-path-analysis/code-path-analyzer.js index 0b5dd33aab8..cc2717a7ff8 100644 --- a/tests/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/tests/lib/linter/code-path-analysis/code-path-analyzer.js @@ -66,11 +66,13 @@ describe("CodePathAnalyzer", () => { beforeEach(() => { actual = []; - linter.defineRule("test", () => ({ - onCodePathStart(codePath) { - actual.push(codePath); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathStart(codePath) { + actual.push(codePath); + } + }) + }); linter.verify( "function foo(a) { if (a) return 0; else throw new Error(); }", { rules: { test: 2 } } @@ -142,22 +144,24 @@ describe("CodePathAnalyzer", () => { assert(actual[1].currentSegments.length === 0); // there is the current segment in progress. - linter.defineRule("test", () => { - let codePath = null; - - return { - onCodePathStart(cp) { - codePath = cp; - }, - ReturnStatement() { - assert(codePath.currentSegments.length === 1); - assert(codePath.currentSegments[0] instanceof CodePathSegment); - }, - ThrowStatement() { - assert(codePath.currentSegments.length === 1); - assert(codePath.currentSegments[0] instanceof CodePathSegment); - } - }; + linter.defineRule("test", { + create() { + let codePath = null; + + return { + onCodePathStart(cp) { + codePath = cp; + }, + ReturnStatement() { + assert(codePath.currentSegments.length === 1); + assert(codePath.currentSegments[0] instanceof CodePathSegment); + }, + ThrowStatement() { + assert(codePath.currentSegments.length === 1); + assert(codePath.currentSegments[0] instanceof CodePathSegment); + } + }; + } }); linter.verify( "function foo(a) { if (a) return 0; else throw new Error(); }", @@ -171,11 +175,13 @@ describe("CodePathAnalyzer", () => { beforeEach(() => { actual = []; - linter.defineRule("test", () => ({ - onCodePathSegmentStart(segment) { - actual.push(segment); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentStart(segment) { + actual.push(segment); + } + }) + }); linter.verify( "function foo(a) { if (a) return 0; else throw new Error(); }", { rules: { test: 2 } } @@ -258,35 +264,37 @@ describe("CodePathAnalyzer", () => { let count = 0; let lastCodePathNodeType = null; - linter.defineRule("test", () => ({ - onCodePathStart(cp, node) { - count += 1; - lastCodePathNodeType = node.type; - - assert(cp instanceof CodePath); - if (count === 1) { - assert(node.type === "Program"); - } else if (count === 2) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 3) { - assert(node.type === "FunctionExpression"); - } else if (count === 4) { - assert(node.type === "ArrowFunctionExpression"); + linter.defineRule("test", { + create: () => ({ + onCodePathStart(cp, node) { + count += 1; + lastCodePathNodeType = node.type; + + assert(cp instanceof CodePath); + if (count === 1) { + assert(node.type === "Program"); + } else if (count === 2) { + assert(node.type === "FunctionDeclaration"); + } else if (count === 3) { + assert(node.type === "FunctionExpression"); + } else if (count === 4) { + assert(node.type === "ArrowFunctionExpression"); + } + }, + Program() { + assert(lastCodePathNodeType === "Program"); + }, + FunctionDeclaration() { + assert(lastCodePathNodeType === "FunctionDeclaration"); + }, + FunctionExpression() { + assert(lastCodePathNodeType === "FunctionExpression"); + }, + ArrowFunctionExpression() { + assert(lastCodePathNodeType === "ArrowFunctionExpression"); } - }, - Program() { - assert(lastCodePathNodeType === "Program"); - }, - FunctionDeclaration() { - assert(lastCodePathNodeType === "FunctionDeclaration"); - }, - FunctionExpression() { - assert(lastCodePathNodeType === "FunctionExpression"); - }, - ArrowFunctionExpression() { - assert(lastCodePathNodeType === "ArrowFunctionExpression"); - } - })); + }) + }); linter.verify( "foo(); function foo() {} var foo = function() {}; var foo = () => {};", { rules: { test: 2 }, env: { es6: true } } @@ -301,35 +309,37 @@ describe("CodePathAnalyzer", () => { let count = 0; let lastNodeType = null; - linter.defineRule("test", () => ({ - onCodePathEnd(cp, node) { - count += 1; - - assert(cp instanceof CodePath); - if (count === 4) { - assert(node.type === "Program"); - } else if (count === 1) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 2) { - assert(node.type === "FunctionExpression"); - } else if (count === 3) { - assert(node.type === "ArrowFunctionExpression"); + linter.defineRule("test", { + create: () => ({ + onCodePathEnd(cp, node) { + count += 1; + + assert(cp instanceof CodePath); + if (count === 4) { + assert(node.type === "Program"); + } else if (count === 1) { + assert(node.type === "FunctionDeclaration"); + } else if (count === 2) { + assert(node.type === "FunctionExpression"); + } else if (count === 3) { + assert(node.type === "ArrowFunctionExpression"); + } + assert(node.type === lastNodeType); + }, + "Program:exit"() { + lastNodeType = "Program"; + }, + "FunctionDeclaration:exit"() { + lastNodeType = "FunctionDeclaration"; + }, + "FunctionExpression:exit"() { + lastNodeType = "FunctionExpression"; + }, + "ArrowFunctionExpression:exit"() { + lastNodeType = "ArrowFunctionExpression"; } - assert(node.type === lastNodeType); - }, - "Program:exit"() { - lastNodeType = "Program"; - }, - "FunctionDeclaration:exit"() { - lastNodeType = "FunctionDeclaration"; - }, - "FunctionExpression:exit"() { - lastNodeType = "FunctionExpression"; - }, - "ArrowFunctionExpression:exit"() { - lastNodeType = "ArrowFunctionExpression"; - } - })); + }) + }); linter.verify( "foo(); function foo() {} var foo = function() {}; var foo = () => {};", { rules: { test: 2 }, env: { es6: true } } @@ -344,35 +354,37 @@ describe("CodePathAnalyzer", () => { let count = 0; let lastCodePathNodeType = null; - linter.defineRule("test", () => ({ - onCodePathSegmentStart(segment, node) { - count += 1; - lastCodePathNodeType = node.type; - - assert(segment instanceof CodePathSegment); - if (count === 1) { - assert(node.type === "Program"); - } else if (count === 2) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 3) { - assert(node.type === "FunctionExpression"); - } else if (count === 4) { - assert(node.type === "ArrowFunctionExpression"); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentStart(segment, node) { + count += 1; + lastCodePathNodeType = node.type; + + assert(segment instanceof CodePathSegment); + if (count === 1) { + assert(node.type === "Program"); + } else if (count === 2) { + assert(node.type === "FunctionDeclaration"); + } else if (count === 3) { + assert(node.type === "FunctionExpression"); + } else if (count === 4) { + assert(node.type === "ArrowFunctionExpression"); + } + }, + Program() { + assert(lastCodePathNodeType === "Program"); + }, + FunctionDeclaration() { + assert(lastCodePathNodeType === "FunctionDeclaration"); + }, + FunctionExpression() { + assert(lastCodePathNodeType === "FunctionExpression"); + }, + ArrowFunctionExpression() { + assert(lastCodePathNodeType === "ArrowFunctionExpression"); } - }, - Program() { - assert(lastCodePathNodeType === "Program"); - }, - FunctionDeclaration() { - assert(lastCodePathNodeType === "FunctionDeclaration"); - }, - FunctionExpression() { - assert(lastCodePathNodeType === "FunctionExpression"); - }, - ArrowFunctionExpression() { - assert(lastCodePathNodeType === "ArrowFunctionExpression"); - } - })); + }) + }); linter.verify( "foo(); function foo() {} var foo = function() {}; var foo = () => {};", { rules: { test: 2 }, env: { es6: true } } @@ -387,35 +399,37 @@ describe("CodePathAnalyzer", () => { let count = 0; let lastNodeType = null; - linter.defineRule("test", () => ({ - onCodePathSegmentEnd(cp, node) { - count += 1; - - assert(cp instanceof CodePathSegment); - if (count === 4) { - assert(node.type === "Program"); - } else if (count === 1) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 2) { - assert(node.type === "FunctionExpression"); - } else if (count === 3) { - assert(node.type === "ArrowFunctionExpression"); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentEnd(cp, node) { + count += 1; + + assert(cp instanceof CodePathSegment); + if (count === 4) { + assert(node.type === "Program"); + } else if (count === 1) { + assert(node.type === "FunctionDeclaration"); + } else if (count === 2) { + assert(node.type === "FunctionExpression"); + } else if (count === 3) { + assert(node.type === "ArrowFunctionExpression"); + } + assert(node.type === lastNodeType); + }, + "Program:exit"() { + lastNodeType = "Program"; + }, + "FunctionDeclaration:exit"() { + lastNodeType = "FunctionDeclaration"; + }, + "FunctionExpression:exit"() { + lastNodeType = "FunctionExpression"; + }, + "ArrowFunctionExpression:exit"() { + lastNodeType = "ArrowFunctionExpression"; } - assert(node.type === lastNodeType); - }, - "Program:exit"() { - lastNodeType = "Program"; - }, - "FunctionDeclaration:exit"() { - lastNodeType = "FunctionDeclaration"; - }, - "FunctionExpression:exit"() { - lastNodeType = "FunctionExpression"; - }, - "ArrowFunctionExpression:exit"() { - lastNodeType = "ArrowFunctionExpression"; - } - })); + }) + }); linter.verify( "foo(); function foo() {} var foo = function() {}; var foo = () => {};", { rules: { test: 2 }, env: { es6: true } } @@ -429,14 +443,16 @@ describe("CodePathAnalyzer", () => { it("should be fired in `while` loops", () => { let count = 0; - linter.defineRule("test", () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); - assert(node.type === "WhileStatement"); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + count += 1; + assert(fromSegment instanceof CodePathSegment); + assert(toSegment instanceof CodePathSegment); + assert(node.type === "WhileStatement"); + } + }) + }); linter.verify( "while (a) { foo(); }", { rules: { test: 2 } } @@ -448,14 +464,16 @@ describe("CodePathAnalyzer", () => { it("should be fired in `do-while` loops", () => { let count = 0; - linter.defineRule("test", () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); - assert(node.type === "DoWhileStatement"); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + count += 1; + assert(fromSegment instanceof CodePathSegment); + assert(toSegment instanceof CodePathSegment); + assert(node.type === "DoWhileStatement"); + } + }) + }); linter.verify( "do { foo(); } while (a);", { rules: { test: 2 } } @@ -467,21 +485,23 @@ describe("CodePathAnalyzer", () => { it("should be fired in `for` loops", () => { let count = 0; - linter.defineRule("test", () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + count += 1; + assert(fromSegment instanceof CodePathSegment); + assert(toSegment instanceof CodePathSegment); - if (count === 1) { + if (count === 1) { - // connect path: "update" -> "test" - assert(node.parent.type === "ForStatement"); - } else if (count === 2) { - assert(node.type === "ForStatement"); + // connect path: "update" -> "test" + assert(node.parent.type === "ForStatement"); + } else if (count === 2) { + assert(node.type === "ForStatement"); + } } - } - })); + }) + }); linter.verify( "for (var i = 0; i < 10; ++i) { foo(); }", { rules: { test: 2 } } @@ -493,21 +513,23 @@ describe("CodePathAnalyzer", () => { it("should be fired in `for-in` loops", () => { let count = 0; - linter.defineRule("test", () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + count += 1; + assert(fromSegment instanceof CodePathSegment); + assert(toSegment instanceof CodePathSegment); - if (count === 1) { + if (count === 1) { - // connect path: "right" -> "left" - assert(node.parent.type === "ForInStatement"); - } else if (count === 2) { - assert(node.type === "ForInStatement"); + // connect path: "right" -> "left" + assert(node.parent.type === "ForInStatement"); + } else if (count === 2) { + assert(node.type === "ForInStatement"); + } } - } - })); + }) + }); linter.verify( "for (var k in obj) { foo(); }", { rules: { test: 2 } } @@ -519,21 +541,23 @@ describe("CodePathAnalyzer", () => { it("should be fired in `for-of` loops", () => { let count = 0; - linter.defineRule("test", () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + count += 1; + assert(fromSegment instanceof CodePathSegment); + assert(toSegment instanceof CodePathSegment); - if (count === 1) { + if (count === 1) { - // connect path: "right" -> "left" - assert(node.parent.type === "ForOfStatement"); - } else if (count === 2) { - assert(node.type === "ForOfStatement"); + // connect path: "right" -> "left" + assert(node.parent.type === "ForOfStatement"); + } else if (count === 2) { + assert(node.type === "ForOfStatement"); + } } - } - })); + }) + }); linter.verify( "for (var x of xs) { foo(); }", { rules: { test: 2 }, env: { es6: true } } @@ -555,11 +579,13 @@ describe("CodePathAnalyzer", () => { assert(expected.length > 0, "/*expected */ comments not found."); - linter.defineRule("test", () => ({ - onCodePathEnd(codePath) { - actual.push(debug.makeDotArrows(codePath)); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathEnd(codePath) { + actual.push(debug.makeDotArrows(codePath)); + } + }) + }); const messages = linter.verify(source, { parserOptions: { ecmaVersion: 2022 }, rules: { test: 2 } diff --git a/tests/lib/linter/code-path-analysis/code-path.js b/tests/lib/linter/code-path-analysis/code-path.js index 40fb017ed7b..7f1d738f6e9 100644 --- a/tests/lib/linter/code-path-analysis/code-path.js +++ b/tests/lib/linter/code-path-analysis/code-path.js @@ -25,11 +25,13 @@ const linter = new Linter(); function parseCodePaths(code) { const retv = []; - linter.defineRule("test", () => ({ - onCodePathStart(codePath) { - retv.push(codePath); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathStart(codePath) { + retv.push(codePath); + } + }) + }); linter.verify(code, { rules: { test: 2 }, diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 407194d47e7..0ece277dc9a 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -80,11 +80,13 @@ describe("Linter", () => { it("an error should be thrown when an error occurs inside of an event handler", () => { const config = { rules: { checker: "error" } }; - linter.defineRule("checker", () => ({ - Program() { - throw new Error("Intentional error."); - } - })); + linter.defineRule("checker", { + create: () => ({ + Program() { + throw new Error("Intentional error."); + } + }) + }); assert.throws(() => { linter.verify(code, config, filename); @@ -94,7 +96,9 @@ describe("Linter", () => { it("does not call rule listeners with a `this` value", () => { const spy = sinon.spy(); - linter.defineRule("checker", () => ({ Program: spy })); + linter.defineRule("checker", { + create: () => ({ Program: spy }) + }); linter.verify("foo", { rules: { checker: "error" } }); assert(spy.calledOnce, "Rule should have been called"); assert.strictEqual(spy.firstCall.thisValue, void 0, "this value should be undefined"); @@ -103,7 +107,9 @@ describe("Linter", () => { it("does not allow listeners to use special EventEmitter values", () => { const spy = sinon.spy(); - linter.defineRule("checker", () => ({ newListener: spy })); + linter.defineRule("checker", { + create: () => ({ newListener: spy }) + }); linter.verify("foo", { rules: { checker: "error", "no-undef": "error" } }); assert(spy.notCalled); }); @@ -120,7 +126,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify("foo + bar", { rules: { checker: "error" } }); assert(spy.calledOnce); @@ -136,7 +142,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, { rules: { checker: "error" } }); assert(spy.calledOnce); }); @@ -148,7 +154,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, { rules: { checker: "error" } }); assert(spy.calledOnce); }); @@ -160,7 +166,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, { rules: { checker: "error" } }); assert(spy.calledOnce); }); @@ -172,7 +178,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, { rules: { checker: "error" } }); assert(spy.calledOnce); }); @@ -184,7 +190,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, { rules: { checker: "error" } }); assert(spy.calledOnce); }); @@ -350,11 +356,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - assert.strictEqual(context.getSource(), TEST_CODE); - }); - return { Program: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getSource(), TEST_CODE); + }); + return { Program: spy }; + } }); linter.verify(code, config); @@ -365,11 +373,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), TEST_CODE); - }); - return { Program: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), TEST_CODE); + }); + return { Program: spy }; + } }); linter.verify(code, config); @@ -380,11 +390,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); - }); - return { Program: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); + }); + return { Program: spy }; + } }); linter.verify(code, config); @@ -395,11 +407,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "6 * 7"); - }); - return { BinaryExpression: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), "6 * 7"); + }); + return { BinaryExpression: spy }; + } }); linter.verify(code, config); @@ -410,11 +424,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); - }); - return { BinaryExpression: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); + }); + return { BinaryExpression: spy }; + } }); linter.verify(code, config); @@ -425,11 +441,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); - }); - return { BinaryExpression: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); + }); + return { BinaryExpression: spy }; + } }); linter.verify(code, config); @@ -440,11 +458,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); - }); - return { BinaryExpression: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); + }); + return { BinaryExpression: spy }; + } }); linter.verify(code, config); @@ -461,13 +481,15 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const ancestors = context.getAncestors(); - assert.strictEqual(ancestors.length, 3); - }); - return { BinaryExpression: spy }; + assert.strictEqual(ancestors.length, 3); + }); + return { BinaryExpression: spy }; + } }); linter.verify(code, config, filename, true); @@ -478,14 +500,16 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const ancestors = context.getAncestors(); - assert.strictEqual(ancestors.length, 0); - }); + assert.strictEqual(ancestors.length, 0); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -503,7 +527,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -515,7 +539,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -530,7 +554,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -542,7 +566,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -560,7 +584,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -578,7 +602,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -592,13 +616,15 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "global"); - }); - return { Program: spy }; + assert.strictEqual(scope.type, "global"); + }); + return { Program: spy }; + } }); linter.verify(code, config); @@ -609,13 +635,15 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - }); - return { FunctionDeclaration: spy }; + assert.strictEqual(scope.type, "function"); + }); + return { FunctionDeclaration: spy }; + } }); linter.verify(code, config); @@ -626,14 +654,16 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.id.name, "foo"); - }); - return { LabeledStatement: spy }; + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.id.name, "foo"); + }); + return { LabeledStatement: spy }; + } }); linter.verify(code, config); @@ -644,15 +674,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); + }); - return { ReturnStatement: spy }; + return { ReturnStatement: spy }; + } }); linter.verify(code, config); @@ -663,15 +695,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "switch"); - assert.strictEqual(scope.block.type, "SwitchStatement"); - }); + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block.type, "SwitchStatement"); + }); - return { SwitchStatement: spy }; + return { SwitchStatement: spy }; + } }); linter.verify("switch(foo){ case 'a': var b = 'foo'; }", config); @@ -682,15 +716,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block.type, "BlockStatement"); + }); - return { BlockStatement: spy }; + return { BlockStatement: spy }; + } }); linter.verify("var x; {let y = 1}", config); @@ -701,15 +737,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block.type, "BlockStatement"); + }); - return { BlockStatement: spy }; + return { BlockStatement: spy }; + } }); linter.verify("if (true) { let x = 1 }", config); @@ -720,15 +758,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionDeclaration"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "FunctionDeclaration"); + }); - return { FunctionDeclaration: spy }; + return { FunctionDeclaration: spy }; + } }); linter.verify("function foo() {}", config); @@ -739,15 +779,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionExpression"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "FunctionExpression"); + }); - return { FunctionExpression: spy }; + return { FunctionExpression: spy }; + } }); linter.verify("(function foo() {})();", config); @@ -758,15 +800,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block.type, "CatchClause"); - }); + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block.type, "CatchClause"); + }); - return { CatchClause: spy }; + return { CatchClause: spy }; + } }); linter.verify("try {} catch (err) {}", config); @@ -777,14 +821,16 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "module"); - }); + assert.strictEqual(scope.type, "module"); + }); - return { AssignmentExpression: spy }; + return { AssignmentExpression: spy }; + } }); linter.verify("var foo = {}; foo.bar = 1;", config); @@ -795,14 +841,16 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, ecmaFeatures: { globalReturn: true } } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - }); + assert.strictEqual(scope.type, "function"); + }); - return { AssignmentExpression: spy }; + return { AssignmentExpression: spy }; + } }); linter.verify("var foo = {}; foo.bar = 1;", config); @@ -815,17 +863,19 @@ describe("Linter", () => { const code = "var a = 1, b = 2;"; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); - const scope = context.getScope(); + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } }); linter.verify(code, { rules: { checker: "error" } }); @@ -835,17 +885,19 @@ describe("Linter", () => { const code = "function abc(a, b) { return 1; }"; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); - const scope = context.getScope(); + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { ReturnStatement: spy }; + return { ReturnStatement: spy }; + } }); linter.verify(code, { rules: { checker: "error" } }); @@ -855,18 +907,20 @@ describe("Linter", () => { const code = "var a, b; function abc() { return 1; }"; let returnSpy, exitSpy; - linter.defineRule("checker", context => { - returnSpy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); - }); - exitSpy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + returnSpy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); + }); + exitSpy = sinon.spy(() => { + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; + return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; + } }); linter.verify(code, { rules: { checker: "error" } }); @@ -878,18 +932,20 @@ describe("Linter", () => { const code = "var a = 1, b = 2;"; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const globalScope = context.getScope(), + childScope = globalScope.childScopes[0]; - assert.isTrue(context.markVariableAsUsed("a")); + assert.isTrue(context.markVariableAsUsed("a")); - assert.isTrue(getVariable(childScope, "a").eslintUsed); - assert.isUndefined(getVariable(childScope, "b").eslintUsed); - }); + assert.isTrue(getVariable(childScope, "a").eslintUsed); + assert.isUndefined(getVariable(childScope, "b").eslintUsed); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } }); linter.verify(code, { rules: { checker: "error" }, env: { node: true } }); @@ -900,18 +956,20 @@ describe("Linter", () => { const code = "var a = 1, b = 2;"; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const globalScope = context.getScope(), + childScope = globalScope.childScopes[0]; - assert.isTrue(context.markVariableAsUsed("a")); + assert.isTrue(context.markVariableAsUsed("a")); - assert.isTrue(getVariable(childScope, "a").eslintUsed); - assert.isUndefined(getVariable(childScope, "b").eslintUsed); - }); + assert.isTrue(getVariable(childScope, "a").eslintUsed); + assert.isUndefined(getVariable(childScope, "b").eslintUsed); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }, filename, true); @@ -922,12 +980,14 @@ describe("Linter", () => { const code = "var a = 1, b = 2;"; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - assert.isFalse(context.markVariableAsUsed("c")); - }); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.isFalse(context.markVariableAsUsed("c")); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } }); linter.verify(code, { rules: { checker: "error" } }); @@ -948,13 +1008,15 @@ describe("Linter", () => { spyIdentifier = sinon.spy(), spyBinaryExpression = sinon.spy(); - linter.defineRule("checker", () => ({ - Literal: spyLiteral, - VariableDeclarator: spyVariableDeclarator, - VariableDeclaration: spyVariableDeclaration, - Identifier: spyIdentifier, - BinaryExpression: spyBinaryExpression - })); + linter.defineRule("checker", { + create: () => ({ + Literal: spyLiteral, + VariableDeclarator: spyVariableDeclarator, + VariableDeclaration: spyVariableDeclaration, + Identifier: spyIdentifier, + BinaryExpression: spyBinaryExpression + }) + }); const messages = linter.verify(code, config, filename, true); const suppressedMessages = linter.getSuppressedMessages(); @@ -969,11 +1031,13 @@ describe("Linter", () => { }); it("should throw an error if a rule reports a problem without a message", () => { - linter.defineRule("invalid-report", context => ({ - Program(node) { - context.report({ node }); - } - })); + linter.defineRule("invalid-report", { + create: context => ({ + Program(node) { + context.report({ node }); + } + }) + }); assert.throws( () => linter.verify("foo", { rules: { "invalid-report": "error" } }), @@ -987,11 +1051,13 @@ describe("Linter", () => { const code = "test-rule"; it("should pass settings to all rules", () => { - linter.defineRule(code, context => ({ - Literal(node) { - context.report(node, context.settings.info); - } - })); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + context.report(node, context.settings.info); + } + }) + }); const config = { rules: {}, settings: { info: "Hello" } }; @@ -1006,13 +1072,15 @@ describe("Linter", () => { }); it("should not have any settings if they were not passed in", () => { - linter.defineRule(code, context => ({ - Literal(node) { - if (Object.getOwnPropertyNames(context.settings).length !== 0) { - context.report(node, "Settings should be empty"); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + if (Object.getOwnPropertyNames(context.settings).length !== 0) { + context.report(node, "Settings should be empty"); + } } - } - })); + }) + }); const config = { rules: {} }; @@ -1037,9 +1105,11 @@ describe("Linter", () => { } }; - linter.defineRule("test-rule", sinon.mock().withArgs( - sinon.match({ parserOptions }) - ).returns({})); + linter.defineRule("test-rule", { + create: sinon.mock().withArgs( + sinon.match({ parserOptions }) + ).returns({}) + }); const config = { rules: { "test-rule": 2 }, parserOptions }; @@ -1050,9 +1120,11 @@ describe("Linter", () => { const parserOptions = {}; - linter.defineRule("test-rule", sinon.mock().withArgs( - sinon.match({ parserOptions }) - ).returns({})); + linter.defineRule("test-rule", { + create: sinon.mock().withArgs( + sinon.match({ parserOptions }) + ).returns({}) + }); const config = { rules: { "test-rule": 2 } }; @@ -1097,9 +1169,11 @@ describe("Linter", () => { const alternateParser = "esprima"; linter.defineParser("esprima", esprima); - linter.defineRule("test-rule", sinon.mock().withArgs( - sinon.match({ parserPath: alternateParser }) - ).returns({})); + linter.defineRule("test-rule", { + create: sinon.mock().withArgs( + sinon.match({ parserPath: alternateParser }) + ).returns({}) + }); const config = { rules: { "test-rule": 2 }, parser: alternateParser }; @@ -1119,14 +1193,16 @@ describe("Linter", () => { it("should expose parser services when using parseForESLint() and services are specified", () => { linter.defineParser("enhanced-parser", testParsers.enhancedParser); - linter.defineRule("test-service-rule", context => ({ - Literal(node) { - context.report({ - node, - message: context.parserServices.test.getMessage() - }); - } - })); + linter.defineRule("test-service-rule", { + create: context => ({ + Literal(node) { + context.report({ + node, + message: context.parserServices.test.getMessage() + }); + } + }) + }); const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" }; const messages = linter.verify("0", config, filename); @@ -1139,14 +1215,16 @@ describe("Linter", () => { it("should use the same parserServices if source code object is reused", () => { linter.defineParser("enhanced-parser", testParsers.enhancedParser); - linter.defineRule("test-service-rule", context => ({ - Literal(node) { - context.report({ - node, - message: context.parserServices.test.getMessage() - }); - } - })); + linter.defineRule("test-service-rule", { + create: context => ({ + Literal(node) { + context.report({ + node, + message: context.parserServices.test.getMessage() + }); + } + }) + }); const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" }; const messages = linter.verify("0", config, filename); @@ -1165,9 +1243,11 @@ describe("Linter", () => { }); it("should pass parser as parserPath to all rules when default parser is used", () => { - linter.defineRule("test-rule", sinon.mock().withArgs( - sinon.match({ parserPath: "espree" }) - ).returns({})); + linter.defineRule("test-rule", { + create: sinon.mock().withArgs( + sinon.match({ parserPath: "espree" }) + ).returns({}) + }); const config = { rules: { "test-rule": 2 } }; @@ -1277,38 +1357,40 @@ describe("Linter", () => { `; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); - const a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"), - d = getVariable(scope, "d"), - e = getVariable(scope, "e"), - f = getVariable(scope, "f"), - mathGlobal = getVariable(scope, "Math"), - arrayGlobal = getVariable(scope, "Array"), - configGlobal = getVariable(scope, "ConfigGlobal"); - - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - assert.strictEqual(d.name, "d"); - assert.strictEqual(d.writeable, false); - assert.strictEqual(e.name, "e"); - assert.strictEqual(e.writeable, true); - assert.strictEqual(f.name, "f"); - assert.strictEqual(f.writeable, true); - assert.strictEqual(mathGlobal, null); - assert.strictEqual(arrayGlobal, null); - assert.strictEqual(configGlobal.name, "ConfigGlobal"); - assert.strictEqual(configGlobal.writeable, false); - }); - - return { Program: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); + const a = getVariable(scope, "a"), + b = getVariable(scope, "b"), + c = getVariable(scope, "c"), + d = getVariable(scope, "d"), + e = getVariable(scope, "e"), + f = getVariable(scope, "f"), + mathGlobal = getVariable(scope, "Math"), + arrayGlobal = getVariable(scope, "Array"), + configGlobal = getVariable(scope, "ConfigGlobal"); + + assert.strictEqual(a.name, "a"); + assert.strictEqual(a.writeable, false); + assert.strictEqual(b.name, "b"); + assert.strictEqual(b.writeable, true); + assert.strictEqual(c.name, "c"); + assert.strictEqual(c.writeable, false); + assert.strictEqual(d.name, "d"); + assert.strictEqual(d.writeable, false); + assert.strictEqual(e.name, "e"); + assert.strictEqual(e.writeable, true); + assert.strictEqual(f.name, "f"); + assert.strictEqual(f.writeable, true); + assert.strictEqual(mathGlobal, null); + assert.strictEqual(arrayGlobal, null); + assert.strictEqual(configGlobal.name, "ConfigGlobal"); + assert.strictEqual(configGlobal.writeable, false); + }); + + return { Program: spy }; + } }); linter.verify(code, config); @@ -1323,22 +1405,24 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + a = getVariable(scope, "a"), + b = getVariable(scope, "b"), + c = getVariable(scope, "c"); - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - }); + assert.strictEqual(a.name, "a"); + assert.strictEqual(a.writeable, false); + assert.strictEqual(b.name, "b"); + assert.strictEqual(b.writeable, true); + assert.strictEqual(c.name, "c"); + assert.strictEqual(c.writeable, false); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1362,17 +1446,19 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - exports = getVariable(scope, "exports"), - window = getVariable(scope, "window"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + exports = getVariable(scope, "exports"), + window = getVariable(scope, "window"); - assert.strictEqual(exports.writeable, true); - assert.strictEqual(window.writeable, false); - }); + assert.strictEqual(exports.writeable, true); + assert.strictEqual(window.writeable, false); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1387,17 +1473,19 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - exports = getVariable(scope, "exports"), - window = getVariable(scope, "window"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + exports = getVariable(scope, "exports"), + window = getVariable(scope, "window"); - assert.strictEqual(exports.writeable, true); - assert.strictEqual(window, null); - }); + assert.strictEqual(exports.writeable, true); + assert.strictEqual(window, null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1419,15 +1507,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse.eslintUsed, true); - }); + assert.strictEqual(horse.eslintUsed, true); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1439,15 +1529,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); - }); + assert.strictEqual(horse, null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1459,15 +1551,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse.eslintUsed, true); - }); + assert.strictEqual(horse.eslintUsed, true); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1479,15 +1573,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); // there is no global scope at all - }); + assert.strictEqual(horse, null); // there is no global scope at all + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1499,15 +1595,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, env: { node: true } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); // there is no global scope at all - }); + assert.strictEqual(horse, null); // there is no global scope at all + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1522,14 +1620,16 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "a"), null); - }); + assert.strictEqual(getVariable(scope, "a"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1544,17 +1644,19 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "a"), null); - assert.strictEqual(getVariable(scope, "b"), null); - assert.strictEqual(getVariable(scope, "foo"), null); - assert.strictEqual(getVariable(scope, "c"), null); - }); + assert.strictEqual(getVariable(scope, "a"), null); + assert.strictEqual(getVariable(scope, "b"), null); + assert.strictEqual(getVariable(scope, "foo"), null); + assert.strictEqual(getVariable(scope, "c"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1569,16 +1671,18 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.notStrictEqual(getVariable(scope, "Object"), null); - assert.notStrictEqual(getVariable(scope, "Array"), null); - assert.notStrictEqual(getVariable(scope, "undefined"), null); - }); + assert.notStrictEqual(getVariable(scope, "Object"), null); + assert.notStrictEqual(getVariable(scope, "Array"), null); + assert.notStrictEqual(getVariable(scope, "undefined"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1589,16 +1693,18 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "Promise"), null); - assert.strictEqual(getVariable(scope, "Symbol"), null); - assert.strictEqual(getVariable(scope, "WeakMap"), null); - }); + assert.strictEqual(getVariable(scope, "Promise"), null); + assert.strictEqual(getVariable(scope, "Symbol"), null); + assert.strictEqual(getVariable(scope, "WeakMap"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1609,16 +1715,18 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, env: { es6: true } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.notStrictEqual(getVariable(scope, "Promise"), null); - assert.notStrictEqual(getVariable(scope, "Symbol"), null); - assert.notStrictEqual(getVariable(scope, "WeakMap"), null); - }); + assert.notStrictEqual(getVariable(scope, "Promise"), null); + assert.notStrictEqual(getVariable(scope, "Symbol"), null); + assert.notStrictEqual(getVariable(scope, "WeakMap"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1629,16 +1737,18 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, globals: { Promise: "off", Symbol: "off", WeakMap: "off" }, env: { es6: true } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "Promise"), null); - assert.strictEqual(getVariable(scope, "Symbol"), null); - assert.strictEqual(getVariable(scope, "WeakMap"), null); - }); + assert.strictEqual(getVariable(scope, "Promise"), null); + assert.strictEqual(getVariable(scope, "Symbol"), null); + assert.strictEqual(getVariable(scope, "WeakMap"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1650,11 +1760,13 @@ describe("Linter", () => { const code = "new-rule"; it("can add a rule dynamically", () => { - linter.defineRule(code, context => ({ - Literal(node) { - context.report(node, "message"); - } - })); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + context.report(node, "message"); + } + }) + }); const config = { rules: {} }; @@ -1680,12 +1792,14 @@ describe("Linter", () => { code.forEach(item => { config.rules[item] = 1; - newRules[item] = function(context) { - return { - Literal(node) { - context.report(node, "message"); - } - }; + newRules[item] = { + create(context) { + return { + Literal(node) { + context.report(node, "message"); + } + }; + } }; }); linter.defineRules(newRules); @@ -1709,11 +1823,13 @@ describe("Linter", () => { const code = "filename-rule"; it("has access to the filename", () => { - linter.defineRule(code, context => ({ - Literal(node) { - context.report(node, context.getFilename()); - } - })); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + context.report(node, context.getFilename()); + } + }) + }); const config = { rules: {} }; @@ -1727,11 +1843,13 @@ describe("Linter", () => { }); it("has access to the physicalFilename", () => { - linter.defineRule(code, context => ({ - Literal(node) { - context.report(node, context.getPhysicalFilename()); - } - })); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + context.report(node, context.getPhysicalFilename()); + } + }) + }); const config = { rules: {} }; @@ -1745,11 +1863,13 @@ describe("Linter", () => { }); it("defaults filename to ''", () => { - linter.defineRule(code, context => ({ - Literal(node) { - context.report(node, context.getFilename()); - } - })); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + context.report(node, context.getFilename()); + } + }) + }); const config = { rules: {} }; @@ -2011,13 +2131,17 @@ describe("Linter", () => { describe("when evaluating code with comments to disable and enable configurable rule as part of plugin", () => { beforeEach(() => { - linter.defineRule("test-plugin/test-rule", context => ({ - Literal(node) { - if (node.value === "trigger violation") { - context.report(node, "Reporting violation."); - } + linter.defineRule("test-plugin/test-rule", { + create(context) { + return { + Literal(node) { + if (node.value === "trigger violation") { + context.report(node, "Reporting violation."); + } + } + }; } - })); + }); }); it("should not report a violation when inline comment enables plugin rule and there's no violation", () => { @@ -2045,11 +2169,13 @@ describe("Linter", () => { it("should report a violation when the report is right before the comment", () => { const code = " /* eslint-disable */ "; - linter.defineRule("checker", context => ({ - Program() { - context.report({ loc: { line: 1, column: 0 }, message: "foo" }); - } - })); + linter.defineRule("checker", { + create: context => ({ + Program() { + context.report({ loc: { line: 1, column: 0 }, message: "foo" }); + } + }) + }); const problems = linter.verify(code, { rules: { checker: "error" } }); const suppressedMessages = linter.getSuppressedMessages(); @@ -2061,11 +2187,13 @@ describe("Linter", () => { it("should not report a violation when the report is right at the start of the comment", () => { const code = " /* eslint-disable */ "; - linter.defineRule("checker", context => ({ - Program() { - context.report({ loc: { line: 1, column: 1 }, message: "foo" }); - } - })); + linter.defineRule("checker", { + create: context => ({ + Program() { + context.report({ loc: { line: 1, column: 1 }, message: "foo" }); + } + }) + }); const problems = linter.verify(code, { rules: { checker: "error" } }); const suppressedMessages = linter.getSuppressedMessages(); @@ -3279,7 +3407,7 @@ var a = "test2"; return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -3289,11 +3417,13 @@ var a = "test2"; const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "'123';"); - }); - return { ExpressionStatement: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), "'123';"); + }); + return { ExpressionStatement: spy }; + } }); linter.verify(code, config); @@ -3431,11 +3561,13 @@ var a = "test2"; describe("when evaluating an empty string", () => { it("runs rules", () => { - linter.defineRule("no-programs", context => ({ - Program(node) { - context.report({ node, message: "No programs allowed." }); - } - })); + linter.defineRule("no-programs", { + create: context => ({ + Program(node) { + context.report({ node, message: "No programs allowed." }); + } + }) + }); assert.strictEqual( linter.verify("", { rules: { "no-programs": "error" } }).length, @@ -3638,8 +3770,8 @@ var a = "test2"; let ok = false; linter.defineRules({ - test(context) { - return { + test: { + create: context => ({ Program() { const scope = context.getScope(); const sourceCode = context.getSourceCode(); @@ -3653,7 +3785,7 @@ var a = "test2"; ok = true; } - }; + }) } }); @@ -3810,11 +3942,13 @@ var a = "test2"; const linterWithOption = new Linter({ cwd }); let spy; - linterWithOption.defineRule("checker", context => { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), cwd); - }); - return { Program: spy }; + linterWithOption.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), cwd); + }); + return { Program: spy }; + } }); linterWithOption.verify(code, config); @@ -3825,12 +3959,14 @@ var a = "test2"; let spy; const linterWithOption = new Linter({ }); - linterWithOption.defineRule("checker", context => { + linterWithOption.defineRule("checker", { + create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), process.cwd()); - }); - return { Program: spy }; + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), process.cwd()); + }); + return { Program: spy }; + } }); linterWithOption.verify(code, config); @@ -3840,12 +3976,14 @@ var a = "test2"; it("should assign process.cwd() to it if the option is undefined", () => { let spy; - linter.defineRule("checker", context => { + linter.defineRule("checker", { + create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), process.cwd()); - }); - return { Program: spy }; + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), process.cwd()); + }); + return { Program: spy }; + } }); linter.verify(code, config); @@ -4051,6 +4189,27 @@ var a = "test2"; assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); }); + it("reports no problems for no-fallthrough despite comment pattern match", () => { + const code = "switch (foo) { case 0: a(); \n// eslint-disable-next-line no-fallthrough\n case 1: }"; + const config = { + reportUnusedDisableDirectives: true, + rules: { + "no-fallthrough": 2 + } + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-fallthrough"); + }); + describe("autofix", () => { const alwaysReportsRule = { create(context) { @@ -4640,11 +4799,13 @@ var a = "test2"; const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "'123';"); - }); - return { ExpressionStatement: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), "'123';"); + }); + return { ExpressionStatement: spy }; + } }); linter.verify(code, config); @@ -4660,7 +4821,7 @@ var a = "test2"; return {}; }); - linter.defineRule("checker", filenameChecker); + linter.defineRule("checker", { create: filenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" }); assert(filenameChecker.calledOnce); }); @@ -4671,7 +4832,7 @@ var a = "test2"; return {}; }); - linter.defineRule("checker", filenameChecker); + linter.defineRule("checker", { create: filenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }, "bar.js"); assert(filenameChecker.calledOnce); }); @@ -4682,7 +4843,7 @@ var a = "test2"; return {}; }); - linter.defineRule("checker", filenameChecker); + linter.defineRule("checker", { create: filenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }, {}); assert(filenameChecker.calledOnce); }); @@ -4693,7 +4854,7 @@ var a = "test2"; return {}; }); - linter.defineRule("checker", filenameChecker); + linter.defineRule("checker", { create: filenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }); assert(filenameChecker.calledOnce); }); @@ -4706,7 +4867,7 @@ var a = "test2"; return {}; }); - linter.defineRule("checker", physicalFilenameChecker); + linter.defineRule("checker", { create: physicalFilenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" }); assert(physicalFilenameChecker.calledOnce); }); @@ -4717,7 +4878,7 @@ var a = "test2"; return {}; }); - linter.defineRule("checker", physicalFilenameChecker); + linter.defineRule("checker", { create: physicalFilenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }, {}); assert(physicalFilenameChecker.calledOnce); }); @@ -4728,7 +4889,7 @@ var a = "test2"; return {}; }); - linter.defineRule("checker", physicalFilenameChecker); + linter.defineRule("checker", { create: physicalFilenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }); assert(physicalFilenameChecker.calledOnce); }); @@ -4777,11 +4938,13 @@ var a = "test2"; let ecmaVersion = null; const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } }; - linter.defineRule("ecma-version", context => ({ - Program() { - ecmaVersion = context.parserOptions.ecmaVersion; - } - })); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.parserOptions.ecmaVersion; + } + }) + }); linter.verify("", config); assert.strictEqual(ecmaVersion, espree.latestEcmaVersion, "ecmaVersion should be 13"); }); @@ -4791,11 +4954,13 @@ var a = "test2"; const config = { rules: { "ecma-version": 2 }, parser: "custom-parser", parserOptions: { ecmaVersion: "latest" } }; linter.defineParser("custom-parser", testParsers.enhancedParser); - linter.defineRule("ecma-version", context => ({ - Program() { - ecmaVersion = context.parserOptions.ecmaVersion; - } - })); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.parserOptions.ecmaVersion; + } + }) + }); linter.verify("", config); assert.strictEqual(ecmaVersion, "latest", "ecmaVersion should be latest"); }); @@ -4804,11 +4969,13 @@ var a = "test2"; let ecmaVersion = null; const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } }; - linter.defineRule("ecma-version", context => ({ - Program() { - ecmaVersion = context.languageOptions.ecmaVersion; - } - })); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.languageOptions.ecmaVersion; + } + }) + }); linter.verify("", config); assert.strictEqual(ecmaVersion, espree.latestEcmaVersion + 2009, "ecmaVersion should be 2022"); }); @@ -4818,11 +4985,13 @@ var a = "test2"; const config = { rules: { "ecma-version": 2 }, parser: "custom-parser", parserOptions: { ecmaVersion: "next" } }; linter.defineParser("custom-parser", testParsers.stubParser); - linter.defineRule("ecma-version", context => ({ - Program() { - ecmaVersion = context.languageOptions.ecmaVersion; - } - })); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.languageOptions.ecmaVersion; + } + }) + }); linter.verify("", config); assert.strictEqual(ecmaVersion, espree.latestEcmaVersion + 2009, "ecmaVersion should be 2022"); }); @@ -4832,11 +5001,13 @@ var a = "test2"; const config = { rules: { "ecma-version": 2 }, parser: "custom-parser" }; linter.defineParser("custom-parser", testParsers.enhancedParser); - linter.defineRule("ecma-version", context => ({ - Program() { - ecmaVersion = context.languageOptions.ecmaVersion; - } - })); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.languageOptions.ecmaVersion; + } + }) + }); linter.verify("", config); assert.strictEqual(ecmaVersion, 5, "ecmaVersion should be 5"); }); @@ -4844,11 +5015,13 @@ var a = "test2"; it("should pass normalized ecmaVersion to eslint-scope", () => { let blockScope = null; - linter.defineRule("block-scope", context => ({ - BlockStatement() { - blockScope = context.getScope(); - } - })); + linter.defineRule("block-scope", { + create: context => ({ + BlockStatement() { + blockScope = context.getScope(); + } + }) + }); linter.defineParser("custom-parser", { parse: (...args) => espree.parse(...args) }); @@ -5247,8 +5420,8 @@ var a = "test2"; let ok = false; linter.defineRules({ - test(context) { - return { + test: { + create: context => ({ Program() { const scope = context.getScope(); const sourceCode = context.getSourceCode(); @@ -5273,7 +5446,7 @@ var a = "test2"; ok = true; } - }; + }) } }); @@ -5321,16 +5494,20 @@ var a = "test2"; let ast1 = null, ast2 = null; - linter.defineRule("save-ast1", () => ({ - Program(node) { - ast1 = node; - } - })); - linter.defineRule("save-ast2", () => ({ - Program(node) { - ast2 = node; - } - })); + linter.defineRule("save-ast1", { + create: () => ({ + Program(node) { + ast1 = node; + } + }) + }); + linter.defineRule("save-ast2", { + create: () => ({ + Program(node) { + ast2 = node; + } + }) + }); linter.verify("function render() { return
    {hello}
    }", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast1": 2 } }); linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast2": 2 } }); @@ -5365,7 +5542,7 @@ var a = "test2"; return {}; }); - linter.defineRule("foo-bar-baz", spy); + linter.defineRule("foo-bar-baz", { create: spy }); linter.verify("x", { rules: { "foo-bar-baz": "error" } }); assert(spy.calledOnce); }); @@ -5827,12 +6004,14 @@ var a = "test2"; function getScope(code, astSelector, ecmaVersion = 5) { let node, scope; - linter.defineRule("get-scope", context => ({ - [astSelector](node0) { - node = node0; - scope = context.getScope(); - } - })); + linter.defineRule("get-scope", { + create: context => ({ + [astSelector](node0) { + node = node0; + scope = context.getScope(); + } + }) + }); linter.verify( code, { @@ -6084,13 +6263,13 @@ var a = "test2"; let ok = false; linter.defineRules({ - test(context) { - return { + test: { + create: context => ({ Program() { scope = context.getScope(); ok = true; } - }; + }) } }); linter.verify(code, { rules: { test: 2 }, globals: { e: true, f: false } }); @@ -6177,78 +6356,80 @@ var a = "test2"; */ function verify(code, type, expectedNamesList) { linter.defineRules({ - test(context) { + test: { + create(context) { - /** - * Assert `context.getDeclaredVariables(node)` is empty. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function checkEmpty(node) { - assert.strictEqual(0, context.getDeclaredVariables(node).length); - } - const rule = { - Program: checkEmpty, - EmptyStatement: checkEmpty, - BlockStatement: checkEmpty, - ExpressionStatement: checkEmpty, - LabeledStatement: checkEmpty, - BreakStatement: checkEmpty, - ContinueStatement: checkEmpty, - WithStatement: checkEmpty, - SwitchStatement: checkEmpty, - ReturnStatement: checkEmpty, - ThrowStatement: checkEmpty, - TryStatement: checkEmpty, - WhileStatement: checkEmpty, - DoWhileStatement: checkEmpty, - ForStatement: checkEmpty, - ForInStatement: checkEmpty, - DebuggerStatement: checkEmpty, - ThisExpression: checkEmpty, - ArrayExpression: checkEmpty, - ObjectExpression: checkEmpty, - Property: checkEmpty, - SequenceExpression: checkEmpty, - UnaryExpression: checkEmpty, - BinaryExpression: checkEmpty, - AssignmentExpression: checkEmpty, - UpdateExpression: checkEmpty, - LogicalExpression: checkEmpty, - ConditionalExpression: checkEmpty, - CallExpression: checkEmpty, - NewExpression: checkEmpty, - MemberExpression: checkEmpty, - SwitchCase: checkEmpty, - Identifier: checkEmpty, - Literal: checkEmpty, - ForOfStatement: checkEmpty, - ArrowFunctionExpression: checkEmpty, - YieldExpression: checkEmpty, - TemplateLiteral: checkEmpty, - TaggedTemplateExpression: checkEmpty, - TemplateElement: checkEmpty, - ObjectPattern: checkEmpty, - ArrayPattern: checkEmpty, - RestElement: checkEmpty, - AssignmentPattern: checkEmpty, - ClassBody: checkEmpty, - MethodDefinition: checkEmpty, - MetaProperty: checkEmpty - }; + /** + * Assert `context.getDeclaredVariables(node)` is empty. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkEmpty(node) { + assert.strictEqual(0, context.getDeclaredVariables(node).length); + } + const rule = { + Program: checkEmpty, + EmptyStatement: checkEmpty, + BlockStatement: checkEmpty, + ExpressionStatement: checkEmpty, + LabeledStatement: checkEmpty, + BreakStatement: checkEmpty, + ContinueStatement: checkEmpty, + WithStatement: checkEmpty, + SwitchStatement: checkEmpty, + ReturnStatement: checkEmpty, + ThrowStatement: checkEmpty, + TryStatement: checkEmpty, + WhileStatement: checkEmpty, + DoWhileStatement: checkEmpty, + ForStatement: checkEmpty, + ForInStatement: checkEmpty, + DebuggerStatement: checkEmpty, + ThisExpression: checkEmpty, + ArrayExpression: checkEmpty, + ObjectExpression: checkEmpty, + Property: checkEmpty, + SequenceExpression: checkEmpty, + UnaryExpression: checkEmpty, + BinaryExpression: checkEmpty, + AssignmentExpression: checkEmpty, + UpdateExpression: checkEmpty, + LogicalExpression: checkEmpty, + ConditionalExpression: checkEmpty, + CallExpression: checkEmpty, + NewExpression: checkEmpty, + MemberExpression: checkEmpty, + SwitchCase: checkEmpty, + Identifier: checkEmpty, + Literal: checkEmpty, + ForOfStatement: checkEmpty, + ArrowFunctionExpression: checkEmpty, + YieldExpression: checkEmpty, + TemplateLiteral: checkEmpty, + TaggedTemplateExpression: checkEmpty, + TemplateElement: checkEmpty, + ObjectPattern: checkEmpty, + ArrayPattern: checkEmpty, + RestElement: checkEmpty, + AssignmentPattern: checkEmpty, + ClassBody: checkEmpty, + MethodDefinition: checkEmpty, + MetaProperty: checkEmpty + }; - rule[type] = function(node) { - const expectedNames = expectedNamesList.shift(); - const variables = context.getDeclaredVariables(node); + rule[type] = function(node) { + const expectedNames = expectedNamesList.shift(); + const variables = context.getDeclaredVariables(node); - assert(Array.isArray(expectedNames)); - assert(Array.isArray(variables)); - assert.strictEqual(expectedNames.length, variables.length); - for (let i = variables.length - 1; i >= 0; i--) { - assert.strictEqual(expectedNames[i], variables[i].name); - } - }; - return rule; + assert(Array.isArray(expectedNames)); + assert(Array.isArray(variables)); + assert.strictEqual(expectedNames.length, variables.length); + for (let i = variables.length - 1; i >= 0; i--) { + assert.strictEqual(expectedNames[i], variables[i].name); + } + }; + return rule; + } } }); linter.verify(code, { @@ -6548,7 +6729,9 @@ var a = "test2"; }); it("loading rule in one doesn't change the other", () => { - linter1.defineRule("mock-rule", () => ({})); + linter1.defineRule("mock-rule", { + create: () => ({}) + }); assert.isTrue(linter1.getRules().has("mock-rule"), "mock rule is present"); assert.isFalse(linter2.getRules().has("mock-rule"), "mock rule is not present"); @@ -6565,13 +6748,15 @@ var a = "test2"; receivedPhysicalFilenames = []; // A rule that always reports the AST with a message equal to the source text - linter.defineRule("report-original-text", context => ({ - Program(ast) { - receivedFilenames.push(context.getFilename()); - receivedPhysicalFilenames.push(context.getPhysicalFilename()); - context.report({ node: ast, message: context.getSourceCode().text }); - } - })); + linter.defineRule("report-original-text", { + create: context => ({ + Program(ast) { + receivedFilenames.push(context.getFilename()); + receivedPhysicalFilenames.push(context.getPhysicalFilename()); + context.report({ node: ast, message: context.getSourceCode().text }); + } + }) + }); }); describe("preprocessors", () => { @@ -6907,11 +7092,13 @@ var a = "test2"; }); it("should throw an error if fix is passed from a legacy-format rule", () => { - linter.defineRule("test-rule", context => ({ - Program(node) { - context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); - } - })); + linter.defineRule("test-rule", { + create: context => ({ + Program(node) { + context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); + } + }) + }); assert.throws(() => { linter.verify("0", { rules: { "test-rule": "error" } }); @@ -7000,7 +7187,9 @@ var a = "test2"; * This test focuses on the instance of https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/conf/environments.js#L26-L28 * This `verify()` takes the instance and runs https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/lib/eslint.js#L416 */ - linter.defineRule("test", () => ({})); + linter.defineRule("test", { + create: () => ({}) + }); linter.verify("var a = 0;", { env: { node: true }, parserOptions: { ecmaVersion: 6, sourceType: "module" }, @@ -7010,13 +7199,15 @@ var a = "test2"; // This `verify()` takes the instance and tests that the instance was not modified. let ok = false; - linter.defineRule("test", context => { - assert( - context.parserOptions.ecmaFeatures.globalReturn, - "`ecmaFeatures.globalReturn` of the node environment should not be modified." - ); - ok = true; - return {}; + linter.defineRule("test", { + create(context) { + assert( + context.parserOptions.ecmaFeatures.globalReturn, + "`ecmaFeatures.globalReturn` of the node environment should not be modified." + ); + ok = true; + return {}; + } }); linter.verify("var a = 0;", { env: { node: true }, @@ -7029,13 +7220,17 @@ var a = "test2"; it("should throw when rule's create() function does not return an object", () => { const config = { rules: { checker: "error" } }; - linter.defineRule("checker", () => null); // returns null + linter.defineRule("checker", { + create: () => null + }); // returns null assert.throws(() => { linter.verify("abc", config, filename); }, "The create() function for rule 'checker' did not return an object."); - linter.defineRule("checker", () => {}); // returns undefined + linter.defineRule("checker", { + create() {} + }); // returns undefined assert.throws(() => { linter.verify("abc", config, filename); @@ -7111,11 +7306,13 @@ var a = "test2"; const nodes = []; - linter.defineRule("collect-node-types", () => ({ - "*"(node) { - nodes.push(node.type); - } - })); + linter.defineRule("collect-node-types", { + create: () => ({ + "*"(node) { + nodes.push(node.type); + } + }) + }); linter.defineParser("non-js-parser", testParsers.nonJSParser); @@ -7166,21 +7363,27 @@ var a = "test2"; beforeEach(() => { types = []; firstChildNodes = []; - linter.defineRule("collect-node-types", () => ({ - "*"(node) { - types.push(node.type); - } - })); - linter.defineRule("save-scope-manager", context => { - scopeManager = context.getSourceCode().scopeManager; - - return {}; + linter.defineRule("collect-node-types", { + create: () => ({ + "*"(node) { + types.push(node.type); + } + }) }); - linter.defineRule("esquery-option", () => ({ - ":first-child"(node) { - firstChildNodes.push(node); + linter.defineRule("save-scope-manager", { + create(context) { + scopeManager = context.getSourceCode().scopeManager; + + return {}; } - })); + }); + linter.defineRule("esquery-option", { + create: () => ({ + ":first-child"(node) { + firstChildNodes.push(node); + } + }) + }); linter.defineParser("enhanced-parser2", testParsers.enhancedParser2); linter.verify("@foo class A {}", { parser: "enhanced-parser2", @@ -7211,11 +7414,13 @@ var a = "test2"; it("should use the same visitorKeys if the source code object is reused", () => { const types2 = []; - linter.defineRule("collect-node-types", () => ({ - "*"(node) { - types2.push(node.type); - } - })); + linter.defineRule("collect-node-types", { + create: () => ({ + "*"(node) { + types2.push(node.type); + } + }) + }); linter.verify(sourceCode, { rules: { "collect-node-types": "error" @@ -7242,11 +7447,13 @@ var a = "test2"; beforeEach(() => { linter.defineParser("enhanced-parser3", testParsers.enhancedParser3); - linter.defineRule("save-scope1", context => ({ - Program() { - scope = context.getScope(); - } - })); + linter.defineRule("save-scope1", { + create: context => ({ + Program() { + scope = context.getScope(); + } + }) + }); linter.verify("@foo class A {}", { parser: "enhanced-parser3", rules: { "save-scope1": 2 } }); sourceCode = linter.getSourceCode(); @@ -7263,11 +7470,13 @@ var a = "test2"; it("should use the same scope if the source code object is reused", () => { let scope2 = null; - linter.defineRule("save-scope2", context => ({ - Program() { - scope2 = context.getScope(); - } - })); + linter.defineRule("save-scope2", { + create: context => ({ + Program() { + scope2 = context.getScope(); + } + }) + }); linter.verify(sourceCode, { rules: { "save-scope2": 2 } }, "test.js"); assert(scope2 !== null); @@ -7410,12 +7619,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.ecmaVersion, 2015); } - }; + }) } } } @@ -7434,12 +7643,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.ecmaVersion, espree.latestEcmaVersion + 2009); } - }; + }) } } } @@ -7455,12 +7664,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.ecmaVersion, 5); } - }; + }) } } } @@ -7479,12 +7688,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.ecmaVersion, espree.latestEcmaVersion + 2009); } - }; + }) } } } @@ -7508,12 +7717,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.sourceType, "module"); } - }; + }) } } } @@ -7529,12 +7738,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.sourceType, "commonjs"); } - }; + }) } } } @@ -7669,15 +7878,8 @@ describe("Linter with FlatConfigArray", () => { }; const config = { - plugins: { - test: { - parsers: { - "test-parser": parser - } - } - }, languageOptions: { - parser: "test/test-parser" + parser } }; @@ -7695,9 +7897,11 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "test-rule": sinon.mock().withArgs( - sinon.match({ languageOptions: { parser: esprima } }) - ).returns({}) + "test-rule": { + create: sinon.mock().withArgs( + sinon.match({ languageOptions: { parser: esprima } }) + ).returns({}) + } } } }, @@ -7714,15 +7918,8 @@ describe("Linter with FlatConfigArray", () => { it("should use parseForESLint() in custom parser when custom parser is specified", () => { const config = { - plugins: { - test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - } - } - }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser } }; @@ -7738,23 +7935,22 @@ describe("Linter with FlatConfigArray", () => { const config = { plugins: { test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - }, rules: { - "test-service-rule": context => ({ - Literal(node) { - context.report({ - node, - message: context.parserServices.test.getMessage() - }); - } - }) + "test-service-rule": { + create: context => ({ + Literal(node) { + context.report({ + node, + message: context.parserServices.test.getMessage() + }); + } + }) + } } } }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser }, rules: { "test/test-service-rule": 2 @@ -7775,23 +7971,22 @@ describe("Linter with FlatConfigArray", () => { const config = { plugins: { test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - }, rules: { - "test-service-rule": context => ({ - Literal(node) { - context.report({ - node, - message: context.parserServices.test.getMessage() - }); - } - }) + "test-service-rule": { + create: context => ({ + Literal(node) { + context.report({ + node, + message: context.parserServices.test.getMessage() + }); + } + }) + } } } }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser }, rules: { "test/test-service-rule": 2 @@ -7829,7 +8024,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "test-rule": spy + "test-rule": { create: spy } } } }, @@ -7921,11 +8116,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "collect-node-types": () => ({ - "*"(node) { - nodes.push(node.type); - } - }) + "collect-node-types": { + create: () => ({ + "*"(node) { + nodes.push(node.type); + } + }) + } } } }, @@ -7988,21 +8185,27 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "collect-node-types": () => ({ - "*"(node) { - types.push(node.type); - } - }), - "save-scope-manager": context => { - scopeManager = context.getSourceCode().scopeManager; - - return {}; + "collect-node-types": { + create: () => ({ + "*"(node) { + types.push(node.type); + } + }) }, - "esquery-option": () => ({ - ":first-child"(node) { - firstChildNodes.push(node); + "save-scope-manager": { + create(context) { + scopeManager = context.getSourceCode().scopeManager; + + return {}; } - }) + }, + "esquery-option": { + create: () => ({ + ":first-child"(node) { + firstChildNodes.push(node); + } + }) + } } } }, @@ -8041,11 +8244,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "collect-node-types": () => ({ - "*"(node) { - types2.push(node.type); - } - }) + "collect-node-types": { + create: () => ({ + "*"(node) { + types2.push(node.type); + } + }) + } } } }, @@ -8079,11 +8284,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "save-scope1": context => ({ - Program() { - scope = context.getScope(); - } - }) + "save-scope1": { + create: context => ({ + Program() { + scope = context.getScope(); + } + }) + } } } }, @@ -8114,11 +8321,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "save-scope2": context => ({ - Program() { - scope2 = context.getScope(); - } - }) + "save-scope2": { + create: context => ({ + Program() { + scope2 = context.getScope(); + } + }) + } } } }, @@ -8170,9 +8379,11 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "test-rule": sinon.mock().withArgs( - sinon.match({ languageOptions: { parserOptions } }) - ).returns({}) + "test-rule": { + create: sinon.mock().withArgs( + sinon.match({ languageOptions: { parserOptions } }) + ).returns({}) + } } } }, @@ -8193,17 +8404,19 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "test-rule": sinon.mock().withArgs( - sinon.match({ - languageOptions: { - parserOptions: { - ecmaFeatures: { - globalReturn: false + "test-rule": { + create: sinon.mock().withArgs( + sinon.match({ + languageOptions: { + parserOptions: { + ecmaFeatures: { + globalReturn: false + } } } - } - }) - ).returns({}) + }) + ).returns({}) + } } } }, @@ -8392,7 +8605,7 @@ describe("Linter with FlatConfigArray", () => { const code = "var enum;"; const messages = linter.verify(code, { languageOptions: { - ...ecmaVersion ? { ecmaVersion } : {}, + ...(ecmaVersion ? { ecmaVersion } : {}), sourceType: "script" } }, filename); @@ -8411,7 +8624,7 @@ describe("Linter with FlatConfigArray", () => { const code = "obj.enum; obj.function; var obj = { enum: 1, function: 2 };"; const messages = linter.verify(code, { languageOptions: { - ...ecmaVersion ? { ecmaVersion } : {}, + ...(ecmaVersion ? { ecmaVersion } : {}), sourceType: "script" } }, filename); @@ -8427,7 +8640,7 @@ describe("Linter with FlatConfigArray", () => { const code = ""; const messages = linter.verify(code, { languageOptions: { - ...ecmaVersion ? { ecmaVersion } : {}, + ...(ecmaVersion ? { ecmaVersion } : {}), sourceType: "script", parserOptions: { allowReserved: true @@ -8453,11 +8666,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - [ruleId]: context => ({ - Literal(node) { - context.report(node, context.settings.info); - } - }) + [ruleId]: { + create: context => ({ + Literal(node) { + context.report(node, context.settings.info); + } + }) + } } } }, @@ -8484,13 +8699,15 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - [ruleId]: context => ({ - Literal(node) { - if (Object.getOwnPropertyNames(context.settings).length !== 0) { - context.report(node, "Settings should be empty"); + [ruleId]: { + create: context => ({ + Literal(node) { + if (Object.getOwnPropertyNames(context.settings).length !== 0) { + context.report(node, "Settings should be empty"); + } } - } - }) + }) + } } } }, @@ -8645,7 +8862,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } } @@ -8665,11 +8882,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: () => ({ - Program() { - throw new Error("Intentional error."); - } - }) + checker: { + create: () => ({ + Program() { + throw new Error("Intentional error."); + } + + }) + } } } }, @@ -8687,9 +8907,11 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: () => ({ - Program: spy - }) + checker: { + create: () => ({ + Program: spy + }) + } } } }, @@ -8707,9 +8929,11 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: () => ({ - newListener: spy - }) + checker: { + create: () => ({ + newListener: spy + }) + } } } }, @@ -8739,7 +8963,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -8763,14 +8987,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker() { - return { - Literal: spyLiteral, - VariableDeclarator: spyVariableDeclarator, - VariableDeclaration: spyVariableDeclaration, - Identifier: spyIdentifier, - BinaryExpression: spyBinaryExpression - }; + checker: { + create() { + return { + Literal: spyLiteral, + VariableDeclarator: spyVariableDeclarator, + VariableDeclaration: spyVariableDeclaration, + Identifier: spyIdentifier, + BinaryExpression: spyBinaryExpression + }; + } } } } @@ -8797,12 +9023,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "invalid-report"(context) { - return { + "invalid-report": { + create: context => ({ Program(node) { context.report({ node }); } - }; + }) } } } @@ -8831,11 +9057,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - [ruleId]: context => ({ - Literal(node) { - context.report(node, context.getFilename()); - } - }) + [ruleId]: { + create: context => ({ + Literal(node) { + context.report(node, context.getFilename()); + } + }) + } } } }, @@ -8857,11 +9085,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - [ruleId]: context => ({ - Literal(node) { - context.report(node, context.getFilename()); - } - }) + [ruleId]: { + create: context => ({ + Literal(node) { + context.report(node, context.getFilename()); + } + }) + } } } }, @@ -8889,11 +9119,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - [ruleId]: context => ({ - Literal(node) { - context.report(node, context.getPhysicalFilename()); - } - }) + [ruleId]: { + create: context => ({ + Literal(node) { + context.report(node, context.getPhysicalFilename()); + } + }) + } } } }, @@ -8924,7 +9156,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -8946,7 +9178,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -8968,7 +9200,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -8990,7 +9222,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9012,7 +9244,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9036,11 +9268,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - assert.strictEqual(context.getSource(), TEST_CODE); - }); - return { Program: spy }; + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getSource(), TEST_CODE); + }); + return { Program: spy }; + } } } } @@ -9060,11 +9294,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), TEST_CODE); - }); - return { Program: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), TEST_CODE); + }); + return { Program: spy }; + } } } } @@ -9083,11 +9319,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); - }); - return { Program: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); + }); + return { Program: spy }; + } } } } @@ -9106,11 +9344,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "6 * 7"); - }); - return { BinaryExpression: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), "6 * 7"); + }); + return { BinaryExpression: spy }; + } } } } @@ -9129,11 +9369,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); - }); - return { BinaryExpression: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); + }); + return { BinaryExpression: spy }; + } } } } @@ -9152,11 +9394,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); - }); - return { BinaryExpression: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); + }); + return { BinaryExpression: spy }; + } } } } @@ -9175,11 +9419,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); - }); - return { BinaryExpression: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); + }); + return { BinaryExpression: spy }; + } } } } @@ -9204,13 +9450,15 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); + checker: { + create(context) { + spy = sinon.spy(() => { + const ancestors = context.getAncestors(); - assert.strictEqual(ancestors.length, 3); - }); - return { BinaryExpression: spy }; + assert.strictEqual(ancestors.length, 3); + }); + return { BinaryExpression: spy }; + } } } } @@ -9229,14 +9477,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); + checker: { + create(context) { + spy = sinon.spy(() => { + const ancestors = context.getAncestors(); - assert.strictEqual(ancestors.length, 0); - }); + assert.strictEqual(ancestors.length, 0); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -9262,7 +9512,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9283,7 +9533,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9307,7 +9557,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9328,7 +9578,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9355,7 +9605,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9382,7 +9632,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9404,13 +9654,15 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "global"); - }); - return { Program: spy }; + assert.strictEqual(scope.type, "global"); + }); + return { Program: spy }; + } } } } @@ -9432,13 +9684,15 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - }); - return { FunctionDeclaration: spy }; + assert.strictEqual(scope.type, "function"); + }); + return { FunctionDeclaration: spy }; + } } } } @@ -9460,14 +9714,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.id.name, "foo"); - }); - return { LabeledStatement: spy }; + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.id.name, "foo"); + }); + return { LabeledStatement: spy }; + } } } } @@ -9490,15 +9746,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); + }); - return { ReturnStatement: spy }; + return { ReturnStatement: spy }; + } } } } @@ -9521,15 +9779,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "switch"); - assert.strictEqual(scope.block.type, "SwitchStatement"); - }); + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block.type, "SwitchStatement"); + }); - return { SwitchStatement: spy }; + return { SwitchStatement: spy }; + } } } } @@ -9551,15 +9811,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block.type, "BlockStatement"); + }); - return { BlockStatement: spy }; + return { BlockStatement: spy }; + } } } } @@ -9582,15 +9844,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block.type, "BlockStatement"); + }); - return { BlockStatement: spy }; + return { BlockStatement: spy }; + } } } } @@ -9613,15 +9877,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionDeclaration"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "FunctionDeclaration"); + }); - return { FunctionDeclaration: spy }; + return { FunctionDeclaration: spy }; + } } } } @@ -9644,15 +9910,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionExpression"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "FunctionExpression"); + }); - return { FunctionExpression: spy }; + return { FunctionExpression: spy }; + } } } } @@ -9675,15 +9943,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block.type, "CatchClause"); - }); + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block.type, "CatchClause"); + }); - return { CatchClause: spy }; + return { CatchClause: spy }; + } } } } @@ -9705,14 +9975,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "module"); - }); + assert.strictEqual(scope.type, "module"); + }); - return { AssignmentExpression: spy }; + return { AssignmentExpression: spy }; + } } } } @@ -9736,14 +10008,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - }); + assert.strictEqual(scope.type, "function"); + }); - return { AssignmentExpression: spy }; + return { AssignmentExpression: spy }; + } } } } @@ -9775,12 +10049,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "get-scope": context => ({ - [astSelector](node0) { - node = node0; - scope = context.getScope(); - } - }) + "get-scope": { + create: context => ({ + [astSelector](node0) { + node = node0; + scope = context.getScope(); + } + }) + } } } }, @@ -10042,13 +10318,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - test(context) { - return { + test: { + create: context => ({ Program() { scope = context.getScope(); ok = true; } - }; + }) } } } @@ -10152,78 +10428,81 @@ describe("Linter with FlatConfigArray", () => { test: { rules: { - test(context) { - - /** - * Assert `context.getDeclaredVariables(node)` is empty. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function checkEmpty(node) { - assert.strictEqual(0, context.getDeclaredVariables(node).length); - } - const rule = { - Program: checkEmpty, - EmptyStatement: checkEmpty, - BlockStatement: checkEmpty, - ExpressionStatement: checkEmpty, - LabeledStatement: checkEmpty, - BreakStatement: checkEmpty, - ContinueStatement: checkEmpty, - WithStatement: checkEmpty, - SwitchStatement: checkEmpty, - ReturnStatement: checkEmpty, - ThrowStatement: checkEmpty, - TryStatement: checkEmpty, - WhileStatement: checkEmpty, - DoWhileStatement: checkEmpty, - ForStatement: checkEmpty, - ForInStatement: checkEmpty, - DebuggerStatement: checkEmpty, - ThisExpression: checkEmpty, - ArrayExpression: checkEmpty, - ObjectExpression: checkEmpty, - Property: checkEmpty, - SequenceExpression: checkEmpty, - UnaryExpression: checkEmpty, - BinaryExpression: checkEmpty, - AssignmentExpression: checkEmpty, - UpdateExpression: checkEmpty, - LogicalExpression: checkEmpty, - ConditionalExpression: checkEmpty, - CallExpression: checkEmpty, - NewExpression: checkEmpty, - MemberExpression: checkEmpty, - SwitchCase: checkEmpty, - Identifier: checkEmpty, - Literal: checkEmpty, - ForOfStatement: checkEmpty, - ArrowFunctionExpression: checkEmpty, - YieldExpression: checkEmpty, - TemplateLiteral: checkEmpty, - TaggedTemplateExpression: checkEmpty, - TemplateElement: checkEmpty, - ObjectPattern: checkEmpty, - ArrayPattern: checkEmpty, - RestElement: checkEmpty, - AssignmentPattern: checkEmpty, - ClassBody: checkEmpty, - MethodDefinition: checkEmpty, - MetaProperty: checkEmpty - }; - - rule[type] = function(node) { - const expectedNames = expectedNamesList.shift(); - const variables = context.getDeclaredVariables(node); + test: { + create(context) { - assert(Array.isArray(expectedNames)); - assert(Array.isArray(variables)); - assert.strictEqual(expectedNames.length, variables.length); - for (let i = variables.length - 1; i >= 0; i--) { - assert.strictEqual(expectedNames[i], variables[i].name); + /** + * Assert `context.getDeclaredVariables(node)` is empty. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkEmpty(node) { + assert.strictEqual(0, context.getDeclaredVariables(node).length); } - }; - return rule; + const rule = { + Program: checkEmpty, + EmptyStatement: checkEmpty, + BlockStatement: checkEmpty, + ExpressionStatement: checkEmpty, + LabeledStatement: checkEmpty, + BreakStatement: checkEmpty, + ContinueStatement: checkEmpty, + WithStatement: checkEmpty, + SwitchStatement: checkEmpty, + ReturnStatement: checkEmpty, + ThrowStatement: checkEmpty, + TryStatement: checkEmpty, + WhileStatement: checkEmpty, + DoWhileStatement: checkEmpty, + ForStatement: checkEmpty, + ForInStatement: checkEmpty, + DebuggerStatement: checkEmpty, + ThisExpression: checkEmpty, + ArrayExpression: checkEmpty, + ObjectExpression: checkEmpty, + Property: checkEmpty, + SequenceExpression: checkEmpty, + UnaryExpression: checkEmpty, + BinaryExpression: checkEmpty, + AssignmentExpression: checkEmpty, + UpdateExpression: checkEmpty, + LogicalExpression: checkEmpty, + ConditionalExpression: checkEmpty, + CallExpression: checkEmpty, + NewExpression: checkEmpty, + MemberExpression: checkEmpty, + SwitchCase: checkEmpty, + Identifier: checkEmpty, + Literal: checkEmpty, + ForOfStatement: checkEmpty, + ArrowFunctionExpression: checkEmpty, + YieldExpression: checkEmpty, + TemplateLiteral: checkEmpty, + TaggedTemplateExpression: checkEmpty, + TemplateElement: checkEmpty, + ObjectPattern: checkEmpty, + ArrayPattern: checkEmpty, + RestElement: checkEmpty, + AssignmentPattern: checkEmpty, + ClassBody: checkEmpty, + MethodDefinition: checkEmpty, + MetaProperty: checkEmpty + }; + + rule[type] = function(node) { + const expectedNames = expectedNamesList.shift(); + const variables = context.getDeclaredVariables(node); + + assert(Array.isArray(expectedNames)); + assert(Array.isArray(variables)); + assert.strictEqual(expectedNames.length, variables.length); + for (let i = variables.length - 1; i >= 0; i--) { + assert.strictEqual(expectedNames[i], variables[i].name); + } + }; + return rule; + } + } } @@ -10396,17 +10675,19 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); + checker: { + create(context) { + spy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); - const scope = context.getScope(); + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } } } } @@ -10429,17 +10710,19 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); + checker: { + create(context) { + spy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); - const scope = context.getScope(); + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { ReturnStatement: spy }; + return { ReturnStatement: spy }; + } } } } @@ -10459,18 +10742,20 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - returnSpy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); - }); - exitSpy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + returnSpy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); + }); + exitSpy = sinon.spy(() => { + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; + return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; + } } } } @@ -10494,18 +10779,20 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; + checker: { + create(context) { + spy = sinon.spy(() => { + const globalScope = context.getScope(), + childScope = globalScope.childScopes[0]; - assert.isTrue(context.markVariableAsUsed("a"), "Call to markVariableAsUsed should return true"); + assert.isTrue(context.markVariableAsUsed("a"), "Call to markVariableAsUsed should return true"); - assert.isTrue(getVariable(childScope, "a").eslintUsed, "'a' should be marked as used."); - assert.isUndefined(getVariable(childScope, "b").eslintUsed, "'b' should be marked as used."); - }); + assert.isTrue(getVariable(childScope, "a").eslintUsed, "'a' should be marked as used."); + assert.isUndefined(getVariable(childScope, "b").eslintUsed, "'b' should be marked as used."); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } } } } @@ -10528,18 +10815,20 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; + checker: { + create(context) { + spy = sinon.spy(() => { + const globalScope = context.getScope(), + childScope = globalScope.childScopes[0]; - assert.isTrue(context.markVariableAsUsed("a")); + assert.isTrue(context.markVariableAsUsed("a")); - assert.isTrue(getVariable(childScope, "a").eslintUsed); - assert.isUndefined(getVariable(childScope, "b").eslintUsed); - }); + assert.isTrue(getVariable(childScope, "a").eslintUsed); + assert.isUndefined(getVariable(childScope, "b").eslintUsed); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } } } } @@ -10563,12 +10852,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - assert.isFalse(context.markVariableAsUsed("c")); - }); + checker: { + create(context) { + spy = sinon.spy(() => { + assert.isFalse(context.markVariableAsUsed("c")); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } } } } @@ -10593,11 +10884,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), cwd); - }); - return { Program: spy }; + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), cwd); + }); + return { Program: spy }; + } } } } @@ -10617,12 +10910,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { + checker: { + create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), process.cwd()); - }); - return { Program: spy }; + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), process.cwd()); + }); + return { Program: spy }; + } } } } @@ -10640,12 +10935,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { + checker: { + create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), process.cwd()); - }); - return { Program: spy }; + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), process.cwd()); + }); + return { Program: spy }; + } } } } @@ -10747,7 +11044,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -10774,7 +11071,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: filenameChecker + checker: { create: filenameChecker } } } }, @@ -10797,7 +11094,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: filenameChecker + checker: { create: filenameChecker } } } }, @@ -10820,7 +11117,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: filenameChecker + checker: { create: filenameChecker } } } }, @@ -10843,7 +11140,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: filenameChecker + checker: { create: filenameChecker } } } }, @@ -10868,7 +11165,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: physicalFilenameChecker + checker: { create: physicalFilenameChecker } } } }, @@ -10891,7 +11188,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: physicalFilenameChecker + checker: { create: physicalFilenameChecker } } } }, @@ -10914,7 +11211,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: physicalFilenameChecker + checker: { create: physicalFilenameChecker } } } }, @@ -10949,38 +11246,40 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); - const a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"), - d = getVariable(scope, "d"), - e = getVariable(scope, "e"), - f = getVariable(scope, "f"), - mathGlobal = getVariable(scope, "Math"), - arrayGlobal = getVariable(scope, "Array"), - configGlobal = getVariable(scope, "ConfigGlobal"); - - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - assert.strictEqual(d.name, "d"); - assert.strictEqual(d.writeable, false); - assert.strictEqual(e.name, "e"); - assert.strictEqual(e.writeable, true); - assert.strictEqual(f.name, "f"); - assert.strictEqual(f.writeable, true); - assert.strictEqual(mathGlobal, null); - assert.strictEqual(arrayGlobal, null); - assert.strictEqual(configGlobal.name, "ConfigGlobal"); - assert.strictEqual(configGlobal.writeable, false); - }); - - return { Program: spy }; + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); + const a = getVariable(scope, "a"), + b = getVariable(scope, "b"), + c = getVariable(scope, "c"), + d = getVariable(scope, "d"), + e = getVariable(scope, "e"), + f = getVariable(scope, "f"), + mathGlobal = getVariable(scope, "Math"), + arrayGlobal = getVariable(scope, "Array"), + configGlobal = getVariable(scope, "ConfigGlobal"); + + assert.strictEqual(a.name, "a"); + assert.strictEqual(a.writeable, false); + assert.strictEqual(b.name, "b"); + assert.strictEqual(b.writeable, true); + assert.strictEqual(c.name, "c"); + assert.strictEqual(c.writeable, false); + assert.strictEqual(d.name, "d"); + assert.strictEqual(d.writeable, false); + assert.strictEqual(e.name, "e"); + assert.strictEqual(e.writeable, true); + assert.strictEqual(f.name, "f"); + assert.strictEqual(f.writeable, true); + assert.strictEqual(mathGlobal, null); + assert.strictEqual(arrayGlobal, null); + assert.strictEqual(configGlobal.name, "ConfigGlobal"); + assert.strictEqual(configGlobal.writeable, false); + }); + + return { Program: spy }; + } } } } @@ -11006,22 +11305,24 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"); - - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - }); - - return { Program: spy }; + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + a = getVariable(scope, "a"), + b = getVariable(scope, "b"), + c = getVariable(scope, "c"); + + assert.strictEqual(a.name, "a"); + assert.strictEqual(a.writeable, false); + assert.strictEqual(b.name, "b"); + assert.strictEqual(b.writeable, true); + assert.strictEqual(c.name, "c"); + assert.strictEqual(c.writeable, false); + }); + + return { Program: spy }; + } } } } @@ -11044,14 +11345,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "a"), null); - }); + assert.strictEqual(getVariable(scope, "a"), null); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11075,17 +11378,19 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "a"), null); - assert.strictEqual(getVariable(scope, "b"), null); - assert.strictEqual(getVariable(scope, "foo"), null); - assert.strictEqual(getVariable(scope, "c"), null); - }); + assert.strictEqual(getVariable(scope, "a"), null); + assert.strictEqual(getVariable(scope, "b"), null); + assert.strictEqual(getVariable(scope, "foo"), null); + assert.strictEqual(getVariable(scope, "c"), null); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11106,8 +11411,8 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - test(context) { - return { + test: { + create: context => ({ Program() { const scope = context.getScope(); const sourceCode = context.getSourceCode(); @@ -11132,7 +11437,7 @@ describe("Linter with FlatConfigArray", () => { ok = true; } - }; + }) } } } @@ -11195,15 +11500,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse.eslintUsed, true); - }); + assert.strictEqual(horse.eslintUsed, true); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11225,15 +11532,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); - }); + assert.strictEqual(horse, null); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11255,15 +11564,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse.eslintUsed, true); - }); + assert.strictEqual(horse.eslintUsed, true); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11285,15 +11596,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); // there is no global scope at all - }); + assert.strictEqual(horse, null); // there is no global scope at all + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11316,15 +11629,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); // there is no global scope at all - }); + assert.strictEqual(horse, null); // there is no global scope at all + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11598,14 +11913,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { "test-plugin": { rules: { - "test-rule"(context) { - return { + "test-rule": { + create: context => ({ Literal(node) { if (node.value === "trigger violation") { context.report(node, "Reporting violation."); } } - }; + }) } } } @@ -11643,11 +11958,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => ({ - Program() { - context.report({ loc: { line: 1, column: 0 }, message: "foo" }); - } - }) + checker: { + create: context => ({ + Program() { + context.report({ loc: { line: 1, column: 0 }, message: "foo" }); + } + }) + } } } }, @@ -11672,11 +11989,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => ({ - Program() { - context.report({ loc: { line: 1, column: 1 }, message: "foo" }); - } - }) + checker: { + create: context => ({ + Program() { + context.report({ loc: { line: 1, column: 1 }, message: "foo" }); + } + }) + } } } }, @@ -13374,8 +13693,8 @@ var a = "test2"; plugins: { test: { rules: { - test(context) { - return { + test: { + create: context => ({ Program() { const scope = context.getScope(); const sourceCode = context.getSourceCode(); @@ -13389,7 +13708,7 @@ var a = "test2"; ok = true; } - }; + }) } } } @@ -14413,16 +14732,18 @@ var a = "test2"; plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.notStrictEqual(getVariable(scope, "Object"), null); - assert.notStrictEqual(getVariable(scope, "Array"), null); - assert.notStrictEqual(getVariable(scope, "undefined"), null); - }); + assert.notStrictEqual(getVariable(scope, "Object"), null); + assert.notStrictEqual(getVariable(scope, "Array"), null); + assert.notStrictEqual(getVariable(scope, "undefined"), null); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -14446,16 +14767,18 @@ var a = "test2"; plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.notStrictEqual(getVariable(scope, "Promise"), null); - assert.notStrictEqual(getVariable(scope, "Symbol"), null); - assert.notStrictEqual(getVariable(scope, "WeakMap"), null); - }); + assert.notStrictEqual(getVariable(scope, "Promise"), null); + assert.notStrictEqual(getVariable(scope, "Symbol"), null); + assert.notStrictEqual(getVariable(scope, "WeakMap"), null); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -14781,7 +15104,9 @@ var a = "test2"; describe("defineRule()", () => { it("should throw an error when called in flat config mode", () => { assert.throws(() => { - linter.defineRule("foo", () => {}); + linter.defineRule("foo", { + create() {} + }); }, /This method cannot be used with flat config/u); }); }); @@ -14982,11 +15307,13 @@ var a = "test2"; plugins: { test: { rules: { - "test-rule": context => ({ - Program(node) { - context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); - } - }) + "test-rule": { + create: context => ({ + Program(node) { + context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); + } + }) + } } } }, @@ -15017,7 +15344,9 @@ var a = "test2"; }); it("loading rule in one doesn't change the other", () => { - linter1.defineRule("mock-rule", () => ({})); + linter1.defineRule("mock-rule", { + create: () => ({}) + }); assert.isTrue(linter1.getRules().has("mock-rule"), "mock rule is present"); assert.isFalse(linter2.getRules().has("mock-rule"), "mock rule is not present"); @@ -15480,17 +15809,21 @@ var a = "test2"; plugins: { test: { rules: { - "save-ast1": () => ({ - Program(node) { - ast1 = node; - } - }), + "save-ast1": { + create: () => ({ + Program(node) { + ast1 = node; + } + }) + }, - "save-ast2": () => ({ - Program(node) { - ast2 = node; - } - }) + "save-ast2": { + create: () => ({ + Program(node) { + ast2 = node; + } + }) + } } } @@ -15530,7 +15863,7 @@ var a = "test2"; plugins: { test: { rules: { - "foo-bar-baz": spy + "foo-bar-baz": { create: spy } } } }, @@ -15552,11 +15885,15 @@ var a = "test2"; plugins: { test: { rules: { - "no-programs": context => ({ - Program(node) { - context.report({ node, message: "No programs allowed." }); + "no-programs": { + create(context) { + return { + Program(node) { + context.report({ node, message: "No programs allowed." }); + } + }; } - }) + } } } }, diff --git a/tests/lib/options.js b/tests/lib/options.js index c84e46d9afd..d8f795b78a2 100644 --- a/tests/lib/options.js +++ b/tests/lib/options.js @@ -10,7 +10,14 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - options = require("../../lib/options"); + createOptions = require("../../lib/options"); + +//----------------------------------------------------------------------------- +// Data +//----------------------------------------------------------------------------- + +const eslintrcOptions = createOptions(false); +const flatOptions = createOptions(true); //------------------------------------------------------------------------------ // Tests @@ -21,372 +28,391 @@ const assert = require("chai").assert, */ describe("options", () => { - describe("--help", () => { - it("should return true for .help when passed", () => { - const currentOptions = options.parse("--help"); - assert.isTrue(currentOptions.help); - }); - }); + describe("Common options", () => { - describe("-h", () => { - it("should return true for .help when passed", () => { - const currentOptions = options.parse("-h"); + [eslintrcOptions, flatOptions].forEach(options => { - assert.isTrue(currentOptions.help); - }); - }); + describe("--help", () => { + it("should return true for .help when passed", () => { + const currentOptions = options.parse("--help"); - describe("--config", () => { - it("should return a string for .config when passed a string", () => { - const currentOptions = options.parse("--config file"); + assert.isTrue(currentOptions.help); + }); + }); - assert.isString(currentOptions.config); - assert.strictEqual(currentOptions.config, "file"); - }); - }); + describe("-h", () => { + it("should return true for .help when passed", () => { + const currentOptions = options.parse("-h"); - describe("-c", () => { - it("should return a string for .config when passed a string", () => { - const currentOptions = options.parse("-c file"); + assert.isTrue(currentOptions.help); + }); + }); - assert.isString(currentOptions.config); - assert.strictEqual(currentOptions.config, "file"); - }); - }); + describe("--config", () => { + it("should return a string for .config when passed a string", () => { + const currentOptions = options.parse("--config file"); - describe("--ext", () => { - it("should return an array with one item when passed .jsx", () => { - const currentOptions = options.parse("--ext .jsx"); + assert.isString(currentOptions.config); + assert.strictEqual(currentOptions.config, "file"); + }); + }); - assert.isArray(currentOptions.ext); - assert.strictEqual(currentOptions.ext[0], ".jsx"); - }); + describe("-c", () => { + it("should return a string for .config when passed a string", () => { + const currentOptions = options.parse("-c file"); - it("should return an array with two items when passed .js and .jsx", () => { - const currentOptions = options.parse("--ext .jsx --ext .js"); + assert.isString(currentOptions.config); + assert.strictEqual(currentOptions.config, "file"); + }); + }); - assert.isArray(currentOptions.ext); - assert.strictEqual(currentOptions.ext[0], ".jsx"); - assert.strictEqual(currentOptions.ext[1], ".js"); - }); - - it("should return an array with two items when passed .jsx,.js", () => { - const currentOptions = options.parse("--ext .jsx,.js"); + describe("--format", () => { + it("should return a string for .format when passed a string", () => { + const currentOptions = options.parse("--format compact"); - assert.isArray(currentOptions.ext); - assert.strictEqual(currentOptions.ext[0], ".jsx"); - assert.strictEqual(currentOptions.ext[1], ".js"); - }); - - it("should not exist when not passed", () => { - const currentOptions = options.parse(""); + assert.isString(currentOptions.format); + assert.strictEqual(currentOptions.format, "compact"); + }); - assert.notProperty(currentOptions, "ext"); - }); - }); + it("should return stylish for .format when not passed", () => { + const currentOptions = options.parse(""); - describe("--rulesdir", () => { - it("should return a string for .rulesdir when passed a string", () => { - const currentOptions = options.parse("--rulesdir /morerules"); + assert.isString(currentOptions.format); + assert.strictEqual(currentOptions.format, "stylish"); + }); + }); - assert.isArray(currentOptions.rulesdir); - assert.deepStrictEqual(currentOptions.rulesdir, ["/morerules"]); - }); - }); + describe("-f", () => { + it("should return a string for .format when passed a string", () => { + const currentOptions = options.parse("-f compact"); - describe("--format", () => { - it("should return a string for .format when passed a string", () => { - const currentOptions = options.parse("--format compact"); + assert.isString(currentOptions.format); + assert.strictEqual(currentOptions.format, "compact"); + }); + }); - assert.isString(currentOptions.format); - assert.strictEqual(currentOptions.format, "compact"); - }); + describe("--version", () => { + it("should return true for .version when passed", () => { + const currentOptions = options.parse("--version"); - it("should return stylish for .format when not passed", () => { - const currentOptions = options.parse(""); + assert.isTrue(currentOptions.version); + }); + }); - assert.isString(currentOptions.format); - assert.strictEqual(currentOptions.format, "stylish"); - }); - }); + describe("-v", () => { + it("should return true for .version when passed", () => { + const currentOptions = options.parse("-v"); - describe("-f", () => { - it("should return a string for .format when passed a string", () => { - const currentOptions = options.parse("-f compact"); + assert.isTrue(currentOptions.version); + }); + }); + + describe("when asking for help", () => { + it("should return string of help text when called", () => { + const helpText = options.generateHelp(); - assert.isString(currentOptions.format); - assert.strictEqual(currentOptions.format, "compact"); - }); - }); + assert.isString(helpText); + }); + }); + + describe("--no-ignore", () => { + it("should return false for .ignore when passed", () => { + const currentOptions = options.parse("--no-ignore"); - describe("--version", () => { - it("should return true for .version when passed", () => { - const currentOptions = options.parse("--version"); + assert.isFalse(currentOptions.ignore); + }); + }); - assert.isTrue(currentOptions.version); - }); - }); + describe("--ignore-pattern", () => { + it("should return a string array for .ignorePattern when passed", () => { + const currentOptions = options.parse("--ignore-pattern *.js"); - describe("-v", () => { - it("should return true for .version when passed", () => { - const currentOptions = options.parse("-v"); + assert.ok(currentOptions.ignorePattern); + assert.strictEqual(currentOptions.ignorePattern.length, 1); + assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); + }); + + it("should return a string array for multiple values", () => { + const currentOptions = options.parse("--ignore-pattern *.js --ignore-pattern *.ts"); + + assert.ok(currentOptions.ignorePattern); + assert.strictEqual(currentOptions.ignorePattern.length, 2); + assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); + assert.strictEqual(currentOptions.ignorePattern[1], "*.ts"); + }); + + it("should return a string array of properly parsed values, when those values include commas", () => { + const currentOptions = options.parse("--ignore-pattern *.js --ignore-pattern foo-{bar,baz}.js"); + + assert.ok(currentOptions.ignorePattern); + assert.strictEqual(currentOptions.ignorePattern.length, 2); + assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); + assert.strictEqual(currentOptions.ignorePattern[1], "foo-{bar,baz}.js"); + }); + }); + + describe("--color", () => { + it("should return true for .color when passed --color", () => { + const currentOptions = options.parse("--color"); + + assert.isTrue(currentOptions.color); + }); + + it("should return false for .color when passed --no-color", () => { + const currentOptions = options.parse("--no-color"); + + assert.isFalse(currentOptions.color); + }); + }); + + describe("--stdin", () => { + it("should return true for .stdin when passed", () => { + const currentOptions = options.parse("--stdin"); + + assert.isTrue(currentOptions.stdin); + }); + }); + + describe("--stdin-filename", () => { + it("should return a string for .stdinFilename when passed", () => { + const currentOptions = options.parse("--stdin-filename test.js"); + + assert.strictEqual(currentOptions.stdinFilename, "test.js"); + }); + }); + + describe("--global", () => { + it("should return an array for a single occurrence", () => { + const currentOptions = options.parse("--global foo"); + + assert.isArray(currentOptions.global); + assert.strictEqual(currentOptions.global.length, 1); + assert.strictEqual(currentOptions.global[0], "foo"); + }); + + it("should split variable names using commas", () => { + const currentOptions = options.parse("--global foo,bar"); + + assert.isArray(currentOptions.global); + assert.strictEqual(currentOptions.global.length, 2); + assert.strictEqual(currentOptions.global[0], "foo"); + assert.strictEqual(currentOptions.global[1], "bar"); + }); + + it("should not split on colons", () => { + const currentOptions = options.parse("--global foo:false,bar:true"); + + assert.isArray(currentOptions.global); + assert.strictEqual(currentOptions.global.length, 2); + assert.strictEqual(currentOptions.global[0], "foo:false"); + assert.strictEqual(currentOptions.global[1], "bar:true"); + }); + + it("should concatenate successive occurrences", () => { + const currentOptions = options.parse("--global foo:true --global bar:false"); - assert.isTrue(currentOptions.version); - }); - }); + assert.isArray(currentOptions.global); + assert.strictEqual(currentOptions.global.length, 2); + assert.strictEqual(currentOptions.global[0], "foo:true"); + assert.strictEqual(currentOptions.global[1], "bar:false"); + }); + }); - describe("when asking for help", () => { - it("should return string of help text when called", () => { - const helpText = options.generateHelp(); - assert.isString(helpText); - }); - }); + describe("--quiet", () => { + it("should return true for .quiet when passed", () => { + const currentOptions = options.parse("--quiet"); - describe("--no-ignore", () => { - it("should return false for .ignore when passed", () => { - const currentOptions = options.parse("--no-ignore"); + assert.isTrue(currentOptions.quiet); + }); + }); - assert.isFalse(currentOptions.ignore); - }); - }); + describe("--max-warnings", () => { + it("should return correct value for .maxWarnings when passed", () => { + const currentOptions = options.parse("--max-warnings 10"); - describe("--ignore-path", () => { - it("should return a string for .ignorePath when passed", () => { - const currentOptions = options.parse("--ignore-path .gitignore"); + assert.strictEqual(currentOptions.maxWarnings, 10); + }); - assert.strictEqual(currentOptions.ignorePath, ".gitignore"); - }); - }); + it("should return -1 for .maxWarnings when not passed", () => { + const currentOptions = options.parse(""); - describe("--ignore-pattern", () => { - it("should return a string array for .ignorePattern when passed", () => { - const currentOptions = options.parse("--ignore-pattern *.js"); + assert.strictEqual(currentOptions.maxWarnings, -1); + }); - assert.ok(currentOptions.ignorePattern); - assert.strictEqual(currentOptions.ignorePattern.length, 1); - assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); - }); + it("should throw an error when supplied with a non-integer", () => { + assert.throws(() => { + options.parse("--max-warnings 10.2"); + }, /Invalid value for option 'max-warnings' - expected type Int/u); + }); + }); - it("should return a string array for multiple values", () => { - const currentOptions = options.parse("--ignore-pattern *.js --ignore-pattern *.ts"); + describe("--init", () => { + it("should return true for --init when passed", () => { + const currentOptions = options.parse("--init"); - assert.ok(currentOptions.ignorePattern); - assert.strictEqual(currentOptions.ignorePattern.length, 2); - assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); - assert.strictEqual(currentOptions.ignorePattern[1], "*.ts"); - }); + assert.isTrue(currentOptions.init); + }); + }); - it("should return a string array of properly parsed values, when those values include commas", () => { - const currentOptions = options.parse("--ignore-pattern *.js --ignore-pattern foo-{bar,baz}.js"); + describe("--fix", () => { + it("should return true for --fix when passed", () => { + const currentOptions = options.parse("--fix"); - assert.ok(currentOptions.ignorePattern); - assert.strictEqual(currentOptions.ignorePattern.length, 2); - assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); - assert.strictEqual(currentOptions.ignorePattern[1], "foo-{bar,baz}.js"); - }); - }); + assert.isTrue(currentOptions.fix); + }); + }); - describe("--color", () => { - it("should return true for .color when passed --color", () => { - const currentOptions = options.parse("--color"); + describe("--fix-type", () => { + it("should return one value with --fix-type is passed", () => { + const currentOptions = options.parse("--fix-type problem"); - assert.isTrue(currentOptions.color); - }); + assert.strictEqual(currentOptions.fixType.length, 1); + assert.strictEqual(currentOptions.fixType[0], "problem"); + }); - it("should return false for .color when passed --no-color", () => { - const currentOptions = options.parse("--no-color"); + it("should return two values when --fix-type is passed twice", () => { + const currentOptions = options.parse("--fix-type problem --fix-type suggestion"); - assert.isFalse(currentOptions.color); - }); - }); + assert.strictEqual(currentOptions.fixType.length, 2); + assert.strictEqual(currentOptions.fixType[0], "problem"); + assert.strictEqual(currentOptions.fixType[1], "suggestion"); + }); - describe("--stdin", () => { - it("should return true for .stdin when passed", () => { - const currentOptions = options.parse("--stdin"); + it("should return two values when --fix-type is passed a comma-separated value", () => { + const currentOptions = options.parse("--fix-type problem,suggestion"); - assert.isTrue(currentOptions.stdin); - }); - }); + assert.strictEqual(currentOptions.fixType.length, 2); + assert.strictEqual(currentOptions.fixType[0], "problem"); + assert.strictEqual(currentOptions.fixType[1], "suggestion"); + }); + }); - describe("--stdin-filename", () => { - it("should return a string for .stdinFilename when passed", () => { - const currentOptions = options.parse("--stdin-filename test.js"); + describe("--debug", () => { + it("should return true for --debug when passed", () => { + const currentOptions = options.parse("--debug"); - assert.strictEqual(currentOptions.stdinFilename, "test.js"); - }); - }); + assert.isTrue(currentOptions.debug); + }); + }); - describe("--global", () => { - it("should return an array for a single occurrence", () => { - const currentOptions = options.parse("--global foo"); + describe("--inline-config", () => { + it("should return false when passed --no-inline-config", () => { + const currentOptions = options.parse("--no-inline-config"); - assert.isArray(currentOptions.global); - assert.strictEqual(currentOptions.global.length, 1); - assert.strictEqual(currentOptions.global[0], "foo"); - }); + assert.isFalse(currentOptions.inlineConfig); + }); - it("should split variable names using commas", () => { - const currentOptions = options.parse("--global foo,bar"); + it("should return true for --inline-config when empty", () => { + const currentOptions = options.parse(""); - assert.isArray(currentOptions.global); - assert.strictEqual(currentOptions.global.length, 2); - assert.strictEqual(currentOptions.global[0], "foo"); - assert.strictEqual(currentOptions.global[1], "bar"); - }); + assert.isTrue(currentOptions.inlineConfig); + }); + }); - it("should not split on colons", () => { - const currentOptions = options.parse("--global foo:false,bar:true"); + describe("--print-config", () => { + it("should return file path when passed --print-config", () => { + const currentOptions = options.parse("--print-config file.js"); - assert.isArray(currentOptions.global); - assert.strictEqual(currentOptions.global.length, 2); - assert.strictEqual(currentOptions.global[0], "foo:false"); - assert.strictEqual(currentOptions.global[1], "bar:true"); + assert.strictEqual(currentOptions.printConfig, "file.js"); + }); + }); }); - it("should concatenate successive occurrences", () => { - const currentOptions = options.parse("--global foo:true --global bar:false"); - - assert.isArray(currentOptions.global); - assert.strictEqual(currentOptions.global.length, 2); - assert.strictEqual(currentOptions.global[0], "foo:true"); - assert.strictEqual(currentOptions.global[1], "bar:false"); - }); }); - describe("--plugin", () => { - it("should return an array when passed a single occurrence", () => { - const currentOptions = options.parse("--plugin single"); - assert.isArray(currentOptions.plugin); - assert.strictEqual(currentOptions.plugin.length, 1); - assert.strictEqual(currentOptions.plugin[0], "single"); - }); - - it("should return an array when passed a comma-delimited string", () => { - const currentOptions = options.parse("--plugin foo,bar"); - - assert.isArray(currentOptions.plugin); - assert.strictEqual(currentOptions.plugin.length, 2); - assert.strictEqual(currentOptions.plugin[0], "foo"); - assert.strictEqual(currentOptions.plugin[1], "bar"); - }); - - it("should return an array when passed multiple times", () => { - const currentOptions = options.parse("--plugin foo --plugin bar"); + describe("--ext", () => { + it("should return an array with one item when passed .jsx", () => { + const currentOptions = eslintrcOptions.parse("--ext .jsx"); - assert.isArray(currentOptions.plugin); - assert.strictEqual(currentOptions.plugin.length, 2); - assert.strictEqual(currentOptions.plugin[0], "foo"); - assert.strictEqual(currentOptions.plugin[1], "bar"); + assert.isArray(currentOptions.ext); + assert.strictEqual(currentOptions.ext[0], ".jsx"); }); - }); - describe("--quiet", () => { - it("should return true for .quiet when passed", () => { - const currentOptions = options.parse("--quiet"); + it("should return an array with two items when passed .js and .jsx", () => { + const currentOptions = eslintrcOptions.parse("--ext .jsx --ext .js"); - assert.isTrue(currentOptions.quiet); + assert.isArray(currentOptions.ext); + assert.strictEqual(currentOptions.ext[0], ".jsx"); + assert.strictEqual(currentOptions.ext[1], ".js"); }); - }); - describe("--max-warnings", () => { - it("should return correct value for .maxWarnings when passed", () => { - const currentOptions = options.parse("--max-warnings 10"); + it("should return an array with two items when passed .jsx,.js", () => { + const currentOptions = eslintrcOptions.parse("--ext .jsx,.js"); - assert.strictEqual(currentOptions.maxWarnings, 10); + assert.isArray(currentOptions.ext); + assert.strictEqual(currentOptions.ext[0], ".jsx"); + assert.strictEqual(currentOptions.ext[1], ".js"); }); - it("should return -1 for .maxWarnings when not passed", () => { - const currentOptions = options.parse(""); - - assert.strictEqual(currentOptions.maxWarnings, -1); - }); + it("should not exist when not passed", () => { + const currentOptions = eslintrcOptions.parse(""); - it("should throw an error when supplied with a non-integer", () => { - assert.throws(() => { - options.parse("--max-warnings 10.2"); - }, /Invalid value for option 'max-warnings' - expected type Int/u); + assert.notProperty(currentOptions, "ext"); }); }); - describe("--init", () => { - it("should return true for --init when passed", () => { - const currentOptions = options.parse("--init"); + describe("--rulesdir", () => { + it("should return a string for .rulesdir when passed a string", () => { + const currentOptions = eslintrcOptions.parse("--rulesdir /morerules"); - assert.isTrue(currentOptions.init); + assert.isArray(currentOptions.rulesdir); + assert.deepStrictEqual(currentOptions.rulesdir, ["/morerules"]); }); }); - describe("--fix", () => { - it("should return true for --fix when passed", () => { - const currentOptions = options.parse("--fix"); + describe("--ignore-path", () => { + it("should return a string for .ignorePath when passed", () => { + const currentOptions = eslintrcOptions.parse("--ignore-path .gitignore"); - assert.isTrue(currentOptions.fix); + assert.strictEqual(currentOptions.ignorePath, ".gitignore"); }); }); - describe("--fix-type", () => { - it("should return one value with --fix-type is passed", () => { - const currentOptions = options.parse("--fix-type problem"); - - assert.strictEqual(currentOptions.fixType.length, 1); - assert.strictEqual(currentOptions.fixType[0], "problem"); - }); - - it("should return two values when --fix-type is passed twice", () => { - const currentOptions = options.parse("--fix-type problem --fix-type suggestion"); - - assert.strictEqual(currentOptions.fixType.length, 2); - assert.strictEqual(currentOptions.fixType[0], "problem"); - assert.strictEqual(currentOptions.fixType[1], "suggestion"); - }); - - it("should return two values when --fix-type is passed a comma-separated value", () => { - const currentOptions = options.parse("--fix-type problem,suggestion"); + describe("--parser", () => { + it("should return a string for --parser when passed", () => { + const currentOptions = eslintrcOptions.parse("--parser test"); - assert.strictEqual(currentOptions.fixType.length, 2); - assert.strictEqual(currentOptions.fixType[0], "problem"); - assert.strictEqual(currentOptions.fixType[1], "suggestion"); + assert.strictEqual(currentOptions.parser, "test"); }); }); - describe("--debug", () => { - it("should return true for --debug when passed", () => { - const currentOptions = options.parse("--debug"); + describe("--plugin", () => { + it("should return an array when passed a single occurrence", () => { + const currentOptions = eslintrcOptions.parse("--plugin single"); - assert.isTrue(currentOptions.debug); + assert.isArray(currentOptions.plugin); + assert.strictEqual(currentOptions.plugin.length, 1); + assert.strictEqual(currentOptions.plugin[0], "single"); }); - }); - describe("--inline-config", () => { - it("should return false when passed --no-inline-config", () => { - const currentOptions = options.parse("--no-inline-config"); + it("should return an array when passed a comma-delimited string", () => { + const currentOptions = eslintrcOptions.parse("--plugin foo,bar"); - assert.isFalse(currentOptions.inlineConfig); + assert.isArray(currentOptions.plugin); + assert.strictEqual(currentOptions.plugin.length, 2); + assert.strictEqual(currentOptions.plugin[0], "foo"); + assert.strictEqual(currentOptions.plugin[1], "bar"); }); - it("should return true for --inline-config when empty", () => { - const currentOptions = options.parse(""); + it("should return an array when passed multiple times", () => { + const currentOptions = eslintrcOptions.parse("--plugin foo --plugin bar"); - assert.isTrue(currentOptions.inlineConfig); + assert.isArray(currentOptions.plugin); + assert.strictEqual(currentOptions.plugin.length, 2); + assert.strictEqual(currentOptions.plugin[0], "foo"); + assert.strictEqual(currentOptions.plugin[1], "bar"); }); }); - describe("--parser", () => { - it("should return a string for --parser when passed", () => { - const currentOptions = options.parse("--parser test"); + describe("--no-config-lookup", () => { + it("should return a string for .rulesdir when passed a string", () => { + const currentOptions = flatOptions.parse("--no-config-lookup foo.js"); - assert.strictEqual(currentOptions.parser, "test"); + assert.isFalse(currentOptions.configLookup); }); }); - describe("--print-config", () => { - it("should return file path when passed --print-config", () => { - const currentOptions = options.parse("--print-config file.js"); - - assert.strictEqual(currentOptions.printConfig, "file.js"); - }); - }); }); diff --git a/tests/lib/rule-tester/flat-rule-tester.js b/tests/lib/rule-tester/flat-rule-tester.js index bdc196e1653..c73099d1d54 100644 --- a/tests/lib/rule-tester/flat-rule-tester.js +++ b/tests/lib/rule-tester/flat-rule-tester.js @@ -142,12 +142,14 @@ describe("FlatRuleTester", () => { FlatRuleTester.setDefaultConfig(config); }; } - assert.throw(setConfig()); - assert.throw(setConfig(1)); - assert.throw(setConfig(3.14)); - assert.throw(setConfig("foo")); - assert.throw(setConfig(null)); - assert.throw(setConfig(true)); + const errorMessage = "FlatRuleTester.setDefaultConfig: config must be an object"; + + assert.throw(setConfig(), errorMessage); + assert.throw(setConfig(1), errorMessage); + assert.throw(setConfig(3.14), errorMessage); + assert.throw(setConfig("foo"), errorMessage); + assert.throw(setConfig(null), errorMessage); + assert.throw(setConfig(true), errorMessage); }); it("should pass-through the globals config to the tester then to the to rule", () => { diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index 0e35258750c..096e639e95f 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -1412,12 +1412,14 @@ describe("RuleTester", () => { RuleTester.setDefaultConfig(config); }; } - assert.throw(setConfig()); - assert.throw(setConfig(1)); - assert.throw(setConfig(3.14)); - assert.throw(setConfig("foo")); - assert.throw(setConfig(null)); - assert.throw(setConfig(true)); + const errorMessage = "RuleTester.setDefaultConfig: config must be an object"; + + assert.throw(setConfig(), errorMessage); + assert.throw(setConfig(1), errorMessage); + assert.throw(setConfig(3.14), errorMessage); + assert.throw(setConfig("foo"), errorMessage); + assert.throw(setConfig(null), errorMessage); + assert.throw(setConfig(true), errorMessage); }); it("should pass-through the globals config to the tester then to the to rule", () => { @@ -2286,7 +2288,7 @@ describe("RuleTester", () => { assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"function-style-rule\" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/developer-guide/working-with-rules", + "\"function-style-rule\" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules", "DeprecationWarning" ] ); @@ -2304,7 +2306,7 @@ describe("RuleTester", () => { assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"rule-with-no-meta-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas", + "\"rule-with-no-meta-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", "DeprecationWarning" ] ); @@ -2322,7 +2324,7 @@ describe("RuleTester", () => { assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"rule-with-no-schema-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas", + "\"rule-with-no-schema-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", "DeprecationWarning" ] ); @@ -2332,7 +2334,7 @@ describe("RuleTester", () => { const ruleWithUndefinedSchema = { meta: { type: "problem", - // eslint-disable-next-line no-undefined -- intentioally added for test case + // eslint-disable-next-line no-undefined -- intentionally added for test case schema: undefined }, create(context) { @@ -2355,7 +2357,7 @@ describe("RuleTester", () => { assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"rule-with-undefined-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas", + "\"rule-with-undefined-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", "DeprecationWarning" ] ); @@ -2387,7 +2389,7 @@ describe("RuleTester", () => { assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"rule-with-null-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas", + "\"rule-with-null-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", "DeprecationWarning" ] ); diff --git a/tests/lib/rules/array-callback-return.js b/tests/lib/rules/array-callback-return.js index 13a01125c99..6343d3e4027 100644 --- a/tests/lib/rules/array-callback-return.js +++ b/tests/lib/rules/array-callback-return.js @@ -57,6 +57,8 @@ ruleTester.run("array-callback-return", rule, { "foo.filter(function() { return true; })", "foo.find(function() { return true; })", "foo.findIndex(function() { return true; })", + "foo.findLast(function() { return true; })", + "foo.findLastIndex(function() { return true; })", "foo.flatMap(function() { return true; })", "foo.forEach(function() { return; })", "foo.map(function() { return true; })", @@ -64,6 +66,7 @@ ruleTester.run("array-callback-return", rule, { "foo.reduceRight(function() { return true; })", "foo.some(function() { return true; })", "foo.sort(function() { return 0; })", + "foo.toSorted(function() { return 0; })", { code: "foo.every(() => { return true; })", parserOptions: { ecmaVersion: 6 } }, "foo.every(function() { if (a) return true; else return false; })", "foo.every(function() { switch (a) { case 0: bar(); default: return true; } })", @@ -77,6 +80,8 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.filter(function() { return; })", options: allowImplicitOptions }, { code: "foo.find(function() { return; })", options: allowImplicitOptions }, { code: "foo.findIndex(function() { return; })", options: allowImplicitOptions }, + { code: "foo.findLast(function() { return; })", options: allowImplicitOptions }, + { code: "foo.findLastIndex(function() { return; })", options: allowImplicitOptions }, { code: "foo.flatMap(function() { return; })", options: allowImplicitOptions }, { code: "foo.forEach(function() { return; })", options: allowImplicitOptions }, { code: "foo.map(function() { return; })", options: allowImplicitOptions }, @@ -84,6 +89,7 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.reduceRight(function() { return; })", options: allowImplicitOptions }, { code: "foo.some(function() { return; })", options: allowImplicitOptions }, { code: "foo.sort(function() { return; })", options: allowImplicitOptions }, + { code: "foo.toSorted(function() { return; })", options: allowImplicitOptions }, { code: "foo.every(() => { return; })", options: allowImplicitOptions, parserOptions: { ecmaVersion: 6 } }, { code: "foo.every(function() { if (a) return; else return a; })", options: allowImplicitOptions }, { code: "foo.every(function() { switch (a) { case 0: bar(); default: return; } })", options: allowImplicitOptions }, @@ -129,8 +135,12 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.filter(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] }, { code: "foo.find(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.find" } }] }, { code: "foo.find(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.find" } }] }, + { code: "foo.findLast(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.findLast" } }] }, + { code: "foo.findLast(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.findLast" } }] }, { code: "foo.findIndex(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.findIndex" } }] }, { code: "foo.findIndex(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.findIndex" } }] }, + { code: "foo.findLastIndex(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.findLastIndex" } }] }, + { code: "foo.findLastIndex(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.findLastIndex" } }] }, { code: "foo.flatMap(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.flatMap" } }] }, { code: "foo.flatMap(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.flatMap" } }] }, { code: "foo.map(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.map" } }] }, @@ -143,6 +153,8 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.some(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.some" } }] }, { code: "foo.sort(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.sort" } }] }, { code: "foo.sort(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.sort" } }] }, + { code: "foo.toSorted(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.toSorted" } }] }, + { code: "foo.toSorted(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.toSorted" } }] }, { code: "foo.bar.baz.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, { code: "foo.bar.baz.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, { code: "foo[\"every\"](function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, @@ -182,6 +194,7 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.bar.baz.every(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, { code: "foo.every(cb || function() {})", options: allowImplicitOptions, errors: ["Array.prototype.every() expects a return value from function."] }, { code: "[\"foo\",\"bar\"].sort(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.sort" } }] }, + { code: "[\"foo\",\"bar\"].toSorted(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.toSorted" } }] }, { code: "foo.forEach(x => x)", options: allowImplicitCheckForEach, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, { code: "foo.forEach(function bar(x) { return x;})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] }, diff --git a/tests/lib/rules/comma-dangle.js b/tests/lib/rules/comma-dangle.js index b02a74a5528..da301dfcaef 100644 --- a/tests/lib/rules/comma-dangle.js +++ b/tests/lib/rules/comma-dangle.js @@ -12,7 +12,8 @@ const path = require("path"), { unIndent } = require("../../_utils"), rule = require("../../../lib/rules/comma-dangle"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -279,6 +280,14 @@ ruleTester.run("comma-dangle", rule, { code: "function foo(a,\nb) {}", options: ["always-multiline"] }, + { + code: "foo(a,\nb\n)", + options: ["always-multiline"] + }, + { + code: "function foo(a,\nb\n) {}", + options: ["always-multiline"] + }, { code: "foo(a,\nb)", options: ["always-multiline"] @@ -321,6 +330,16 @@ ruleTester.run("comma-dangle", rule, { options: ["always-multiline"], parserOptions: { ecmaVersion: 7 } }, + { + code: "function foo(a,\nb\n) {}", + options: ["always-multiline"], + parserOptions: { ecmaVersion: 7 } + }, + { + code: "foo(a,\nb\n)", + options: ["always-multiline"], + parserOptions: { ecmaVersion: 7 } + }, { code: "function foo(a,\nb) {}", options: ["only-multiline"], @@ -1853,3 +1872,100 @@ let d = 0;export {d,}; } ] }); + +const flatRuleTester = new FlatRuleTester(); + +// https://github.com/eslint/eslint/issues/16442 +flatRuleTester.run("comma-dangle", rule, { + valid: [ + { + code: "function f(\n a,\n b\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + } + }, + { + code: "f(\n a,\n b\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + } + }, + { + code: "function f(\n a,\n b\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2016 + } + }, + { + code: "f(\n a,\n b\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2016 + } + } + ], + + invalid: [ + { + code: "function f(\n a,\n b\n) {}", + output: "function f(\n a,\n b,\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2017 + }, + errors: [{ + messageId: "missing", + type: "Identifier", + line: 3, + column: 3 + }] + }, + { + code: "f(\n a,\n b\n);", + output: "f(\n a,\n b,\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2017 + }, + errors: [{ + messageId: "missing", + type: "Identifier", + line: 3, + column: 3 + }] + }, + { + code: "function f(\n a,\n b\n) {}", + output: "function f(\n a,\n b,\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: "latest" + }, + errors: [{ + messageId: "missing", + type: "Identifier", + line: 3, + column: 3 + }] + }, + { + code: "f(\n a,\n b\n);", + output: "f(\n a,\n b,\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: "latest" + }, + errors: [{ + messageId: "missing", + type: "Identifier", + line: 3, + column: 3 + }] + } + ] +}); diff --git a/tests/lib/rules/func-name-matching.js b/tests/lib/rules/func-name-matching.js index 7e215254410..c020066e752 100644 --- a/tests/lib/rules/func-name-matching.js +++ b/tests/lib/rules/func-name-matching.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/func-name-matching"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -504,7 +505,8 @@ ruleTester.run("func-name-matching", rule, { code: "class C { #x; foo() { a.b.#x = function y() {}; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } - } + }, + "var obj = { '\\u1885': function foo() {} };" // not a valid identifier in es5 ], invalid: [ { @@ -878,6 +880,39 @@ ruleTester.run("func-name-matching", rule, { errors: [ { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } ] + }, + { + code: "var obj = { '\\u1885': function foo() {} };", // valid identifier in es2015 + parserOptions: { ecmaVersion: 6 }, + errors: [ + { messageId: "matchProperty", data: { funcName: "foo", name: "\u1885" } } + ] + } + ] +}); + +const flatRuleTester = new FlatRuleTester(); + +flatRuleTester.run("func-name-matching", rule, { + valid: [ + { + code: "var obj = { '\\u1885': function foo() {} };", // not a valid identifier in es5 + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + } + } + ], + + invalid: [ + { + code: "var obj = { '\\u1885': function foo() {} };", // valid identifier in es2015 + languageOptions: { + ecmaVersion: 2015 + }, + errors: [ + { messageId: "matchProperty", data: { funcName: "foo", name: "\u1885" } } + ] } ] }); diff --git a/tests/lib/rules/getter-return.js b/tests/lib/rules/getter-return.js index 92032c066fb..3fb755caca5 100644 --- a/tests/lib/rules/getter-return.js +++ b/tests/lib/rules/getter-return.js @@ -57,11 +57,27 @@ ruleTester.run("getter-return", rule, { "Object.defineProperties(foo, { bar: { get: function () {return true;}} });", "Object.defineProperties(foo, { bar: { get: function () { ~function (){ return true; }(); return true;}} });", + /* + * test reflect.defineProperty(s) + * option: {allowImplicit: false} + */ + "Reflect.defineProperty(foo, \"bar\", { get: function () {return true;}});", + "Reflect.defineProperty(foo, \"bar\", { get: function () { ~function (){ return true; }();return true;}});", + + /* + * test object.create(s) + * option: {allowImplicit: false} + */ + "Object.create(foo, { bar: { get() {return true;} } });", + "Object.create(foo, { bar: { get: function () {return true;} } });", + "Object.create(foo, { bar: { get: () => {return true;} } });", + // option: {allowImplicit: true} { code: "Object.defineProperty(foo, \"bar\", { get: function () {return true;}});", options }, { code: "Object.defineProperty(foo, \"bar\", { get: function (){return;}});", options }, { code: "Object.defineProperties(foo, { bar: { get: function () {return true;}} });", options }, { code: "Object.defineProperties(foo, { bar: { get: function () {return;}} });", options }, + { code: "Reflect.defineProperty(foo, \"bar\", { get: function () {return true;}});", options }, // not getter. "var get = function(){};", @@ -73,7 +89,10 @@ ruleTester.run("getter-return", rule, { "var foo = { bar: function(){return true;} };", "var foo = { get: function () {} }", "var foo = { get: () => {}};", - "class C { get; foo() {} }" + "class C { get; foo() {} }", + "foo.defineProperty(null, { get() {} });", + "foo.defineProperties(null, { bar: { get() {} } });", + "foo.create(null, { bar: { get() {} } });" ], invalid: [ @@ -220,11 +239,67 @@ ruleTester.run("getter-return", rule, { { code: "Object.defineProperty(foo, \"bar\", { get: function (){if(bar) {return true;}}});", errors: [{ messageId: "expectedAlways" }] }, { code: "Object.defineProperty(foo, \"bar\", { get: function (){ ~function () { return true; }()}});", errors: [{ messageId: "expected" }] }, + /* + * test reflect.defineProperty(s) + * option: {allowImplicit: false} + */ + { + code: "Reflect.defineProperty(foo, 'bar', { get: function (){}});", + errors: [{ + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 38, + endLine: 1, + endColumn: 52 + }] + }, + + /* + * test object.create(s) + * option: {allowImplicit: false} + */ + { + code: "Object.create(foo, { bar: { get: function() {} } })", + errors: [{ + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 29, + endLine: 1, + endColumn: 42 + }] + }, + { + code: "Object.create(foo, { bar: { get() {} } })", + errors: [{ + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 29, + endLine: 1, + endColumn: 32 + }] + }, + { + code: "Object.create(foo, { bar: { get: () => {} } })", + errors: [{ + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 29, + endLine: 1, + endColumn: 34 + }] + }, + // option: {allowImplicit: true} { code: "Object.defineProperties(foo, { bar: { get: function () {}} });", options, errors: [{ messageId: "expected" }] }, { code: "Object.defineProperties(foo, { bar: { get: function (){if(bar) {return true;}}}});", options, errors: [{ messageId: "expectedAlways" }] }, { code: "Object.defineProperties(foo, { bar: { get: function () {~function () { return true; }()}} });", options, errors: [{ messageId: "expected" }] }, { code: "Object.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ messageId: "expected" }] }, + { code: "Object.create(foo, { bar: { get: function (){} } });", options, errors: [{ messageId: "expected" }] }, + { code: "Reflect.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ messageId: "expected" }] }, // Optional chaining { @@ -248,6 +323,12 @@ ruleTester.run("getter-return", rule, { options, parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "expected", data: { name: "method 'get'" } }] + }, + { + code: "(Object?.create)(foo, { bar: { get: function (){} } });", + options, + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "expected", data: { name: "method 'get'" } }] } ] }); diff --git a/tests/lib/rules/id-length.js b/tests/lib/rules/id-length.js index e9a023bcfd1..871db908f92 100644 --- a/tests/lib/rules/id-length.js +++ b/tests/lib/rules/id-length.js @@ -113,6 +113,123 @@ ruleTester.run("id-length", rule, { code: "class Foo { #abc = 1 }", options: [{ max: 3 }], parserOptions: { ecmaVersion: 2022 } + }, + + // Identifier consisting of two code units + { + code: "var 𠮟 = 2", + options: [{ min: 1, max: 1 }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var 葛󠄀 = 2", // 2 code points but only 1 grapheme + options: [{ min: 1, max: 1 }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var a = { 𐌘: 1 };", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "(𐌘) => { 𐌘 * 𐌘 };", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "class 𠮟 { }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "class F { 𐌘() {} }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "class F { #𐌘() {} }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class F { 𐌘 = 1 }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class F { #𐌘 = 1 }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "function f(...𐌘) { }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "function f([𐌘]) { }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "var [ 𐌘 ] = a;", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "var { p: [𐌘]} = {};", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "function f({𐌘}) { }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "var { 𐌘 } = {};", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "var { p: 𐌘} = {};", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "({ prop: o.𐌘 } = {});", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } } ], invalid: [ @@ -564,6 +681,157 @@ ruleTester.run("id-length", rule, { errors: [ tooLongErrorPrivate ] + }, + + // Identifier consisting of two code units + { + code: "var 𠮟 = 2", + parserOptions: { ecmaVersion: 6 }, + errors: [ + tooShortError + ] + }, + { + code: "var 葛󠄀 = 2", // 2 code points but only 1 grapheme + parserOptions: { ecmaVersion: 6 }, + errors: [ + tooShortError + ] + }, + { + code: "var myObj = { 𐌘: 1 };", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "(𐌘) => { 𐌘 * 𐌘 };", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "class 𠮟 { }", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "class Foo { 𐌘() {} }", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "class Foo1 { #𐌘() {} }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [ + tooShortErrorPrivate + ] + }, + { + code: "class Foo2 { 𐌘 = 1 }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [ + tooShortError + ] + }, + { + code: "class Foo3 { #𐌘 = 1 }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [ + tooShortErrorPrivate + ] + }, + { + code: "function foo1(...𐌘) { }", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "function foo([𐌘]) { }", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "var [ 𐌘 ] = arr;", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "var { prop: [𐌘]} = {};", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "function foo({𐌘}) { }", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "var { 𐌘 } = {};", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "var { prop: 𐌘} = {};", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "({ prop: obj.𐌘 } = {});", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] } ] }); diff --git a/tests/lib/rules/key-spacing.js b/tests/lib/rules/key-spacing.js index dd0fcbc4dcf..9ac345d39c1 100644 --- a/tests/lib/rules/key-spacing.js +++ b/tests/lib/rules/key-spacing.js @@ -278,7 +278,9 @@ ruleTester.run("key-spacing", rule, { " method() {", " return 42;", " },", - " baz: 456", + " baz: 456,", + " 10: ", + " 10", "};" ].join("\n"), options: [{ align: "value" }], @@ -360,6 +362,10 @@ ruleTester.run("key-spacing", rule, { " bat: function() {", " return this.a;", " },", + " barfoo:", + " [", + " 1", + " ],", " baz: 42", "};" ].join("\n"), @@ -633,6 +639,10 @@ ruleTester.run("key-spacing", rule, { " internalGroup: {", " internal : true,", " ext : false", + " },", + " func3:", + " function () {", + " var test3 = true;", " }", "})" ].join("\n"), @@ -971,6 +981,109 @@ ruleTester.run("key-spacing", rule, { } }], parserOptions: { ecmaVersion: 6 } + }, + + // https://github.com/eslint/eslint/issues/16490 + { + code: ` + var foo = + { + id: 1, + code: 2, + [n]: 3, + message: + "some value on the next line", + }; + `, + options: [{ + align: "value" + }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: ` + var foo = + { + id : 1, + code : 2, + message : + "some value on the next line", + }; + `, + options: [{ + align: "colon", + beforeColon: true + }] + }, + { + code: ` + ({ + a: 1, + // different group + bcd: + 2 + }) + `, + options: [{ + align: "value" + }] + }, + { + code: ` + ({ + foo : 1, + bar : 2, + foobar : + 3 + }) + `, + options: [{ + align: "value", + beforeColon: true, + mode: "minimum" + }] + }, + { + code: ` + ({ + oneLine: 1, + ["some key " + + "spanning multiple lines"]: 2 + }) + `, + options: [{ + align: "value" + }], + parserOptions: { ecmaVersion: 6 } + }, + + // https://github.com/eslint/eslint/issues/16674 + { + code: ` + a = { + item : 123, + longerItem : ( + 1 + 1 + ), + }; + `, + options: [{ + align: { + beforeColon: true, + afterColon: true, + on: "colon" + } + }] + }, + { + code: ` + a = { + item: 123, + longerItem: // a comment - not a token + (1 + 1), + }; + `, + options: [{ align: "value" }] }], invalid: [{ code: "var a ={'key' : value };", @@ -1464,7 +1577,9 @@ ruleTester.run("key-spacing", rule, { " method() {", " return 42;", " },", - " baz: 456", + " baz: 456,", + " 10: ", + " 10", "};" ].join("\n"), output: [ @@ -1473,7 +1588,9 @@ ruleTester.run("key-spacing", rule, { " method() {", " return 42;", " },", - " baz: 456", + " baz: 456,", + " 10: ", + " 10", "};" ].join("\n"), options: [{ align: "value" }], @@ -2374,6 +2491,149 @@ ruleTester.run("key-spacing", rule, { { messageId: "extraValue", data: { computed: "", key: "🎁" }, line: 4, column: 21, type: "Literal" }, { messageId: "extraValue", data: { computed: "", key: "🇮🇳" }, line: 5, column: 23, type: "Literal" } ] - } - ] + }, + + // https://github.com/eslint/eslint/issues/16490 + { + code: ` + var foo = + { + id: 1, + code: 2, + [n]: 3, + message: + "some value on the next line", + }; + `, + output: ` + var foo = + { + id: 1, + code: 2, + [n]: 3, + message: + "some value on the next line", + }; + `, + options: [{ + align: "value" + }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { messageId: "extraValue", data: { computed: "", key: "id" }, line: 4, column: 19, type: "Literal" }, + { messageId: "extraValue", data: { computed: "", key: "code" }, line: 5, column: 21, type: "Literal" }, + { messageId: "extraValue", data: { computed: "computed ", key: "n" }, line: 6, column: 20, type: "Literal" } + ] + }, + { + code: ` + var foo = + { + id : 1, + code : 2, + message : + "some value on the next line", + }; + `, + output: ` + var foo = + { + id : 1, + code : 2, + message : + "some value on the next line", + }; + `, + options: [{ + align: "colon", + beforeColon: true + }], + errors: [ + { messageId: "extraKey", data: { computed: "", key: "id" }, line: 4, column: 19, type: "Identifier" }, + { messageId: "extraKey", data: { computed: "", key: "code" }, line: 5, column: 21, type: "Identifier" } + ] + }, + { + code: ` + ({ + a: 1, + // different group + bcd: + 2 + }) + `, + output: ` + ({ + a: 1, + // different group + bcd: + 2 + }) + `, + options: [{ + align: "value" + }], + errors: [ + { messageId: "extraValue", data: { computed: "", key: "a" }, line: 3, column: 18, type: "Literal" } + ] + }, + { + code: [ + "({", + " singleLine : 10,", + " newGroup :", + " function() {", + " var test3 = true;", + " }", + "})" + ].join("\n"), + output: [ + "({", + " singleLine: 10,", + " newGroup:", + " function() {", + " var test3 = true;", + " }", + "})" + ].join("\n"), + options: [{ + multiLine: { + beforeColon: false + }, + align: { + on: "colon", + beforeColon: true + } + }], + errors: [ + { messageId: "extraKey", data: { computed: "", key: "singleLine" }, line: 2, column: 15, type: "Identifier" }, + { messageId: "extraKey", data: { computed: "", key: "newGroup" }, line: 3, column: 13, type: "Identifier" } + ] + }, + + // https://github.com/eslint/eslint/issues/16674 + { + code: + ` + c = { + item: 123, + longerItem: ( + 1 + 1 + ), + }; + `, + output: + ` + c = { + item : 123, + longerItem: ( + 1 + 1 + ), + }; + `, + options: [{ align: "colon" }], + errors: [ + { messageId: "missingKey", data: { computed: "", key: "item" }, line: 3, column: 13, type: "Identifier" } + ] + }] }); diff --git a/tests/lib/rules/lines-around-comment.js b/tests/lib/rules/lines-around-comment.js index 379698549e4..a55d6fe2719 100644 --- a/tests/lib/rules/lines-around-comment.js +++ b/tests/lib/rules/lines-around-comment.js @@ -1051,6 +1051,25 @@ ruleTester.run("lines-around-comment", rule, { { code: "foo\n/* this is pragmatic */", options: [{ applyDefaultIgnorePatterns: false, ignorePattern: "pragma" }] + }, + + // Hashbang comment + { + code: "#!comment\n\nvar a = 1;", + options: [{ afterHashbangComment: true }] + }, + "#!comment\nvar a = 1;", + { + code: "#!comment\nvar a = 1;", + options: [{}] + }, + { + code: "#!comment\nvar a = 1;", + options: [{ afterHashbangComment: false }] + }, + { + code: "#!comment\nvar a = 1;", + options: [{ afterLineComment: true, afterBlockComment: true }] } ], @@ -2193,6 +2212,14 @@ ruleTester.run("lines-around-comment", rule, { afterLineComment: true }], errors: [{ messageId: "before", type: "Line" }] + }, + + // Hashbang comment + { + code: "#!foo\nvar a = 1;", + output: "#!foo\n\nvar a = 1;", + options: [{ afterHashbangComment: true }], + errors: [{ messageId: "after", type: "Shebang" }] } ] diff --git a/tests/lib/rules/logical-assignment-operators.js b/tests/lib/rules/logical-assignment-operators.js new file mode 100644 index 00000000000..ba839b5c6a8 --- /dev/null +++ b/tests/lib/rules/logical-assignment-operators.js @@ -0,0 +1,1460 @@ +/** + * @fileoverview Tests for logical-assignment-operators. + * @author Daniel Martens + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/logical-assignment-operators"), + { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } }); + +ruleTester.run("logical-assignment-operators", rule, { + valid: [ + + // Unrelated + "a || b", + "a && b", + "a ?? b", + "a || a || b", + "var a = a || b", + "a === undefined ? a : b", + "while (a) a = b", + + // Preferred + "a ||= b", + "a &&= b", + "a ??= b", + + // > Operator + "a += a || b", + "a *= a || b", + "a ||= a || b", + "a &&= a || b", + + // > Right + "a = a", + "a = b", + "a = a === b", + "a = a + b", + "a = a / b", + "a = fn(a) || b", + + // > Reference + "a = false || c", + "a = f() || g()", + "a = b || c", + "a = b || a", + "object.a = object.b || c", + "[a] = a || b", + "({ a } = a || b)", + + // Logical + "(a = b) || a", + "a + (a = b)", + "a || (b ||= c)", + "a || (b &&= c)", + "a || b === 0", + "a || fn()", + "a || (b && c)", + "a || (b ?? c)", + + // > Reference + "a || (b = c)", + "a || (a ||= b)", + "fn() || (a = b)", + "a.b || (a = b)", + "a?.b || (a.b = b)", + { + code: "class Class { #prop; constructor() { this.#prop || (this.prop = value) } }", + parserOptions: { ecmaVersion: 2022 } + }, { + code: "class Class { #prop; constructor() { this.prop || (this.#prop = value) } }", + parserOptions: { ecmaVersion: 2022 } + }, + + // If + "if (a) a = b", + { + code: "if (a) a = b", + options: ["always", { enforceForIfStatements: false }] + }, { + code: "if (a) { a = b } else {}", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) { a = b } else if (a) {}", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (unrelated) {} else if (a) a = b; else {}", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (unrelated) {} else if (a) a = b; else if (unrelated) {}", + options: ["always", { enforceForIfStatements: true }] + }, + + // > Body + { + code: "if (a) {}", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) { before; a = b }", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) { a = b; after }", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) throw new Error()", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) a", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) a ||= b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) b = a", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) { a() }", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) { a += a || b }", + options: ["always", { enforceForIfStatements: true }] + }, + + // > Test + { + code: "if (true) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (predicate(a)) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a?.b) a.b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (!a?.b) a.b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === b) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a != null) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null && a === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === 0 || a === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === 1) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a == null || a == undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === !0) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === +0) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === null) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === undefined || a === void 0) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === void void 0) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === void 'string') a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === void fn()) a = b", + options: ["always", { enforceForIfStatements: true }] + }, + + // > Test > Yoda + { + code: "if (a == a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a == b) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null == null) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (undefined == undefined) undefined = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null == x) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null == fn()) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null === a || a === 0) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (0 === a || null === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (1 === a || a === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (undefined === a || 1 === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === b) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (b === undefined || a === null) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null === a || b === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null === null || undefined === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null === null || a === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (undefined === undefined || a === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null === undefined || a === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, + + // > Test > Undefined + { + code: [ + "{", + " const undefined = 0;", + " if (a == undefined) a = b", + "}" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "(() => {", + " const undefined = 0;", + " if (condition) {", + " if (a == undefined) a = b", + " }", + "})()" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "{", + " if (a == undefined) a = b", + "}", + "var undefined = 0;" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "{", + " const undefined = 0;", + " if (undefined == null) undefined = b", + "}" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "{", + " const undefined = 0;", + " if (a === undefined || a === null) a = b", + "}" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "{", + " const undefined = 0;", + " if (undefined === a || null === a) a = b", + "}" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, + + // > Reference + { + code: "if (a) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (!a) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (!!a) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a == null) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === undefined) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || b === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || b === undefined) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (Boolean(a)) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "function fn(Boolean) {", + " if (Boolean(a)) a = b", + "}" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, + + // Never + { + code: "a = a || b", + options: ["never"] + }, { + code: "a = a && b", + options: ["never"] + }, { + code: "a = a ?? b", + options: ["never"] + }, { + code: "a = b", + options: ["never"] + }, { + code: "a += b", + options: ["never"] + }, { + code: "a -= b", + options: ["never"] + }, { + code: "a.b = a.b || c", + options: ["never"] + } + ], + invalid: [ + + // Assignment + { + code: "a = a || b", + output: "a ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a && b", + output: "a &&= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "&&=" }, suggestions: [] }] + }, { + code: "a = a ?? b", + output: "a ??= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "foo = foo || bar", + output: "foo ||= bar", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, + + // > Right + { + code: "a = a || fn()", + output: "a ||= fn()", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || b && c", + output: "a ||= b && c", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || (b || c)", + output: "a ||= (b || c)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || (b ? c : d)", + output: "a ||= (b ? c : d)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, + + // > Comments + { + code: "/* before */ a = a || b", + output: "/* before */ a ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || b // after", + output: "a ||= b // after", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a /* between */ = a || b", + output: null, + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = /** @type */ a || b", + output: null, + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || /* between */ b", + output: null, + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, + + // > Parenthesis + { + code: "(a) = a || b", + output: "(a) ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = (a) || b", + output: "a ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || (b)", + output: "a ||= (b)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || ((b))", + output: "a ||= ((b))", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "(a = a || b)", + output: "(a ||= b)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || (f(), b)", + output: "a ||= (f(), b)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, + + // > Suggestions + { + code: "a.b = a.b ?? c", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a.b ??= c" + }] + }] + }, { + code: "a.b.c = a.b.c ?? d", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a.b.c ??= d" + }] + }] + }, { + code: "a[b] = a[b] ?? c", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a[b] ??= c" + }] + }] + }, { + code: "a['b'] = a['b'] ?? c", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a['b'] ??= c" + }] + }] + }, { + code: "a.b = a['b'] ?? c", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a.b ??= c" + }] + }] + }, { + code: "a['b'] = a.b ?? c", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a['b'] ??= c" + }] + }] + }, { + code: "this.prop = this.prop ?? {}", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "this.prop ??= {}" + }] + }] + }, + + // > With + { + code: "with (object) a = a || b", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "||=" }, + + output: "with (object) a ||= b" + }] + }] + }, { + code: "with (object) { a = a || b }", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "||=" }, + output: "with (object) { a ||= b }" + }] + }] + }, { + code: "with (object) { if (condition) a = a || b }", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "||=" }, + output: "with (object) { if (condition) a ||= b }" + }] + }] + }, { + code: "with (a = a || b) {}", + output: "with (a ||= b) {}", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "with (object) {} a = a || b", + output: "with (object) {} a ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || b; with (object) {}", + output: "a ||= b; with (object) {}", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "if (condition) a = a || b", + output: "if (condition) a ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: [ + "with (object) {", + ' "use strict";', + " a = a || b", + "}" + ].join("\n"), + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "||=" }, + output: [ + "with (object) {", + ' "use strict";', + " a ||= b", + "}" + ].join("\n") + }] + }] + }, + + // > Context + { + code: "fn(a = a || b)", + output: "fn(a ||= b)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "fn((a = a || b))", + output: "fn((a ||= b))", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "(a = a || b) ? c : d", + output: "(a ||= b) ? c : d", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = b = b || c", + output: "a = b ||= c", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, + + // Logical + { + code: "a || (a = b)", + output: "a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a && (a = b)", + output: "a &&= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "&&=" } }] + }, { + code: "a ?? (a = b)", + output: "a ??= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "??=" } }] + }, { + code: "foo ?? (foo = bar)", + output: "foo ??= bar", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "??=" } }] + }, + + // > Right + { + code: "a || (a = 0)", + output: "a ||= 0", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (a = fn())", + output: "a ||= fn()", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (a = (b || c))", + output: "a ||= (b || c)", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, + + // > Parenthesis + { + code: "(a) || (a = b)", + output: "a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || ((a) = b)", + output: "(a) ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (a = (b))", + output: "a ||= (b)", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || ((a = b))", + output: "a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (((a = b)))", + output: "a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || ( ( a = b ) )", + output: "a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, + + // > Comments + { + code: "/* before */ a || (a = b)", + output: "/* before */ a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (a = b) // after", + output: "a ||= b // after", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a /* between */ || (a = b)", + output: null, + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || /* between */ (a = b)", + output: null, + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, + + // > Fix Condition + { + code: "a.b || (a.b = c)", + output: "a.b ||= c", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "class Class { #prop; constructor() { this.#prop || (this.#prop = value) } }", + output: "class Class { #prop; constructor() { this.#prop ||= value } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a['b'] || (a['b'] = c)", + output: "a['b'] ||= c", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a[0] || (a[0] = b)", + output: "a[0] ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a[this] || (a[this] = b)", + output: "a[this] ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "foo.bar || (foo.bar = baz)", + output: "foo.bar ||= baz", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a.b.c || (a.b.c = d)", + output: null, + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "a.b.c ||= d" + }] + }] + }, { + code: "a[b.c] || (a[b.c] = d)", + output: null, + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "a[b.c] ||= d" + }] + }] + }, { + code: "a[b?.c] || (a[b?.c] = d)", + output: null, + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "a[b?.c] ||= d" + }] + }] + }, { + code: "with (object) a.b || (a.b = c)", + output: null, + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "with (object) a.b ||= c" + }] + }] + }, + + // > Context + { + code: "a = a.b || (a.b = {})", + output: "a = a.b ||= {}", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" }, suggestions: [] }] + }, + { + code: "a || (a = 0) || b", + output: "(a ||= 0) || b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "(a || (a = 0)) || b", + output: "(a ||= 0) || b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (b || (b = 0))", + output: "a || (b ||= 0)", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a = b || (b = c)", + output: "a = b ||= c", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (a = 0) ? b : c", + output: "(a ||= 0) ? b : c", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "fn(a || (a = 0))", + output: "fn(a ||= 0)", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, + + // If + { + code: "if (a) a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (Boolean(a)) a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (!!a) a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (!a) a = b", + output: "a ||= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "||=" } }] + }, { + code: "if (!Boolean(a)) a = b", + output: "a ||= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "||=" } }] + }, { + code: "if (a == undefined) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a == null) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a === null || a === undefined) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a === undefined || a === null) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a === null || a === void 0) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a === void 0 || a === null) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a) { a = b; }", + output: "a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: [ + "{ const undefined = 0; }", + "if (a == undefined) a = b" + ].join("\n"), + output: [ + "{ const undefined = 0; }", + "a ??= b" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: [ + "if (a == undefined) a = b", + "{ const undefined = 0; }" + ].join("\n"), + output: [ + "a ??= b", + "{ const undefined = 0; }" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, + + // > Yoda + { + code: "if (null == a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (undefined == a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (undefined === a || a === null) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (a === undefined || null === a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (undefined === a || null === a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (null === a || a === undefined) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (a === null || undefined === a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (null === a || undefined === a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, + + // > Parenthesis + { + code: "if ((a)) a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) (a) = b", + output: "(a) &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) a = (b)", + output: "a &&= (b)", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) (a = b)", + output: "(a &&= b)", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Previous statement + { + code: ";if (a) (a) = b", + output: ";(a) &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "{ if (a) (a) = b }", + output: "{ (a) &&= b }", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "fn();if (a) (a) = b", + output: "fn();(a) &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "fn()\nif (a) a = b", + output: "fn()\na &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "id\nif (a) (a) = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "object.prop\nif (a) (a) = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "object[computed]\nif (a) (a) = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "fn()\nif (a) (a) = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Adding semicolon + { + code: "if (a) a = b; fn();", + output: "a &&= b; fn();", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) { a = b }", + output: "a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) { a = b; }\nfn();", + output: "a &&= b;\nfn();", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) { a = b }\nfn();", + output: "a &&= b;\nfn();", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) { a = b } fn();", + output: "a &&= b; fn();", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) { a = b\n} fn();", + output: "a &&= b; fn();", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Spacing + { + code: "if (a) a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a)\n a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) {\n a = b; \n}", + output: "a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Comments + { + code: "/* before */ if (a) a = b", + output: "/* before */ a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) a = b /* after */", + output: "a &&= b /* after */", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) /* between */ a = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) a = /* between */ b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Members > Single Property Access + { + code: "if (a.b) a.b = c", + output: "a.b &&= c", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" }, suggestions: [] }] + }, { + code: "if (a[b]) a[b] = c", + output: "a[b] &&= c", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" }, suggestions: [] }] + }, { + code: "if (a['b']) a['b'] = c", + output: "a['b'] &&= c", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" }, suggestions: [] }] + }, { + code: "if (this.prop) this.prop = value", + output: "this.prop &&= value", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", suggestions: [] }] + }, { + code: "(class extends SuperClass { method() { if (super.prop) super.prop = value } })", + output: "(class extends SuperClass { method() { super.prop &&= value } })", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" }, suggestions: [] }] + }, { + code: "with (object) if (a) a = b", + output: "with (object) a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" }, suggestions: [] }] + }, + + // > Members > Possible Multiple Property Accesses + { + code: "if (a.b === undefined || a.b === null) a.b = c", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ + messageId: "if", + type: "IfStatement", + data: { operator: "??=" }, + suggestions: [{ + messageId: "convertIf", + data: { operator: "??=" }, + output: "a.b ??= c" + }] + }] + }, { + code: "if (a.b.c) a.b.c = d", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ + messageId: "if", + type: "IfStatement", + data: { operator: "&&=" }, + suggestions: [{ + messageId: "convertIf", + data: { operator: "&&=" }, + output: "a.b.c &&= d" + }] + }] + }, { + code: "if (a.b.c.d) a.b.c.d = e", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ + messageId: "if", + type: "IfStatement", + data: { operator: "&&=" }, + suggestions: [{ + messageId: "convertIf", + data: { operator: "&&=" }, + output: "a.b.c.d &&= e" + }] + }] + }, { + code: "if (a[b].c) a[b].c = d", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ + messageId: "if", + type: "IfStatement", + data: { operator: "&&=" }, + suggestions: [{ + messageId: "convertIf", + data: { operator: "&&=" }, + output: "a[b].c &&= d" + }] + }] + }, { + code: "with (object) if (a.b) a.b = c", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ + messageId: "if", + type: "IfStatement", + data: { operator: "&&=" }, + suggestions: [{ + messageId: "convertIf", + data: { operator: "&&=" }, + output: "with (object) a.b &&= c" + }] + }] + }, + + // > Else if + { + code: "if (unrelated) {} else if (a) a = b;", + output: "if (unrelated) {} else a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) {} else if (b) {} else if (a) a = b;", + output: "if (a) {} else if (b) {} else a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {} else\nif (a) a = b;", + output: "if (unrelated) {} else\na &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {\n}\nelse if (a) {\na = b;\n}", + output: "if (unrelated) {\n}\nelse a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) statement; else if (a) a = b;", + output: "if (unrelated) statement; else a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) id\nelse if (a) (a) = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {} else if (a) a = b; else if (c) c = d", + output: "if (unrelated) {} else if (a) a = b; else c &&= d", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Else if > Comments + { + code: "if (unrelated) { /* body */ } else if (a) a = b;", + output: "if (unrelated) { /* body */ } else a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {} /* before else */ else if (a) a = b;", + output: "if (unrelated) {} /* before else */ else a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {} else // Line\nif (a) a = b;", + output: "if (unrelated) {} else // Line\na &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {} else /* Block */ if (a) a = b;", + output: "if (unrelated) {} else /* Block */ a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Patterns + { + code: "if (array) array = array.filter(predicate)", + output: "array &&= array.filter(predicate)", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // Never + { + code: "a ||= b", + output: "a = a || b", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a &&= b", + output: "a = a && b", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "&&=" } }] + }, { + code: "a ??= b", + output: "a = a ?? b", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }, { + code: "foo ||= bar", + output: "foo = foo || bar", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, + + // > Suggestions + { + code: "a.b ||= c", + output: null, + options: ["never"], + errors: [{ + messageId: "unexpected", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "separate", + output: "a.b = a.b || c" + }] + }] + }, { + code: "a[b] ||= c", + output: null, + options: ["never"], + errors: [{ + messageId: "unexpected", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "separate", + output: "a[b] = a[b] || c" + }] + }] + }, { + code: "a['b'] ||= c", + output: null, + options: ["never"], + errors: [{ + messageId: "unexpected", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "separate", + output: "a['b'] = a['b'] || c" + }] + }] + }, { + code: "this.prop ||= 0", + output: null, + options: ["never"], + errors: [{ + messageId: "unexpected", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "separate", + output: "this.prop = this.prop || 0" + }] + }] + }, { + code: "with (object) a ||= b", + output: null, + options: ["never"], + errors: [{ + messageId: "unexpected", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "separate", + output: "with (object) a = a || b" + }] + }] + }, + + // > Parenthesis + { + code: "(a) ||= b", + output: "(a) = a || b", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a ||= (b)", + output: "a = a || (b)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "(a ||= b)", + output: "(a = a || b)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, + + // > Comments + { + code: "/* before */ a ||= b", + output: "/* before */ a = a || b", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a ||= b // after", + output: "a = a || b // after", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a /* before */ ||= b", + output: null, + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a ||= /* after */ b", + output: null, + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, + + // > Precedence + { + code: "a ||= b && c", + output: "a = a || b && c", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a &&= b || c", + output: "a = a && (b || c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "&&=" } }] + }, { + code: "a ||= b || c", + output: "a = a || (b || c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a &&= b && c", + output: "a = a && (b && c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "&&=" } }] + }, + + // > Mixed + { + code: "a ??= b || c", + output: "a = a ?? (b || c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }, { + code: "a ??= b && c", + output: "a = a ?? (b && c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }, { + code: "a ??= b ?? c", + output: "a = a ?? (b ?? c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }, { + code: "a ??= (b || c)", + output: "a = a ?? (b || c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }, { + code: "a ??= b + c", + output: "a = a ?? b + c", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }] +}); diff --git a/tests/lib/rules/multiline-comment-style.js b/tests/lib/rules/multiline-comment-style.js index 1f2302f9a22..a127d7ec4cb 100644 --- a/tests/lib/rules/multiline-comment-style.js +++ b/tests/lib/rules/multiline-comment-style.js @@ -628,6 +628,20 @@ ruleTester.run("multiline-comment-style", rule, { options: ["separate-lines"], errors: [{ messageId: "expectedLines", line: 2 }] }, + { + code: ` + /** + * JSDoc + * Comment + */ + `, + output: ` + // JSDoc + // Comment + `, + options: ["separate-lines", { checkJSDoc: true }], + errors: [{ messageId: "expectedLines", line: 2 }] + }, { code: ` /* foo diff --git a/tests/lib/rules/no-constant-binary-expression.js b/tests/lib/rules/no-constant-binary-expression.js index c430c7787fd..d931e835d73 100644 --- a/tests/lib/rules/no-constant-binary-expression.js +++ b/tests/lib/rules/no-constant-binary-expression.js @@ -59,7 +59,11 @@ ruleTester.run("no-constant-binary-expression", rule, { "function foo(undefined) { undefined === true;}", "[...arr, 1] == true", "[,,,] == true", - { code: "new Foo() === bar;", globals: { Foo: "writable" } } + { code: "new Foo() === bar;", globals: { Foo: "writable" } }, + "(foo && true) ?? bar", + "foo ?? null ?? bar", + "a ?? (doSomething(), undefined) ?? b", + "a ?? (something = null) ?? b" ], invalid: [ @@ -308,6 +312,10 @@ ruleTester.run("no-constant-binary-expression", rule, { { code: "x === /[a-z]/", errors: [{ messageId: "alwaysNew" }] }, // It's not obvious what this does, but it compares the old value of `x` to the new object. - { code: "x === (x = {})", errors: [{ messageId: "alwaysNew" }] } + { code: "x === (x = {})", errors: [{ messageId: "alwaysNew" }] }, + + { code: "window.abc && false && anything", errors: [{ messageId: "constantShortCircuit" }] }, + { code: "window.abc || true || anything", errors: [{ messageId: "constantShortCircuit" }] }, + { code: "window.abc ?? 'non-nullish' ?? anything", errors: [{ messageId: "constantShortCircuit" }] } ] }); diff --git a/tests/lib/rules/no-empty-static-block.js b/tests/lib/rules/no-empty-static-block.js new file mode 100644 index 00000000000..592c840ff3d --- /dev/null +++ b/tests/lib/rules/no-empty-static-block.js @@ -0,0 +1,51 @@ +/** + * @fileoverview Tests for no-empty-static-block rule. + * @author Sosuke Suzuki + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/no-empty-static-block"), + { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2022 } +}); + +ruleTester.run("no-empty-static-block", rule, { + valid: [ + "class Foo { static { bar(); } }", + "class Foo { static { /* comments */ } }", + "class Foo { static {\n// comment\n} }", + "class Foo { static { bar(); } static { bar(); } }" + ], + invalid: [ + { + code: "class Foo { static {} }", + errors: [{ messageId: "unexpected" }] + }, + { + code: "class Foo { static { } }", + errors: [{ messageId: "unexpected" }] + }, + { + code: "class Foo { static { \n\n } }", + errors: [{ messageId: "unexpected" }] + }, + { + code: "class Foo { static { bar(); } static {} }", + errors: [{ messageId: "unexpected" }] + }, + { + code: "class Foo { static // comment\n {} }", + errors: [{ messageId: "unexpected" }] + } + ] +}); diff --git a/tests/lib/rules/no-empty.js b/tests/lib/rules/no-empty.js index 98651a41256..812aa8de994 100644 --- a/tests/lib/rules/no-empty.js +++ b/tests/lib/rules/no-empty.js @@ -44,36 +44,187 @@ ruleTester.run("no-empty", rule, { { code: "try { foo(); } catch (ex) {} finally { bar(); }", options: [{ allowEmptyCatch: true }] } ], invalid: [ - { code: "try {} catch (ex) {throw ex}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "try { foo() } catch (ex) {}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "try { foo() } catch (ex) {throw ex} finally {}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "if (foo) {}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "while (foo) {}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "for (;foo;) {}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "switch(foo) {}", errors: [{ messageId: "unexpected", data: { type: "switch" }, type: "SwitchStatement" }] }, + { + code: "try {} catch (ex) {throw ex}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "try { /* empty */ } catch (ex) {throw ex}" + }] + }] + }, + { + code: "try { foo() } catch (ex) {}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "try { foo() } catch (ex) { /* empty */ }" + }] + }] + }, + { + code: "try { foo() } catch (ex) {throw ex} finally {}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "try { foo() } catch (ex) {throw ex} finally { /* empty */ }" + }] + }] + }, + { + code: "if (foo) {}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "if (foo) { /* empty */ }" + }] + }] + }, + { + code: "while (foo) {}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "while (foo) { /* empty */ }" + }] + }] + }, + { + code: "for (;foo;) {}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "for (;foo;) { /* empty */ }" + }] + }] + }, + { + code: "switch(foo) {}", + errors: [{ + messageId: "unexpected", + data: { type: "switch" }, + type: "SwitchStatement", + suggestions: null + }] + }, + { + code: "switch (foo) { /* empty */ }", + errors: [{ + messageId: "unexpected", + data: { type: "switch" }, + type: "SwitchStatement", + suggestions: null + }] + }, { code: "try {} catch (ex) {}", options: [{ allowEmptyCatch: true }], - errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "try { /* empty */ } catch (ex) {}" + }] + }] }, { code: "try { foo(); } catch (ex) {} finally {}", options: [{ allowEmptyCatch: true }], - errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "try { foo(); } catch (ex) {} finally { /* empty */ }" + }] + }] }, { code: "try {} catch (ex) {} finally {}", options: [{ allowEmptyCatch: true }], errors: [ - { messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }, - { messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" } + { + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [ + { + messageId: "suggestComment", + data: { type: "block" }, + output: "try { /* empty */ } catch (ex) {} finally {}" + } + ] + }, + { + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [ + { + messageId: "suggestComment", + data: { type: "block" }, + output: "try {} catch (ex) {} finally { /* empty */ }" + } + ] + } ] }, { code: "try { foo(); } catch (ex) {} finally {}", errors: [ - { messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }, - { messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" } + { + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [ + { + messageId: "suggestComment", + data: { type: "block" }, + output: "try { foo(); } catch (ex) { /* empty */ } finally {}" + } + ] + }, + { + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [ + { + messageId: "suggestComment", + data: { type: "block" }, + output: "try { foo(); } catch (ex) {} finally { /* empty */ }" + } + ] + } ] } ] diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js index acaacd7a525..c51a47cfd3f 100644 --- a/tests/lib/rules/no-extra-parens.js +++ b/tests/lib/rules/no-extra-parens.js @@ -735,6 +735,54 @@ ruleTester.run("no-extra-parens", rule, { code: "var foo = (function(){}?.call())", options: ["all", { enforceForFunctionPrototypeMethods: false }], parserOptions: { ecmaVersion: 2020 } + }, + { + code: "(Object.prototype.toString.call())", + options: ["functions"] + }, + + // "allowParensAfterCommentPattern" option + { + code: "const span = /**@type {HTMLSpanElement}*/(event.currentTarget);", + options: ["all", { allowParensAfterCommentPattern: "@type" }] + }, + { + code: "if (/** @type {Compiler | MultiCompiler} */(options).hooks) console.log('good');", + options: ["all", { allowParensAfterCommentPattern: "@type" }] + }, + { + code: ` + validate(/** @type {Schema} */ (schema), options, { + name: "Dev Server", + baseDataPath: "options", + }); + `, + options: ["all", { allowParensAfterCommentPattern: "@type" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + (options.server.options).requestCert = false; + } + `, + options: ["all", { allowParensAfterCommentPattern: "@type" }] + }, + { + code: "const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));", + options: ["all", { allowParensAfterCommentPattern: "@type" }] + }, + + // https://github.com/eslint/eslint/issues/16850 + "(a) = function () {};", + "(a) = () => {};", + "(a) = class {};", + "(a) ??= function () {};", + "(a) &&= class extends SuperClass {};", + "(a) ||= async () => {}", + { + code: "((a)) = function () {};", + options: ["functions"] } ], @@ -2334,13 +2382,8 @@ ruleTester.run("no-extra-parens", rule, { invalid("[...(a.b)] = []", "[...a.b] = []", "MemberExpression"), invalid("({ a: (b) } = {})", "({ a: b } = {})", "Identifier"), invalid("({ a: (b.c) } = {})", "({ a: b.c } = {})", "MemberExpression"), - - /* - * TODO: Add these tests for RestElement's parenthesized arguments in object patterns when that becomes supported by Espree. - * - * invalid("({ ...(a) } = {})", "({ ...a } = {})", "Identifier"), - * invalid("({ ...(a.b) } = {})", "({ ...a.b } = {})", "MemberExpression") - */ + invalid("({ ...(a) } = {})", "({ ...a } = {})", "Identifier"), + invalid("({ ...(a.b) } = {})", "({ ...a.b } = {})", "MemberExpression"), // https://github.com/eslint/eslint/issues/11706 (also in valid[]) { @@ -3192,6 +3235,160 @@ ruleTester.run("no-extra-parens", rule, { errors: [{ messageId: "unexpected" }] }, + // "allowParensAfterCommentPattern" option (off by default) + { + code: "const span = /**@type {HTMLSpanElement}*/(event.currentTarget);", + output: "const span = /**@type {HTMLSpanElement}*/event.currentTarget;", + options: ["all"], + errors: [{ messageId: "unexpected" }] + }, + { + code: "if (/** @type {Compiler | MultiCompiler} */(options).hooks) console.log('good');", + output: "if (/** @type {Compiler | MultiCompiler} */options.hooks) console.log('good');", + options: ["all"], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + validate(/** @type {Schema} */ (schema), options, { + name: "Dev Server", + baseDataPath: "options", + }); + `, + output: ` + validate(/** @type {Schema} */ schema, options, { + name: "Dev Server", + baseDataPath: "options", + }); + `, + options: ["all"], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + (options.server.options).requestCert = false; + } + `, + output: ` + if (condition) { + /** @type {ServerOptions} */ + options.server.options.requestCert = false; + } + `, + options: ["all"], + errors: [{ messageId: "unexpected" }] + }, + { + code: "const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));", + output: "const net = ipaddr.parseCIDR(/** @type {string} */ cidr);", + options: ["all"], + errors: [{ messageId: "unexpected" }] + }, + { + code: "const span = /**@type {HTMLSpanElement}*/(event.currentTarget);", + output: "const span = /**@type {HTMLSpanElement}*/event.currentTarget;", + options: ["all", { allowParensAfterCommentPattern: "invalid" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: "if (/** @type {Compiler | MultiCompiler} */(options).hooks) console.log('good');", + output: "if (/** @type {Compiler | MultiCompiler} */options.hooks) console.log('good');", + options: ["all", { allowParensAfterCommentPattern: "invalid" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + validate(/** @type {Schema} */ (schema), options, { + name: "Dev Server", + baseDataPath: "options", + }); + `, + output: ` + validate(/** @type {Schema} */ schema, options, { + name: "Dev Server", + baseDataPath: "options", + }); + `, + options: ["all", { allowParensAfterCommentPattern: "invalid" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + (options.server.options).requestCert = false; + } + `, + output: ` + if (condition) { + /** @type {ServerOptions} */ + options.server.options.requestCert = false; + } + `, + options: ["all", { allowParensAfterCommentPattern: "invalid" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + /** extra comment */ + (options.server.options).requestCert = false; + } + `, + output: ` + if (condition) { + /** @type {ServerOptions} */ + /** extra comment */ + options.server.options.requestCert = false; + } + `, + options: ["all", { allowParensAfterCommentPattern: "@type" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + ((options.server.options)).requestCert = false; + } + `, + output: ` + if (condition) { + /** @type {ServerOptions} */ + (options.server.options).requestCert = false; + } + `, + options: ["all", { allowParensAfterCommentPattern: "@type" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + let foo = "bar"; + (options.server.options).requestCert = false; + } + `, + output: ` + if (condition) { + /** @type {ServerOptions} */ + let foo = "bar"; + options.server.options.requestCert = false; + } + `, + options: ["all", { allowParensAfterCommentPattern: "@type" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: "const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));", + output: "const net = ipaddr.parseCIDR(/** @type {string} */ cidr);", + options: ["all", { allowParensAfterCommentPattern: "invalid" }], + errors: [{ messageId: "unexpected" }] + }, + // Optional chaining { code: "var v = (obj?.aaa)?.aaa", @@ -3218,6 +3415,35 @@ ruleTester.run("no-extra-parens", rule, { options: ["all", { enforceForFunctionPrototypeMethods: true }], parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpected" }] - } + }, + { + code: "(Object.prototype.toString.call())", + output: "Object.prototype.toString.call()", + options: ["all"], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + + // https://github.com/eslint/eslint/issues/16850 + invalid("(a) = function foo() {};", "a = function foo() {};", "Identifier"), + invalid("(a) = class Bar {};", "a = class Bar {};", "Identifier"), + invalid("(a.b) = function () {};", "a.b = function () {};", "MemberExpression"), + { + code: "(newClass) = [(one)] = class { static * [Symbol.iterator]() { yield 1; } };", + output: "newClass = [one] = class { static * [Symbol.iterator]() { yield 1; } };", + errors: [ + { messageId: "unexpected", type: "Identifier" }, + { messageId: "unexpected", type: "Identifier" } + ] + }, + invalid("((a)) = () => {};", "(a) = () => {};", "Identifier"), + invalid("(a) = (function () {})();", "a = (function () {})();", "Identifier"), + ...["**=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", ">>>=", "&=", "^=", "|="].map( + operator => invalid( + `(a) ${operator} function () {};`, + `a ${operator} function () {};`, + "Identifier" + ) + ) ] }); diff --git a/tests/lib/rules/no-fallthrough.js b/tests/lib/rules/no-fallthrough.js index 0b98d3293e9..80fa61733f3 100644 --- a/tests/lib/rules/no-fallthrough.js +++ b/tests/lib/rules/no-fallthrough.js @@ -63,6 +63,7 @@ ruleTester.run("no-fallthrough", rule, { "switch (foo) { case 0: try {} finally { break; } default: b(); }", "switch (foo) { case 0: try { throw 0; } catch (err) { break; } default: b(); }", "switch (foo) { case 0: do { throw 0; } while(a); default: b(); }", + "switch (foo) { case 0: a(); \n// eslint-disable-next-line no-fallthrough\n case 1: }", { code: "switch(foo) { case 0: a(); /* no break */ case 1: b(); }", options: [{ @@ -92,6 +93,22 @@ ruleTester.run("no-fallthrough", rule, { options: [{ commentPattern: "break[\\s\\w]+omitted" }] + }, + { + code: "switch(foo) { case 0: \n\n\n case 1: b(); }", + options: [{ allowEmptyCase: true }] + }, + { + code: "switch(foo) { case 0: \n /* with comments */ \n case 1: b(); }", + options: [{ allowEmptyCase: true }] + }, + { + code: "switch (a) {\n case 1: ; break; \n case 3: }", + options: [{ allowEmptyCase: true }] + }, + { + code: "switch (a) {\n case 1: ; break; \n case 3: }", + options: [{ allowEmptyCase: false }] } ], invalid: [ @@ -214,6 +231,85 @@ ruleTester.run("no-fallthrough", rule, { column: 1 } ] + }, + { + code: "switch(foo) { case 0: \n /* with comments */ \ncase 1: b(); }", + errors: [ + { + messageId: "case", + type: "SwitchCase", + line: 3, + column: 1 + } + ] + }, + { + code: "switch(foo) { case 0:\n\ncase 1: b(); }", + options: [{ + allowEmptyCase: false + }], + errors: [ + { + messageId: "case", + type: "SwitchCase", + line: 3, + column: 1 + } + ] + }, + { + code: "switch(foo) { case 0:\n\ncase 1: b(); }", + options: [{}], + errors: [ + { + messageId: "case", + type: "SwitchCase", + line: 3, + column: 1 + } + ] + }, + { + code: "switch (a) { case 1: \n ; case 2: }", + options: [{ allowEmptyCase: false }], + errors: [ + { + messageId: "case", + type: "SwitchCase", + line: 2, + column: 4 + } + ] + }, + { + code: "switch (a) { case 1: ; case 2: ; case 3: }", + options: [{ allowEmptyCase: true }], + errors: [ + { + messageId: "case", + type: "SwitchCase", + line: 1, + column: 24 + }, + { + messageId: "case", + type: "SwitchCase", + line: 1, + column: 34 + } + ] + }, + { + code: "switch (foo) { case 0: a(); \n// eslint-enable no-fallthrough\n case 1: }", + options: [{}], + errors: [ + { + messageId: "case", + type: "SwitchCase", + line: 3, + column: 2 + } + ] } ] }); diff --git a/tests/lib/rules/no-implicit-coercion.js b/tests/lib/rules/no-implicit-coercion.js index f7ca9dcff3f..e935081e6f3 100644 --- a/tests/lib/rules/no-implicit-coercion.js +++ b/tests/lib/rules/no-implicit-coercion.js @@ -104,7 +104,12 @@ ruleTester.run("no-implicit-coercion", rule, { { code: "String(foo) + ``", parserOptions: { ecmaVersion: 6 } }, { code: "`${'foo'}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } }, { code: "`${`foo`}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } }, - { code: "`${String(foo)}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } } + { code: "`${String(foo)}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } }, + + // https://github.com/eslint/eslint/issues/16373 + "console.log(Math.PI * 1/4)", + "a * 1 / 2", + "a * 1 / b" ], invalid: [ { @@ -426,6 +431,44 @@ ruleTester.run("no-implicit-coercion", rule, { data: { recommendation: "(foo?.indexOf)(1) !== -1" }, type: "UnaryExpression" }] + }, + + // https://github.com/eslint/eslint/issues/16373 regression tests + { + code: "1 * a / 2", + output: "Number(a) / 2", + errors: [{ + messageId: "useRecommendation", + data: { recommendation: "Number(a)" }, + type: "BinaryExpression" + }] + }, + { + code: "(a * 1) / 2", + output: "(Number(a)) / 2", + errors: [{ + messageId: "useRecommendation", + data: { recommendation: "Number(a)" }, + type: "BinaryExpression" + }] + }, + { + code: "a * 1 / (b * 1)", + output: "a * 1 / (Number(b))", + errors: [{ + messageId: "useRecommendation", + data: { recommendation: "Number(b)" }, + type: "BinaryExpression" + }] + }, + { + code: "a * 1 + 2", + output: "Number(a) + 2", + errors: [{ + messageId: "useRecommendation", + data: { recommendation: "Number(a)" }, + type: "BinaryExpression" + }] } ] }); diff --git a/tests/lib/rules/no-implicit-globals.js b/tests/lib/rules/no-implicit-globals.js index 348ef6feb55..412ba766134 100644 --- a/tests/lib/rules/no-implicit-globals.js +++ b/tests/lib/rules/no-implicit-globals.js @@ -412,7 +412,92 @@ ruleTester.run("no-implicit-globals", rule, { // This rule doesn't disallow assignments to properties of readonly globals "Array.from = 1;", "Object['assign'] = 1;", - "/*global foo:readonly*/ foo.bar = 1;" + "/*global foo:readonly*/ foo.bar = 1;", + + + //------------------------------------------------------------------------------ + // exported + //------------------------------------------------------------------------------ + + // `var` and functions + "/* exported foo */ var foo = 'foo';", + "/* exported foo */ function foo() {}", + { + code: "/* exported foo */ function *foo() {}", + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported foo */ async function foo() {}", + parserOptions: { ecmaVersion: 2017 } + }, + { + code: "/* exported foo */ async function *foo() {}", + parserOptions: { ecmaVersion: 2018 } + }, + "/* exported foo */ var foo = function() {};", + "/* exported foo */ var foo = function foo() {};", + { + code: "/* exported foo */ var foo = function*() {};", + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported foo */ var foo = function *foo() {};", + parserOptions: { ecmaVersion: 2015 } + }, + "/* exported foo, bar */ var foo = 1, bar = 2;", + + + // `const`, `let` and `class` + { + code: "/* exported a */ const a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a */ let a;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a */ let a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported A */ class A {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b */ const a = 1; const b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b */ const a = 1, b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b */ let a, b = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b, C */ const a = 1; let b; class C {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b, c */ const [a, b, ...c] = [];", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b, c */ let { a, foo: b, bar: { c } } = {};", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + } ], invalid: [ @@ -1241,6 +1326,298 @@ ruleTester.run("no-implicit-globals", rule, { type: "VariableDeclarator" } ] + }, + + //------------------------------------------------------------------------------ + // exported + //------------------------------------------------------------------------------ + + // `var` and `function` + { + code: "/* exported bar */ var foo = 'text';", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ function foo() {}", + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ function *foo() {}", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ async function foo() {}", + parserOptions: { ecmaVersion: 2017 }, + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ async function *foo() {}", + parserOptions: { ecmaVersion: 2018 }, + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ var foo = function() {};", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = function foo() {};", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = function*() {};", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = function *foo() {};", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = 1, bar = 2;", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + + // `let`, `const` and `class` + { + code: "/* exported b */ const a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported b */ let a;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported b */ let a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported B */ class A {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: classMessage, + type: "ClassDeclaration" + } + ] + }, + { + code: "/* exported a */ const a = 1; const b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ const a = 1, b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ let a, b = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ const a = 1; let b; class C {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + }, + { + message: classMessage, + type: "ClassDeclaration" + } + ] + }, + { + code: "/* exported a */ const [a, b, ...c] = [];", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + }, + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ let { a, foo: b, bar: { c } } = {};", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + }, + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + + // Global variable leaks + { + code: "/* exported foo */ foo = 1", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ foo = function() {};", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ foo = function*() {};", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ window.foo = function() { bar = 1; }", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ (function() {}(foo = 1));", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ for (foo in {});", + errors: [ + { + message: leakMessage, + type: "ForInStatement" + } + ] + }, + { + code: "/* exported foo */ for (foo of []);", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: leakMessage, + type: "ForOfStatement" + } + ] } ] }); diff --git a/tests/lib/rules/no-invalid-regexp.js b/tests/lib/rules/no-invalid-regexp.js index a34752dcda5..ceaa8f13a44 100644 --- a/tests/lib/rules/no-invalid-regexp.js +++ b/tests/lib/rules/no-invalid-regexp.js @@ -57,6 +57,12 @@ ruleTester.run("no-invalid-regexp", rule, { options: [{ allowConstructorFlags: ["a"] }] }, + // unknown pattern + "new RegExp(pattern, 'g')", + "new RegExp('.' + '', 'g')", + "new RegExp(pattern, '')", + "new RegExp(pattern)", + // ES2020 "new RegExp('(?<\\\\ud835\\\\udc9c>.)', 'g')", "new RegExp('(?<\\\\u{1d49c}>.)', 'g')", @@ -65,6 +71,15 @@ ruleTester.run("no-invalid-regexp", rule, { // ES2022 "new RegExp('a+(?z)?', 'd')", + "new RegExp('\\\\p{Script=Cpmn}', 'u')", + "new RegExp('\\\\p{Script=Cypro_Minoan}', 'u')", + "new RegExp('\\\\p{Script=Old_Uyghur}', 'u')", + "new RegExp('\\\\p{Script=Ougr}', 'u')", + "new RegExp('\\\\p{Script=Tangsa}', 'u')", + "new RegExp('\\\\p{Script=Tnsa}', 'u')", + "new RegExp('\\\\p{Script=Toto}', 'u')", + "new RegExp('\\\\p{Script=Vith}', 'u')", + "new RegExp('\\\\p{Script=Vithkuqi}', 'u')", // allowConstructorFlags { @@ -87,6 +102,14 @@ ruleTester.run("no-invalid-regexp", rule, { code: "new RegExp('.', 'ga')", options: [{ allowConstructorFlags: ["a"] }] }, + { + code: "new RegExp(pattern, 'ga')", + options: [{ allowConstructorFlags: ["a"] }] + }, + { + code: "new RegExp('.' + '', 'ga')", + options: [{ allowConstructorFlags: ["a"] }] + }, { code: "new RegExp('.', 'a')", options: [{ allowConstructorFlags: ["a", "z"] }] @@ -237,6 +260,34 @@ ruleTester.run("no-invalid-regexp", rule, { data: { message: "Invalid regular expression: /\\/: \\ at end of pattern" }, type: "NewExpression" }] + }, + + // https://github.com/eslint/eslint/issues/16573 + { + code: "RegExp(')' + '', 'a');", + errors: [{ + messageId: "regexMessage", + data: { message: "Invalid flags supplied to RegExp constructor 'a'" }, + type: "CallExpression" + }] + }, + { + code: "new RegExp('.' + '', 'az');", + options: [{ allowConstructorFlags: ["z"] }], + errors: [{ + messageId: "regexMessage", + data: { message: "Invalid flags supplied to RegExp constructor 'a'" }, + type: "NewExpression" + }] + }, + { + code: "new RegExp(pattern, 'az');", + options: [{ allowConstructorFlags: ["a"] }], + errors: [{ + messageId: "regexMessage", + data: { message: "Invalid flags supplied to RegExp constructor 'z'" }, + type: "NewExpression" + }] } ] }); diff --git a/tests/lib/rules/no-magic-numbers.js b/tests/lib/rules/no-magic-numbers.js index 6afc328f040..e168249d117 100644 --- a/tests/lib/rules/no-magic-numbers.js +++ b/tests/lib/rules/no-magic-numbers.js @@ -257,6 +257,33 @@ ruleTester.run("no-magic-numbers", rule, { code: "foo?.[777]", options: [{ ignoreArrayIndexes: true }], parserOptions: { ecmaVersion: 2020 } + }, + + // ignoreClassFieldInitialValues + { + code: "class C { foo = 2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = -2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static foo = 2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #foo = 2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static #foo = 2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -812,6 +839,88 @@ ruleTester.run("no-magic-numbers", rule, { { messageId: "noMagic", data: { raw: "1" }, line: 1 }, { messageId: "noMagic", data: { raw: "2" }, line: 1 } ] + }, + + // ignoreClassFieldInitialValues + { + code: "class C { foo = 2; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 17 } + ] + }, + { + code: "class C { foo = 2; }", + options: [{}], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 17 } + ] + }, + { + code: "class C { foo = 2; }", + options: [{ ignoreClassFieldInitialValues: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 17 } + ] + }, + { + code: "class C { foo = -2; }", + options: [{ ignoreClassFieldInitialValues: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "-2" }, line: 1, column: 17 } + ] + }, + { + code: "class C { static foo = 2; }", + options: [{ ignoreClassFieldInitialValues: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 24 } + ] + }, + { + code: "class C { #foo = 2; }", + options: [{ ignoreClassFieldInitialValues: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 18 } + ] + }, + { + code: "class C { static #foo = 2; }", + options: [{ ignoreClassFieldInitialValues: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 25 } + ] + }, + { + code: "class C { foo = 2 + 3; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 17 }, + { messageId: "noMagic", data: { raw: "3" }, line: 1, column: 21 } + ] + }, + { + code: "class C { 2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 11 } + ] + }, + { + code: "class C { [2]; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 12 } + ] } ] }); diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index 0aaf34e5942..5cc5cba5261 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -9,7 +9,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/no-misleading-character-class"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -622,3 +623,33 @@ ruleTester.run("no-misleading-character-class", rule, { } ] }); + +const flatRuleTester = new FlatRuleTester(); + +flatRuleTester.run("no-misleading-character-class", rule, { + valid: [], + + invalid: [ + { + code: "var r = /[👍]/", + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: null // ecmaVersion doesn't support the 'u' flag + }] + }, + { + code: "var r = /[👍]/", + languageOptions: { + ecmaVersion: 2015 + }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[👍]/u" }] + }] + } + ] +}); diff --git a/tests/lib/rules/no-new-native-nonconstructor.js b/tests/lib/rules/no-new-native-nonconstructor.js new file mode 100644 index 00000000000..9637ad79959 --- /dev/null +++ b/tests/lib/rules/no-new-native-nonconstructor.js @@ -0,0 +1,68 @@ +/** + * @fileoverview Tests for the no-new-native-nonconstructor rule + * @author Sosuke Suzuki + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/no-new-native-nonconstructor"), + { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ env: { es2022: true } }); + +ruleTester.run("no-new-native-nonconstructor", rule, { + valid: [ + + // Symbol + "var foo = Symbol('foo');", + "function bar(Symbol) { var baz = new Symbol('baz');}", + "function Symbol() {} new Symbol();", + "new foo(Symbol);", + "new foo(bar, Symbol);", + + // BigInt + "var foo = BigInt(9007199254740991);", + "function bar(BigInt) { var baz = new BigInt(9007199254740991);}", + "function BigInt() {} new BigInt();", + "new foo(BigInt);", + "new foo(bar, BigInt);" + ], + invalid: [ + + // Symbol + { + code: "var foo = new Symbol('foo');", + errors: [{ + message: "`Symbol` cannot be called as a constructor." + }] + }, + { + code: "function bar() { return function Symbol() {}; } var baz = new Symbol('baz');", + errors: [{ + message: "`Symbol` cannot be called as a constructor." + }] + }, + + // BigInt + { + code: "var foo = new BigInt(9007199254740991);", + errors: [{ + message: "`BigInt` cannot be called as a constructor." + }] + }, + { + code: "function bar() { return function BigInt() {}; } var baz = new BigInt(9007199254740991);", + errors: [{ + message: "`BigInt` cannot be called as a constructor." + }] + } + ] +}); diff --git a/tests/lib/rules/no-obj-calls.js b/tests/lib/rules/no-obj-calls.js index 6270e7a7280..febb13fda03 100644 --- a/tests/lib/rules/no-obj-calls.js +++ b/tests/lib/rules/no-obj-calls.js @@ -45,6 +45,14 @@ ruleTester.run("no-obj-calls", rule, { code: "new Atomics.foo()", env: { es2017: true } }, + { + code: "new Intl.Segmenter()", + env: { browser: true } + }, + { + code: "Intl.foo()", + env: { browser: true } + }, { code: "globalThis.Math();", env: { es6: true } }, { code: "var x = globalThis.Math();", env: { es6: true } }, @@ -58,6 +66,8 @@ ruleTester.run("no-obj-calls", rule, { { code: "/*globals Reflect: true*/ globalThis.Reflect();", env: { es2017: true } }, { code: "var x = globalThis.Atomics();", env: { es2017: true } }, { code: "var x = globalThis.Atomics();", globals: { Atomics: false }, env: { es2017: true } }, + { code: "var x = globalThis.Intl();", env: { browser: true } }, + { code: "var x = globalThis.Intl();", globals: { Intl: false }, env: { browser: true } }, // non-existing variables "/*globals Math: off*/ Math();", @@ -78,6 +88,8 @@ ruleTester.run("no-obj-calls", rule, { code: "Atomics();", env: { es6: true } }, + "Intl()", + "new Intl()", // shadowed variables "var Math; Math();", @@ -119,6 +131,20 @@ ruleTester.run("no-obj-calls", rule, { { code: "var construct = typeof Reflect !== \"undefined\" ? Reflect.construct : undefined; construct();", globals: { Reflect: false } + }, + { + code: "function foo(Intl) { Intl(); }", + env: { browser: true } + }, + { + code: "if (foo) { const Intl = 1; Intl(); }", + parserOptions: { ecmaVersion: 2015 }, + env: { browser: true } + }, + { + code: "if (foo) { const Intl = 1; new Intl(); }", + parserOptions: { ecmaVersion: 2015 }, + env: { browser: true } } ], invalid: [ @@ -225,6 +251,24 @@ ruleTester.run("no-obj-calls", rule, { globals: { Atomics: "writable" }, errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "NewExpression" }] }, + { + code: "var x = Intl();", + env: { browser: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] + }, + { + code: "var x = new Intl();", + env: { browser: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "NewExpression" }] + }, + { + code: "/*globals Intl: true*/ Intl();", + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] + }, + { + code: "/*globals Intl: true*/ new Intl();", + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "NewExpression" }] + }, { code: "var x = globalThis.Math();", env: { es2020: true }, @@ -288,6 +332,21 @@ ruleTester.run("no-obj-calls", rule, { env: { es2020: true }, errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }] }, + { + code: "var x = globalThis.Intl();", + env: { browser: true, es2020: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] + }, + { + code: "var x = new globalThis.Intl;", + env: { browser: true, es2020: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "NewExpression" }] + }, + { + code: "/*globals Intl: true*/ Intl();", + env: { browser: true, es2020: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] + }, { code: "var foo = bar ? baz: JSON; foo();", errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "JSON" }, type: "CallExpression" }] @@ -316,6 +375,16 @@ ruleTester.run("no-obj-calls", rule, { env: { es2020: true, browser: true }, errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Atomics" }, type: "NewExpression" }] }, + { + code: "var foo = window.Intl; foo();", + env: { es2020: true, browser: true }, + errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Intl" }, type: "CallExpression" }] + }, + { + code: "var foo = window.Intl; new foo;", + env: { es2020: true, browser: true }, + errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Intl" }, type: "NewExpression" }] + }, // Optional chaining { diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index 631fd6f02fa..9505e8ff6c3 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -107,7 +107,31 @@ ruleTester.run("no-restricted-exports", rule, { { code: "export default 1;", options: [{ restrictedNamedExports: ["default"] }] }, // "default" does not disallow re-exporting a renamed default export from another module - { code: "export { default as a } from 'foo';", options: [{ restrictedNamedExports: ["default"] }] } + { code: "export { default as a } from 'foo';", options: [{ restrictedNamedExports: ["default"] }] }, + + // restrictDefaultExports.direct option + { code: "export default foo;", options: [{ restrictDefaultExports: { direct: false } }] }, + { code: "export default 42;", options: [{ restrictDefaultExports: { direct: false } }] }, + { code: "export default function foo() {}", options: [{ restrictDefaultExports: { direct: false } }] }, + + // restrictDefaultExports.named option + { code: "const foo = 123;\nexport { foo as default };", options: [{ restrictDefaultExports: { named: false } }] }, + + // restrictDefaultExports.defaultFrom option + { code: "export { default } from 'mod';", options: [{ restrictDefaultExports: { defaultFrom: false } }] }, + { code: "export { default as default } from 'mod';", options: [{ restrictDefaultExports: { defaultFrom: false } }] }, + { code: "export { foo as default } from 'mod';", options: [{ restrictDefaultExports: { defaultFrom: true } }] }, + { code: "export { default } from 'mod';", options: [{ restrictDefaultExports: { named: true, defaultFrom: false } }] }, + { code: "export { 'default' } from 'mod'; ", options: [{ restrictDefaultExports: { defaultFrom: false } }] }, + + // restrictDefaultExports.namedFrom option + { code: "export { foo as default } from 'mod';", options: [{ restrictDefaultExports: { namedFrom: false } }] }, + { code: "export { default as default } from 'mod';", options: [{ restrictDefaultExports: { namedFrom: true } }] }, + { code: "export { default as default } from 'mod';", options: [{ restrictDefaultExports: { namedFrom: false } }] }, + { code: "export { 'default' } from 'mod'; ", options: [{ restrictDefaultExports: { defaultFrom: false, namedFrom: true } }] }, + + // restrictDefaultExports.namespaceFrom option + { code: "export * as default from 'mod';", options: [{ restrictDefaultExports: { namespaceFrom: false } }] } ], invalid: [ @@ -519,6 +543,66 @@ ruleTester.run("no-restricted-exports", rule, { code: "export { default } from 'foo';", options: [{ restrictedNamedExports: ["default"] }], errors: [{ messageId: "restrictedNamed", data: { name: "default" }, type: "Identifier", column: 10 }] + }, + + // restrictDefaultExports.direct option + { + code: "export default foo;", + options: [{ restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + { + code: "export default 42;", + options: [{ restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + { + code: "export default function foo() {}", + options: [{ restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + { + code: "export default foo;", + options: [{ restrictedNamedExports: ["bar"], restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + + // restrictDefaultExports.named option + { + code: "const foo = 123;\nexport { foo as default };", + options: [{ restrictDefaultExports: { named: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 2, column: 17 }] + }, + + // restrictDefaultExports.defaultFrom option + { + code: "export { default } from 'mod';", + options: [{ restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 10 }] + }, + { + code: "export { default as default } from 'mod';", + options: [{ restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 21 }] + }, + { + code: "export { 'default' } from 'mod';", + options: [{ restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Literal", line: 1, column: 10 }] + }, + + // restrictDefaultExports.namedFrom option + { + code: "export { foo as default } from 'mod';", + options: [{ restrictDefaultExports: { namedFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 17 }] + }, + + // restrictDefaultExports.namespaceFrom option + { + code: "export * as default from 'mod';", + options: [{ restrictDefaultExports: { namespaceFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 13 }] } ] }); diff --git a/tests/lib/rules/no-return-await.js b/tests/lib/rules/no-return-await.js index 508d7cb8528..b9184a0a532 100644 --- a/tests/lib/rules/no-return-await.js +++ b/tests/lib/rules/no-return-await.js @@ -16,8 +16,24 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -// pending https://github.com/eslint/espree/issues/304, the type should be "Keyword" -const errors = [{ messageId: "redundantUseOfAwait", type: "Identifier" }]; +/** + * Creates the list of errors that should be found by this rule + * @param {Object} options Options for creating errors + * @param {string} options.suggestionOutput The suggested output + * @returns {Array} the list of errors + */ +function createErrorList({ suggestionOutput: output } = {}) { + + // pending https://github.com/eslint/espree/issues/304, the type should be "Keyword" + return [{ + messageId: "redundantUseOfAwait", + type: "Identifier", + suggestions: output ? [{ + messageId: "removeAwait", output + }] : [] + }]; +} + const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2017 } }); @@ -138,99 +154,103 @@ ruleTester.run("no-return-await", rule, { invalid: [ { code: "\nasync function foo() {\n\treturn await bar();\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn bar();\n}\n" }) + }, + { + code: "\nasync function foo() {\n\treturn await(bar());\n}\n", + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a, await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a, bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a, b, await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a, b, bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a && await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a && bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a && b && await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a && b && bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a || await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a || bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a, b, (c, d, await bar()));\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a, b, (c, d, bar()));\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a, b, (c && await bar()));\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a, b, (c && bar()));\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (await baz(), b, await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (await baz(), b, bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? await bar() : b);\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? bar() : b);\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? a : await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? a : bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? (a, await bar()) : b);\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? (a, bar()) : b);\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? a : (b, await bar()));\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? a : (b, bar()));\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? (a && await bar()) : b);\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? (a && bar()) : b);\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? a : (b && await bar()));\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? a : (b && bar()));\n}\n" }) }, { code: "\nasync () => { return await bar(); }\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => { return bar(); }\n" }) }, { code: "\nasync () => await bar()\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => bar()\n" }) }, { code: "\nasync () => (a, b, await bar())\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => (a, b, bar())\n" }) }, { code: "\nasync () => (a && await bar())\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => (a && bar())\n" }) }, { code: "\nasync () => (baz() ? await bar() : b)\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => (baz() ? bar() : b)\n" }) }, { code: "\nasync () => (baz() ? a : (b, await bar()))\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => (baz() ? a : (b, bar()))\n" }) }, { code: "\nasync () => (baz() ? a : (b && await bar()))\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => (baz() ? a : (b && bar()))\n" }) }, { code: "\nasync function foo() {\nif (a) {\n\t\tif (b) {\n\t\t\treturn await bar();\n\t\t}\n\t}\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\nif (a) {\n\t\tif (b) {\n\t\t\treturn bar();\n\t\t}\n\t}\n}\n" }) }, { code: "\nasync () => {\nif (a) {\n\t\tif (b) {\n\t\t\treturn await bar();\n\t\t}\n\t}\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => {\nif (a) {\n\t\tif (b) {\n\t\t\treturn bar();\n\t\t}\n\t}\n}\n" }) }, { code: ` @@ -241,7 +261,16 @@ ruleTester.run("no-return-await", rule, { } } `, - errors + errors: createErrorList({ + suggestionOutput: ` + async function foo() { + try {} + finally { + return bar(); + } + } + ` + }) }, { code: ` @@ -252,7 +281,16 @@ ruleTester.run("no-return-await", rule, { } } `, - errors + errors: createErrorList({ + suggestionOutput: ` + async function foo() { + try {} + catch (e) { + return bar(); + } + } + ` + }) }, { code: ` @@ -262,7 +300,15 @@ ruleTester.run("no-return-await", rule, { } } catch (e) {} `, - errors + errors: createErrorList({ + suggestionOutput: ` + try { + async function foo() { + return bar(); + } + } catch (e) {} + ` + }) }, { code: ` @@ -270,7 +316,13 @@ ruleTester.run("no-return-await", rule, { async () => await bar(); } catch (e) {} `, - errors + errors: createErrorList({ + suggestionOutput: ` + try { + async () => bar(); + } catch (e) {} + ` + }) }, { code: ` @@ -284,7 +336,64 @@ ruleTester.run("no-return-await", rule, { } } `, - errors + errors: createErrorList({ + suggestionOutput: ` + async function foo() { + try {} + catch (e) { + try {} + catch (e) { + return bar(); + } + } + } + ` + }) + }, + { + code: ` + async function foo() { + return await new Promise(resolve => { + resolve(5); + }); + } + `, + errors: createErrorList({ + suggestionOutput: ` + async function foo() { + return new Promise(resolve => { + resolve(5); + }); + } + ` + }) + }, + { + code: ` + async () => { + return await ( + foo() + ) + }; + `, + errors: createErrorList({ + suggestionOutput: ` + async () => { + return ( + foo() + ) + }; + ` + }) + }, + { + code: ` + async function foo() { + return await // Test + 5; + } + `, + errors: createErrorList() } ] }); diff --git a/tests/lib/rules/no-underscore-dangle.js b/tests/lib/rules/no-underscore-dangle.js index f3e11cc3e76..cec2c2ac722 100644 --- a/tests/lib/rules/no-underscore-dangle.js +++ b/tests/lib/rules/no-underscore-dangle.js @@ -70,6 +70,17 @@ ruleTester.run("no-underscore-dangle", rule, { { code: "function foo( { _bar }) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } }, { code: "function foo( { _bar = 0 } = {}) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } }, { code: "function foo(...[_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 2016 } }, + { code: "const [_foo] = arr", parserOptions: { ecmaVersion: 6 } }, + { code: "const [_foo] = arr", options: [{}], parserOptions: { ecmaVersion: 6 } }, + { code: "const [_foo] = arr", options: [{ allowInArrayDestructuring: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const [foo, ...rest] = [1, 2, 3]", options: [{ allowInArrayDestructuring: false }], parserOptions: { ecmaVersion: 2022 } }, + { code: "const [foo, _bar] = [1, 2, 3]", options: [{ allowInArrayDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } }, + { code: "const { _foo } = obj", parserOptions: { ecmaVersion: 6 } }, + { code: "const { _foo } = obj", options: [{}], parserOptions: { ecmaVersion: 6 } }, + { code: "const { _foo } = obj", options: [{ allowInObjectDestructuring: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const { foo, bar: _bar } = { foo: 1, bar: 2 }", options: [{ allowInObjectDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } }, + { code: "const { foo, _bar } = { foo: 1, _bar: 2 }", options: [{ allowInObjectDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } }, + { code: "const { foo, _bar: bar } = { foo: 1, _bar: 2 }", options: [{ allowInObjectDestructuring: false }], parserOptions: { ecmaVersion: 2022 } }, { code: "class foo { _field; }", parserOptions: { ecmaVersion: 2022 } }, { code: "class foo { _field; }", options: [{ enforceInClassFields: false }], parserOptions: { ecmaVersion: 2022 } }, { code: "class foo { #_field; }", parserOptions: { ecmaVersion: 2022 } }, @@ -103,6 +114,77 @@ ruleTester.run("no-underscore-dangle", rule, { { code: "const foo = { onClick(..._bar) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] }, { code: "const foo = (..._bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] }, { + code: "const [foo, _bar] = [1, 2]", + options: [{ allowInArrayDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" } }] + }, { + code: "const [_foo = 1] = arr", + options: [{ allowInArrayDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const [foo, ..._rest] = [1, 2, 3]", + options: [{ allowInArrayDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_rest" } }] + }, { + code: "const [foo, [bar_, baz]] = [1, [2, 3]]", + options: [{ allowInArrayDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "bar_" } }] + }, { + code: "const { _foo, bar } = { _foo: 1, bar: 2 }", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const { _foo = 1 } = obj", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const { bar: _foo = 1 } = obj", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const { foo: _foo, bar } = { foo: 1, bar: 2 }", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const { foo, ..._rest} = { foo: 1, bar: 2, baz: 3 }", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_rest" } }] + }, { + code: "const { foo: [_bar, { a: _a, b } ] } = { foo: [1, { a: 'a', b: 'b' }] }", + options: [{ allowInArrayDestructuring: false, allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unexpectedUnderscore", data: { identifier: "_bar" } }, + { messageId: "unexpectedUnderscore", data: { identifier: "_a" } } + ] + }, { + code: "const { foo: [_bar, { a: _a, b } ] } = { foo: [1, { a: 'a', b: 'b' }] }", + options: [{ allowInArrayDestructuring: true, allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_a" } }] + }, { + code: "const [{ foo: [_bar, _, { bar: _baz }] }] = [{ foo: [1, 2, { bar: 'a' }] }]", + options: [{ allowInArrayDestructuring: false, allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unexpectedUnderscore", data: { identifier: "_bar" } }, + { messageId: "unexpectedUnderscore", data: { identifier: "_baz" } } + ] + }, { + code: "const { foo, bar: { baz, _qux } } = { foo: 1, bar: { baz: 3, _qux: 4 } }", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_qux" } }] + }, { code: "class foo { #_bar() {} }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 2022 }, diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js index b723a29abcf..2f145fa418f 100644 --- a/tests/lib/rules/no-unused-vars.js +++ b/tests/lib/rules/no-unused-vars.js @@ -18,20 +18,22 @@ const rule = require("../../../lib/rules/no-unused-vars"), const ruleTester = new RuleTester(); -ruleTester.defineRule("use-every-a", context => { - - /** - * Mark a variable as used - * @returns {void} - * @private - */ - function useA() { - context.markVariableAsUsed("a"); +ruleTester.defineRule("use-every-a", { + create(context) { + + /** + * Mark a variable as used + * @returns {void} + * @private + */ + function useA() { + context.markVariableAsUsed("a"); + } + return { + VariableDeclaration: useA, + ReturnStatement: useA + }; } - return { - VariableDeclaration: useA, - ReturnStatement: useA - }; }); /** diff --git a/tests/lib/rules/no-var.js b/tests/lib/rules/no-var.js index 84e14ae19c9..26b0a8464f4 100644 --- a/tests/lib/rules/no-var.js +++ b/tests/lib/rules/no-var.js @@ -319,6 +319,74 @@ ruleTester.run("no-var", rule, { code: "function foo() { var { let } = {}; }", output: null, errors: [{ messageId: "unexpectedVar" }] + }, + + // https://github.com/eslint/eslint/issues/16610 + { + code: "var fx = function (i = 0) { if (i < 5) { return fx(i + 1); } console.log(i); }; fx();", + output: "let fx = function (i = 0) { if (i < 5) { return fx(i + 1); } console.log(i); }; fx();", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var foo = function () { foo() };", + output: "let foo = function () { foo() };", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var foo = () => foo();", + output: "let foo = () => foo();", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var foo = (function () { foo(); })();", + output: null, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var foo = bar(function () { foo(); });", + output: null, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var bar = foo, foo = function () { foo(); };", + output: null, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var bar = foo; var foo = function () { foo(); };", + output: "let bar = foo; var foo = function () { foo(); };", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { messageId: "unexpectedVar" }, + { messageId: "unexpectedVar" } + ] + }, + { + code: "var { foo = foo } = function () { foo(); };", + output: null, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var { bar = foo, foo } = function () { foo(); };", + output: null, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var bar = function () { foo(); }; var foo = function() {};", + output: "let bar = function () { foo(); }; var foo = function() {};", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { messageId: "unexpectedVar" }, + { messageId: "unexpectedVar" } + ] } ] }); diff --git a/tests/lib/rules/no-warning-comments.js b/tests/lib/rules/no-warning-comments.js index 6d4137a5e84..74122456121 100644 --- a/tests/lib/rules/no-warning-comments.js +++ b/tests/lib/rules/no-warning-comments.js @@ -37,7 +37,9 @@ ruleTester.run("no-warning-comments", rule, { { code: "// special regex characters don't cause a problem", options: [{ terms: ["[aeiou]"], location: "anywhere" }] }, "/*eslint no-warning-comments: [2, { \"terms\": [\"todo\", \"fixme\", \"any other term\"], \"location\": \"anywhere\" }]*/\n\nvar x = 10;\n", { code: "/*eslint no-warning-comments: [2, { \"terms\": [\"todo\", \"fixme\", \"any other term\"], \"location\": \"anywhere\" }]*/\n\nvar x = 10;\n", options: [{ location: "anywhere" }] }, - { code: "// foo", options: [{ terms: ["foo-bar"] }] } + { code: "// foo", options: [{ terms: ["foo-bar"] }] }, + "/** multi-line block comment with lines starting with\nTODO\nFIXME or\nXXX\n*/", + { code: "//!TODO ", options: [{ decoration: ["*"] }] } ], invalid: [ { @@ -387,6 +389,58 @@ ruleTester.run("no-warning-comments", rule, { } } ] + }, + { + code: "/*\nTODO undecorated multi-line block comment (start)\n*/", + options: [{ terms: ["todo"], location: "start" }], + errors: [ + { + messageId: "unexpectedComment", + data: { + matchedTerm: "todo", + comment: "TODO undecorated multi-line block..." + } + } + ] + }, + { + code: "///// TODO decorated single-line comment with decoration array \n /////", + options: [{ terms: ["todo"], location: "start", decoration: ["*", "/"] }], + errors: [ + { + messageId: "unexpectedComment", + data: { + matchedTerm: "todo", + comment: "/// TODO decorated single-line comment..." + } + } + ] + }, + { + code: "///*/*/ TODO decorated single-line comment with multiple decoration characters (start) \n /////", + options: [{ terms: ["todo"], location: "start", decoration: ["*", "/"] }], + errors: [ + { + messageId: "unexpectedComment", + data: { + matchedTerm: "todo", + comment: "/*/*/ TODO decorated single-line comment..." + } + } + ] + }, + { + code: "//**TODO term starts with a decoration character", + options: [{ terms: ["*todo"], location: "start", decoration: ["*"] }], + errors: [ + { + messageId: "unexpectedComment", + data: { + matchedTerm: "*todo", + comment: "**TODO term starts with a decoration..." + } + } + ] } ] }); diff --git a/tests/lib/rules/prefer-arrow-callback.js b/tests/lib/rules/prefer-arrow-callback.js index cb46c4a5021..eba8c051140 100644 --- a/tests/lib/rules/prefer-arrow-callback.js +++ b/tests/lib/rules/prefer-arrow-callback.js @@ -205,6 +205,46 @@ ruleTester.run("prefer-arrow-callback", rule, { code: "foo((function() { return this; }?.bind)(this));", output: null, errors + }, + + // https://github.com/eslint/eslint/issues/16718 + { + code: ` + test( + function () + { } + ); + `, + output: ` + test( + () => + { } + ); + `, + errors + }, + { + code: ` + test( + function ( + ...args + ) /* Lorem ipsum + dolor sit amet. */ { + return args; + } + ); + `, + output: ` + test( + ( + ...args + ) => /* Lorem ipsum + dolor sit amet. */ { + return args; + } + ); + `, + errors } ] }); diff --git a/tests/lib/rules/prefer-const.js b/tests/lib/rules/prefer-const.js index 0a44b0bd017..b4c185f12a7 100644 --- a/tests/lib/rules/prefer-const.js +++ b/tests/lib/rules/prefer-const.js @@ -19,11 +19,13 @@ const rule = require("../../../lib/rules/prefer-const"), const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); -ruleTester.defineRule("use-x", context => ({ - VariableDeclaration() { - context.markVariableAsUsed("x"); - } -})); +ruleTester.defineRule("use-x", { + create: context => ({ + VariableDeclaration() { + context.markVariableAsUsed("x"); + } + }) +}); ruleTester.run("prefer-const", rule, { valid: [ @@ -684,6 +686,78 @@ ruleTester.run("prefer-const", rule, { errors: [ { messageId: "useConst", data: { name: "a" }, type: "Identifier" } ] + }, + + // https://github.com/eslint/eslint/issues/16266 + { + code: ` + let { itemId, list } = {}, + obj = [], + total = 0; + total = 9; + console.log(itemId, list, obj, total); + `, + output: null, + options: [{ destructuring: "any", ignoreReadBeforeAssign: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "useConst", data: { name: "itemId" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "list" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "obj" }, type: "Identifier" } + ] + }, + { + code: ` + let { itemId, list } = {}, + obj = []; + console.log(itemId, list, obj); + `, + output: ` + const { itemId, list } = {}, + obj = []; + console.log(itemId, list, obj); + `, + options: [{ destructuring: "any", ignoreReadBeforeAssign: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "useConst", data: { name: "itemId" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "list" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "obj" }, type: "Identifier" } + ] + }, + { + code: ` + let [ itemId, list ] = [], + total = 0; + total = 9; + console.log(itemId, list, total); + `, + output: null, + options: [{ destructuring: "any", ignoreReadBeforeAssign: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "useConst", data: { name: "itemId" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "list" }, type: "Identifier" } + ] + }, + { + code: ` + let [ itemId, list ] = [], + obj = []; + console.log(itemId, list, obj); + `, + output: ` + const [ itemId, list ] = [], + obj = []; + console.log(itemId, list, obj); + `, + options: [{ destructuring: "any", ignoreReadBeforeAssign: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "useConst", data: { name: "itemId" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "list" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "obj" }, type: "Identifier" } + ] } ] }); diff --git a/tests/lib/rules/prefer-named-capture-group.js b/tests/lib/rules/prefer-named-capture-group.js index 0faf1d6be65..dad3d7c0290 100644 --- a/tests/lib/rules/prefer-named-capture-group.js +++ b/tests/lib/rules/prefer-named-capture-group.js @@ -82,7 +82,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 13 + endColumn: 13, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?[0-9]{4})/" + }, + { + messageId: "addNonCapture", + output: "/(?:[0-9]{4})/" + } + ] }] }, { @@ -93,7 +103,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 25 + endColumn: 25, + suggestions: [ + { + messageId: "addGroupName", + output: "new RegExp('(?[0-9]{4})')" + }, + { + messageId: "addNonCapture", + output: "new RegExp('(?:[0-9]{4})')" + } + ] }] }, { @@ -104,7 +124,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 21 + endColumn: 21, + suggestions: [ + { + messageId: "addGroupName", + output: "RegExp('(?[0-9]{4})')" + }, + { + messageId: "addNonCapture", + output: "RegExp('(?:[0-9]{4})')" + } + ] }] }, { @@ -112,7 +142,44 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "NewExpression", - data: { group: "(bc)" } + data: { group: "(bc)" }, + suggestions: [ + { + messageId: "addGroupName", + output: "new RegExp(`a(?bc)d`)" + }, + { + messageId: "addNonCapture", + output: "new RegExp(`a(?:bc)d`)" + } + ] + }] + }, + { + code: "new RegExp('\u1234\u5678(?:a)(b)');", + errors: [{ + messageId: "required", + type: "NewExpression", + data: { group: "(b)" }, + suggestions: [ + { + messageId: "addGroupName", + output: "new RegExp('\u1234\u5678(?:a)(?b)');" + }, + { + messageId: "addNonCapture", + output: "new RegExp('\u1234\u5678(?:a)(?:b)');" + } + ] + }] + }, + { + code: "new RegExp('\\u1234\\u5678(?:a)(b)');", + errors: [{ + messageId: "required", + type: "NewExpression", + data: { group: "(b)" }, + suggestions: null }] }, { @@ -124,7 +191,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 21 + endColumn: 21, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?[0-9]{4})-(\\w{5})/" + }, + { + messageId: "addNonCapture", + output: "/(?:[0-9]{4})-(\\w{5})/" + } + ] }, { messageId: "required", @@ -132,7 +209,173 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "(\\w{5})" }, line: 1, column: 1, - endColumn: 21 + endColumn: 21, + suggestions: [ + { + messageId: "addGroupName", + output: "/([0-9]{4})-(?\\w{5})/" + }, + { + messageId: "addNonCapture", + output: "/([0-9]{4})-(?:\\w{5})/" + } + ] + } + ] + }, + { + code: "/([0-9]{4})-(5)/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "([0-9]{4})" }, + line: 1, + column: 1, + endColumn: 17, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?[0-9]{4})-(5)/" + }, + { + messageId: "addNonCapture", + output: "/(?:[0-9]{4})-(5)/" + } + ] + }, + { + messageId: "required", + type: "Literal", + data: { group: "(5)" }, + line: 1, + column: 1, + endColumn: 17, + suggestions: [ + { + messageId: "addGroupName", + output: "/([0-9]{4})-(?5)/" + }, + { + messageId: "addNonCapture", + output: "/([0-9]{4})-(?:5)/" + } + ] + } + ] + }, + { + code: "/(?(a))/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "(a)" }, + line: 1, + column: 1, + endColumn: 16, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?(?a))/" + }, + { + messageId: "addNonCapture", + output: "/(?(?:a))/" + } + ] + } + ] + }, + { + code: "/(?(a)(?b))/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "(a)" }, + line: 1, + column: 1, + endColumn: 27, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?(?a)(?b))/" + }, + { + messageId: "addNonCapture", + output: "/(?(?:a)(?b))/" + } + ] + } + ] + }, + { + code: "/(?[0-9]{4})-(\\w{5})/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "(\\w{5})" }, + line: 1, + column: 1, + endColumn: 29, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?[0-9]{4})-(?\\w{5})/" + }, + { + messageId: "addNonCapture", + output: "/(?[0-9]{4})-(?:\\w{5})/" + } + ] + } + ] + }, + { + code: "/(?[0-9]{4})-(5)/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "(5)" }, + line: 1, + column: 1, + endColumn: 25, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?[0-9]{4})-(?5)/" + }, + { + messageId: "addNonCapture", + output: "/(?[0-9]{4})-(?:5)/" + } + ] + } + ] + }, + { + code: "/(?a)(?a)(a)(?a)/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "(a)" }, + line: 1, + column: 1, + endColumn: 39, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?a)(?a)(?a)(?a)/" + }, + { + messageId: "addNonCapture", + output: "/(?a)(?a)(?:a)(?a)/" + } + ] } ] }, @@ -141,7 +384,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "NewExpression", - data: { group: "(a)" } + data: { group: "(a)" }, + suggestions: null }] }, { @@ -149,7 +393,34 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "NewExpression", - data: { group: "(bc)" } + data: { group: "(bc)" }, + suggestions: null + }] + }, + { + code: "new RegExp(\"foo\" + \"(a)\" + \"(b)\");", + errors: [ + { + messageId: "required", + type: "NewExpression", + data: { group: "(a)" }, + suggestions: null + }, + { + messageId: "required", + type: "NewExpression", + data: { group: "(b)" }, + suggestions: null + } + ] + }, + { + code: "new RegExp(\"foo\" + \"(?:a)\" + \"(b)\");", + errors: [{ + messageId: "required", + type: "NewExpression", + data: { group: "(b)" }, + suggestions: null }] }, { @@ -157,7 +428,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "CallExpression", - data: { group: "(a)" } + data: { group: "(a)" }, + suggestions: null }] }, { @@ -165,7 +437,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "CallExpression", - data: { group: "(ab)" } + data: { group: "(ab)" }, + suggestions: null }] }, { @@ -173,7 +446,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "NewExpression", - data: { group: "(ab)" } + data: { group: "(ab)" }, + suggestions: null }] }, { @@ -185,7 +459,17 @@ ruleTester.run("prefer-named-capture-group", rule, { line: 1, column: 1, endLine: 2, - endColumn: 3 + endColumn: 3, + suggestions: [ + { + messageId: "addGroupName", + output: "new RegExp(`(?a)\n`)" + }, + { + messageId: "addNonCapture", + output: "new RegExp(`(?:a)\n`)" + } + ] }] }, { @@ -193,7 +477,17 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "CallExpression", - data: { group: "(b\nc)" } + data: { group: "(b\nc)" }, + suggestions: [ + { + messageId: "addGroupName", + output: "RegExp(`a(?b\nc)d`)" + }, + { + messageId: "addNonCapture", + output: "RegExp(`a(?:b\nc)d`)" + } + ] }] }, { @@ -201,7 +495,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "NewExpression", - data: { group: "(b)" } + data: { group: "(b)" }, + suggestions: null }] }, { @@ -209,7 +504,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "CallExpression", - data: { group: "(a)" } + data: { group: "(a)" }, + suggestions: null }] }, { @@ -217,7 +513,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "CallExpression", - data: { group: "(b)" } + data: { group: "(b)" }, + suggestions: null }] }, { @@ -229,7 +526,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 36 + endColumn: 36, + suggestions: [ + { + messageId: "addGroupName", + output: "new globalThis.RegExp('(?[0-9]{4})')" + }, + { + messageId: "addNonCapture", + output: "new globalThis.RegExp('(?:[0-9]{4})')" + } + ] }] }, { @@ -241,7 +548,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 32 + endColumn: 32, + suggestions: [ + { + messageId: "addGroupName", + output: "globalThis.RegExp('(?[0-9]{4})')" + }, + { + messageId: "addNonCapture", + output: "globalThis.RegExp('(?:[0-9]{4})')" + } + ] }] }, { @@ -256,7 +573,23 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 3, column: 17, - endColumn: 52 + endColumn: 52, + suggestions: [ + { + messageId: "addGroupName", + output: ` + function foo() { var globalThis = bar; } + new globalThis.RegExp('(?[0-9]{4})'); + ` + }, + { + messageId: "addNonCapture", + output: ` + function foo() { var globalThis = bar; } + new globalThis.RegExp('(?:[0-9]{4})'); + ` + } + ] }] } ] diff --git a/tests/lib/rules/prefer-regex-literals.js b/tests/lib/rules/prefer-regex-literals.js index f2982cbfb72..054d89be1d7 100644 --- a/tests/lib/rules/prefer-regex-literals.js +++ b/tests/lib/rules/prefer-regex-literals.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/prefer-regex-literals"); -const { RuleTester } = require("../../../lib/rule-tester"); +const { RuleTester } = require("../../../lib/rule-tester"), + FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -575,7 +576,13 @@ ruleTester.run("prefer-regex-literals", rule, { messageId: "unexpectedRedundantRegExp", type: "NewExpression", line: 1, - column: 1 + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteral", + output: "/a/;" + } + ] } ] }, @@ -591,7 +598,187 @@ ruleTester.run("prefer-regex-literals", rule, { messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, - column: 1 + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/u;", + data: { + flags: "u" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/g, '');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/;", + data: { + flags: "" + } + }, + { + messageId: "replaceWithIntendedLiteralAndFlags", + output: "/a/g;", + data: { + flags: "g" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/g, 'g');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/g;", + data: { + flags: "g" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/ig, 'g');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/g;", + data: { + flags: "g" + } + }, + { + messageId: "replaceWithIntendedLiteralAndFlags", + output: "/a/ig;", + data: { + flags: "ig" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/g, 'ig');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/ig;", + data: { + flags: "ig" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/i, 'g');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/g;", + data: { + flags: "g" + } + }, + { + messageId: "replaceWithIntendedLiteralAndFlags", + output: "/a/ig;", + data: { + flags: "ig" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/i, 'i');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/i;", + data: { + flags: "i" + } + } + ] } ] }, @@ -607,12 +794,21 @@ ruleTester.run("prefer-regex-literals", rule, { messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, - column: 1 + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/u;", + data: { + flags: "u" + } + } + ] } ] }, { - code: "new RegExp(/a/, String.raw`u`);", + code: "new RegExp(/a/, `gi`);", options: [ { disallowRedundantWrapping: true @@ -623,7 +819,16 @@ ruleTester.run("prefer-regex-literals", rule, { messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, - column: 1 + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/gi;", + data: { + flags: "gi" + } + } + ] } ] }, @@ -649,6 +854,138 @@ ruleTester.run("prefer-regex-literals", rule, { } ] }, + { + code: "new RegExp(/a/, String.raw`u`);", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/u;", + data: { + flags: "u" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/ /* comment */);", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExp", + type: "NewExpression", + line: 1, + column: 1, + suggestions: null + } + ] + }, + { + code: "new RegExp(/a/, 'd');", + options: [ + { + disallowRedundantWrapping: true + } + ], + parserOptions: { + ecmaVersion: 2021 + }, + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: null + } + ] + }, + { + code: "(a)\nnew RegExp(/b/);", + options: [{ + disallowRedundantWrapping: true + }], + errors: [ + { + messageId: "unexpectedRedundantRegExp", + type: "NewExpression", + line: 2, + column: 1, + suggestions: null + } + ] + }, + { + code: "(a)\nnew RegExp(/b/, 'g');", + options: [{ + disallowRedundantWrapping: true + }], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 2, + column: 1, + suggestions: null + } + ] + }, + { + code: "a/RegExp(/foo/);", + options: [{ + disallowRedundantWrapping: true + }], + errors: [ + { + messageId: "unexpectedRedundantRegExp", + type: "CallExpression", + line: 1, + column: 3, + suggestions: [ + { + messageId: "replaceWithLiteral", + output: "a/ /foo/;" + } + ] + } + ] + }, + { + code: "RegExp(/foo/)in a;", + options: [{ + disallowRedundantWrapping: true + }], + errors: [ + { + messageId: "unexpectedRedundantRegExp", + type: "CallExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteral", + output: "/foo/ in a;" + } + ] + } + ] + }, { code: "new RegExp((String?.raw)`a`);", errors: [ @@ -2474,3 +2811,27 @@ ruleTester.run("prefer-regex-literals", rule, { } ] }); + +const flatRuleTester = new FlatRuleTester(); + +flatRuleTester.run("prefer-regex-literals", rule, { + valid: [], + + invalid: [ + { + code: "var regex = new RegExp('foo', 'u');", + languageOptions: { + ecmaVersion: 2015 + }, + errors: [{ + messageId: "unexpectedRegExp", + suggestions: [ + { + messageId: "replaceWithLiteral", + output: "var regex = /foo/u;" + } + ] + }] + } + ] +}); diff --git a/tests/lib/rules/require-unicode-regexp.js b/tests/lib/rules/require-unicode-regexp.js index 16b6be4ff11..22ada7aacf6 100644 --- a/tests/lib/rules/require-unicode-regexp.js +++ b/tests/lib/rules/require-unicode-regexp.js @@ -25,8 +25,10 @@ ruleTester.run("require-unicode-regexp", rule, { "/foo/u", "/foo/gimuy", "RegExp('', 'u')", + "RegExp('', `u`)", "new RegExp('', 'u')", "RegExp('', 'gimuy')", + "RegExp('', `gimuy`)", "new RegExp('', 'gimuy')", "const flags = 'u'; new RegExp('', flags)", "const flags = 'g'; new RegExp('', flags + 'u')", @@ -44,60 +46,239 @@ ruleTester.run("require-unicode-regexp", rule, { { code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } } ], invalid: [ + { + code: "/\\a/", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, { code: "/foo/", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "/foo/u" + } + ] + }] }, { code: "/foo/gimy", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "/foo/gimyu" + } + ] + }] }, { code: "RegExp('foo')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', \"u\")" + } + ] + }] + }, + { + code: "RegExp('\\\\a')", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "RegExp('foo', '')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', 'u')" + } + ] + }] }, { code: "RegExp('foo', 'gimy')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', 'gimyu')" + } + ] + }] + }, + { + code: "RegExp('foo', `gimy`)", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', `gimyu`)" + } + ] + }] }, { code: "new RegExp('foo')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', \"u\")" + } + ] + }] + }, + { + code: "new RegExp('foo', false)", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "new RegExp('foo', 1)", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "new RegExp('foo', '')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', 'u')" + } + ] + }] }, { code: "new RegExp('foo', 'gimy')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', 'gimyu')" + } + ] + }] + }, + { + code: "new RegExp(('foo'))", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp(('foo'), \"u\")" + } + ] + }] + }, + { + code: "new RegExp(('unrelated', 'foo'))", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp(('unrelated', 'foo'), \"u\")" + } + ] + }] }, { code: "const flags = 'gi'; new RegExp('foo', flags)", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "const flags = 'gi'; new RegExp('foo', ('unrelated', flags))", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "let flags; new RegExp('foo', flags = 'g')", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "const flags = `gi`; new RegExp(`foo`, (`unrelated`, flags))", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "const flags = 'gimu'; new RegExp('foo', flags[0])", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "new window.RegExp('foo')", env: { browser: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new window.RegExp('foo', \"u\")" + } + ] + }] }, { code: "new global.RegExp('foo')", env: { node: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new global.RegExp('foo', \"u\")" + } + ] + }] }, { code: "new globalThis.RegExp('foo')", env: { es2020: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new globalThis.RegExp('foo', \"u\")" + } + ] + }] } ] }); diff --git a/tests/lib/rules/strict.js b/tests/lib/rules/strict.js index e3ea705fe7e..2798bc8cd53 100644 --- a/tests/lib/rules/strict.js +++ b/tests/lib/rules/strict.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/strict"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -659,3 +660,46 @@ ruleTester.run("strict", rule, { } ] }); + +const flatRuleTester = new FlatRuleTester(); + +// TODO: merge these tests into `ruleTester.run` once we switch to FlatRuleTester (when FlatRuleTester becomes RuleTester). +flatRuleTester.run("strict", rule, { + valid: [ + { + code: "'use strict'; module.exports = function identity (value) { return value; }", + languageOptions: { + sourceType: "commonjs" + } + }, + { + code: "'use strict'; module.exports = function identity (value) { return value; }", + options: ["safe"], + languageOptions: { + sourceType: "commonjs" + } + } + ], + + invalid: [ + { + code: "module.exports = function identity (value) { return value; }", + options: ["safe"], + languageOptions: { + sourceType: "commonjs" + }, + errors: [ + { messageId: "global", line: 1 } + ] + }, + { + code: "module.exports = function identity (value) { return value; }", + languageOptions: { + sourceType: "commonjs" + }, + errors: [ + { messageId: "global", line: 1 } + ] + } + ] +}); diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index 1cd8727b46b..3f9da731676 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -61,22 +61,26 @@ describe("ast-utils", () => { describe("isTokenOnSameLine", () => { it("should return false if the tokens are not on the same line", () => { - linter.defineRule("checker", mustCall(context => ({ - BlockStatement: mustCall(node => { - assert.isFalse(astUtils.isTokenOnSameLine(context.getTokenBefore(node), node)); - }) - }))); + linter.defineRule("checker", { + create: mustCall(context => ({ + BlockStatement: mustCall(node => { + assert.isFalse(astUtils.isTokenOnSameLine(context.getTokenBefore(node), node)); + }) + })) + }); linter.verify("if(a)\n{}", { rules: { checker: "error" } }); }); it("should return true if the tokens are on the same line", () => { - linter.defineRule("checker", mustCall(context => ({ - BlockStatement: mustCall(node => { - assert.isTrue(astUtils.isTokenOnSameLine(context.getTokenBefore(node), node)); - }) - }))); + linter.defineRule("checker", { + create: mustCall(context => ({ + BlockStatement: mustCall(node => { + assert.isTrue(astUtils.isTokenOnSameLine(context.getTokenBefore(node), node)); + }) + })) + }); linter.verify("if(a){}", { rules: { checker: "error" } }); }); @@ -116,64 +120,74 @@ describe("ast-utils", () => { // catch it("should return true if reference is assigned for catch", () => { - linter.defineRule("checker", mustCall(context => ({ - CatchClause: mustCall(node => { - const variables = context.getDeclaredVariables(node); + linter.defineRule("checker", { + create: mustCall(context => ({ + CatchClause: mustCall(node => { + const variables = context.getDeclaredVariables(node); - assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); - }) - }))); + assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); + }) + })) + }); linter.verify("try { } catch (e) { e = 10; }", { rules: { checker: "error" } }); }); // const it("should return true if reference is assigned for const", () => { - linter.defineRule("checker", mustCall(context => ({ - VariableDeclaration: mustCall(node => { - const variables = context.getDeclaredVariables(node); + linter.defineRule("checker", { + create: mustCall(context => ({ + VariableDeclaration: mustCall(node => { + const variables = context.getDeclaredVariables(node); - assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); - }) - }))); + assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); + }) + })) + }); linter.verify("const a = 1; a = 2;", { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); }); it("should return false if reference is not assigned for const", () => { - linter.defineRule("checker", mustCall(context => ({ - VariableDeclaration: mustCall(node => { - const variables = context.getDeclaredVariables(node); + linter.defineRule("checker", { + create: mustCall(context => ({ + VariableDeclaration: mustCall(node => { + const variables = context.getDeclaredVariables(node); - assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 0); - }) - }))); + assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 0); + }) + })) + }); linter.verify("const a = 1; c = 2;", { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); }); // class it("should return true if reference is assigned for class", () => { - linter.defineRule("checker", mustCall(context => ({ - ClassDeclaration: mustCall(node => { - const variables = context.getDeclaredVariables(node); + linter.defineRule("checker", { + create: mustCall(context => ({ + ClassDeclaration: mustCall(node => { + const variables = context.getDeclaredVariables(node); - assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); - assert.lengthOf(astUtils.getModifyingReferences(variables[1].references), 0); - }) - }))); + assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); + assert.lengthOf(astUtils.getModifyingReferences(variables[1].references), 0); + }) + })) + }); linter.verify("class A { }\n A = 1;", { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); }); it("should return false if reference is not assigned for class", () => { - linter.defineRule("checker", mustCall(context => ({ - ClassDeclaration: mustCall(node => { - const variables = context.getDeclaredVariables(node); + linter.defineRule("checker", { + create: mustCall(context => ({ + ClassDeclaration: mustCall(node => { + const variables = context.getDeclaredVariables(node); - assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 0); - }) - }))); + assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 0); + }) + })) + }); linter.verify("class A { } foo(A);", { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); }); @@ -372,11 +386,13 @@ describe("ast-utils", () => { function assertNodeTypeInLoop(code, nodeType, expectedInLoop) { const results = []; - linter.defineRule("checker", mustCall(() => ({ - [nodeType]: mustCall(node => { - results.push(astUtils.isInLoop(node)); - }) - }))); + linter.defineRule("checker", { + create: mustCall(() => ({ + [nodeType]: mustCall(node => { + results.push(astUtils.isInLoop(node)); + }) + })) + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.lengthOf(results, 1); @@ -897,14 +913,16 @@ describe("ast-utils", () => { Object.keys(expectedResults).forEach(key => { it(`should return "${expectedResults[key]}" for "${key}".`, () => { - linter.defineRule("checker", mustCall(() => ({ - ":function": mustCall(node => { - assert.strictEqual( - astUtils.getFunctionNameWithKind(node), - expectedResults[key] - ); - }) - }))); + linter.defineRule("checker", { + create: mustCall(() => ({ + ":function": mustCall(node => { + assert.strictEqual( + astUtils.getFunctionNameWithKind(node), + expectedResults[key] + ); + }) + })) + }); linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 13 } }); }); @@ -975,14 +993,16 @@ describe("ast-utils", () => { }; it(`should return "${JSON.stringify(expectedLoc)}" for "${key}".`, () => { - linter.defineRule("checker", mustCall(() => ({ - ":function": mustCall(node => { - assert.deepStrictEqual( - astUtils.getFunctionHeadLoc(node, linter.getSourceCode()), - expectedLoc - ); - }) - }))); + linter.defineRule("checker", { + create: mustCall(() => ({ + ":function": mustCall(node => { + assert.deepStrictEqual( + astUtils.getFunctionHeadLoc(node, linter.getSourceCode()), + expectedLoc + ); + }) + })) + }); linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 13 } }, "test.js", true); }); @@ -1486,10 +1506,14 @@ describe("ast-utils", () => { [["a/", "+b"], true], [["a+", "/^regex$/"], true], [["a/", "/^regex$/"], false], + [["a+", "/**/"], true], [["a+", "/**/b"], true], + [["//", "a"], false], [["a/", "/**/b"], false], + [["a+", "//"], true], [["a+", "//\nb"], true], [["a/", "//\nb"], false], + [["/**/", "b"], true], [["a/**/", "b"], true], [["/**/a", "b"], false], [["a", "/**/b"], true], diff --git a/tests/lib/rules/valid-jsdoc.js b/tests/lib/rules/valid-jsdoc.js index 3b9d9d09cf6..325064fc189 100644 --- a/tests/lib/rules/valid-jsdoc.js +++ b/tests/lib/rules/valid-jsdoc.js @@ -541,7 +541,7 @@ ruleTester.run("valid-jsdoc", rule, { options: [{ requireReturn: false }] }, - // https://github.com/eslint/eslint/issues/9412 - different orders for jsodc tags + // https://github.com/eslint/eslint/issues/9412 - different orders for jsdoc tags { code: "/**\n" + diff --git a/tests/lib/shared/config-validator.js b/tests/lib/shared/config-validator.js index 61d616b3ae0..d54d26e360d 100644 --- a/tests/lib/shared/config-validator.js +++ b/tests/lib/shared/config-validator.js @@ -32,59 +32,48 @@ const assert = require("chai").assert, const linter = new Linter(); -/** - * Fake a rule object - * @param {Object} context context passed to the rules by eslint - * @returns {Object} mocked rule listeners - * @private - */ -function mockRule(context) { - return { - Program(node) { - context.report(node, "Expected a validation error."); - } - }; -} - -mockRule.schema = [ - { - enum: ["first", "second"] +const mockRule = { + meta: { + schema: [{ + enum: ["first", "second"] + }] + }, + create(context) { + return { + Program(node) { + context.report(node, "Expected a validation error."); + } + }; } -]; - -/** - * Fake a rule object - * @param {Object} context context passed to the rules by eslint - * @returns {Object} mocked rule listeners - * @private - */ -function mockObjectRule(context) { - return { - Program(node) { - context.report(node, "Expected a validation error."); - } - }; -} - -mockObjectRule.schema = { - enum: ["first", "second"] }; -/** - * Fake a rule with no options - * @param {Object} context context passed to the rules by eslint - * @returns {Object} mocked rule listeners - * @private - */ -function mockNoOptionsRule(context) { - return { - Program(node) { - context.report(node, "Expected a validation error."); +const mockObjectRule = { + meta: { + schema: { + enum: ["first", "second"] } - }; -} + }, + create(context) { + return { + Program(node) { + context.report(node, "Expected a validation error."); + } + }; + } +}; -mockNoOptionsRule.schema = []; +const mockNoOptionsRule = { + meta: { + schema: [] + }, + create(context) { + return { + Program(node) { + context.report(node, "Expected a validation error."); + } + }; + } +}; const mockRequiredOptionsRule = { meta: { diff --git a/tests/lib/shared/runtime-info.js b/tests/lib/shared/runtime-info.js index efe57bb0baa..ac549c0d001 100644 --- a/tests/lib/shared/runtime-info.js +++ b/tests/lib/shared/runtime-info.js @@ -206,7 +206,7 @@ describe("RuntimeInfo", () => { spawnSyncStubArgs[2] = "This is not JSON"; setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); - assert.throws(RuntimeInfo.environment, "Unexpected token T in JSON at position 0"); + assert.throws(RuntimeInfo.environment, /^Unexpected token .*T.* JSON/u); assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint`"); }); @@ -214,7 +214,7 @@ describe("RuntimeInfo", () => { spawnSyncStubArgs[4] = "This is not JSON"; setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); - assert.throws(RuntimeInfo.environment, "Unexpected token T in JSON at position 0"); + assert.throws(RuntimeInfo.environment, /^Unexpected token .*T.* JSON/u); assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint -g`"); }); }); diff --git a/tests/lib/source-code/source-code.js b/tests/lib/source-code/source-code.js index c44d4a2e434..d65045ad384 100644 --- a/tests/lib/source-code/source-code.js +++ b/tests/lib/source-code/source-code.js @@ -253,7 +253,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -281,7 +287,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -311,7 +323,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledTwice, "Event handler should be called twice."); @@ -343,7 +361,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -371,7 +395,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -400,7 +430,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -431,7 +467,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -462,7 +504,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -492,7 +540,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -524,7 +578,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -555,7 +615,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -586,7 +652,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ ArrowFunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + ArrowFunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -615,7 +687,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -648,7 +726,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledTwice, "Event handler should be called."); }); @@ -680,7 +764,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledTwice, "Event handler should be called."); }); @@ -710,7 +800,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -748,7 +844,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledTwice, "Event handler should be called."); }); @@ -777,7 +879,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ ClassExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + ClassExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -806,7 +914,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ ClassDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + ClassDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -835,7 +949,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -868,7 +988,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -899,7 +1025,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -956,13 +1088,17 @@ describe("SourceCode", () => { "/* Trailing comment for VariableDeclaration */" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(1, 1), - VariableDeclarator: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(1, 1), + VariableDeclarator: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -975,13 +1111,17 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 1), - CallExpression: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 1), + CallExpression: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -992,12 +1132,16 @@ describe("SourceCode", () => { "if (/* Leading comment for Identifier */ a) {}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - IfStatement: assertCommentCount(1, 0), - Identifier: assertCommentCount(1, 0), - BlockStatement: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + IfStatement: assertCommentCount(1, 0), + Identifier: assertCommentCount(1, 0), + BlockStatement: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1012,15 +1156,19 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(0, 0), - VariableDeclarator: assertCommentCount(0, 0), - ObjectExpression: assertCommentCount(0, 1), - ReturnStatement: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(0, 0), + VariableDeclarator: assertCommentCount(0, 0), + ObjectExpression: assertCommentCount(0, 1), + ReturnStatement: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1034,15 +1182,19 @@ describe("SourceCode", () => { "var baz;" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(0, 0), - VariableDeclarator: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - ObjectExpression: assertCommentCount(0, 0), - Property: assertCommentCount(0, 1), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(0, 0), + VariableDeclarator: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + ObjectExpression: assertCommentCount(0, 0), + Property: assertCommentCount(0, 1), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1061,16 +1213,20 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ExportDefaultDeclaration: assertCommentCount(1, 0), - ClassDeclaration: assertCommentCount(0, 0), - ClassBody: assertCommentCount(0, 0), - MethodDefinition: assertCommentCount(1, 0), - Identifier: assertCommentCount(0, 0), - FunctionExpression: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ExportDefaultDeclaration: assertCommentCount(1, 0), + ClassDeclaration: assertCommentCount(0, 0), + ClassBody: assertCommentCount(0, 0), + MethodDefinition: assertCommentCount(1, 0), + Identifier: assertCommentCount(0, 0), + FunctionExpression: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1084,19 +1240,25 @@ describe("SourceCode", () => { ].join("\n"); let varDeclCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration(node) { - if (varDeclCount === 0) { - assertCommentCount(1, 1)(node); - } else { - assertCommentCount(1, 0)(node); - } - varDeclCount++; - }, - VariableDeclarator: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + + VariableDeclaration(node) { + if (varDeclCount === 0) { + assertCommentCount(1, 1)(node); + } else { + assertCommentCount(1, 0)(node); + } + varDeclCount++; + }, + + VariableDeclarator: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1110,18 +1272,24 @@ describe("SourceCode", () => { ].join("\n"); let varDeclCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration(node) { - if (varDeclCount === 0) { - assertCommentCount(1, 1)(node); - } else { - assertCommentCount(1, 0)(node); - } - varDeclCount++; - }, - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + + VariableDeclaration(node) { + if (varDeclCount === 0) { + assertCommentCount(1, 1)(node); + } else { + assertCommentCount(1, 0)(node); + } + varDeclCount++; + }, + + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1129,7 +1297,13 @@ describe("SourceCode", () => { it("should include shebang comment when program only contains shebang", () => { const code = "#!/usr/bin/env node"; - linter.defineRule("checker", () => ({ Program: assertCommentCount(1, 0) })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(1, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1141,13 +1315,17 @@ describe("SourceCode", () => { "// Trailing comment for VariableDeclaration" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(1, 1), - VariableDeclarator: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 1), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(1, 1), + VariableDeclarator: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 1), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1161,14 +1339,18 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(1, 1), - CallExpression: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(1, 1), + CallExpression: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1182,13 +1364,17 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - DebuggerStatement: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + DebuggerStatement: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1202,13 +1388,17 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - ReturnStatement: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + ReturnStatement: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1222,13 +1412,17 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - ThrowStatement: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + ThrowStatement: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1243,16 +1437,20 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - WhileStatement: assertCommentCount(1, 1), - Literal: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(1, 0), - VariableDeclarator: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + WhileStatement: assertCommentCount(1, 1), + Literal: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(1, 0), + VariableDeclarator: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1271,24 +1469,30 @@ describe("SourceCode", () => { ].join("\n"); let switchCaseCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 0), - SwitchCase: node => { - if (switchCaseCount === 0) { - assertCommentCount(1, 1)(node); - } else { - assertCommentCount(1, 0)(node); - } - switchCaseCount++; - }, - Literal: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 0), - CallExpression: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 0), + + SwitchCase(node) { + if (switchCaseCount === 0) { + assertCommentCount(1, 1)(node); + } else { + assertCommentCount(1, 0)(node); + } + switchCaseCount++; + }, + + Literal: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 0), + CallExpression: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1305,21 +1509,27 @@ describe("SourceCode", () => { ].join("\n"); let switchCaseCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 0), - SwitchCase: node => { - if (switchCaseCount === 0) { - assertCommentCount(1, 1)(node); - } else { - assertCommentCount(1, 0)(node); - } - switchCaseCount++; - }, - Literal: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 0), - CallExpression: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 0), + + SwitchCase(node) { + if (switchCaseCount === 0) { + assertCommentCount(1, 1)(node); + } else { + assertCommentCount(1, 0)(node); + } + switchCaseCount++; + }, + + Literal: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 0), + CallExpression: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1338,23 +1548,29 @@ describe("SourceCode", () => { ].join("\n"); let breakStatementCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 0), - SwitchCase: node => { - if (breakStatementCount === 0) { - assertCommentCount(0, 0)(node); - } else { - assertCommentCount(0, 1)(node); - } - breakStatementCount++; - }, - BreakStatement: assertCommentCount(0, 0), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 0), + + SwitchCase(node) { + if (breakStatementCount === 0) { + assertCommentCount(0, 0)(node); + } else { + assertCommentCount(0, 1)(node); + } + breakStatementCount++; + }, + + BreakStatement: assertCommentCount(0, 0), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1368,14 +1584,18 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - SwitchCase: assertCommentCount(0, 1), - BreakStatement: assertCommentCount(0, 0), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + SwitchCase: assertCommentCount(0, 1), + BreakStatement: assertCommentCount(0, 0), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1394,22 +1614,26 @@ describe("SourceCode", () => { "};" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 0), - AssignmentExpression: assertCommentCount(0, 0), - MemberExpression: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - FunctionExpression: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 0), - SwitchCase: assertCommentCount(0, 1), - ReturnStatement: assertCommentCount(0, 0), - CallExpression: assertCommentCount(0, 0), - BinaryExpression: assertCommentCount(0, 0), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 0), + AssignmentExpression: assertCommentCount(0, 0), + MemberExpression: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + FunctionExpression: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 0), + SwitchCase: assertCommentCount(0, 1), + ReturnStatement: assertCommentCount(0, 0), + CallExpression: assertCommentCount(0, 0), + BinaryExpression: assertCommentCount(0, 0), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1420,7 +1644,13 @@ describe("SourceCode", () => { "/*another comment*/" ].join("\n"); - linter.defineRule("checker", () => ({ Program: assertCommentCount(2, 0) })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(2, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1433,10 +1663,14 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 2) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 2) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1449,11 +1683,15 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ClassDeclaration: assertCommentCount(0, 0), - ClassBody: assertCommentCount(0, 2) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ClassDeclaration: assertCommentCount(0, 0), + ClassBody: assertCommentCount(0, 2) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1466,11 +1704,15 @@ describe("SourceCode", () => { "})" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 0), - ObjectExpression: assertCommentCount(0, 2) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 0), + ObjectExpression: assertCommentCount(0, 2) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1483,11 +1725,15 @@ describe("SourceCode", () => { "]" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 0), - ArrayExpression: assertCommentCount(0, 2) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 0), + ArrayExpression: assertCommentCount(0, 2) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1500,11 +1746,15 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 2), - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 2), + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1519,21 +1769,27 @@ describe("SourceCode", () => { ].join("\n"); let varDeclCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(1, 2), - VariableDeclarator: node => { - if (varDeclCount === 0) { - assertCommentCount(0, 0)(node); - } else if (varDeclCount === 1) { - assertCommentCount(1, 0)(node); - } else { - assertCommentCount(1, 0)(node); - } - varDeclCount++; - }, - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(1, 2), + + VariableDeclarator(node) { + if (varDeclCount === 0) { + assertCommentCount(0, 0)(node); + } else if (varDeclCount === 1) { + assertCommentCount(1, 0)(node); + } else { + assertCommentCount(1, 0)(node); + } + varDeclCount++; + }, + + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1545,12 +1801,16 @@ describe("SourceCode", () => { " a;" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(0, 0), - VariableDeclarator: assertCommentCount(2, 0), - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(0, 0), + VariableDeclarator: assertCommentCount(2, 0), + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1558,12 +1818,16 @@ describe("SourceCode", () => { it("should return attached comments between tokens to the correct nodes for empty function declarations", () => { const code = "/* 1 */ function /* 2 */ foo(/* 3 */) /* 4 */ { /* 5 */ } /* 6 */"; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(1, 1), - Identifier: assertCommentCount(1, 0), - BlockStatement: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(1, 1), + Identifier: assertCommentCount(1, 0), + BlockStatement: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1572,19 +1836,25 @@ describe("SourceCode", () => { const code = "/* 1 */ class /* 2 */ Foo /* 3 */ extends /* 4 */ Bar /* 5 */ { /* 6 */ } /* 7 */"; let idCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ClassDeclaration: assertCommentCount(1, 1), - Identifier: node => { - if (idCount === 0) { - assertCommentCount(1, 1)(node); - } else { - assertCommentCount(1, 1)(node); - } - idCount++; - }, - ClassBody: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ClassDeclaration: assertCommentCount(1, 1), + + Identifier(node) { + if (idCount === 0) { + assertCommentCount(1, 1)(node); + } else { + assertCommentCount(1, 1)(node); + } + idCount++; + }, + + ClassBody: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1592,11 +1862,15 @@ describe("SourceCode", () => { it("should return attached comments between tokens to the correct nodes for empty switch statements", () => { const code = "/* 1 */ switch /* 2 */ (/* 3 */ foo /* 4 */) /* 5 */ { /* 6 */ } /* 7 */"; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(1, 6), - Identifier: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(1, 6), + Identifier: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1735,7 +2009,6 @@ describe("SourceCode", () => { }); - describe("getNodeByRangeIndex()", () => { let sourceCode; @@ -2737,4 +3010,277 @@ describe("SourceCode", () => { assert.strictEqual(sourceCode.getIndexFromLoc({ line: 8, column: 0 }), CODE.length); }); }); + + describe("getScope()", () => { + + it("should throw an error when argument is missing", () => { + + linter.defineRule("get-scope", { + create: context => ({ + Program() { + context.getSourceCode().getScope(); + } + }) + }); + + assert.throws(() => { + linter.verify( + "foo", + { + rules: { "get-scope": 2 } + } + ); + }, /Missing required argument: node/u); + + }); + + /** + * Get the scope on the node `astSelector` specified. + * @param {string} code The source code to verify. + * @param {string} astSelector The AST selector to get scope. + * @param {number} [ecmaVersion=5] The ECMAScript version. + * @returns {{node: ASTNode, scope: escope.Scope}} Gotten scope. + */ + function getScope(code, astSelector, ecmaVersion = 5) { + let node, scope; + + linter.defineRule("get-scope", { + create: context => ({ + [astSelector](node0) { + node = node0; + scope = context.getSourceCode().getScope(node); + } + }) + }); + linter.verify( + code, + { + parserOptions: { ecmaVersion }, + rules: { "get-scope": 2 } + } + ); + + return { node, scope }; + } + + it("should return 'function' scope on FunctionDeclaration (ES5)", () => { + const { node, scope } = getScope("function f() {}", "FunctionDeclaration"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node); + }); + + it("should return 'function' scope on FunctionExpression (ES5)", () => { + const { node, scope } = getScope("!function f() {}", "FunctionExpression"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node); + }); + + it("should return 'function' scope on the body of FunctionDeclaration (ES5)", () => { + const { node, scope } = getScope("function f() {}", "BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent); + }); + + it("should return 'function' scope on the body of FunctionDeclaration (ES2015)", () => { + const { node, scope } = getScope("function f() {}", "BlockStatement", 2015); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent); + }); + + it("should return 'function' scope on BlockStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { { var b; } }", "BlockStatement > BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'block' scope on BlockStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { { let a; var b; } }", "BlockStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "function"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); + assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'block' scope on nested BlockStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { { let a; { let b; var c; } } }", "BlockStatement > BlockStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "block"); + assert.strictEqual(scope.upper.upper.type, "function"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["a"]); + assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "c"]); + }); + + it("should return 'function' scope on SwitchStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'switch' scope on SwitchStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchStatement", 2015); + + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); + }); + + it("should return 'function' scope on SwitchCase in functions (ES5)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchCase"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'switch' scope on SwitchCase in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchCase", 2015); + + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block, node.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); + }); + + it("should return 'catch' scope on CatchClause in functions (ES5)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause"); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); + }); + + it("should return 'catch' scope on CatchClause in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause", 2015); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); + }); + + it("should return 'catch' scope on the block of CatchClause in functions (ES5)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause > BlockStatement"); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); + }); + + it("should return 'block' scope on the block of CatchClause in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); + }); + + it("should return 'function' scope on ForStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); + }); + + it("should return 'for' scope on ForStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["i"]); + }); + + it("should return 'function' scope on the block body of ForStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); + }); + + it("should return 'block' scope on the block body of ForStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), []); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["i"]); + }); + + it("should return 'function' scope on ForInStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); + }); + + it("should return 'for' scope on ForInStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["key"]); + }); + + it("should return 'function' scope on the block body of ForInStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement > BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); + }); + + it("should return 'block' scope on the block body of ForInStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), []); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["key"]); + }); + + it("should return 'for' scope on ForOfStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["x"]); + }); + + it("should return 'block' scope on the block body of ForOfStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), []); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["x"]); + }); + + it("should shadow the same name variable by the iteration variable.", () => { + const { node, scope } = getScope("let x; for (let x of x) {}", "ForOfStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.upper.type, "global"); + assert.strictEqual(scope.block, node); + assert.strictEqual(scope.upper.variables[0].references.length, 0); + assert.strictEqual(scope.references[0].identifier, node.left.declarations[0].id); + assert.strictEqual(scope.references[1].identifier, node.right); + assert.strictEqual(scope.references[1].resolved, scope.variables[0]); + }); + }); }); diff --git a/tests/lib/source-code/token-store.js b/tests/lib/source-code/token-store.js index ff54a6a838a..3b9db7cb95f 100644 --- a/tests/lib/source-code/token-store.js +++ b/tests/lib/source-code/token-store.js @@ -627,8 +627,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the first of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not start with a token: it can start with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getFirstToken( { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] }, @@ -644,8 +644,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the first of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not start with a token: it can start with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getFirstToken( { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] } @@ -654,6 +654,38 @@ describe("TokenStore", () => { assert.strictEqual(token.value, "c"); }); + it("should retrieve the first token if the root node contains a trailing comment", () => { + const parser = require("../../fixtures/parsers/all-comments-parser"); + const code = "foo // comment"; + const ast = parser.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast); + + assert.strictEqual(token, ast.tokens[0]); + }); + + it("should return null if the source contains only comments", () => { + const code = "// comment"; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast, { + filter() { + assert.fail("Unexpected call to filter callback"); + } + }); + + assert.strictEqual(token, null); + }); + + it("should return null if the source is empty", () => { + const code = ""; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast); + + assert.strictEqual(token, null); + }); + }); describe("when calling getLastTokens", () => { @@ -814,8 +846,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the last of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not end with a token: it can end with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getLastToken( { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] }, @@ -831,8 +863,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the last of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not end with a token: it can end with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getLastToken( { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] } @@ -841,6 +873,38 @@ describe("TokenStore", () => { assert.strictEqual(token.value, "b"); }); + it("should retrieve the last token if the root node contains a trailing comment", () => { + const parser = require("../../fixtures/parsers/all-comments-parser"); + const code = "foo // comment"; + const ast = parser.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast); + + assert.strictEqual(token, ast.tokens[0]); + }); + + it("should return null if the source contains only comments", () => { + const code = "// comment"; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast, { + filter() { + assert.fail("Unexpected call to filter callback"); + } + }); + + assert.strictEqual(token, null); + }); + + it("should return null if the source is empty", () => { + const code = ""; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast); + + assert.strictEqual(token, null); + }); + }); describe("when calling getFirstTokensBetween", () => { diff --git a/tests/tools/eslint-fuzzer.js b/tests/tools/eslint-fuzzer.js index 1c894b99595..da267de24c1 100644 --- a/tests/tools/eslint-fuzzer.js +++ b/tests/tools/eslint-fuzzer.js @@ -15,7 +15,7 @@ const configRule = require("../../tools/config-rule"); //------------------------------------------------------------------------------ describe("eslint-fuzzer", function() { - let fakeRule, fuzz; + let fuzz; /* * These tests take awhile because isolating which rule caused an error requires running eslint up to hundreds of @@ -39,9 +39,6 @@ describe("eslint-fuzzer", function() { // Make sure the config generator generates a config for "test-fuzzer-rule" sinon.stub(configRule, "createCoreRuleConfigs").returns(Object.assign(realCoreRuleConfigs, { "test-fuzzer-rule": [2] })); - // Create a closure around `fakeRule` so that tests can reassign it and have the changes take effect. - linter.defineRule("test-fuzzer-rule", Object.assign(context => fakeRule(context), { meta: { fixable: "code" } })); - fuzz = require("../../tools/eslint-fuzzer"); }); @@ -52,12 +49,14 @@ describe("eslint-fuzzer", function() { describe("when running in crash-only mode", () => { describe("when a rule crashes on the given input", () => { it("should report the crash with a minimal config", () => { - fakeRule = context => ({ - Program() { - if (context.getSourceCode().text === "foo") { - throw CRASH_BUG; + linter.defineRule("test-fuzzer-rule", { + create: context => ({ + Program() { + if (context.getSourceCode().text === "foo") { + throw CRASH_BUG; + } } - } + }) }); const results = fuzz({ count: 1, codeGenerator: () => "foo", checkAutofixes: false, linter }); @@ -72,7 +71,7 @@ describe("eslint-fuzzer", function() { describe("when no rules crash", () => { it("should return an empty array", () => { - fakeRule = () => ({}); + linter.defineRule("test-fuzzer-rule", { create: () => ({}) }); assert.deepStrictEqual(fuzz({ count: 1, codeGenerator: () => "foo", checkAutofixes: false, linter }), []); }); @@ -91,12 +90,14 @@ describe("eslint-fuzzer", function() { describe("when a rule crashes on the given input", () => { it("should report the crash with a minimal config", () => { - fakeRule = context => ({ - Program() { - if (context.getSourceCode().text === "foo") { - throw CRASH_BUG; + linter.defineRule("test-fuzzer-rule", { + create: context => ({ + Program() { + if (context.getSourceCode().text === "foo") { + throw CRASH_BUG; + } } - } + }) }); const results = fuzz({ count: 1, codeGenerator: () => "foo", checkAutofixes: false, linter }); @@ -113,16 +114,19 @@ describe("eslint-fuzzer", function() { it("does not report any errors", () => { // Replaces programs that start with "foo" with "bar" - fakeRule = context => ({ - Program(node) { - if (context.getSourceCode().text === `foo ${disableFixableRulesComment}`) { - context.report({ - node, - message: "no foos allowed", - fix: fixer => fixer.replaceText(node, `bar ${disableFixableRulesComment}`) - }); + linter.defineRule("test-fuzzer-rule", { + meta: { fixable: "code" }, + create: context => ({ + Program(node) { + if (context.getSourceCode().text === `foo ${disableFixableRulesComment}`) { + context.report({ + node, + message: "no foos allowed", + fix: fixer => fixer.replaceText(node, `bar ${disableFixableRulesComment}`) + }); + } } - } + }) }); const results = fuzz({ @@ -145,18 +149,21 @@ describe("eslint-fuzzer", function() { it("reports an autofix error with a minimal config", () => { // Replaces programs that start with "foo" with invalid syntax - fakeRule = context => ({ - Program(node) { - const sourceCode = context.getSourceCode(); - - if (sourceCode.text === `foo ${disableFixableRulesComment}`) { - context.report({ - node, - message: "no foos allowed", - fix: fixer => fixer.replaceTextRange([0, sourceCode.text.length], INVALID_SYNTAX) - }); + linter.defineRule("test-fuzzer-rule", { + meta: { fixable: "code" }, + create: context => ({ + Program(node) { + const sourceCode = context.getSourceCode(); + + if (sourceCode.text === `foo ${disableFixableRulesComment}`) { + context.report({ + node, + message: "no foos allowed", + fix: fixer => fixer.replaceTextRange([0, sourceCode.text.length], INVALID_SYNTAX) + }); + } } - } + }) }); const results = fuzz({ @@ -186,23 +193,26 @@ describe("eslint-fuzzer", function() { const intermediateCode = `bar ${disableFixableRulesComment}`; // Replaces programs that start with "foo" with invalid syntax - fakeRule = context => ({ - Program(node) { - const sourceCode = context.getSourceCode(); - - if (sourceCode.text.startsWith("foo") || sourceCode.text === intermediateCode) { - context.report({ - node, - message: "no foos allowed", - fix(fixer) { - return fixer.replaceTextRange( - [0, sourceCode.text.length], - sourceCode.text === intermediateCode ? INVALID_SYNTAX : intermediateCode - ); - } - }); + linter.defineRule("test-fuzzer-rule", { + meta: { fixable: "code" }, + create: context => ({ + Program(node) { + const sourceCode = context.getSourceCode(); + + if (sourceCode.text.startsWith("foo") || sourceCode.text === intermediateCode) { + context.report({ + node, + message: "no foos allowed", + fix(fixer) { + return fixer.replaceTextRange( + [0, sourceCode.text.length], + sourceCode.text === intermediateCode ? INVALID_SYNTAX : intermediateCode + ); + } + }); + } } - } + }) }); const results = fuzz({ @@ -231,20 +241,23 @@ describe("eslint-fuzzer", function() { it("reports a crash error with a minimal config", () => { // Replaces programs that start with "foo" with invalid syntax - fakeRule = context => ({ - Program(node) { - const sourceCode = context.getSourceCode(); - - if (sourceCode.text.startsWith("foo")) { - context.report({ - node, - message: "no foos allowed", - fix: fixer => fixer.replaceText(node, "bar") - }); - } else if (sourceCode.text === `bar ${disableFixableRulesComment}`) { - throw CRASH_BUG; + linter.defineRule("test-fuzzer-rule", { + meta: { fixable: "code" }, + create: context => ({ + Program(node) { + const sourceCode = context.getSourceCode(); + + if (sourceCode.text.startsWith("foo")) { + context.report({ + node, + message: "no foos allowed", + fix: fixer => fixer.replaceText(node, "bar") + }); + } else if (sourceCode.text === `bar ${disableFixableRulesComment}`) { + throw CRASH_BUG; + } } - } + }) }); const results = fuzz({ diff --git a/tools/commit-readme.sh b/tools/commit-readme.sh new file mode 100644 index 00000000000..f2341f0ddca --- /dev/null +++ b/tools/commit-readme.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +#------------------------------------------------------------------------------ +# Commits the data files if any have changed +#------------------------------------------------------------------------------ + +if [ -z "$(git status --porcelain)" ]; then + echo "Data did not change." +else + echo "Data changed!" + + # commit the result + git add README.md + git commit -m "docs: Update README" + + # push back to source control + git push origin HEAD +fi diff --git a/tools/fetch-docs-links.js b/tools/fetch-docs-links.js index 3d92633f37f..9cdd05aab1a 100644 --- a/tools/fetch-docs-links.js +++ b/tools/fetch-docs-links.js @@ -7,7 +7,7 @@ * * To fetch info for just selected files (for use with lint-staged): * - * node tools/fetch-docs-links.js docs/src/user-guide/index.md + * node tools/fetch-docs-links.js docs/src/use/index.md * * @author Nicholas C. Zakas */ @@ -99,7 +99,7 @@ async function fetchLinkMeta(url) { console.error("Could not fetch data for", url); console.error(ex.message); console.error(ex.stack); - process.exit(1); // eslint-disable-line n/no-process-exit -- used in tools + process.exit(1); } } } diff --git a/tools/fuzzer-runner.js b/tools/fuzzer-runner.js index b056c701f9e..805cc025258 100644 --- a/tools/fuzzer-runner.js +++ b/tools/fuzzer-runner.js @@ -58,7 +58,7 @@ function run({ amount = 300, fuzzBrokenAutofixes = true } = {}) { linter, count: crashTestCount, checkAutofixes: false, - progressCallback: elapsedErrors => { + progressCallback(elapsedErrors) { progressBar.tick(1, { elapsedErrors }); progressBar.render(); } @@ -68,7 +68,7 @@ function run({ amount = 300, fuzzBrokenAutofixes = true } = {}) { linter, count: autofixTestCount, checkAutofixes: true, - progressCallback: elapsedErrors => { + progressCallback(elapsedErrors) { progressBar.tick(ESTIMATED_CRASH_AUTOFIX_PERFORMANCE_RATIO, { elapsedErrors: crashTestResults.length + elapsedErrors }); progressBar.render(); } diff --git a/tools/internal-testers/event-generator-tester.js b/tools/internal-testers/event-generator-tester.js index ce4449a6b6a..46cbafb0ffe 100644 --- a/tools/internal-testers/event-generator-tester.js +++ b/tools/internal-testers/event-generator-tester.js @@ -4,7 +4,7 @@ */ "use strict"; -/* eslint-env mocha -- Mocha */ +/* globals describe, it -- Mocha globals */ //------------------------------------------------------------------------------ // Requirements @@ -24,7 +24,7 @@ module.exports = { * @param {Function} method A test logic. * @returns {any} The returned value with the test logic. */ - describe: (typeof describe === "function") ? describe : /* istanbul ignore next */ function(text, method) { + describe: (typeof describe === "function") ? describe : /* c8 ignore next */ function(text, method) { return method.apply(this); }, @@ -34,7 +34,7 @@ module.exports = { * @param {Function} method A test logic. * @returns {any} The returned value with the test logic. */ - it: (typeof it === "function") ? it : /* istanbul ignore next */ function(text, method) { + it: (typeof it === "function") ? it : /* c8 ignore next */ function(text, method) { return method.apply(this); }, diff --git a/tools/rule-types.json b/tools/rule-types.json index d69028448a1..f3fe8f80cd1 100644 --- a/tools/rule-types.json +++ b/tools/rule-types.json @@ -59,6 +59,7 @@ "lines-around-comment": "layout", "lines-around-directive": "layout", "lines-between-class-members": "layout", + "logical-assignment-operators": "suggestion", "max-classes-per-file": "suggestion", "max-depth": "suggestion", "max-len": "layout", @@ -109,6 +110,7 @@ "no-empty-character-class": "problem", "no-empty-function": "suggestion", "no-empty-pattern": "problem", + "no-empty-static-block": "suggestion", "no-eq-null": "suggestion", "no-eval": "suggestion", "no-ex-assign": "problem", @@ -153,6 +155,7 @@ "no-nested-ternary": "suggestion", "no-new": "suggestion", "no-new-func": "suggestion", + "no-new-native-nonconstructor": "problem", "no-new-object": "suggestion", "no-new-require": "suggestion", "no-new-symbol": "problem", diff --git a/conf/eslint-all.js b/tools/update-eslint-all.js similarity index 54% rename from conf/eslint-all.js rename to tools/update-eslint-all.js index 10c5304fd3f..b964876f736 100644 --- a/conf/eslint-all.js +++ b/tools/update-eslint-all.js @@ -1,14 +1,15 @@ /** - * @fileoverview Config to enable all rules. - * @author Robert Fletcher + * @fileoverview Script to update the eslint:all configuration. + * @author Nicholas C. Zakas */ "use strict"; -//------------------------------------------------------------------------------ +//----------------------------------------------------------------------------- // Requirements -//------------------------------------------------------------------------------ +//----------------------------------------------------------------------------- +const fs = require("fs"); const builtInRules = require("../lib/rules"); //------------------------------------------------------------------------------ @@ -23,9 +24,19 @@ for (const [ruleId, rule] of builtInRules) { } } -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ +//----------------------------------------------------------------------------- +// Main +//----------------------------------------------------------------------------- + +const code = `/* + * WARNING: This file is autogenerated using the tools/update-eslint-all.js + * script. Do not edit manually. + */ +"use strict"; + +/* eslint quote-props: off -- autogenerated so don't lint */ + +module.exports = Object.freeze(${JSON.stringify({ rules: allRules }, null, 4)}); +`; -/** @type {import("../lib/shared/types").ConfigData} */ -module.exports = { rules: allRules }; +fs.writeFileSync("./packages/js/src/configs/eslint-all.js", code, "utf8"); diff --git a/tools/update-readme.js b/tools/update-readme.js index 0704ceff137..4564df0b603 100644 --- a/tools/update-readme.js +++ b/tools/update-readme.js @@ -13,20 +13,19 @@ // Requirements //----------------------------------------------------------------------------- -const path = require("path"); const fs = require("fs"); const { stripIndents } = require("common-tags"); const ejs = require("ejs"); +const got = require("got"); //----------------------------------------------------------------------------- // Data //----------------------------------------------------------------------------- -const README_FILE_PATH = path.resolve(__dirname, "../README.md"); -const WEBSITE_DATA_PATH = path.resolve(__dirname, "../../website/_data"); +const SPONSORS_URL = "https://raw.githubusercontent.com/eslint/eslint.org/main/src/_data/sponsors.json"; +const TEAM_URL = "https://raw.githubusercontent.com/eslint/eslint.org/main/src/_data/team.json"; +const README_FILE_PATH = "./README.md"; -const team = JSON.parse(fs.readFileSync(path.join(WEBSITE_DATA_PATH, "team.json"))); -const allSponsors = JSON.parse(fs.readFileSync(path.join(WEBSITE_DATA_PATH, "sponsors.json"))); const readme = fs.readFileSync(README_FILE_PATH, "utf8"); const heights = { @@ -35,13 +34,31 @@ const heights = { bronze: 32 }; -// remove backers from sponsors list - not shown on readme -delete allSponsors.backers; - //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- +/** + * Fetches the latest sponsors data from the website. + * @returns {Object} The sponsors data object. + */ +async function fetchSponsorsData() { + const data = await got(SPONSORS_URL).json(); + + // remove backers from sponsors list - not shown on readme + delete data.backers; + + return data; +} + +/** + * Fetches the latest team data from the website. + * @returns {Object} The sponsors data object. + */ +async function fetchTeamData() { + return got(TEAM_URL).json(); +} + /** * Formats an array of team members for inclusion in the readme. * @param {Array} members The array of members to format. @@ -54,7 +71,7 @@ function formatTeamMembers(members) { members.map((member, index) => `
    ${(index + 1) % 9 === 0 ? "" : ""}`).join("") }
    `; @@ -74,7 +91,7 @@ function formatSponsors(sponsors) { ${ nonEmptySponsors.map(tier => `

    ${tier[0].toUpperCase()}${tier.slice(1)} Sponsors

    ${ - sponsors[tier].map(sponsor => `${sponsor.name}`).join(" ") + sponsors[tier].map(sponsor => `${sponsor.name}`).join(" ") }

    `).join("") } `; @@ -111,20 +128,38 @@ const HTML_TEMPLATE = stripIndents` <%- formatTeamMembers(team.committers) %> + <% } %> + + <% if (team.website.length > 0) { %> + ### Website Team + + Team members who focus specifically on eslint.org + + <%- formatTeamMembers(team.website) %> + <% } %> `; -// replace all of the section -let newReadme = readme.replace(/[\w\W]*?/u, ejs.render(HTML_TEMPLATE, { - team, - formatTeamMembers -})); +(async () => { + + const [allSponsors, team] = await Promise.all([ + fetchSponsorsData(), + fetchTeamData() + ]); + + // replace all of the section + let newReadme = readme.replace(/[\w\W]*?/u, ejs.render(HTML_TEMPLATE, { + team, + formatTeamMembers + })); + + newReadme = newReadme.replace(/[\w\W]*?/u, formatSponsors(allSponsors)); -newReadme = newReadme.replace(/[\w\W]*?/u, formatSponsors(allSponsors)); + // replace multiple consecutive blank lines with just one blank line + newReadme = newReadme.replace(/(?<=^|\n)\n{2,}/gu, "\n"); -// replace multiple consecutive blank lines with just one blank line -newReadme = newReadme.replace(/(?<=^|\n)\n{2,}/gu, "\n"); + // output to the file + fs.writeFileSync(README_FILE_PATH, newReadme, "utf8"); -// output to the file -fs.writeFileSync(README_FILE_PATH, newReadme, "utf8"); +})();