diff --git a/Makefile b/Makefile
index 44a29a1..16d5de3 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ DSC=${PACKAGE}_${DEB_VERSION_UPSTREAM_REVISION}.dsc
SRCDIR=src
UPSTREAM=eslint
-UPSTREAMTAG=v7.2.0
+UPSTREAMTAG=v7.12.1
BUILDSRC=${UPSTREAM}-${UPSTREAMTAG}
all: ${DEB}
diff --git a/eslint/.eslintrc.js b/eslint/.eslintrc.js
index a53fedb..1457d9b 100644
--- a/eslint/.eslintrc.js
+++ b/eslint/.eslintrc.js
@@ -95,12 +95,8 @@ module.exports = {
files: ["lib/rules/*", "tools/internal-rules/*"],
excludedFiles: ["index.js"],
rules: {
- "internal-rules/no-invalid-meta": "error"
-
- /*
- * TODO: enable it when all the rules using meta.messages
- * "internal-rules/consistent-meta-messages": "error"
- */
+ "internal-rules/no-invalid-meta": "error",
+ "internal-rules/consistent-meta-messages": "error"
}
},
{
diff --git a/eslint/.github/CODEOWNERS.md b/eslint/.github/CODEOWNERS.md
new file mode 100644
index 0000000..b032d30
--- /dev/null
+++ b/eslint/.github/CODEOWNERS.md
@@ -0,0 +1,17 @@
+# 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/eslint/.github/ISSUE_TEMPLATE.md b/eslint/.github/ISSUE_TEMPLATE.md
index 44fe780..3af794a 100644
--- a/eslint/.github/ISSUE_TEMPLATE.md
+++ b/eslint/.github/ISSUE_TEMPLATE.md
@@ -22,7 +22,7 @@
* **Node Version:**
* **npm Version:**
-**What parser (default, Babel-ESLint, etc.) are you using?**
+**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?**
**Please show your full configuration:**
diff --git a/eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md
index 1758715..1b1699a 100644
--- a/eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md
+++ b/eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md
@@ -32,7 +32,7 @@ assignees: ''
* **Node Version:**
* **npm Version:**
-**What parser (default, Babel-ESLint, etc.) are you using?**
+**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?**
**Please show your full configuration:**
diff --git a/eslint/CHANGELOG.md b/eslint/CHANGELOG.md
index 52ff78d..75b641e 100644
--- a/eslint/CHANGELOG.md
+++ b/eslint/CHANGELOG.md
@@ -1,3 +1,226 @@
+v7.12.1 - October 26, 2020
+
+* [`08f33e8`](https://github.com/eslint/eslint/commit/08f33e8b9a353c3183be6f937785db7a30fb90eb) Upgrade: @eslint/eslintrc to fix rule schema validation (fixes #13793) (#13794) (Brandon Mills)
+* [`aeef485`](https://github.com/eslint/eslint/commit/aeef485dc790571b1a82ac09904329e0226b66a9) Fix: Pass internal config paths in FileEnumerator default (fixes #13789) (#13792) (Brandon Mills)
+* [`631ae8b`](https://github.com/eslint/eslint/commit/631ae8b50e5f7975f10860e9e763b70b4f25182e) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.12.0 - October 23, 2020
+
+* [`cbf3585`](https://github.com/eslint/eslint/commit/cbf3585f1d6c60414c07380367a8b4505ee3538d) Update: skip keyword check for fns in space-before-blocks (fixes #13553) (#13712) (Milos Djermanovic)
+* [`256f656`](https://github.com/eslint/eslint/commit/256f656455b47bcf9ed3fc30fbf72532678f97da) Fix: autofix shouldn't produce template literals with `\8` or `\9` (#13737) (Milos Djermanovic)
+* [`b165aa5`](https://github.com/eslint/eslint/commit/b165aa5f4d4d19328f13ab80e5f058cbce94c3a6) Fix: yoda rule autofix produces syntax errors with adjacent tokens (#13760) (Milos Djermanovic)
+* [`3175316`](https://github.com/eslint/eslint/commit/3175316db26aebef4b19e269aca90c8ce3955363) Fix: prefer-destructuring invalid autofix with comma operator (#13761) (Milos Djermanovic)
+* [`1a9f171`](https://github.com/eslint/eslint/commit/1a9f17151a4e93eb17c8a2bf4f0a5320cce616de) Chore: Remove more ESLintRC-related files (refs #13481) (#13762) (Nicholas C. Zakas)
+* [`bfddced`](https://github.com/eslint/eslint/commit/bfddcedace5587d662c840c2edf33062b54a178e) Update: remove suggestion if it didn't provide a fix (fixes #13723) (#13772) (Milos Djermanovic)
+* [`5183b14`](https://github.com/eslint/eslint/commit/5183b14a2420b42b4089fb134a61ae57142f31fd) Update: check template literal in no-script-url (#13775) (YeonJuan)
+* [`bfe97d2`](https://github.com/eslint/eslint/commit/bfe97d2332e711ca76b1fd2e7f8548b0cc84cb1c) Sponsors: Sync README with website (ESLint Jenkins)
+* [`6c51ade`](https://github.com/eslint/eslint/commit/6c51adeb86f1de292cd02d2ee19f7b56182e358b) Sponsors: Sync README with website (ESLint Jenkins)
+* [`603de04`](https://github.com/eslint/eslint/commit/603de04cab5e700df12999af2918decd4da9d11b) Update: treat all literals like boolean literal in no-constant-condition (#13245) (Zen)
+* [`289aa6f`](https://github.com/eslint/eslint/commit/289aa6fcef3874ba5f86455f9302dc4209ea83e5) Sponsors: Sync README with website (ESLint Jenkins)
+* [`9a1f669`](https://github.com/eslint/eslint/commit/9a1f6694e59eb3e584d4c5a98b98675c895a9783) Sponsors: Sync README with website (ESLint Jenkins)
+* [`637f818`](https://github.com/eslint/eslint/commit/637f8187404ded600fb3d4013b3cd495d5ae675b) Docs: add more examples for no-func-assign (fixes #13705) (#13777) (Nitin Kumar)
+* [`17cc0dd`](https://github.com/eslint/eslint/commit/17cc0dd9b5d2d500359c36881cd3e5637443c133) Chore: add test case for no-func-assign (refs #13705) (#13783) (Nitin Kumar)
+* [`dee0f77`](https://github.com/eslint/eslint/commit/dee0f7764a1d5a323c89b22c4db94acee2b3c718) Docs: add TOC to user-guide/configuring.md (#13727) (metasean)
+* [`0510621`](https://github.com/eslint/eslint/commit/05106212985cb1ffa1e6fa996a57f6fd2fc3c970) Update: Fix && vs || short-circuiting false negatives (fixes #13634) (#13769) (Brandon Mills)
+* [`8b6ed69`](https://github.com/eslint/eslint/commit/8b6ed691c48189b7d096339441a78cb5874d4137) Sponsors: Sync README with website (ESLint Jenkins)
+* [`1457509`](https://github.com/eslint/eslint/commit/145750991b04fd4cfb3fff3c5d4211a4428e011c) Docs: fix broken links in Node.js API docs (#13771) (Laura Barluzzi)
+* [`7c813d4`](https://github.com/eslint/eslint/commit/7c813d458f9aedf7a94351d137728a4647542879) Docs: Fix typo in v7 migration page (#13778) (Yusuke Sasaki)
+* [`b025795`](https://github.com/eslint/eslint/commit/b0257953be704d0bb387fc15afd7859fd6f19ba5) Docs: Fix the format option name in the document (#13770) (Hideki Igarashi)
+* [`84fd591`](https://github.com/eslint/eslint/commit/84fd591c234accc41bb5af555f178825012fd35d) Chore: Increase Mocha timeout for copying fixtures (#13768) (Brandon Mills)
+* [`1faeb84`](https://github.com/eslint/eslint/commit/1faeb84e663d88c5d85a3cb3f15cd224cc552c2d) Docs: clarify that space-unary-ops doesn't apply when space is required (#13767) (Taylor Morgan)
+* [`67c0605`](https://github.com/eslint/eslint/commit/67c06059dd1ddcee6f369c650ce71220da1510c3) Update: check computed keys in no-prototype-builtins (fixes #13088) (#13755) (Milos Djermanovic)
+* [`b5e011c`](https://github.com/eslint/eslint/commit/b5e011c865e95d700d29cb9a4ba71c671d99e423) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.11.0 - October 9, 2020
+
+* [`23e966f`](https://github.com/eslint/eslint/commit/23e966f6cf2a6c6b699dff5d6950ece3cc396498) Chore: Refactor CLIEngine tests (refs #13481) (#13709) (Nicholas C. Zakas)
+* [`fa9429a`](https://github.com/eslint/eslint/commit/fa9429aac0ffed505f3f02e8fc75f646c69f5c61) Fix: don't count line after EOF in max-lines (#13735) (Milos Djermanovic)
+* [`d973675`](https://github.com/eslint/eslint/commit/d973675a5c06a2bd4f8ce640c78b67842cfebfd4) Docs: Update anchor links to use existing linkrefs (refs #13715) (#13741) (Brandon Mills)
+* [`2c6d774`](https://github.com/eslint/eslint/commit/2c6d774c89dcd14f386bd9d73d451fa2a892c3ef) Docs: Fix typos (#13730) (Frieder Bluemle)
+* [`cc468c0`](https://github.com/eslint/eslint/commit/cc468c01021385a028de727eefcd442e7f34875c) Upgrade: eslint-visitor-keys@2.0.0 (#13732) (Milos Djermanovic)
+* [`ab0ac6c`](https://github.com/eslint/eslint/commit/ab0ac6c532fb7b7d49779c8913146244d680743b) Docs: Fix anchor links (#13715) (Gary Moore)
+* [`27f0de6`](https://github.com/eslint/eslint/commit/27f0de62e6281c28043be38ef051818c9edc15cd) Fix: account for linebreaks before postfix `++`/`--` in no-extra-parens (#13731) (Milos Djermanovic)
+* [`da78fa1`](https://github.com/eslint/eslint/commit/da78fa11632a2908db4ac494012a16f5d5a88a64) Update: support async arrow fn in function-paren-newline (fixes #13728) (#13729) (Michal Dziekonski)
+* [`fe301b8`](https://github.com/eslint/eslint/commit/fe301b8cc0762d7f4edd59603ca51ed0ec0c2a43) Docs: Add configuration comments in examples (#13738) (YeonJuan)
+* [`504408c`](https://github.com/eslint/eslint/commit/504408cd65e9d8827b2b8bbeb8f589df90eee523) Sponsors: Sync README with website (ESLint Jenkins)
+* [`3900659`](https://github.com/eslint/eslint/commit/390065985b2289ad4412a83598e3e833c382d27e) Sponsors: Sync README with website (ESLint Jenkins)
+* [`c1974b3`](https://github.com/eslint/eslint/commit/c1974b3f7169a8e5fab7007df92d02d8c1a8d5a3) Sponsors: Sync README with website (ESLint Jenkins)
+* [`6f4abe5`](https://github.com/eslint/eslint/commit/6f4abe5d5ade2711cc4c21bc8485af952763c2d3) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.10.0 - September 26, 2020
+
+* [`6919fbb`](https://github.com/eslint/eslint/commit/6919fbb83f86552b0f49ae749da866e4edc7c46a) Docs: Clarify that ignorePattern should be a string (refs #13029) (#13718) (Brandon Mills)
+* [`07d9bea`](https://github.com/eslint/eslint/commit/07d9bea7c6f953e8f754afffc9752edcee799431) Update: Add ignorePattern to no-inline-comments (#13029) (Edie Lemoine)
+* [`d79bbe9`](https://github.com/eslint/eslint/commit/d79bbe982930b53358d34ad91cc6e5eaac8ddede) Docs: fix typo (#13717) (Alexander Liu)
+* [`9b8490e`](https://github.com/eslint/eslint/commit/9b8490ee6391c986b1314540a92b71d8c1e0efc4) Docs: grammatical error (#13687) (rajdeep)
+* [`cb44e93`](https://github.com/eslint/eslint/commit/cb44e93f4780e925a75a68ce2f7f6d065b5f756c) Fix: prefer-destructuring invalid autofix with computed property access (#13704) (Milos Djermanovic)
+* [`46c73b1`](https://github.com/eslint/eslint/commit/46c73b159a5ceed2f7f26f254fd97e459fb0e81a) Upgrade: eslint-scope@5.1.1 (#13716) (Milos Djermanovic)
+* [`b7b12ba`](https://github.com/eslint/eslint/commit/b7b12ba0bd4e9c66883f11e97de8ed84b600cdaa) Chore: Move comment to make tests more organized (#13707) (Yusuke Tanaka)
+* [`51674a4`](https://github.com/eslint/eslint/commit/51674a4113a1ca877094606bbf4938ab06cc1aad) Docs: Add missing quotes (#13714) (Lucio Paiva)
+* [`7c34a98`](https://github.com/eslint/eslint/commit/7c34a982aaf93a02348f56c9ce887c7dcf51b5bd) Chore: remove mistakenly added file (#13710) (Milos Djermanovic)
+* [`30b76c9`](https://github.com/eslint/eslint/commit/30b76c9a13fae3dff59f7db406d6c66f11152973) Docs: Clarify package.json requirement in Getting Started (refs #13549) (#13696) (Nicholas C. Zakas)
+* [`044560d`](https://github.com/eslint/eslint/commit/044560dcc74db98b28e293da2e2f3b41ecbf5884) Sponsors: Sync README with website (ESLint Jenkins)
+* [`54000d1`](https://github.com/eslint/eslint/commit/54000d13f27d5255851b5ac0606ad027e2b8d331) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.9.0 - September 12, 2020
+
+* [`3ca2700`](https://github.com/eslint/eslint/commit/3ca27004ece5016ba7aed775f01ad13bc9282296) Fix: Corrected notice for invalid (:) plugin names (#13473) (Josh Goldberg)
+* [`fc5783d`](https://github.com/eslint/eslint/commit/fc5783d2ff9e3b0d7a1f9664928d49270b4a6c01) Docs: Fix leaky anchors in v4 migration page (#13635) (Timo Tijhof)
+* [`f1d07f1`](https://github.com/eslint/eslint/commit/f1d07f112be96c64dfdaa154aa9ac81985b16238) Docs: Provide install commands for Yarn (#13661) (Nikita Baksalyar)
+* [`29d1cdc`](https://github.com/eslint/eslint/commit/29d1cdceedd6c056a39149723cf9ff2fbb260cbf) Fix: prefer-destructuring removes comments (refs #13678) (#13682) (Milos Djermanovic)
+* [`b4da0a7`](https://github.com/eslint/eslint/commit/b4da0a7ca7995435bdfc116fd374eb0649470131) Docs: fix typo in working with plugins docs (#13683) (啸生)
+* [`6f87db7`](https://github.com/eslint/eslint/commit/6f87db7c318225e48ccbbf0bec8b3758ea839b82) Update: fix id-length false negatives on Object.prototype property names (#13670) (Milos Djermanovic)
+* [`361ac4d`](https://github.com/eslint/eslint/commit/361ac4d895c15086fb4351d4dca1405b2fdc4bd5) Fix: NonOctalDecimalIntegerLiteral is decimal integer (fixes #13588) (#13664) (Milos Djermanovic)
+* [`f260716`](https://github.com/eslint/eslint/commit/f260716695064e4b4193337107b60401bd4b3f20) Docs: update outdated link (#13677) (klkhan)
+* [`5138c91`](https://github.com/eslint/eslint/commit/5138c913c256e4266ffb68278783af45bf70af84) Docs: add missing eslint directive comments in no-await-in-loop (#13673) (Milos Djermanovic)
+* [`17b58b5`](https://github.com/eslint/eslint/commit/17b58b528df62bf96813d50c087cafdf83306810) Docs: clarify correct example in no-return-await (fixes #13656) (#13657) (Milos Djermanovic)
+* [`9171f0a`](https://github.com/eslint/eslint/commit/9171f0a99bb4d7c53f109b1c2b215004a7c27713) Chore: fix typo (#13660) (Nitin Kumar)
+* [`6d9f8fb`](https://github.com/eslint/eslint/commit/6d9f8fbb7ed4361b475fb50d04e6d25744d5b1a2) Sponsors: Sync README with website (ESLint Jenkins)
+* [`97b0dd9`](https://github.com/eslint/eslint/commit/97b0dd9a1af1ae4ae3857adcfe6eeac7837101ed) Sponsors: Sync README with website (ESLint Jenkins)
+* [`deab125`](https://github.com/eslint/eslint/commit/deab125fc9220dab43baeb32c6cf78942ad25a83) Sponsors: Sync README with website (ESLint Jenkins)
+* [`bf2e367`](https://github.com/eslint/eslint/commit/bf2e367bf4f6fde9930af9de8b8d8bc3d8b5782f) Sponsors: Sync README with website (ESLint Jenkins)
+* [`8929208`](https://github.com/eslint/eslint/commit/89292084bf91ba5ae5bf966c6c56fa3da139ce57) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.8.1 - September 1, 2020
+
+* [`f542b5d`](https://github.com/eslint/eslint/commit/f542b5d0679b73326ad249fc44a54c3f848bd3e6) Fix: Update broken @eslint/eslintrc version (fixes #13641) (#13647) (Nicholas C. Zakas)
+* [`c1b5696`](https://github.com/eslint/eslint/commit/c1b56966c2354e12d16e8394443de49fa54f4290) Sponsors: Sync README with website (ESLint Jenkins)
+* [`8ddeda0`](https://github.com/eslint/eslint/commit/8ddeda01afdb1e9656a43853b8e25c9c4582e6ad) Sponsors: Sync README with website (ESLint Jenkins)
+* [`e02e2fe`](https://github.com/eslint/eslint/commit/e02e2fe019a1ed9a34a7b96e4c8961c35093b0ce) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.8.0 - August 31, 2020
+
+* [`58abd93`](https://github.com/eslint/eslint/commit/58abd9311900a8af5a3c0963daaf64675bdd8383) Update: support logical assignments in code path analysis (refs #13569) (#13612) (Milos Djermanovic)
+* [`db7488e`](https://github.com/eslint/eslint/commit/db7488e6326fd1b7ea04c5062beb1c5f75fc15ed) Update: support logical assignments in core rules (refs #13569) (#13618) (Milos Djermanovic)
+* [`3729219`](https://github.com/eslint/eslint/commit/372921924778f2e525535985e17c97b988546210) Docs: Update Step 1 of Development Environment documentation (klkhan)
+* [`a320324`](https://github.com/eslint/eslint/commit/a32032430a0779a4e3b2d137d4d0682844084b82) Chore: Test formatted integers in no-dupe-keys (refs #13568) (#13626) (Brandon Mills)
+* [`88a9ade`](https://github.com/eslint/eslint/commit/88a9ade7643bb166efbab45cee15f3269496f4be) Update: add es2021 environment (refs #13602) (#13603) (Milos Djermanovic)
+* [`0003dc0`](https://github.com/eslint/eslint/commit/0003dc0f966f2b47555595586f84eb3163cb0179) Update: support numeric separators (refs #13568) (#13581) (Milos Djermanovic)
+* [`96b11a0`](https://github.com/eslint/eslint/commit/96b11a0717bf32b94ec768611574372320fb774b) Update: Add exceptionPatterns to id-length rule (fixes #13094) (#13576) (sodam)
+* [`3439fea`](https://github.com/eslint/eslint/commit/3439fea5c0ed330d01d874b0c9df51dd51ae792c) Update: support numeric-separator in no-loss-of-precision (refs #13568) (#13574) (Anix)
+* [`ed64767`](https://github.com/eslint/eslint/commit/ed64767859d776145d68145419a61f5379b4dd63) Update: add comment to message in no-warning-comments (fixes #12327) (#13522) (Anix)
+* [`e60ec07`](https://github.com/eslint/eslint/commit/e60ec07fad0c1d4c966f28d214c5379da753ff4e) Sponsors: Sync README with website (ESLint Jenkins)
+* [`483bf7f`](https://github.com/eslint/eslint/commit/483bf7f3cc40e0d866798d6ca9ee1c19aa77ddd2) Docs: fix examples in object-curly-newline (#13605) (Soobin Bak)
+* [`1c35d57`](https://github.com/eslint/eslint/commit/1c35d57b0a5f374cc55f1727a7561bcab1962e83) Docs: Remove stale Keybase 2FA instructions (#13622) (Brandon Mills)
+* [`82669fa`](https://github.com/eslint/eslint/commit/82669fa66670a00988db5b1d10fe8f3bf30be84e) Chore: Extract some functionality to eslintrc (refs #13481) (#13613) (Nicholas C. Zakas)
+* [`4111d21`](https://github.com/eslint/eslint/commit/4111d21a046b73892e2c84f92815a21ef4db63e1) Docs: Fix typo and missing article before noun in docs (#13611) (Patrice Sandhu)
+* [`091e52a`](https://github.com/eslint/eslint/commit/091e52ae1ca408f3e668f394c14d214c9ce806e6) Upgrade: espree@7.3.0 (refs #13568) (#13609) (Kai Cataldo)
+* [`05074fb`](https://github.com/eslint/eslint/commit/05074fb2c243e904e8c09d714ad9d084acdd80d2) Sponsors: Sync README with website (ESLint Jenkins)
+* [`bdb65ec`](https://github.com/eslint/eslint/commit/bdb65ec2e672c9815bee356b61d1cd60a1072152) Chore: add 3rd party parsers in BUG_REPORT template (#13606) (YeonJuan)
+* [`f954476`](https://github.com/eslint/eslint/commit/f954476fb6b0664679c73babd5e8a0647572b81f) Chore: add common 3rd party parsers to issue template (#13596) (Kai Cataldo)
+* [`2bee6d2`](https://github.com/eslint/eslint/commit/2bee6d256ae0516c9a9003bb3fdca24ff93253b5) Chore: Mark config-related files (refs #13481) (#13597) (Nicholas C. Zakas)
+* [`66442a9`](https://github.com/eslint/eslint/commit/66442a9faf9872db4a40f56dde28c48f4d02fc7b) Update: Add no-magic-numbers 'ignoreDefaultValues' option (#12611) (Dieter Luypaert)
+* [`b487164`](https://github.com/eslint/eslint/commit/b487164d01dd0bf66fdf2df0e374ce1c3bdb0339) Docs: add exponentiation operators to operator-assignment documentation (#13577) (Milos Djermanovic)
+* [`2f27836`](https://github.com/eslint/eslint/commit/2f27836e989f3dfe236e34054b490febc359bc48) Sponsors: Sync README with website (ESLint Jenkins)
+* [`60eafc1`](https://github.com/eslint/eslint/commit/60eafc15075f38955cb6816bf1f0bcf6e6e6d3a6) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.7.0 - August 14, 2020
+
+* [`b46f3ee`](https://github.com/eslint/eslint/commit/b46f3ee0dae4add9df99cae940b641ad8de58b9e) Update: allowFunctionParams option in no-underscore-dangle (fixes 12579) (#13545) (Sunghyun Cho)
+* [`26aa245`](https://github.com/eslint/eslint/commit/26aa2452b5f407fabc25dad21182180e4d3be532) Docs: clarify "case" specifier in padding-line-between-statements (#13562) (Milos Djermanovic)
+* [`082891c`](https://github.com/eslint/eslint/commit/082891c042d72953fe86cd3ce9c96e661760793d) Docs: Update semantic versioning policy (#13563) (Nicholas C. Zakas)
+* [`4e0b672`](https://github.com/eslint/eslint/commit/4e0b672eb4bf39f7502a550b08b25a56a196f19f) Fix: revert "Update: disallow multiple options in comma-dangle schema" (#13564) (Kai Cataldo)
+* [`254990e`](https://github.com/eslint/eslint/commit/254990e87914457ca25ea2d7ee012964e56fc9e5) Fix: indent for async arrow functions (fixes #13497) (#13544) (Anix)
+* [`28ca339`](https://github.com/eslint/eslint/commit/28ca339259b07c96c73f2ef28cbf112b96395855) Sponsors: Sync README with website (ESLint Jenkins)
+* [`2e4158d`](https://github.com/eslint/eslint/commit/2e4158d3ec9cfed6400bf70795fd7171e96ff9b3) Sponsors: Sync README with website (ESLint Jenkins)
+* [`488d159`](https://github.com/eslint/eslint/commit/488d1595aef43c4d52cccdb2c97977884f0375a8) Sponsors: Sync README with website (ESLint Jenkins)
+* [`c44306e`](https://github.com/eslint/eslint/commit/c44306e52778309a79232ceab8b55a9aa0f2dfda) Sponsors: Sync README with website (ESLint Jenkins)
+* [`6677180`](https://github.com/eslint/eslint/commit/6677180495e16a02d150d0552e7e5d5f6b77fcc5) Sponsors: Sync README with website (ESLint Jenkins)
+* [`07db7b8`](https://github.com/eslint/eslint/commit/07db7b8080c2f68ee28e7d447db356c33e6fddce) Sponsors: Sync README with website (ESLint Jenkins)
+* [`d4ce4d3`](https://github.com/eslint/eslint/commit/d4ce4d3b8492c3e4654ed1f51f2c48e6c0ad272f) Sponsors: Sync README with website (ESLint Jenkins)
+* [`284e954`](https://github.com/eslint/eslint/commit/284e954f93126c50e0aa9b88f42afb03a47ad967) Sponsors: Sync README with website (ESLint Jenkins)
+* [`ae9b54e`](https://github.com/eslint/eslint/commit/ae9b54e59b01aa9f50ee31f5b6787d86e6b59de6) Sponsors: Sync README with website (ESLint Jenkins)
+* [`9124a15`](https://github.com/eslint/eslint/commit/9124a1599638a1caf4b7e252d1cb66abdc5e51c6) Chore: remove leche (fixes #13287) (#13533) (Mark de Dios)
+* [`5c4c7f5`](https://github.com/eslint/eslint/commit/5c4c7f515c2e8e83f2186a66ddce75d6477abeb0) Sponsors: Sync README with website (ESLint Jenkins)
+* [`48d8ec8`](https://github.com/eslint/eslint/commit/48d8ec8cf320c69aed17c6b6c78f19e7c1e587ca) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.6.0 - July 31, 2020
+
+* [`ecb2b73`](https://github.com/eslint/eslint/commit/ecb2b7343a0d14fb57d297a16be6c1b176fb3dbf) Update: require `meta` for fixable rules in RuleTester (refs #13349) (#13489) (Milos Djermanovic)
+* [`6fb4edd`](https://github.com/eslint/eslint/commit/6fb4edde3b7a7ae2faf8ac956a7342fbf80865fc) Docs: fix broken links in developer guide (#13518) (Sam Chen)
+* [`318fe10`](https://github.com/eslint/eslint/commit/318fe103dbf2548eee293ff456ef0b829dbe3db3) Fix: Do not output `undefined` as line and column when it's unavailable (#13519) (haya14busa)
+* [`493b5b4`](https://github.com/eslint/eslint/commit/493b5b40cae7a076fdeb19740f8c88fb4ae9c1fb) Sponsors: Sync README with website (ESLint Jenkins)
+* [`f100143`](https://github.com/eslint/eslint/commit/f100143fa5f529aacb2b50e650a00d2697ca4c54) Sponsors: Sync README with website (ESLint Jenkins)
+* [`16b10fe`](https://github.com/eslint/eslint/commit/16b10fe8ba3c78939d5ada4a25caf2f0c9e6a058) Fix: Update the chatroom link to go directly to help channel (#13536) (Nicholas C. Zakas)
+* [`f937eb9`](https://github.com/eslint/eslint/commit/f937eb95407f60d3772bcb956e227aaf99e48777) Sponsors: Sync README with website (ESLint Jenkins)
+* [`e71e298`](https://github.com/eslint/eslint/commit/e71e2980cd2e319afc70d8c859c7ffd59cf4157b) Update: Change no-duplicate-case to comparing tokens (fixes #13485) (#13494) (Yosuke Ota)
+* [`6c4aea4`](https://github.com/eslint/eslint/commit/6c4aea44fd78e1eecea5fe3c37e1921e3b1e98a6) Docs: add ECMAScript 2020 to README (#13510) (Milos Djermanovic)
+
+v7.5.0 - July 18, 2020
+
+* [`6ea3178`](https://github.com/eslint/eslint/commit/6ea3178776eae0e40c3f5498893e8aab0e23686b) Update: optional chaining support (fixes #12642) (#13416) (Toru Nagashima)
+* [`540b1af`](https://github.com/eslint/eslint/commit/540b1af77278ae649b621aa8d4bf8d6de03c3155) Chore: enable consistent-meta-messages internal rule (#13487) (Milos Djermanovic)
+* [`885a145`](https://github.com/eslint/eslint/commit/885a1455691265db88dc0befe9b48a69d69e8b9c) Docs: clarify behavior if `meta.fixable` is omitted (refs #13349) (#13493) (Milos Djermanovic)
+* [`1a01b42`](https://github.com/eslint/eslint/commit/1a01b420eaab0de03dab5cc190a9f2a860c21a84) Docs: Update technology sponsors in README (#13478) (Nicholas C. Zakas)
+* [`6ed9e8e`](https://github.com/eslint/eslint/commit/6ed9e8e4ff038c0259b0e7fe7ab7f4fd4ec55801) Upgrade: lodash@4.17.19 (#13499) (Yohan Siguret)
+* [`45cdf00`](https://github.com/eslint/eslint/commit/45cdf00da6aeff3d584d37b0710fc8d6ad9456d6) Sponsors: Sync README with website (ESLint Jenkins)
+* [`f1cc725`](https://github.com/eslint/eslint/commit/f1cc725ba1b8646dcf06a83716d96ad9bb726172) Docs: fix linebreaks between versions in changelog (#13488) (Milos Djermanovic)
+* [`f4d7b9e`](https://github.com/eslint/eslint/commit/f4d7b9e1a599346b2f21ff9de003b311b51411e6) Update: deprecate id-blacklist rule (#13465) (Dimitri Mitropoulos)
+* [`e14a645`](https://github.com/eslint/eslint/commit/e14a645aa495558081490f990ba221e21aa6b27c) Chore: use espree.latestEcmaVersion in fuzzer (#13484) (Milos Djermanovic)
+* [`61097fe`](https://github.com/eslint/eslint/commit/61097fe5cc275d414a0c8e19b31c6060cb5568b7) Docs: Update int rule level to string (#13483) (Brandon Mills)
+* [`c8f9c82`](https://github.com/eslint/eslint/commit/c8f9c8210cf4b9da8f07922093d7b219abad9f10) Update: Improve report location no-irregular-whitespace (refs #12334) (#13462) (Milos Djermanovic)
+* [`f2e68ec`](https://github.com/eslint/eslint/commit/f2e68ec1d6cee6299e8a5cdf76c522c11d3008dd) Build: update webpack resolve.mainFields to match website config (#13457) (Milos Djermanovic)
+* [`a96bc5e`](https://github.com/eslint/eslint/commit/a96bc5ec06f3a48bfe458bccd68d4d3b2a280ed9) Fix: arrow-body-style fixer for `in` wrap (fixes #11849) (#13228) (Anix)
+* [`748734f`](https://github.com/eslint/eslint/commit/748734fdd497fbf61f3a616ff4a09169135b9396) Upgrade: Updated puppeteer version to v4.0.0 (#13444) (odidev)
+* [`e951457`](https://github.com/eslint/eslint/commit/e951457b7aaa1b12b135588d36e3f4db4d7b8463) Docs: fix wording in configuring.md (#13469) (Piper)
+* [`0af1d28`](https://github.com/eslint/eslint/commit/0af1d2828d27885483737867653ba1659af72005) Update: add allowSeparatedGroups option to sort-imports (fixes #12951) (#13455) (Milos Djermanovic)
+* [`1050ee7`](https://github.com/eslint/eslint/commit/1050ee78a95da9484ff333dc1c74dac64c05da6f) Update: Improve report location for no-unneeded-ternary (refs #12334) (#13456) (Milos Djermanovic)
+* [`b77b420`](https://github.com/eslint/eslint/commit/b77b4202bd1d5d1306f6f645e88d7a41a51715db) Update: Improve report location for max-len (refs #12334) (#13458) (Milos Djermanovic)
+* [`095194c`](https://github.com/eslint/eslint/commit/095194c0fc0eb02aa69fde6b4280696e0e4de214) Fix: add end location to reports in object-curly-newline (refs #12334) (#13460) (Milos Djermanovic)
+* [`10251bb`](https://github.com/eslint/eslint/commit/10251bbaeba80ac15244f385fc42cf2f2a30e5d2) Fix: add end location to reports in keyword-spacing (refs #12334) (#13461) (Milos Djermanovic)
+* [`2ea7ee5`](https://github.com/eslint/eslint/commit/2ea7ee51a4e05ee76a6dae5954c3b6263b0970a3) Sponsors: Sync README with website (ESLint Jenkins)
+* [`b55fd3b`](https://github.com/eslint/eslint/commit/b55fd3b8c05a29a465a794a524b06c1a28cddf0c) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.4.0 - July 3, 2020
+
+* [`f21bad2`](https://github.com/eslint/eslint/commit/f21bad2680406a2671b877f8dba47f4475d0cc64) Docs: fix description for `never` in multiline-ternary (fixes #13368) (#13452) (Milos Djermanovic)
+* [`ada2c89`](https://github.com/eslint/eslint/commit/ada2c891298382f82dfabf37cacd59a1057b2bb7) Fix: support typescript generics in arrow-parens (fixes #12570) (#13451) (Milos Djermanovic)
+* [`89ee01e`](https://github.com/eslint/eslint/commit/89ee01e083f1e02293bf8d1447f9b0fdb3cb9384) Fix: Revert config cloning (fixes #13447) (#13449) (薛定谔的猫)
+* [`0a463db`](https://github.com/eslint/eslint/commit/0a463dbf7cc5a77d442879c9117204d4d38db972) Docs: fix no-multiple-empty-lines examples (fixes #13432) (#13433) (Milos Djermanovic)
+* [`ff5317e`](https://github.com/eslint/eslint/commit/ff5317e93425f93cfdf808609551ee67b2032543) Update: Improve array-callback-return report message (#13395) (Philip (flip) Kromer)
+* [`3f51930`](https://github.com/eslint/eslint/commit/3f51930eea7cddc921a9ee3cb0328c7b649c0f83) Fix: false positive new with member in no-extra-parens (fixes #12740) (#13375) (YeonJuan)
+* [`825a5b9`](https://github.com/eslint/eslint/commit/825a5b98d3d84f6eb72b75f7d8519de763cc8898) Fix: Clarify documentation on implicit ignore behavior (fixes #12348) (#12600) (Scott Hardin)
+* [`c139156`](https://github.com/eslint/eslint/commit/c1391566a5f765f25716527de7b5cdee16c0ce36) Sponsors: Sync README with website (ESLint Jenkins)
+* [`0c17e9d`](https://github.com/eslint/eslint/commit/0c17e9d2ac307cc288eea6ed7971bd5a7d33321a) Sponsors: Sync README with website (ESLint Jenkins)
+* [`c680387`](https://github.com/eslint/eslint/commit/c680387ba61f6dccf0390d24a85d871fa83e9fea) Sponsors: Sync README with website (ESLint Jenkins)
+* [`bf3939b`](https://github.com/eslint/eslint/commit/bf3939bbd9a33d0eb96cebe6a53bf61c855f9ba6) Sponsors: Sync README with website (ESLint Jenkins)
+* [`7baf02e`](https://github.com/eslint/eslint/commit/7baf02e983af909800261263f125cca901a5bd0f) Sponsors: Sync README with website (ESLint Jenkins)
+* [`5c4c3fd`](https://github.com/eslint/eslint/commit/5c4c3fdfbda18a13223ad36f44283adbfee8c496) Sponsors: Sync README with website (ESLint Jenkins)
+* [`53912aa`](https://github.com/eslint/eslint/commit/53912aab1856327b399cca26cbb2ba81fd01bfa2) Sponsors: Sync README with website (ESLint Jenkins)
+* [`51e42ec`](https://github.com/eslint/eslint/commit/51e42eca3e87d8259815d736ffe81e604f184057) Update: Add option "ignoreGlobals" to camelcase rule (fixes #11716) (#12782) (David Gasperoni)
+* [`0655f66`](https://github.com/eslint/eslint/commit/0655f66525d167ca1288167b79a77087cfc8fcf6) Update: improve report location in arrow-body-style (refs #12334) (#13424) (YeonJuan)
+* [`d53d69a`](https://github.com/eslint/eslint/commit/d53d69af08cfe55f42e0a0ca725b1014dabccc21) Update: prefer-regex-literal detect regex literals (fixes #12840) (#12842) (Mathias Schreck)
+* [`004adae`](https://github.com/eslint/eslint/commit/004adae3f959414f56e44e5884f6221e9dcda142) Update: rename id-blacklist to id-denylist (fixes #13407) (#13408) (Kai Cataldo)
+
+v7.3.1 - June 22, 2020
+
+* [`de77c11`](https://github.com/eslint/eslint/commit/de77c11e7515f2097ff355ddc0d7b6db9c83c892) Fix: Replace Infinity with Number.MAX_SAFE_INTEGER (fixes #13427) (#13435) (Nicholas C. Zakas)
+
+v7.3.0 - June 19, 2020
+
+* [`638a6d6`](https://github.com/eslint/eslint/commit/638a6d6be18b4a37cfdc7223e1f5acd3718694be) Update: add missing `additionalProperties: false` to some rules' schema (#13198) (Milos Djermanovic)
+* [`949a5cd`](https://github.com/eslint/eslint/commit/949a5cd741c2e930cfb43d80a9b6b084f9d677c3) Update: fix operator-linebreak overrides schema (#13199) (Milos Djermanovic)
+* [`9e1414e`](https://github.com/eslint/eslint/commit/9e1414ee16b8caf582920f8fdf3b6ee1eb0b7cd5) New: Add no-promise-executor-return rule (fixes #12640) (#12648) (Milos Djermanovic)
+* [`09cc0a2`](https://github.com/eslint/eslint/commit/09cc0a2bb5bcf3bcb0766a3c989871f268518437) Update: max-lines reporting loc improvement (refs #12334) (#13318) (Anix)
+* [`ee2fc2e`](https://github.com/eslint/eslint/commit/ee2fc2e90d0f9dfcdba852b0609156bee5280b92) Update: object-property-newline end location (refs #12334) (#13399) (Anix)
+* [`d98152a`](https://github.com/eslint/eslint/commit/d98152a3d8c72e4f5ac4c6fa10a615b12090c8f7) Update: added empty error array check for false negative (#13200) (Anix)
+* [`7fb45cf`](https://github.com/eslint/eslint/commit/7fb45cf13e9908d489bd6d5fba3b7243c01508b9) Fix: clone config before validating (fixes #12592) (#13034) (Anix)
+* [`aed46f6`](https://github.com/eslint/eslint/commit/aed46f69d54da167d9838149954ceeb4b02be5fd) Sponsors: Sync README with website (ESLint Jenkins)
+* [`7686d7f`](https://github.com/eslint/eslint/commit/7686d7feaccc7b8fee927eda6602d641d8de1e5c) Update: semi-spacing should check do-while statements (#13358) (Milos Djermanovic)
+* [`cbd0d00`](https://github.com/eslint/eslint/commit/cbd0d00a1ec2824d7e025bbbc084855ed0bf08bb) Update: disallow multiple options in comma-dangle schema (fixes #13165) (#13166) (Milos Djermanovic)
+* [`b550330`](https://github.com/eslint/eslint/commit/b550330d739c73a7a8f887064e7c911d05a95f9a) New: Add no-unreachable-loop rule (fixes #12381) (#12660) (Milos Djermanovic)
+* [`13999d2`](https://github.com/eslint/eslint/commit/13999d292080f814fa4fb266e011d61c184197c4) Update: curly should check consequent `if` statements (#12947) (Milos Djermanovic)
+* [`c42e548`](https://github.com/eslint/eslint/commit/c42e54893b79b470ca7745bd2a626ffd069e017b) Chore: enable exceptRange option in the yoda rule (#12857) (Milos Djermanovic)
+* [`6cfbd03`](https://github.com/eslint/eslint/commit/6cfbd03b3f22edb4d1c9c61c64eea7c129da71aa) Update: Drop @typescript-eslint/eslint-recommended from `eslint --init` (#13340) (Minh Nguyen)
+* [`796f269`](https://github.com/eslint/eslint/commit/796f269e448fdcbf8a5a62edf1990bd857efd1af) Chore: update eslint-config-eslint's required node version (#13379) (薛定谔的猫)
+* [`9d0186e`](https://github.com/eslint/eslint/commit/9d0186e55bee769ea6aa08dc5a62682f58316412) Docs: Fix changelog versions (#13410) (Tony Brix)
+* [`1ee3c42`](https://github.com/eslint/eslint/commit/1ee3c42ceeee56b650bcc4206ed783b795f65643) Docs: On maxEOF with eol-last (fixes #12742) (#13374) (Arthur Dias)
+* [`2a21049`](https://github.com/eslint/eslint/commit/2a210499288ed14ec9a6fd72decabfb77504c197) Update: key-spacing loc changes for extra space (refs #12334) (#13362) (Anix)
+* [`7ce7988`](https://github.com/eslint/eslint/commit/7ce7988f411da64248a64a9d9d2b7884d5ba39e0) Chore: Replace the inquirer dependency with enquirer (#13254) (Selwyn)
+* [`0f1f5ed`](https://github.com/eslint/eslint/commit/0f1f5ed2a20b8fb575d4360316861cf4c2b9b7bc) Docs: Add security policy link to README (#13403) (Nicholas C. Zakas)
+* [`9e9ba89`](https://github.com/eslint/eslint/commit/9e9ba897c566601cfe90522099c635ea316b235f) Sponsors: Sync README with website (ESLint Jenkins)
+* [`ca59fb9`](https://github.com/eslint/eslint/commit/ca59fb95a395c0a02ed23768a70e086480ab1f6d) Sponsors: Sync README with website (ESLint Jenkins)
+
v7.2.0 - June 5, 2020
* [`b735a48`](https://github.com/eslint/eslint/commit/b735a485e77bcc791e4c4c6b8716801d94e98b2c) Update: add enforceForFunctionPrototypeMethods option to no-extra-parens (#12895) (Milos Djermanovic)
@@ -17,6 +240,7 @@ v7.2.0 - June 5, 2020
* [`ee30e5d`](https://github.com/eslint/eslint/commit/ee30e5d8bb1a4c82a2a3fbe1b9ee9f979b55c5c4) Sponsors: Sync README with website (ESLint Jenkins)
* [`c29bd9f`](https://github.com/eslint/eslint/commit/c29bd9f75582e5b1a403a8ffd0aafd1ffc8c58e1) Chore: Add breaking/core change link to issue templates (#13344) (Kai Cataldo)
* [`d55490f`](https://github.com/eslint/eslint/commit/d55490fa73ff69416de375e4c1cd67b6edba531c) Sponsors: Sync README with website (ESLint Jenkins)
+
v7.1.0 - May 22, 2020
* [`a93083a`](https://github.com/eslint/eslint/commit/a93083af89c6f9714dcdd4a7f27c8655a0b0dba6) Fix: astUtils.getNextLocation returns invalid location after CRLF (#13275) (Milos Djermanovic)
@@ -46,6 +270,7 @@ v7.1.0 - May 22, 2020
* [`f44a6b4`](https://github.com/eslint/eslint/commit/f44a6b4fd92602af8e2c75d5852f796ec064aa8e) Chore: fix invalid syntax in require-await tests (#13277) (Milos Djermanovic)
* [`2c778fb`](https://github.com/eslint/eslint/commit/2c778fb6e31b7943bb27a47a6e15dcbfd8336f39) Fix: remove custom plugins from replacedBy metadata (#13274) (Kai Cataldo)
* [`0db3b1d`](https://github.com/eslint/eslint/commit/0db3b1d5cc5e4e1de21462679581b7a4d89ff36e) Sponsors: Sync README with website (ESLint Jenkins)
+
v7.0.0 - May 8, 2020
* [`b98d8bd`](https://github.com/eslint/eslint/commit/b98d8bda4630fe8278c5aa2b6650630770568fe5) Upgrade: eslint-release@2.0.0 (#13271) (Kai Cataldo)
@@ -256,6 +481,7 @@ v7.0.0 - May 8, 2020
* [`39f5a45`](https://github.com/eslint/eslint/commit/39f5a453579b2ad732212edeb71f84ecb0991f97) Chore: add test cases for for-direction (#12698) (YeonJuan)
* [`b340304`](https://github.com/eslint/eslint/commit/b3403045e535921df6d34785a4ce053e14ba27fd) Chore: Add extra test, improve docs (#12492) (Kevin Partington)
* [`827259e`](https://github.com/eslint/eslint/commit/827259ea009f98a0fdf3f7ebf1bfb6cd661ce28d) Build: package.json update for eslint-config-eslint release (ESLint Jenkins)
+
v7.0.0-rc.0 - April 24, 2020
* [`0b1d65a`](https://github.com/eslint/eslint/commit/0b1d65a45aa5dfe08cd596c420490e81b546317e) Update: Improve report location for array-callback-return (refs #12334) (#13109) (Milos Djermanovic)
diff --git a/eslint/Makefile.js b/eslint/Makefile.js
index f5f3fd5..840a51a 100644
--- a/eslint/Makefile.js
+++ b/eslint/Makefile.js
@@ -550,7 +550,7 @@ target.mocha = () => {
errors++;
}
- lastReturn = exec(`${getBinFile("nyc")} check-coverage --statement 99 --branch 98 --function 99 --lines 99`);
+ lastReturn = exec(`${getBinFile("nyc")} check-coverage --statement 98 --branch 97 --function 98 --lines 98`);
if (lastReturn.code !== 0) {
errors++;
}
diff --git a/eslint/README.md b/eslint/README.md
index 0cb3963..7740b39 100644
--- a/eslint/README.md
+++ b/eslint/README.md
@@ -1,4 +1,4 @@
-[](https://www.npmjs.com/package/eslint)
+[](https://www.npmjs.com/package/eslint)
[](https://www.npmjs.com/package/eslint)
[](https://github.com/eslint/eslint/actions)
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_shield)
@@ -33,11 +33,12 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
4. [Filing Issues](#filing-issues)
5. [Frequently Asked Questions](#faq)
6. [Releases](#releases)
-7. [Semantic Versioning Policy](#semantic-versioning-policy)
-8. [License](#license)
-9. [Team](#team)
-10. [Sponsors](#sponsors)
-11. [Technology Sponsors](#technology-sponsors)
+7. [Security Policy](#security-policy)
+8. [Semantic Versioning Policy](#semantic-versioning-policy)
+9. [License](#license)
+10. [Team](#team)
+11. [Sponsors](#sponsors)
+12. [Technology Sponsors](#technology-sponsors)
## Installation and Usage
@@ -121,7 +122,7 @@ Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [confi
### What ECMAScript versions does ESLint support?
-ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, and 2019. 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, and 2020. 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).
### What about experimental features?
@@ -139,32 +140,41 @@ Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](ht
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
+
+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
ESLint follows [semantic versioning](https://semver.org). However, due to the nature of ESLint as a code quality tool, it's not always clear when a minor or major version bump occurs. To help clarify this for everyone, we've defined the following semantic versioning policy for ESLint:
* Patch release (intended to not break your lint build)
- * A bug fix in a rule that results in ESLint reporting fewer errors.
+ * A bug fix in a rule that results in ESLint reporting fewer linting errors.
* A bug fix to the CLI or core (including formatters).
* Improvements to documentation.
* Non-user-facing changes such as refactoring code, adding, deleting, or modifying tests, and increasing test coverage.
* Re-releasing after a failed release (i.e., publishing a release that doesn't work for anyone).
* Minor release (might break your lint build)
- * A bug fix in a rule that results in ESLint reporting more errors.
+ * A bug fix in a rule that results in ESLint reporting more linting errors.
* A new rule is created.
- * A new option to an existing rule that does not result in ESLint reporting more errors by default.
+ * A new option to an existing rule that does not result in ESLint reporting more linting errors by default.
* An existing rule is deprecated.
* A new CLI capability is created.
* New capabilities to the public API are added (new classes, new methods, new arguments to existing methods, etc.).
* A new formatter is created.
- * `eslint:recommended` is updated and will result in strictly fewer errors (e.g., rule removals).
+ * `eslint:recommended` is updated and will result in strictly fewer linting errors (e.g., rule removals).
* Major release (likely to break your lint build)
- * `eslint:recommended` is updated and may result in new errors (e.g., rule additions, most rule option updates).
- * A new option to an existing rule that results in ESLint reporting more errors by default.
+ * `eslint:recommended` is updated and may result in new linting errors (e.g., rule additions, most rule option updates).
+ * A new option to an existing rule that results in ESLint reporting more linting errors by default.
* An existing formatter is removed.
- * Part of the public API is removed or changed in an incompatible way.
+ * Part of the public API is removed or changed in an incompatible way. The public API includes:
+ * Rule schemas
+ * Configuration schema
+ * Command-line options
+ * Node.js API
+ * Rule, formatter, parser, plugin APIs
-According to our policy, any minor update may report more errors than the previous release (ex: from a bug fix). As such, we recommend using the tilde (`~`) in `package.json` e.g. `"eslint": "~3.1.0"` to guarantee the results of your builds.
+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.
## License
@@ -201,6 +211,11 @@ Toru Nagashima

Kai Cataldo
+
+
+
+Milos Djermanovic
+
|
@@ -213,11 +228,6 @@ The people who review and implement new features.

薛定谔的猫
-
-
-
-Milos Djermanovic
-
|
@@ -233,6 +243,11 @@ The people who review and fix bugs and help triage issues.
Pig Fang
+
+
+Anix
+
+ |

YeonJuan
@@ -248,12 +263,15 @@ The following companies, organizations, and individuals support ESLint's ongoing
-Gold Sponsors
- 
Silver Sponsors
+Platinum Sponsors
+
Gold Sponsors
+ 
Silver Sponsors

Bronze 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)
+* Password management is sponsored by [1Password](https://www.1password.com)
diff --git a/eslint/conf/config-schema.js b/eslint/conf/config-schema.js
index 712fc42..b83f657 100644
--- a/eslint/conf/config-schema.js
+++ b/eslint/conf/config-schema.js
@@ -1,3 +1,15 @@
+/*
+ * STOP!!! DO NOT MODIFY.
+ *
+ * This file is part of the ongoing work to move the eslintrc-style config
+ * system into the @eslint/eslintrc package. This file needs to remain
+ * unchanged in order for this work to proceed.
+ *
+ * If you think you need to change this file, please contact @nzakas first.
+ *
+ * Thanks in advance for your cooperation.
+ */
+
/**
* @fileoverview Defines a schema for configs.
* @author Sylvan Mably
diff --git a/eslint/conf/environments.js b/eslint/conf/environments.js
deleted file mode 100644
index 90589b1..0000000
--- a/eslint/conf/environments.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/**
- * @fileoverview Defines environment settings and globals.
- * @author Elan Shanker
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const globals = require("globals");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Get the object that has difference.
- * @param {Record} current The newer object.
- * @param {Record} prev The older object.
- * @returns {Record} The difference object.
- */
-function getDiff(current, prev) {
- const retv = {};
-
- for (const [key, value] of Object.entries(current)) {
- if (!Object.hasOwnProperty.call(prev, key)) {
- retv[key] = value;
- }
- }
-
- return retv;
-}
-
-const newGlobals2015 = getDiff(globals.es2015, globals.es5); // 19 variables such as Promise, Map, ...
-const newGlobals2017 = {
- Atomics: false,
- SharedArrayBuffer: false
-};
-const newGlobals2020 = {
- BigInt: false,
- BigInt64Array: false,
- BigUint64Array: false,
- globalThis: false
-};
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-/** @type {Map} */
-module.exports = new Map(Object.entries({
-
- // Language
- builtin: {
- globals: globals.es5
- },
- es6: {
- globals: newGlobals2015,
- parserOptions: {
- ecmaVersion: 6
- }
- },
- es2015: {
- globals: newGlobals2015,
- parserOptions: {
- ecmaVersion: 6
- }
- },
- es2017: {
- globals: { ...newGlobals2015, ...newGlobals2017 },
- parserOptions: {
- ecmaVersion: 8
- }
- },
- es2020: {
- globals: { ...newGlobals2015, ...newGlobals2017, ...newGlobals2020 },
- parserOptions: {
- ecmaVersion: 11
- }
- },
-
- // Platforms
- browser: {
- globals: globals.browser
- },
- node: {
- globals: globals.node,
- parserOptions: {
- ecmaFeatures: {
- globalReturn: true
- }
- }
- },
- "shared-node-browser": {
- globals: globals["shared-node-browser"]
- },
- worker: {
- globals: globals.worker
- },
- serviceworker: {
- globals: globals.serviceworker
- },
-
- // Frameworks
- commonjs: {
- globals: globals.commonjs,
- parserOptions: {
- ecmaFeatures: {
- globalReturn: true
- }
- }
- },
- amd: {
- globals: globals.amd
- },
- mocha: {
- globals: globals.mocha
- },
- jasmine: {
- globals: globals.jasmine
- },
- jest: {
- globals: globals.jest
- },
- phantomjs: {
- globals: globals.phantomjs
- },
- jquery: {
- globals: globals.jquery
- },
- qunit: {
- globals: globals.qunit
- },
- prototypejs: {
- globals: globals.prototypejs
- },
- shelljs: {
- globals: globals.shelljs
- },
- meteor: {
- globals: globals.meteor
- },
- mongo: {
- globals: globals.mongo
- },
- protractor: {
- globals: globals.protractor
- },
- applescript: {
- globals: globals.applescript
- },
- nashorn: {
- globals: globals.nashorn
- },
- atomtest: {
- globals: globals.atomtest
- },
- embertest: {
- globals: globals.embertest
- },
- webextensions: {
- globals: globals.webextensions
- },
- greasemonkey: {
- globals: globals.greasemonkey
- }
-}));
diff --git a/eslint/docs/developer-guide/contributing/README.md b/eslint/docs/developer-guide/contributing/README.md
index a22ea5d..e341083 100644
--- a/eslint/docs/developer-guide/contributing/README.md
+++ b/eslint/docs/developer-guide/contributing/README.md
@@ -4,11 +4,11 @@ One of the great things about open source projects is that anyone can contribute
This guide is intended for anyone who wants to contribute to an ESLint project. Please read it carefully as it answers a lot of the questions many newcomers have when first working with our projects.
-## Read the [Code of Conduct](https://js.foundation/community/code-of-conduct)
+## Read the [Code of Conduct](https://eslint.org/conduct)
-ESLint welcomes contributions from everyone and adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct). We kindly request that you read over our code of conduct before contributing.
+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.
-## [Signing the CLA](https://js.foundation/CLA)
+## [Signing the CLA](https://openjsf.org/about/the-openjs-foundation-cla/)
In order to submit code or documentation to an ESLint project, you will need to electronically sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint). The CLA is you giving us permission to use your contribution.
diff --git a/eslint/docs/developer-guide/development-environment.md b/eslint/docs/developer-guide/development-environment.md
index a93e1ef..a1fc071 100644
--- a/eslint/docs/developer-guide/development-environment.md
+++ b/eslint/docs/developer-guide/development-environment.md
@@ -6,7 +6,7 @@ ESLint has a very lightweight development environment that makes updating code f
Go to to download and install the latest stable version for your operating system.
-Most of the installers come with [npm](https://www.npmjs.com/) already installed, but if for some reason it doesn't work on your system, you can install it manually using the instructions on the site.
+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
@@ -23,7 +23,7 @@ You must be connected to the Internet for this step to work. You'll see a lot of
## Step 3: Add the upstream source
-The *upstream source* is the main ESLint repository that active development happens on. 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.
+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.
To add the upstream source for ESLint, run the following in your repository:
diff --git a/eslint/docs/developer-guide/nodejs-api.md b/eslint/docs/developer-guide/nodejs-api.md
index 991a1cb..cfa0372 100644
--- a/eslint/docs/developer-guide/nodejs-api.md
+++ b/eslint/docs/developer-guide/nodejs-api.md
@@ -8,18 +8,18 @@ While ESLint is designed to be run on the command line, it's possible to use ESL
* [ESLint]
* [constructor()][eslint-constructor]
- * [lintFiles()][eslint-lintFiles]
- * [lintText()][eslint-lintText]
- * [calculateConfigForFile()][eslint-calculateConfigForFile]
- * [isPathIgnored()][eslint-isPathIgnored]
- * [loadFormatter()][eslint-loadFormatter]
+ * [lintFiles()][eslint-lintfiles]
+ * [lintText()][eslint-linttext]
+ * [calculateConfigForFile()][eslint-calculateconfigforfile]
+ * [isPathIgnored()][eslint-ispathignored]
+ * [loadFormatter()][eslint-loadformatter]
* [static version][eslint-version]
- * [static outputFixes()][eslint-outputFixes]
- * [static getErrorResults()][eslint-getErrorResults]
- * [LintResult type](lintresult)
- * [LintMessage type](lintmessage)
- * [EditInfo type](editinfo)
- * [Formatter type](formatter)
+ * [static outputFixes()][eslint-outputfixes]
+ * [static getErrorResults()][eslint-geterrorresults]
+ * [LintResult type][lintresult]
+ * [LintMessage type][lintmessage]
+ * [EditInfo type][editinfo]
+ * [Formatter type][formatter]
* [SourceCode](#sourcecode)
* [splitLines()](#sourcecode-splitlines)
* [Linter](#linter)
@@ -926,7 +926,7 @@ The top-level report object has a `results` array containing all linting results
* `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, so you can use that to rewrite the files if necessary. This property is omitted if no fix is available.
-The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files. Additionally, `usedDeprecatedRules` signals any deprecated rules used and their replacement (if available). Specifically, it is array of objects with properties like so:
+The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files. Additionally, `usedDeprecatedRules` signals any deprecated rules used and their replacement (if available). Specifically, it is an array of objects with properties like so:
* `ruleId` - The name of the rule (e.g. `indent-legacy`).
* `replacedBy` - An array of rules that replace the deprecated rule (e.g. `["indent"]`).
@@ -1383,14 +1383,14 @@ ruleTester.run("my-rule", myRule, {
[thirdparty-formatters]: https://www.npmjs.com/search?q=eslintformatter
[eslint]: #eslint-class
[eslint-constructor]: #-new-eslintoptions
-[eslint-lintfiles]: #-eslintlintFilespatterns
-[eslint-linttext]: #-eslintlintTextcode-options
-[eslint-calculateconfigforfile]: #-eslintcalculateConfigForFilefilePath
-[eslint-ispathignored]: #-eslintisPathIgnoredfilePath
-[eslint-loadformatter]: #-eslintloadFormatternameOrPath
+[eslint-lintfiles]: #-eslintlintfilespatterns
+[eslint-linttext]: #-eslintlinttextcode-options
+[eslint-calculateconfigforfile]: #-eslintcalculateconfigforfilefilepath
+[eslint-ispathignored]: #-eslintispathignoredfilepath
+[eslint-loadformatter]: #-eslintloadformatternameorpath
[eslint-version]: #-eslintversion
-[eslint-outputfixes]: #-eslintoutputFixesresults
-[eslint-geterrorresults]: #-eslintgetErrorResultsresults
+[eslint-outputfixes]: #-eslintoutputfixesresults
+[eslint-geterrorresults]: #-eslintgeterrorresultsresults
[lintresult]: #-lintresult-type
[lintmessage]: #-lintmessage-type
[editinfo]: #-editinfo-type
diff --git a/eslint/docs/developer-guide/source-code.md b/eslint/docs/developer-guide/source-code.md
index a7e9434..a6b7fa5 100644
--- a/eslint/docs/developer-guide/source-code.md
+++ b/eslint/docs/developer-guide/source-code.md
@@ -22,7 +22,7 @@ Once you have a local copy and have Node.JS and npm installed, you'll need to in
Now when you run `eslint`, it will be running your local copy and showing your changes.
-**Note:** It's a good idea to re-rerun `npm install` whenever you pull from the main repository to ensure you have the latest development dependencies.
+**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
diff --git a/eslint/docs/developer-guide/working-with-custom-formatters.md b/eslint/docs/developer-guide/working-with-custom-formatters.md
index 8cb6738..7efed41 100644
--- a/eslint/docs/developer-guide/working-with-custom-formatters.md
+++ b/eslint/docs/developer-guide/working-with-custom-formatters.md
@@ -11,7 +11,7 @@ module.exports = function(results) {
};
```
-To run ESLint with this formatter, you can use the `-f` (or `--formatter`) command line flag:
+To run ESLint with this formatter, you can use the `-f` (or `--format`) command line flag:
```bash
eslint -f ./my-awesome-formatter.js src/
@@ -50,7 +50,7 @@ The [Using Rule metadata](#using-rule-metadata) example shows how to use the `da
## Packaging the Custom Formatter
-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 `--formatter`) flag like this:
+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:
```bash
eslint -f awesome src/
diff --git a/eslint/docs/developer-guide/working-with-plugins.md b/eslint/docs/developer-guide/working-with-plugins.md
index a2b238c..521b9eb 100644
--- a/eslint/docs/developer-guide/working-with-plugins.md
+++ b/eslint/docs/developer-guide/working-with-plugins.md
@@ -180,7 +180,7 @@ module.exports = {
env: ["node"],
rules: {
"myPlugin/my-rule": "off",
- "eslint-plugin-myPlugin/another-rule": "off"
+ "eslint-plugin-myPlugin/another-rule": "off",
"eslint-plugin-myPlugin/yet-another-rule": "error"
}
}
diff --git a/eslint/docs/developer-guide/working-with-rules.md b/eslint/docs/developer-guide/working-with-rules.md
index 17058b4..89d37c4 100644
--- a/eslint/docs/developer-guide/working-with-rules.md
+++ b/eslint/docs/developer-guide/working-with-rules.md
@@ -68,7 +68,7 @@ The source file for a rule exports an object with the following properties.
* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface.md#fix) automatically fixes problems reported by the rule
- **Important:** Without the `fixable` property, ESLint does not [apply fixes](#applying-fixes) even if the rule implements `fix` functions. Omit the `fixable` property if the rule is not fixable.
+ **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.
* `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring.md#configuring-rules)
@@ -301,7 +301,7 @@ context.report({
Here, the `fix()` function is used to insert a semicolon after the node. Note that a fix is not immediately applied, and may not be applied at all if there are conflicts with other fixes. After applying fixes, ESLint will run all of the enabled rules again on the fixed code, potentially applying more fixes. This process will repeat up to 10 times, or until no more fixable problems are found. Afterwards, any remaining problems will be reported as usual.
-**Important:** Unless the rule [exports](#rule-basics) the `meta.fixable` property, ESLint does not apply fixes even if the rule implements `fix` functions.
+**Important:** The `meta.fixable` property is mandatory for fixable rules. ESLint will throw an error if a rule that implements `fix` functions does not [export](#rule-basics) the `meta.fixable` property.
The `fixer` object has the following methods:
@@ -375,13 +375,15 @@ context.report({
{% endraw %}
```
-Note: Suggestions will be applied as a stand-alone change, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation, or confirm to user preferences on presence/absence of semicolumns. All of those things can be corrected by multipass autofix when the user triggers it.
+Note: Suggestions will be applied as a stand-alone change, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation, or confirm to user preferences on presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it.
Best practices for suggestions:
1. Don't try to do too much and suggest large refactors that could introduce a lot of breaking changes.
1. As noted above, don't try to conform to user-defined styles.
+Suggestions are intended to provide fixes. ESLint will automatically remove the whole suggestion from the linting output if the suggestion's `fix` function returned `null` or an empty array/sequence.
+
#### Suggestion `messageId`s
Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageIds)). Here is an example of how to use it in a rule:
@@ -733,5 +735,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 (e.g., `eslint_rules`).
-2. Create a [configuration file](../user-guide/configuring.md) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file.
+2. Create a [configuration file](../user-guide/configuring.md) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `"warn"` or `"error"` in the configuration file.
3. Run the [command line interface](../user-guide/command-line-interface.md) using the `--rulesdir` option to specify the location of your runtime rules.
diff --git a/eslint/docs/maintainer-guide/npm-2fa.md b/eslint/docs/maintainer-guide/npm-2fa.md
deleted file mode 100644
index e9a5177..0000000
--- a/eslint/docs/maintainer-guide/npm-2fa.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# npm two-factor authentication
-
-The `eslint` npm account has two-factor authentication (2FA) enabled. The 2FA secret is distributed using a team on [Keybase](https://keybase.io). Anyone doing a release of a package from the Jenkins server needs to have access to the 2FA secret.
-
-If you're on ESLint's TSC, you should perform the following steps to obtain the 2FA secret:
-
-1. Download the [Keybase app](https://keybase.io/download) on a smartphone.
-1. Open the app and create an account.
-1. From the app, link your Keybase username with your GitHub username. (At the time of writing, the UI for this is to tap the face icon in the bottom-left of the app, then the profile picture in the top-right, then tap "Prove your GitHub" and follow the instructions.)
-1. Mention your Keybase username in the team chatroom, and wait for someone to add you to the Keybase team.
-1. Download an authenticator app like [Google Authenticator](https://support.google.com/accounts/answer/1066447) or [Authy](https://authy.com/), if you don't have one installed already.
-1. In the Keybase app, navigate to the Keybase filesystem (at the time of writing, the UI for this is to tap the hamburger icon in the bottom-right, then tap "Files") and then navigate to `/team/eslint/auth`.
- * If your authenticator app is downloaded on the same device as your Keybase app (this will usually be the case if you're using the Keybase mobile app), then open `npm_2fa_code.txt` and copy the contents to the clipboard. Open your authenticator app, and paste the contents as a new key (by selecting something like "Enter a provided key" or "Enter key manually").
- * If your authenticator app is downloaded on a *different* device from your Keybase app (e.g. if you're using a Keybase desktop app), then open `npm_2fa_code.png` and scan it as a QR code from your authenticator app.
-
-You should now be able to generate 6-digit 2FA codes for the `eslint` npm account using your authenticator app.
diff --git a/eslint/docs/maintainer-guide/releases.md b/eslint/docs/maintainer-guide/releases.md
index 94e1780..d406475 100644
--- a/eslint/docs/maintainer-guide/releases.md
+++ b/eslint/docs/maintainer-guide/releases.md
@@ -33,7 +33,7 @@ On the day of a scheduled release, the release team should follow these steps:
* Small bugfixes written by a team member.
1. Log into Jenkins and schedule a build for the "ESLint Release" job.
1. Watch the console output of the build on Jenkins. At some point, the build will pause and a link will be produced with an input field for a six-digit 2FA code.
-1. Enter the current six-digit 2FA code from your authenticator app. (Also see: [npm-2fa](./npm-2fa))
+1. Enter the current six-digit 2FA code from your authenticator app.
1. Continue the build and wait for it to finish.
1. Update the release blog post with a "Highlights" section, including new rules and anything else that's important.
1. Make a release announcement in the public chatroom.
diff --git a/eslint/docs/rules/camelcase.md b/eslint/docs/rules/camelcase.md
index 96135f8..164dfab 100644
--- a/eslint/docs/rules/camelcase.md
+++ b/eslint/docs/rules/camelcase.md
@@ -16,6 +16,8 @@ This rule has an object option:
* `"ignoreDestructuring": true` does not check destructured identifiers (but still checks any use of those identifiers later in the code)
* `"ignoreImports": false` (default) enforces camelcase style for ES2015 imports
* `"ignoreImports": true` does not check ES2015 imports (but still checks any use of the imports later in the code except function arguments)
+* `"ignoreGlobals": false` (default) enforces camelcase style for global variables
+* `"ignoreGlobals": true` does not enforce camelcase style for global variables
* `allow` (`string[]`) list of properties to accept. Accept regex.
### properties: "always"
@@ -217,6 +219,28 @@ Examples of **correct** code for this rule with the `{ "ignoreImports": true }`
import { snake_cased } from 'mod';
```
+### ignoreGlobals: false
+
+Examples of **incorrect** code for this rule with the default `{ "ignoreGlobals": false }` option:
+
+```js
+/*eslint camelcase: ["error", {ignoreGlobals: false}]*/
+/* global no_camelcased */
+
+const foo = no_camelcased;
+```
+
+### ignoreGlobals: true
+
+Examples of **correct** code for this rule with the `{ "ignoreGlobals": true }` option:
+
+```js
+/*eslint camelcase: ["error", {ignoreGlobals: true}]*/
+/* global no_camelcased */
+
+const foo = no_camelcased;
+```
+
## allow
Examples of **correct** code for this rule with the `allow` option:
diff --git a/eslint/docs/rules/comma-spacing.md b/eslint/docs/rules/comma-spacing.md
index ffa2bfd..2310771 100644
--- a/eslint/docs/rules/comma-spacing.md
+++ b/eslint/docs/rules/comma-spacing.md
@@ -113,7 +113,7 @@ If your project will not be following a consistent comma-spacing pattern, turn t
## Further Reading
-* [Javascript](http://javascript.crockford.com/code.html)
+* [JavaScript](http://javascript.crockford.com/code.html)
* [Dojo Style Guide](https://dojotoolkit.org/reference-guide/1.9/developer/styleguide.html)
diff --git a/eslint/docs/rules/id-blacklist.md b/eslint/docs/rules/id-blacklist.md
index a7685c4..87a7d95 100644
--- a/eslint/docs/rules/id-blacklist.md
+++ b/eslint/docs/rules/id-blacklist.md
@@ -1,82 +1,3 @@
# disallow specified identifiers (id-blacklist)
-> "There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton
-
-Bad names can lead to hard-to-decipher code. Generic names, such as `data`, don't infer much about the code and the values it receives. This rule allows you to configure a blacklist of bad identifier names, that you don't want to see in your code.
-
-## Rule Details
-
-This rule disallows specified identifiers in assignments and `function` definitions.
-
-This rule will catch blacklisted identifiers that are:
-
-- variable declarations
-- function declarations
-- object properties assigned to during object creation
-
-It will not catch blacklisted identifiers that are:
-
-- function calls (so you can still use functions you do not have control over)
-- object properties (so you can still use objects you do not have control over)
-
-## Options
-
-The rule takes one or more strings as options: the names of restricted identifiers.
-
-For example, to restrict the use of common generic identifiers:
-
-```json
-{
- "id-blacklist": ["error", "data", "err", "e", "cb", "callback"]
-}
-```
-
-Examples of **incorrect** code for this rule with sample `"data", "callback"` restricted identifiers:
-
-```js
-/*eslint id-blacklist: ["error", "data", "callback"] */
-
-var data = {...};
-
-function callback() {
- // ...
-}
-
-element.callback = function() {
- // ...
-};
-
-var itemSet = {
- data: [...]
-};
-```
-
-Examples of **correct** code for this rule with sample `"data", "callback"` restricted identifiers:
-
-```js
-/*eslint id-blacklist: ["error", "data", "callback"] */
-
-var encodingOptions = {...};
-
-function processFileResult() {
- // ...
-}
-
-element.successHandler = function() {
- // ...
-};
-
-var itemSet = {
- entities: [...]
-};
-
-callback(); // all function calls are ignored
-
-foo.callback(); // all function calls are ignored
-
-foo.data; // all property names that are not assignments are ignored
-```
-
-## When Not To Use It
-
-You can turn this rule off if you are happy for identifiers to be named freely.
+This rule was **deprecated** in ESLint v7.5.0 and replaced by the [id-denylist](id-denylist.md) rule.
diff --git a/eslint/docs/rules/id-denylist.md b/eslint/docs/rules/id-denylist.md
new file mode 100644
index 0000000..040f26e
--- /dev/null
+++ b/eslint/docs/rules/id-denylist.md
@@ -0,0 +1,82 @@
+# disallow specified identifiers (id-denylist)
+
+> "There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton
+
+Generic names can lead to hard-to-decipher code. This rule allows you to specify a deny list of disallowed identifier names to avoid this practice.
+
+## Rule Details
+
+This rule disallows specified identifiers in assignments and `function` definitions.
+
+This rule will catch disallowed identifiers that are:
+
+- variable declarations
+- function declarations
+- object properties assigned to during object creation
+
+It will not catch disallowed identifiers that are:
+
+- function calls (so you can still use functions you do not have control over)
+- object properties (so you can still use objects you do not have control over)
+
+## Options
+
+The rule takes one or more strings as options: the names of restricted identifiers.
+
+For example, to restrict the use of common generic identifiers:
+
+```json
+{
+ "id-denylist": ["error", "data", "err", "e", "cb", "callback"]
+}
+```
+
+Examples of **incorrect** code for this rule with sample `"data", "callback"` restricted identifiers:
+
+```js
+/*eslint id-denylist: ["error", "data", "callback"] */
+
+var data = {...};
+
+function callback() {
+ // ...
+}
+
+element.callback = function() {
+ // ...
+};
+
+var itemSet = {
+ data: [...]
+};
+```
+
+Examples of **correct** code for this rule with sample `"data", "callback"` restricted identifiers:
+
+```js
+/*eslint id-denylist: ["error", "data", "callback"] */
+
+var encodingOptions = {...};
+
+function processFileResult() {
+ // ...
+}
+
+element.successHandler = function() {
+ // ...
+};
+
+var itemSet = {
+ entities: [...]
+};
+
+callback(); // all function calls are ignored
+
+foo.callback(); // all function calls are ignored
+
+foo.data; // all property names that are not assignments are ignored
+```
+
+## When Not To Use It
+
+You can turn this rule off if you do not want to restrict the use of certain identifiers.
diff --git a/eslint/docs/rules/id-length.md b/eslint/docs/rules/id-length.md
index f9f6de1..e9f8d9e 100644
--- a/eslint/docs/rules/id-length.md
+++ b/eslint/docs/rules/id-length.md
@@ -82,6 +82,7 @@ This rule has an object option:
* `"properties": always` (default) enforces identifier length convention for property names
* `"properties": never` ignores identifier length convention for property names
* `"exceptions"` allows an array of specified identifier names
+* `"exceptionPatterns"` array of strings representing regular expression patterns, allows identifiers that match any of the patterns.
### min
@@ -217,6 +218,29 @@ const { x } = foo;
const { a: x } = foo;
```
+### exceptionPatterns
+
+Examples of additional **correct** code for this rule with the `{ "exceptionPatterns": ["E|S", "[x-z]"] }` option:
+
+```js
+/*eslint id-length: ["error", { "exceptionPatterns": ["E|S", "[x-z]"] }]*/
+/*eslint-env es6*/
+
+var E = 5;
+function S() { return 42; }
+obj.x = document.body;
+var foo = function (x) { /* do stuff */ };
+try {
+ dangerousStuff();
+} catch (x) {
+ // ignore as many do
+}
+(y) => {return y * y};
+var [E] = arr;
+const { y } = foo;
+const { a: z } = foo;
+```
+
## Related Rules
* [max-len](max-len.md)
diff --git a/eslint/docs/rules/lines-around-comment.md b/eslint/docs/rules/lines-around-comment.md
index 61af596..ae543bd 100644
--- a/eslint/docs/rules/lines-around-comment.md
+++ b/eslint/docs/rules/lines-around-comment.md
@@ -437,7 +437,7 @@ const [
### ignorePattern
-By default this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`. An alternative regular expression can be provided.
+By default this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`. To ignore more comments in addition to the defaults, set the `ignorePattern` 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 the `ignorePattern` option:
diff --git a/eslint/docs/rules/lines-between-class-members.md b/eslint/docs/rules/lines-between-class-members.md
index c3b1449..f950a6b 100644
--- a/eslint/docs/rules/lines-between-class-members.md
+++ b/eslint/docs/rules/lines-between-class-members.md
@@ -84,7 +84,7 @@ class Foo{
Examples of **correct** code for this rule with the object option:
```js
-/* eslint lines-between-class-members: ["error", "always", { exceptAfterSingleLine: true }]*/
+/* eslint lines-between-class-members: ["error", "always", { "exceptAfterSingleLine": true }]*/
class Foo{
bar(){} // single line class member
baz(){
diff --git a/eslint/docs/rules/max-lines.md b/eslint/docs/rules/max-lines.md
index 0019e98..de4ea0d 100644
--- a/eslint/docs/rules/max-lines.md
+++ b/eslint/docs/rules/max-lines.md
@@ -6,6 +6,7 @@ Some people consider large files a code smell. Large files tend to do a lot of t
This rule enforces a maximum number of lines per file, in order to aid in maintainability and reduce complexity.
+Please note that most editors show an additional empty line at the end if the file ends with a line break. This rule does not count that extra line.
## Options
diff --git a/eslint/docs/rules/multiline-ternary.md b/eslint/docs/rules/multiline-ternary.md
index e9461a1..0c38bf7 100644
--- a/eslint/docs/rules/multiline-ternary.md
+++ b/eslint/docs/rules/multiline-ternary.md
@@ -27,7 +27,7 @@ This rule has a string option:
* `"always"` (default) enforces newlines between the operands of a ternary expression.
* `"always-multiline"` enforces newlines between the operands of a ternary expression if the expression spans multiple lines.
-* `"never"` disallows newlines between the operands of a ternary expression (enforcing that the entire ternary expression is on one line).
+* `"never"` disallows newlines between the operands of a ternary expression.
### always
@@ -134,6 +134,10 @@ Examples of **correct** code for this rule with the `"never"` option:
foo > bar ? value1 : value2;
foo > bar ? (baz > qux ? value1 : value2) : value3;
+
+foo > bar ? (
+ baz > qux ? value1 : value2
+) : value3;
```
## When Not To Use It
diff --git a/eslint/docs/rules/no-await-in-loop.md b/eslint/docs/rules/no-await-in-loop.md
index 9edf073..eebf142 100644
--- a/eslint/docs/rules/no-await-in-loop.md
+++ b/eslint/docs/rules/no-await-in-loop.md
@@ -42,6 +42,8 @@ This rule disallows the use of `await` within loop bodies.
Examples of **correct** code for this rule:
```js
+/*eslint no-await-in-loop: "error"*/
+
async function foo(things) {
const results = [];
for (const thing of things) {
@@ -56,6 +58,8 @@ async function foo(things) {
Examples of **incorrect** code for this rule:
```js
+/*eslint no-await-in-loop: "error"*/
+
async function foo(things) {
const results = [];
for (const thing of things) {
diff --git a/eslint/docs/rules/no-func-assign.md b/eslint/docs/rules/no-func-assign.md
index b4c582e..e35b0ed 100644
--- a/eslint/docs/rules/no-func-assign.md
+++ b/eslint/docs/rules/no-func-assign.md
@@ -22,6 +22,10 @@ foo = bar;
function foo() {
foo = bar;
}
+
+var a = function hello() {
+ hello = 123;
+};
```
Examples of **incorrect** code for this rule, unlike the corresponding rule in JSHint:
diff --git a/eslint/docs/rules/no-inline-comments.md b/eslint/docs/rules/no-inline-comments.md
index cde77e9..237b248 100644
--- a/eslint/docs/rules/no-inline-comments.md
+++ b/eslint/docs/rules/no-inline-comments.md
@@ -87,3 +87,25 @@ var quux = (
)
```
+
+## Options
+
+### ignorePattern
+
+To make this rule ignore specific comments, set the `ignorePattern` 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 the `ignorePattern` option:
+
+```js
+/*eslint no-inline-comments: ["error", { "ignorePattern": "webpackChunkName:\\s.+" }]*/
+
+import(/* webpackChunkName: "my-chunk-name" */ './locale/en');
+```
+
+Examples of **incorrect** code for the `ignorePattern` option:
+
+```js
+/*eslint no-inline-comments: ["error", { "ignorePattern": "something" }] */
+
+var foo = 4; // other thing
+```
diff --git a/eslint/docs/rules/no-loss-of-precision.md b/eslint/docs/rules/no-loss-of-precision.md
index e1fcc4e..62d0659 100644
--- a/eslint/docs/rules/no-loss-of-precision.md
+++ b/eslint/docs/rules/no-loss-of-precision.md
@@ -16,6 +16,7 @@ const x = 5123000000000000000000000000001
const x = 1230000000000000000000000.0
const x = .1230000000000000000000000
const x = 0X20000000000001
+const x = 0X2_000000000_0001;
```
Examples of **correct** code for this rule:
@@ -29,4 +30,5 @@ const x = 123e34
const x = 12300000000000000000000000
const x = 0x1FFFFFFFFFFFFF
const x = 9007199254740991
+const x = 9007_1992547409_91
```
diff --git a/eslint/docs/rules/no-magic-numbers.md b/eslint/docs/rules/no-magic-numbers.md
index ee6d3de..dfe21d8 100644
--- a/eslint/docs/rules/no-magic-numbers.md
+++ b/eslint/docs/rules/no-magic-numbers.md
@@ -128,6 +128,27 @@ a = data[4294967295]; // above the max array index
a = data[1e500]; // same as data["Infinity"]
```
+### ignoreDefaultValues
+
+A boolean to specify if numbers used in default value assignments are considered okay. `false` by default.
+
+Examples of **correct** code for the `{ "ignoreDefaultValues": true }` option:
+
+```js
+/*eslint no-magic-numbers: ["error", { "ignoreDefaultValues": true }]*/
+
+const { tax = 0.25 } = accountancy;
+
+function mapParallel(concurrency = 3) { /***/ }
+```
+
+```js
+/*eslint no-magic-numbers: ["error", { "ignoreDefaultValues": true }]*/
+
+let head;
+[head = 100] = []
+```
+
### enforceConst
A boolean to specify if we should check for the const keyword in variable declaration of numbers. `false` by default.
diff --git a/eslint/docs/rules/no-multiple-empty-lines.md b/eslint/docs/rules/no-multiple-empty-lines.md
index 87b2425..aff6c2e 100644
--- a/eslint/docs/rules/no-multiple-empty-lines.md
+++ b/eslint/docs/rules/no-multiple-empty-lines.md
@@ -10,9 +10,9 @@ This rule aims to reduce the scrolling required when reading through your code.
This rule has an object option:
-* `"max"` (default: `2`) enforces a maximum number of consecutive empty lines.
-* `"maxEOF"` enforces a maximum number of consecutive empty lines at the end of files.
-* `"maxBOF"` enforces a maximum number of consecutive empty lines at the beginning of files.
+- `"max"` (default: `2`) enforces a maximum number of consecutive empty lines.
+- `"maxEOF"` enforces a maximum number of consecutive empty lines at the end of files.
+- `"maxBOF"` enforces a maximum number of consecutive empty lines at the beginning of files.
### max
@@ -41,10 +41,10 @@ var bar = 3;
### maxEOF
-Examples of **incorrect** code for this rule with the `{ max: 2, maxEOF: 1 }` options:
+Examples of **incorrect** code for this rule with the `{ max: 2, maxEOF: 0 }` options:
```js
-/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 1 }]*/
+/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/
var foo = 5;
@@ -54,16 +54,42 @@ var bar = 3;
```
-Examples of **correct** code for this rule with the `{ max: 2, maxEOF: 1 }` options:
+Examples of **correct** code for this rule with the `{ max: 2, maxEOF: 0 }` options:
```js
-/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 1 }]*/
+/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/
var foo = 5;
var bar = 3;
+```
+**Note**: Although this ensures zero empty lines at the EOF, most editors will still show one empty line at the end if the file ends with a line break, as illustrated below. There is no empty line at the end of a file after the last `\n`, although editors may show an additional line. A true additional line would be represented by `\n\n`.
+
+**Incorrect**:
+
+```
+1 /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎
+2 ⏎
+3 var foo = 5;⏎
+4 ⏎
+5 ⏎
+6 var bar = 3;⏎
+7 ⏎
+8
+```
+
+**Correct**:
+
+```
+1 /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎
+2 ⏎
+3 var foo = 5;⏎
+4 ⏎
+5 ⏎
+6 var bar = 3;⏎
+7
```
### maxBOF
diff --git a/eslint/docs/rules/no-promise-executor-return.md b/eslint/docs/rules/no-promise-executor-return.md
new file mode 100644
index 0000000..1adfe37
--- /dev/null
+++ b/eslint/docs/rules/no-promise-executor-return.md
@@ -0,0 +1,96 @@
+# Disallow returning values from Promise executor functions (no-promise-executor-return)
+
+The `new Promise` constructor accepts a single argument, called an *executor*.
+
+```js
+const myPromise = new Promise(function executor(resolve, reject) {
+ readFile('foo.txt', function(err, result) {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(result);
+ }
+ });
+});
+```
+
+The executor function usually initiates some asynchronous operation. Once it is finished, the executor should call `resolve` with the result, or `reject` if an error occurred.
+
+The return value of the executor is ignored. Returning a value from an executor function is a possible error because the returned value cannot be used and it doesn't affect the promise in any way.
+
+## Rule Details
+
+This rule disallows returning values from Promise executor functions.
+
+Only `return` without a value is allowed, as it's a control flow statement.
+
+Examples of **incorrect** code for this rule:
+
+```js
+/*eslint no-promise-executor-return: "error"*/
+
+new Promise((resolve, reject) => {
+ if (someCondition) {
+ return defaultResult;
+ }
+ getSomething((err, result) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(result);
+ }
+ });
+});
+
+new Promise((resolve, reject) => getSomething((err, data) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(data);
+ }
+}));
+
+new Promise(() => {
+ return 1;
+});
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/*eslint no-promise-executor-return: "error"*/
+
+new Promise((resolve, reject) => {
+ if (someCondition) {
+ resolve(defaultResult);
+ return;
+ }
+ getSomething((err, result) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(result);
+ }
+ });
+});
+
+new Promise((resolve, reject) => {
+ getSomething((err, data) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(data);
+ }
+ });
+});
+
+Promise.resolve(1);
+```
+
+## Further Reading
+
+* [MDN Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+
+## Related Rules
+
+* [no-async-promise-executor](no-async-promise-executor.md)
diff --git a/eslint/docs/rules/no-return-await.md b/eslint/docs/rules/no-return-await.md
index b1c60f0..e3f7be2 100644
--- a/eslint/docs/rules/no-return-await.md
+++ b/eslint/docs/rules/no-return-await.md
@@ -11,6 +11,8 @@ This rule aims to prevent a likely common performance hazard due to a lack of un
Examples of **incorrect** code for this rule:
```js
+/*eslint no-return-await: "error"*/
+
async function foo() {
return await bar();
}
@@ -19,6 +21,8 @@ async function foo() {
Examples of **correct** code for this rule:
```js
+/*eslint no-return-await: "error"*/
+
async function foo() {
return bar();
}
@@ -28,11 +32,13 @@ async function foo() {
return;
}
+// This is essentially the same as `return await bar();`, but the rule checks only `await` in `return` statements
async function foo() {
const x = await bar();
return x;
}
+// In this example the `await` is necessary to be able to catch errors thrown from `bar()`
async function foo() {
try {
return await bar();
@@ -40,8 +46,6 @@ async function foo() {
}
```
-In the last example the `await` is necessary to be able to catch errors thrown from `bar()`.
-
## When Not To Use It
There are a few reasons you might want to turn this rule off:
diff --git a/eslint/docs/rules/no-script-url.md b/eslint/docs/rules/no-script-url.md
index 475959d..5c71eb4 100644
--- a/eslint/docs/rules/no-script-url.md
+++ b/eslint/docs/rules/no-script-url.md
@@ -10,6 +10,8 @@ Examples of **incorrect** code for this rule:
/*eslint no-script-url: "error"*/
location.href = "javascript:void(0)";
+
+location.href = `javascript:void(0)`;
```
## Compatibility
diff --git a/eslint/docs/rules/no-underscore-dangle.md b/eslint/docs/rules/no-underscore-dangle.md
index ebe7fd7..ded4281 100644
--- a/eslint/docs/rules/no-underscore-dangle.md
+++ b/eslint/docs/rules/no-underscore-dangle.md
@@ -33,17 +33,21 @@ var _ = require('underscore');
var obj = _.contains(items, item);
obj.__proto__ = {};
var file = __filename;
+function foo(_bar) {};
+const foo = { onClick(_bar) {} };
+const foo = (_bar) => {};
```
## Options
This rule has an object option:
-* `"allow"` allows specified identifiers to have dangling underscores
-* `"allowAfterThis": false` (default) disallows dangling underscores in members of the `this` object
-* `"allowAfterSuper": false` (default) disallows dangling underscores in members of the `super` object
-* `"allowAfterThisConstructor": false` (default) disallows dangling underscores in members of the `this.constructor` object
-* `"enforceInMethodNames": false` (default) allows dangling underscores in method names
+- `"allow"` allows specified identifiers to have dangling underscores
+- `"allowAfterThis": false` (default) disallows dangling underscores in members of the `this` object
+- `"allowAfterSuper": false` (default) disallows dangling underscores in members of the `super` object
+- `"allowAfterThisConstructor": false` (default) disallows dangling underscores in members of the `this.constructor` object
+- `"enforceInMethodNames": false` (default) allows dangling underscores in method names
+- `"allowFunctionParams": true` (default) allows dangling underscores in function parameter names
### allow
@@ -113,6 +117,26 @@ const o = {
};
```
+### allowFunctionParams
+
+Examples of **incorrect** code for this rule with the `{ "allowFunctionParams": false }` option:
+
+```js
+/*eslint no-underscore-dangle: ["error", { "allowFunctionParams": false }]*/
+
+function foo (_bar) {}
+function foo (_bar = 0) {}
+function foo (..._bar) {}
+
+const foo = function onClick (_bar) {}
+const foo = function onClick (_bar = 0) {}
+const foo = function onClick (..._bar) {}
+
+const foo = (_bar) => {};
+const foo = (_bar = 0) => {};
+const foo = (..._bar) => {};
+```
+
## When Not To Use It
If you want to allow dangling underscores in identifiers, then you can safely turn this rule off.
diff --git a/eslint/docs/rules/no-unmodified-loop-condition.md b/eslint/docs/rules/no-unmodified-loop-condition.md
index 688c350..7d9bcee 100644
--- a/eslint/docs/rules/no-unmodified-loop-condition.md
+++ b/eslint/docs/rules/no-unmodified-loop-condition.md
@@ -29,6 +29,10 @@ If a reference is inside of a dynamic expression (e.g. `CallExpression`,
Examples of **incorrect** code for this rule:
```js
+/*eslint no-unmodified-loop-condition: "error"*/
+
+var node = something;
+
while (node) {
doSomething(node);
}
@@ -46,6 +50,8 @@ while (node !== root) {
Examples of **correct** code for this rule:
```js
+/*eslint no-unmodified-loop-condition: "error"*/
+
while (node) {
doSomething(node);
node = node.parent;
diff --git a/eslint/docs/rules/no-unreachable-loop.md b/eslint/docs/rules/no-unreachable-loop.md
new file mode 100644
index 0000000..c58b97f
--- /dev/null
+++ b/eslint/docs/rules/no-unreachable-loop.md
@@ -0,0 +1,190 @@
+# Disallow loops with a body that allows only one iteration (no-unreachable-loop)
+
+A loop that can never reach the second iteration is a possible error in the code.
+
+```js
+for (let i = 0; i < arr.length; i++) {
+ if (arr[i].name === myName) {
+ doSomething(arr[i]);
+ // break was supposed to be here
+ }
+ break;
+}
+```
+
+In rare cases where only one iteration (or at most one iteration) is intended behavior, the code should be refactored to use `if` conditionals instead of `while`, `do-while` and `for` loops. It's considered a best practice to avoid using loop constructs for such cases.
+
+## Rule Details
+
+This rule aims to detect and disallow loops that can have at most one iteration, by performing static code path analysis on loop bodies.
+
+In particular, this rule will disallow a loop with a body that exits the loop in all code paths. If all code paths in the loop's body will end with either a `break`, `return` or a `throw` statement, the second iteration of such loop is certainly unreachable, regardless of the loop's condition.
+
+This rule checks `while`, `do-while`, `for`, `for-in` and `for-of` loops. You can optionally disable checks for each of these constructs.
+
+Examples of **incorrect** code for this rule:
+
+```js
+/*eslint no-unreachable-loop: "error"*/
+
+while (foo) {
+ doSomething(foo);
+ foo = foo.parent;
+ break;
+}
+
+function verifyList(head) {
+ let item = head;
+ do {
+ if (verify(item)) {
+ return true;
+ } else {
+ return false;
+ }
+ } while (item);
+}
+
+function findSomething(arr) {
+ for (var i = 0; i < arr.length; i++) {
+ if (isSomething(arr[i])) {
+ return arr[i];
+ } else {
+ throw new Error("Doesn't exist.");
+ }
+ }
+}
+
+for (key in obj) {
+ if (key.startsWith("_")) {
+ break;
+ }
+ firstKey = key;
+ firstValue = obj[key];
+ break;
+}
+
+for (foo of bar) {
+ if (foo.id === id) {
+ doSomething(foo);
+ }
+ break;
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/*eslint no-unreachable-loop: "error"*/
+
+while (foo) {
+ doSomething(foo);
+ foo = foo.parent;
+}
+
+function verifyList(head) {
+ let item = head;
+ do {
+ if (verify(item)) {
+ item = item.next;
+ } else {
+ return false;
+ }
+ } while (item);
+
+ return true;
+}
+
+function findSomething(arr) {
+ for (var i = 0; i < arr.length; i++) {
+ if (isSomething(arr[i])) {
+ return arr[i];
+ }
+ }
+ throw new Error("Doesn't exist.");
+}
+
+for (key in obj) {
+ if (key.startsWith("_")) {
+ continue;
+ }
+ firstKey = key;
+ firstValue = obj[key];
+ break;
+}
+
+for (foo of bar) {
+ if (foo.id === id) {
+ doSomething(foo);
+ break;
+ }
+}
+```
+
+Please note that this rule is not designed to check loop conditions, and will not warn in cases such as the following examples.
+
+Examples of additional **correct** code for this rule:
+
+```js
+/*eslint no-unreachable-loop: "error"*/
+
+do {
+ doSomething();
+} while (false)
+
+for (let i = 0; i < 1; i++) {
+ doSomething(i);
+}
+
+for (const a of [1]) {
+ doSomething(a);
+}
+```
+
+## Options
+
+This rule has an object option, with one option:
+
+* `"ignore"` - an optional array of loop types that will be ignored by this rule.
+
+## ignore
+
+You can specify up to 5 different elements in the `"ignore"` array:
+
+* `"WhileStatement"` - to ignore all `while` loops.
+* `"DoWhileStatement"` - to ignore all `do-while` loops.
+* `"ForStatement"` - to ignore all `for` loops (does not apply to `for-in` and `for-of` loops).
+* `"ForInStatement"` - to ignore all `for-in` loops.
+* `"ForOfStatement"` - to ignore all `for-of` loops.
+
+Examples of **correct** code for this rule with the `"ignore"` option:
+
+```js
+/*eslint no-unreachable-loop: ["error", { "ignore": ["ForInStatement", "ForOfStatement"] }]*/
+
+for (var key in obj) {
+ hasEnumerableProperties = true;
+ break;
+}
+
+for (const a of b) break;
+```
+
+## Known Limitations
+
+Static code path analysis, in general, does not evaluate conditions. Due to this fact, this rule might miss reporting cases such as the following:
+
+```js
+for (let i = 0; i < 10; i++) {
+ doSomething(i);
+ if (true) {
+ break;
+ }
+}
+```
+
+## Related Rules
+
+* [no-unreachable](no-unreachable.md)
+* [no-constant-condition](no-constant-condition.md)
+* [no-unmodified-loop-condition](no-unmodified-loop-condition.md)
+* [for-direction](for-direction.md)
diff --git a/eslint/docs/rules/object-curly-newline.md b/eslint/docs/rules/object-curly-newline.md
index eabc03e..302c81c 100644
--- a/eslint/docs/rules/object-curly-newline.md
+++ b/eslint/docs/rules/object-curly-newline.md
@@ -335,19 +335,32 @@ let d = {
foo: 1, bar: 2};
let e = {foo: function() {
dosomething();
-}};
+ }
+};
+let f = {
+ foo: function() {
+ dosomething();}};
-let {f
+let {g
} = obj;
let {
- g} = obj;
-let {h, i
+ h} = obj;
+let {i, j
+} = obj;
+let {k, l
} = obj;
let {
- j, k} = obj;
-let {l = function() {
+ m, n} = obj;
+let {
+ o, p} = obj;
+let {q = function() {
dosomething();
-}} = obj;
+ }
+} = obj;
+let {
+ r = function() {
+ dosomething();
+ }} = obj;
```
Examples of **correct** code for this rule with the default `{ "consistent": true }` option:
@@ -356,27 +369,35 @@ Examples of **correct** code for this rule with the default `{ "consistent": tru
/*eslint object-curly-newline: ["error", { "consistent": true }]*/
/*eslint-env es6*/
-let a = {};
-let b = {foo: 1};
-let c = {
+
+let empty1 = {};
+let empty2 = {
+};
+let a = {foo: 1};
+let b = {
foo: 1
};
-let d = {
+let c = {
foo: 1, bar: 2
};
-let e = {
+let d = {
foo: 1,
bar: 2
};
-let f = {foo: function() {dosomething();}};
-let g = {
+let e = {foo: function() {dosomething();}};
+let f = {
foo: function() {
dosomething();
}
};
let {} = obj;
-let {h} = obj;
+let {
+} = obj;
+let {g} = obj;
+let {
+ h
+} = obj;
let {i, j} = obj;
let {
k, l
diff --git a/eslint/docs/rules/operator-assignment.md b/eslint/docs/rules/operator-assignment.md
index 570f57c..b926462 100644
--- a/eslint/docs/rules/operator-assignment.md
+++ b/eslint/docs/rules/operator-assignment.md
@@ -10,6 +10,7 @@ JavaScript provides shorthand operators that combine variable assignment and som
x *= y | x = x * y
x /= y | x = x / y
x %= y | x = x % y
+ x **= y | x = x ** y
x <<= y | x = x << y
x >>= y | x = x >> y
x >>>= y | x = x >>> y
@@ -22,6 +23,8 @@ JavaScript provides shorthand operators that combine variable assignment and som
This rule requires or disallows assignment operator shorthand where possible.
+The rule applies to the operators listed in the above table. It does not report the logical assignment operators `&&=`, `||=`, and `??=` because their short-circuiting behavior is different from the other assignment operators.
+
## Options
This rule has a single string option:
diff --git a/eslint/docs/rules/operator-linebreak.md b/eslint/docs/rules/operator-linebreak.md
index 3b1986e..159bd52 100644
--- a/eslint/docs/rules/operator-linebreak.md
+++ b/eslint/docs/rules/operator-linebreak.md
@@ -22,7 +22,7 @@ This rule enforces a consistent linebreak style for operators.
## Options
-This rule has one option, which can be a string option or an object option.
+This rule has two options, a string option and an object option.
String option:
diff --git a/eslint/docs/rules/padding-line-between-statements.md b/eslint/docs/rules/padding-line-between-statements.md
index 1f2c4ab..2f2f67e 100644
--- a/eslint/docs/rules/padding-line-between-statements.md
+++ b/eslint/docs/rules/padding-line-between-statements.md
@@ -48,14 +48,14 @@ You can supply any number of configurations. If a statement pair matches multipl
- `"block"` is lonely blocks.
- `"block-like"` is block like statements. This matches statements that the last token is the closing brace of blocks; e.g. `{ }`, `if (a) { }`, and `while (a) { }`. Also matches immediately invoked function expression statements.
- `"break"` is `break` statements.
- - `"case"` is `case` labels.
+ - `"case"` is `case` clauses in `switch` statements.
- `"cjs-export"` is `export` statements of CommonJS; e.g. `module.exports = 0`, `module.exports.foo = 1`, and `exports.foo = 2`. This is a special case of assignment.
- `"cjs-import"` is `import` statements of CommonJS; e.g. `const foo = require("foo")`. This is a special case of variable declarations.
- `"class"` is `class` declarations.
- `"const"` is `const` variable declarations, both single-line and multiline.
- `"continue"` is `continue` statements.
- `"debugger"` is `debugger` statements.
- - `"default"` is `default` labels.
+ - `"default"` is `default` clauses in `switch` statements.
- `"directive"` is directive prologues. This matches directives; e.g. `"use strict"`.
- `"do"` is `do-while` statements. This matches all statements that the first token is `do` keyword.
- `"empty"` is empty statements.
@@ -212,6 +212,55 @@ Examples of **correct** code for the `[{ blankLine: "always", prev: "directive",
foo();
```
+----
+
+This configuration would require blank lines between clauses in `switch` statements.
+
+Examples of **incorrect** code for the `[{ blankLine: "always", prev: ["case", "default"], next: "*" }]` configuration:
+
+```js
+/*eslint padding-line-between-statements: [
+ "error",
+ { blankLine: "always", prev: ["case", "default"], next: "*" }
+]*/
+
+switch (foo) {
+ case 1:
+ bar();
+ break;
+ case 2:
+ case 3:
+ baz();
+ break;
+ default:
+ quux();
+}
+```
+
+Examples of **correct** code for the `[{ blankLine: "always", prev: ["case", "default"], next: "*" }]` configuration:
+
+```js
+/*eslint padding-line-between-statements: [
+ "error",
+ { blankLine: "always", prev: ["case", "default"], next: "*" }
+]*/
+
+switch (foo) {
+ case 1:
+ bar();
+ break;
+
+ case 2:
+
+ case 3:
+ baz();
+ break;
+
+ default:
+ quux();
+}
+```
+
## Compatibility
- **JSCS:** [requirePaddingNewLineAfterVariableDeclaration]
diff --git a/eslint/docs/rules/prefer-destructuring.md b/eslint/docs/rules/prefer-destructuring.md
index 00b5577..7f18559 100644
--- a/eslint/docs/rules/prefer-destructuring.md
+++ b/eslint/docs/rules/prefer-destructuring.md
@@ -21,6 +21,8 @@ The rule has a second object with a single key, `enforceForRenamedProperties`, w
- Accessing an object property whose key is an integer will fall under the category `array` destructuring.
- Accessing an array element through a computed index will fall under the category `object` destructuring.
+The `--fix` option on the command line fixes only problems reported in variable declarations, and among them only those that fall under the category `object` destructuring. Furthermore, the name of the declared variable has to be the same as the name used for non-computed member access in the initializer. For example, `var foo = object.foo` can be automatically fixed by this rule. Problems that involve computed member access (e.g., `var foo = object[foo]`) or renamed properties (e.g., `var foo = object.bar`) are not automatically fixed.
+
Examples of **incorrect** code for this rule:
```javascript
diff --git a/eslint/docs/rules/prefer-object-spread.md b/eslint/docs/rules/prefer-object-spread.md
index 95a6460..50d721b 100644
--- a/eslint/docs/rules/prefer-object-spread.md
+++ b/eslint/docs/rules/prefer-object-spread.md
@@ -9,6 +9,7 @@ Introduced in ES2018, object spread is a declarative alternative which may perfo
Examples of **incorrect** code for this rule:
```js
+/*eslint prefer-object-spread: "error"*/
Object.assign({}, foo)
@@ -31,6 +32,7 @@ Object.assign({ foo: bar });
Examples of **correct** code for this rule:
```js
+/*eslint prefer-object-spread: "error"*/
Object.assign(...foo);
diff --git a/eslint/docs/rules/prefer-regex-literals.md b/eslint/docs/rules/prefer-regex-literals.md
index fea589d..2ba8cac 100644
--- a/eslint/docs/rules/prefer-regex-literals.md
+++ b/eslint/docs/rules/prefer-regex-literals.md
@@ -88,6 +88,38 @@ RegExp(`${prefix}abc`);
new RegExp(String.raw`^\d\. ${suffix}`);
```
+## Options
+
+This rule has an object option:
+
+* `disallowRedundantWrapping` set to `true` additionally checks for unnecessarily wrapped regex literals (Default `false`).
+
+### `disallowRedundantWrapping`
+
+By default, this rule doesn’t check when a regex literal is unnecessarily wrapped in a `RegExp` constructor call. When the option `disallowRedundantWrapping` is set to `true`, the rule will also disallow such unnecessary patterns.
+
+Examples of `incorrect` code for `{ "disallowRedundantWrapping": true }`
+
+```js
+/*eslint prefer-regex-literals: ["error", {"disallowRedundantWrapping": true}]*/
+
+new RegExp(/abc/);
+
+new RegExp(/abc/, 'u');
+```
+
+Examples of `correct` code for `{ "disallowRedundantWrapping": true }`
+
+```js
+/*eslint prefer-regex-literals: ["error", {"disallowRedundantWrapping": true}]*/
+
+/abc/;
+
+/abc/u;
+
+new RegExp(/abc/, flags);
+```
+
## Further Reading
* [MDN: Regular Expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
diff --git a/eslint/docs/rules/sort-imports.md b/eslint/docs/rules/sort-imports.md
index f6fd04f..637c9fc 100644
--- a/eslint/docs/rules/sort-imports.md
+++ b/eslint/docs/rules/sort-imports.md
@@ -41,6 +41,7 @@ This rule accepts an object with its properties as
* `all` = import all members provided by exported bindings.
* `multiple` = import multiple members.
* `single` = import single member.
+* `allowSeparatedGroups` (default: `false`)
Default option settings are:
@@ -50,7 +51,8 @@ Default option settings are:
"ignoreCase": false,
"ignoreDeclarationSort": false,
"ignoreMemberSort": false,
- "memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
+ "memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
+ "allowSeparatedGroups": false
}]
}
```
@@ -226,6 +228,53 @@ import {a, b} from 'foo.js';
Default is `["none", "all", "multiple", "single"]`.
+### `allowSeparatedGroups`
+
+When `true` the rule checks the sorting of import declaration statements only for those that appear on consecutive lines.
+
+In other words, a blank line or a comment line or line with any other statement after an import declaration statement will reset the sorting of import declaration statements.
+
+Examples of **incorrect** code for this rule with the `{ "allowSeparatedGroups": true }` option:
+
+```js
+/*eslint sort-imports: ["error", { "allowSeparatedGroups": true }]*/
+
+import b from 'foo.js';
+import c from 'bar.js';
+import a from 'baz.js';
+```
+
+Examples of **correct** code for this rule with the `{ "allowSeparatedGroups": true }` option:
+
+```js
+/*eslint sort-imports: ["error", { "allowSeparatedGroups": true }]*/
+
+import b from 'foo.js';
+import c from 'bar.js';
+
+import a from 'baz.js';
+```
+
+```js
+/*eslint sort-imports: ["error", { "allowSeparatedGroups": true }]*/
+
+import b from 'foo.js';
+import c from 'bar.js';
+// comment
+import a from 'baz.js';
+```
+
+```js
+/*eslint sort-imports: ["error", { "allowSeparatedGroups": true }]*/
+
+import b from 'foo.js';
+import c from 'bar.js';
+quux();
+import a from 'baz.js';
+```
+
+Default is `false`.
+
## When Not To Use It
This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing imports isn't a part of your coding standards, then you can leave this rule disabled.
diff --git a/eslint/docs/rules/space-unary-ops.md b/eslint/docs/rules/space-unary-ops.md
index d04bd4a..676a921 100644
--- a/eslint/docs/rules/space-unary-ops.md
+++ b/eslint/docs/rules/space-unary-ops.md
@@ -6,6 +6,8 @@ Some style guides require or disallow spaces before or after unary operators. Th
This rule enforces consistency regarding the spaces after `words` unary operators and after/before `nonwords` unary operators.
+For `words` operators, this rule only applies when a space is not syntactically required. For instance, `delete obj.foo` requires the space and will not be considered by this rule. The equivalent `delete(obj.foo)` has an optional space (`delete (obj.foo)`), therefore this rule will apply to it.
+
Examples of unary `words` operators:
```js
@@ -103,14 +105,17 @@ Examples of **correct** code for this rule with the `{"words": true, "nonwords":
```js
/*eslint space-unary-ops: "error"*/
-// Word unary operator "delete" is followed by a whitespace.
-delete foo.bar;
-
-// Word unary operator "new" is followed by a whitespace.
-new Foo;
+// Word unary operator "typeof" is followed by a whitespace.
+typeof !foo;
// Word unary operator "void" is followed by a whitespace.
-void 0;
+void {foo:0};
+
+// Word unary operator "new" is followed by a whitespace.
+new [foo][0];
+
+// Word unary operator "delete" is followed by a whitespace.
+delete (foo.bar);
// Unary operator "++" is not followed by whitespace.
++foo;
diff --git a/eslint/docs/rules/valid-typeof.md b/eslint/docs/rules/valid-typeof.md
index b1884a8..825e3bf 100644
--- a/eslint/docs/rules/valid-typeof.md
+++ b/eslint/docs/rules/valid-typeof.md
@@ -37,6 +37,8 @@ typeof bar === typeof qux
Examples of **incorrect** code with the `{ "requireStringLiterals": true }` option:
```js
+/*eslint valid-typeof: ["error", { "requireStringLiterals": true }]*/
+
typeof foo === undefined
typeof bar == Object
typeof baz === "strnig"
@@ -48,6 +50,8 @@ typeof foo == 5
Examples of **correct** code with the `{ "requireStringLiterals": true }` option:
```js
+/*eslint valid-typeof: ["error", { "requireStringLiterals": true }]*/
+
typeof foo === "undefined"
typeof bar == "object"
typeof baz === "string"
diff --git a/eslint/docs/user-guide/configuring.md b/eslint/docs/user-guide/configuring.md
index 7f88019..96b8d6e 100644
--- a/eslint/docs/user-guide/configuring.md
+++ b/eslint/docs/user-guide/configuring.md
@@ -13,6 +13,27 @@ There are several pieces of information that can be configured:
All of these options give you fine-grained control over how ESLint treats your code.
+## Table of Contents
+
+* [Specifying Parser Options](#specifying-parser-options)
+* [Specifying Parser](#specifying-parser)
+* [Specifying Processor](#specifying-processor)
+* [Specifying Environments](#specifying-environments)
+* [Specifying Globals](#specifying-globals)
+* [Configuring Plugins](#configuring-plugins)
+* [Configuring Rules](#configuring-rules)
+* [Disabling Rules with Inline Comments](#disabling-rules-with-inline-comments)
+* [Configuring Inline Comment Behaviors](#configuring-inline-comment-behaviors)
+* [Adding Shared Settings](#adding-shared-settings)
+* [Using Configuration Files](#using-configuration-files-1)
+* [Configuration File Formats](#configuration-file-formats)
+* [Configuration Cascading and Hierarchy](#configuration-cascading-and-hierarchy)
+* [Extending Configuration Files](#extending-configuration-files)
+* [Configuration Based on Glob Patterns](#configuration-based-on-glob-patterns)
+* [Comments in Configuration Files](#comments-in-configuration-files)
+* [Ignoring Files and Directories](#ignoring-files-and-directories)
+* [Personal Configuration File (deprecated)](#personal-configuration-file-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.
@@ -24,7 +45,7 @@ For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 glo
{ "es6": true } }`. `{ "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:
-* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10 or 11 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) or 2020 (same as 11) to use the year-based naming.
+* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, or 12 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), or 2021 (same as 12) to use the year-based naming.
* `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules.
* `ecmaFeatures` - an object indicating which additional language features you'd like to use:
* `globalReturn` - allow `return` statements in the global scope
@@ -73,7 +94,7 @@ To indicate the npm module to use as your parser, specify it using the `parser`
The following parsers are compatible with ESLint:
* [Esprima](https://www.npmjs.com/package/esprima)
-* [Babel-ESLint](https://www.npmjs.com/package/babel-eslint) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint.
+* [@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.
@@ -91,7 +112,7 @@ To specify processors in a configuration file, use the `processor` key with the
}
```
-To specify processors for a specific kind 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.
+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
{
@@ -138,6 +159,7 @@ An environment defines global variables that are predefined. The available envir
* `es6` - enable all ECMAScript 6 features except for modules (this automatically sets the `ecmaVersion` parser option to 6).
* `es2017` - adds all ECMAScript 2017 globals and automatically sets the `ecmaVersion` parser option to 8.
* `es2020` - adds all ECMAScript 2020 globals and automatically sets the `ecmaVersion` parser option to 11.
+* `es2021` - adds all ECMAScript 2021 globals and automatically sets the `ecmaVersion` parser option to 12.
* `worker` - web workers global variables.
* `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/wiki/AMD) spec.
* `mocha` - adds all of the Mocha testing global variables.
@@ -1070,18 +1092,36 @@ Of particular note is that like `.gitignore` files, all paths used as patterns f
Please see `.gitignore`'s specification for further examples of valid syntax.
-In addition to any patterns in a `.eslintignore` file, ESLint ignores files in `/**/node_modules/*` by default. It can still be added using `!`.
+In addition to any patterns in the `.eslintignore` file, ESLint always follows a couple implicit ignore rules even if the `--no-ignore` flag is passed. The implicit rules are as follows:
-For example, placing the following `.eslintignore` file in the current working directory will not ignore `node_modules/*` and ignore anything in the `build/` directory except `build/index.js`:
+* `node_modules/` is ignored.
+* Dotfiles (except for `.eslintrc.*`) as well as Dotfolders and their contents are ignored.
-```text
-# node_modules/* is ignored by default, but can be added using !
-!node_modules/*
+There are also some exceptions to these rules:
-# Ignore built files except build/index.js
-build/*
-!build/index.js
-```
+* If the path to lint is a glob pattern or directory path and contains a Dotfolder, all Dotfiles and Dotfolders will be linted. This includes sub-dotfiles and sub-dotfolders that are buried deeper in the directory structure.
+
+ For example, `eslint .config/` will lint all Dotfolders and Dotfiles 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.
+
+ 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.
+
+* Allowlist and denylist rules specified via `--ignore-pattern` or `.eslintignore` are prioritized above implicit ignore rules.
+
+ For example, in this scenario, `.build/test.js` is the desired file to allowlist. Because all Dotfolders and their children are ignored by default, `.build` must first be allowlisted so that eslint because aware of its children. Then, `.build/test.js` must be explicitly allowlisted, while the rest of the content is denylisted. This is done with the following `.eslintignore` file:
+
+ ```text
+ # Allowlist 'test.js' in the '.build' folder
+ # But do not allow anything else in the '.build' folder to be linted
+ !.build
+ .build/*
+ !.build/test.js
+ ```
+
+ The following `--ignore-pattern` is also equivalent:
+
+ eslint --ignore-pattern '!.build' --ignore-pattern '.build/*' --ignore-pattern '!.build/test.js' parent-folder/
### Using an Alternate File
@@ -1127,13 +1167,28 @@ You'll see this warning:
```text
foo.js
- 0:0 warning File ignored because of your .eslintignore file. Use --no-ignore to override.
+ 0:0 warning File ignored because of a matching ignore pattern. Use "--no-ignore" to override.
✖ 1 problem (0 errors, 1 warning)
```
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 Dotfile or Dotfolder, but have forgotten to specifically allow those files in your `.eslintignore` file. You would run something like this:
+
+ eslint .config/foo.js
+
+You would see this warning:
+
+```text
+.config/foo.js
+ 0:0 warning File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!'") to override
+
+✖ 1 problem (0 errors, 1 warning)
+```
+
+This messages 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.
+
## Personal Configuration File (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).
diff --git a/eslint/docs/user-guide/getting-started.md b/eslint/docs/user-guide/getting-started.md
index 8438d50..93fef31 100644
--- a/eslint/docs/user-guide/getting-started.md
+++ b/eslint/docs/user-guide/getting-started.md
@@ -20,16 +20,26 @@ npm install eslint --save-dev
yarn add eslint --dev
```
-You should then set up a configuration file:
+You should then set up a configuration file, and the easiest way to do that is to use the `--init` flag:
```
$ npx eslint --init
+
+# or
+
+$ yarn run eslint --init
```
+**Note:** `--init` 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:
```
$ 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.
diff --git a/eslint/docs/user-guide/migrating-to-4.0.0.md b/eslint/docs/user-guide/migrating-to-4.0.0.md
index de1d378..b62ef05 100644
--- a/eslint/docs/user-guide/migrating-to-4.0.0.md
+++ b/eslint/docs/user-guide/migrating-to-4.0.0.md
@@ -30,7 +30,7 @@ The lists below are ordered roughly by the number of users each change is expect
---
-## `eslint:recommended` changes
+## `eslint:recommended` changes
Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config:
@@ -50,7 +50,7 @@ Two new rules have been added to the [`eslint:recommended`](https://eslint.org/d
}
```
-## The `indent` rule is more strict
+## 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.
@@ -69,25 +69,25 @@ To make the upgrade process easier, we've introduced the [`indent-legacy`](/docs
}
```
-## Unrecognized properties in config files now cause a fatal error
+## Unrecognized properties in config files now cause a fatal error
When creating a config, users sometimes make typos or misunderstand how the config is supposed to be structured. Previously, ESLint did not validate the properties of a config file, so a typo in a config could be very tedious to debug. Starting in 4.0.0, ESLint will raise an error if a property in a config file is unrecognized or has the wrong type.
**To address:** If you see a config validation error after upgrading, verify that your config doesn't contain any typos. If you are using an unrecognized property, you should be able to remove it from your config to restore the previous behavior.
-## .eslintignore patterns are now resolved from the location of the file
+## .eslintignore patterns are now resolved from the location of the file
Due to a bug, glob patterns in an `.eslintignore` file were previously resolved from the current working directory of the process, rather than the location of the `.eslintignore` file. Starting in 4.0, patterns in an `.eslintignore` file will be resolved from the `.eslintignore` file's location.
**To address:** If you use an `.eslintignore` file and you frequently run ESLint from somewhere other than the project root, it's possible that the patterns will be matched differently. You should update the patterns in the `.eslintignore` file to ensure they are relative to the file, not to the working directory.
-## The `padded-blocks` rule is more strict by default
+## The `padded-blocks` rule is more strict by default
By default, the [`padded-blocks`](/docs/rules/padded-blocks) rule will now enforce padding in class bodies and switch statements. Previously, the rule would ignore these cases unless the user opted into enforcing them.
**To address:** If this change results in more linting errors in your codebase, you should fix them or reconfigure the rule.
-## The `space-before-function-paren` rule is more strict by default
+## 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.
@@ -105,7 +105,7 @@ By default, the [`space-before-function-paren`](/docs/rules/space-before-functio
}
```
-## The `no-multi-spaces` rule is more strict by default
+## 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.
@@ -119,7 +119,7 @@ By default, the [`no-multi-spaces`](/docs/rules/no-multi-spaces) rule will now d
}
```
-## References to scoped plugins in config files are now required to include the scope
+## References to scoped plugins in config files are now required to include the scope
In 3.x, there was a bug where references to scoped NPM packages as plugins in config files could omit the scope. For example, in 3.x the following config was legal:
@@ -153,13 +153,13 @@ To avoid this ambiguity, in 4.0 references to scoped plugins must include the sc
---
-## `RuleTester` now validates properties of test cases
+## `RuleTester` now validates properties of test cases
Starting in 4.0, the `RuleTester` utility will validate properties of test case objects, and an error will be thrown if an unknown property is encountered. This change was added because we found that it was relatively common for developers to make typos in rule tests, often invalidating the assertions that the test cases were trying to make.
**To address:** If your tests for custom rules have extra properties, you should remove those properties.
-## AST Nodes no longer have comment properties
+## AST Nodes no longer have comment properties
Prior to 4.0, ESLint required parsers to implement comment attachment, a process where AST nodes would gain additional properties corresponding to their leading and trailing comments in the source file. This made it difficult for users to develop custom parsers, because they would have to replicate the confusing comment attachment semantics required by ESLint.
@@ -177,7 +177,7 @@ Finally, please note that the following `SourceCode` methods have been deprecate
* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option
* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option
-## `LineComment` and `BlockComment` events will no longer be emitted during AST traversal
+## `LineComment` and `BlockComment` events will no longer be emitted during AST traversal
Starting in 4.0, `LineComment` and `BlockComments` events will not be emitted during AST traversal. There are two reasons for this:
@@ -191,7 +191,7 @@ sourceCode.getAllComments().filter(comment => comment.type === "Line");
sourceCode.getAllComments().filter(comment => comment.type === "Block");
```
-## Shebangs are now returned from comment APIs
+## Shebangs are now returned from comment APIs
Prior to 4.0, shebang comments in a source file would not appear in the output of `sourceCode.getAllComments()` or `sourceCode.getComments()`, but they would appear in the output of `sourceCode.getTokenOrCommentBefore` as line comments. This inconsistency led to some confusion for rule developers.
@@ -205,13 +205,13 @@ sourceCode.getAllComments().filter(comment => comment.type !== "Shebang");
---
-## The `global` property in the `linter.verify()` API is no longer supported
+## The `global` property in the `linter.verify()` API is no longer supported
Previously, the `linter.verify()` API accepted a `global` config option, which was a synonym for the documented `globals` property. The `global` option was never documented or officially supported, and did not work in config files. It has been removed in 4.0.
**To address:** If you were using the `global` property, please use the `globals` property instead, which does the same thing.
-## More report messages now have full location ranges
+## More report messages now have full location ranges
Starting in 3.1.0, rules have been able to specify the *end* location of a reported problem, in addition to the start location, by explicitly specifying an end location in the `report` call. This is useful for tools like editor integrations, which can use the range to precisely display where a reported problem occurs. Starting in 4.0, if a *node* is reported rather than a location, the end location of the range will automatically be inferred from the end location of the node. As a result, many more reported problems will have end locations.
@@ -219,7 +219,7 @@ This is not expected to cause breakage. However, it will likely result in larger
**To address:** If you have an integration that deals with the ranges of reported problems, make sure you handle large report ranges in a user-friendly way.
-## Some exposed APIs are now ES2015 classes
+## Some exposed APIs are now ES2015 classes
The `CLIEngine`, `SourceCode`, and `RuleTester` modules from ESLint's Node.js API are now ES2015 classes. This will not break any documented behavior, but it does have some observable effects (for example, the methods on `CLIEngine.prototype` are now non-enumerable).
diff --git a/eslint/docs/user-guide/migrating-to-7.0.0.md b/eslint/docs/user-guide/migrating-to-7.0.0.md
index cb39336..2a640a7 100644
--- a/eslint/docs/user-guide/migrating-to-7.0.0.md
+++ b/eslint/docs/user-guide/migrating-to-7.0.0.md
@@ -171,7 +171,7 @@ Several rules have been enhanced and now report additional errors:
- [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-number](https://eslint.org/docs/rules/no-magic-number) rule now recognizes bigint literals.
+- [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.
diff --git a/eslint/karma.conf.js b/eslint/karma.conf.js
index dfc6bab..974c897 100644
--- a/eslint/karma.conf.js
+++ b/eslint/karma.conf.js
@@ -1,6 +1,13 @@
"use strict";
+const os = require("os");
-process.env.CHROME_BIN = require("puppeteer").executablePath();
+if (os.arch() === "arm64") {
+
+ // For arm64 architecture, install chromium-browser using "apt-get install chromium-browser"
+ process.env.CHROME_BIN = "/usr/bin/chromium-browser";
+} else {
+ process.env.CHROME_BIN = require("puppeteer").executablePath();
+}
module.exports = function(config) {
config.set({
diff --git a/eslint/lib/cli-engine/cascading-config-array-factory.js b/eslint/lib/cli-engine/cascading-config-array-factory.js
deleted file mode 100644
index f54605c..0000000
--- a/eslint/lib/cli-engine/cascading-config-array-factory.js
+++ /dev/null
@@ -1,490 +0,0 @@
-/**
- * @fileoverview `CascadingConfigArrayFactory` class.
- *
- * `CascadingConfigArrayFactory` class has a responsibility:
- *
- * 1. Handles cascading of config files.
- *
- * It provides two methods:
- *
- * - `getConfigArrayForFile(filePath)`
- * Get the corresponded configuration of a given file. This method doesn't
- * throw even if the given file didn't exist.
- * - `clearCache()`
- * Clear the internal cache. You have to call this method when
- * `additionalPluginPool` was updated if `baseConfig` or `cliConfig` depends
- * on the additional plugins. (`CLIEngine#addPlugin()` method calls this.)
- *
- * @author Toru Nagashima
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const os = require("os");
-const path = require("path");
-const { validateConfigArray } = require("../shared/config-validator");
-const { emitDeprecationWarning } = require("../shared/deprecation-warnings");
-const { ConfigArrayFactory } = require("./config-array-factory");
-const { ConfigArray, ConfigDependency, IgnorePattern } = require("./config-array");
-const loadRules = require("./load-rules");
-const debug = require("debug")("eslint:cascading-config-array-factory");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-// Define types for VSCode IntelliSense.
-/** @typedef {import("../shared/types").ConfigData} ConfigData */
-/** @typedef {import("../shared/types").Parser} Parser */
-/** @typedef {import("../shared/types").Plugin} Plugin */
-/** @typedef {ReturnType} ConfigArray */
-
-/**
- * @typedef {Object} CascadingConfigArrayFactoryOptions
- * @property {Map} [additionalPluginPool] The map for additional plugins.
- * @property {ConfigData} [baseConfig] The config by `baseConfig` option.
- * @property {ConfigData} [cliConfig] The config by CLI options (`--env`, `--global`, `--ignore-pattern`, `--parser`, `--parser-options`, `--plugin`, and `--rule`). CLI options overwrite the setting in config files.
- * @property {string} [cwd] The base directory to start lookup.
- * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
- * @property {string[]} [rulePaths] The value of `--rulesdir` option.
- * @property {string} [specificConfigPath] The value of `--config` option.
- * @property {boolean} [useEslintrc] if `false` then it doesn't load config files.
- */
-
-/**
- * @typedef {Object} CascadingConfigArrayFactoryInternalSlots
- * @property {ConfigArray} baseConfigArray The config array of `baseConfig` option.
- * @property {ConfigData} baseConfigData The config data of `baseConfig` option. This is used to reset `baseConfigArray`.
- * @property {ConfigArray} cliConfigArray The config array of CLI options.
- * @property {ConfigData} cliConfigData The config data of CLI options. This is used to reset `cliConfigArray`.
- * @property {ConfigArrayFactory} configArrayFactory The factory for config arrays.
- * @property {Map} configCache The cache from directory paths to config arrays.
- * @property {string} cwd The base directory to start lookup.
- * @property {WeakMap} finalizeCache The cache from config arrays to finalized config arrays.
- * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
- * @property {string[]|null} rulePaths The value of `--rulesdir` option. This is used to reset `baseConfigArray`.
- * @property {string|null} specificConfigPath The value of `--config` option. This is used to reset `cliConfigArray`.
- * @property {boolean} useEslintrc if `false` then it doesn't load config files.
- */
-
-/** @type {WeakMap} */
-const internalSlotsMap = new WeakMap();
-
-/**
- * Create the config array from `baseConfig` and `rulePaths`.
- * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
- * @returns {ConfigArray} The config array of the base configs.
- */
-function createBaseConfigArray({
- configArrayFactory,
- baseConfigData,
- rulePaths,
- cwd
-}) {
- const baseConfigArray = configArrayFactory.create(
- baseConfigData,
- { name: "BaseConfig" }
- );
-
- /*
- * Create the config array element for the default ignore patterns.
- * This element has `ignorePattern` property that ignores the default
- * patterns in the current working directory.
- */
- baseConfigArray.unshift(configArrayFactory.create(
- { ignorePatterns: IgnorePattern.DefaultPatterns },
- { name: "DefaultIgnorePattern" }
- )[0]);
-
- /*
- * Load rules `--rulesdir` option as a pseudo plugin.
- * Use a pseudo plugin to define rules of `--rulesdir`, so we can validate
- * the rule's options with only information in the config array.
- */
- if (rulePaths && rulePaths.length > 0) {
- baseConfigArray.push({
- type: "config",
- name: "--rulesdir",
- filePath: "",
- plugins: {
- "": new ConfigDependency({
- definition: {
- rules: rulePaths.reduce(
- (map, rulesPath) => Object.assign(
- map,
- loadRules(rulesPath, cwd)
- ),
- {}
- )
- },
- filePath: "",
- id: "",
- importerName: "--rulesdir",
- importerPath: ""
- })
- }
- });
- }
-
- return baseConfigArray;
-}
-
-/**
- * Create the config array from CLI options.
- * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
- * @returns {ConfigArray} The config array of the base configs.
- */
-function createCLIConfigArray({
- cliConfigData,
- configArrayFactory,
- cwd,
- ignorePath,
- specificConfigPath
-}) {
- const cliConfigArray = configArrayFactory.create(
- cliConfigData,
- { name: "CLIOptions" }
- );
-
- cliConfigArray.unshift(
- ...(ignorePath
- ? configArrayFactory.loadESLintIgnore(ignorePath)
- : configArrayFactory.loadDefaultESLintIgnore())
- );
-
- if (specificConfigPath) {
- cliConfigArray.unshift(
- ...configArrayFactory.loadFile(
- specificConfigPath,
- { name: "--config", basePath: cwd }
- )
- );
- }
-
- return cliConfigArray;
-}
-
-/**
- * The error type when there are files matched by a glob, but all of them have been ignored.
- */
-class ConfigurationNotFoundError extends Error {
-
- // eslint-disable-next-line jsdoc/require-description
- /**
- * @param {string} directoryPath The directory path.
- */
- constructor(directoryPath) {
- super(`No ESLint configuration found in ${directoryPath}.`);
- this.messageTemplate = "no-config-found";
- this.messageData = { directoryPath };
- }
-}
-
-/**
- * This class provides the functionality that enumerates every file which is
- * matched by given glob patterns and that configuration.
- */
-class CascadingConfigArrayFactory {
-
- /**
- * Initialize this enumerator.
- * @param {CascadingConfigArrayFactoryOptions} options The options.
- */
- constructor({
- additionalPluginPool = new Map(),
- baseConfig: baseConfigData = null,
- cliConfig: cliConfigData = null,
- cwd = process.cwd(),
- ignorePath,
- resolvePluginsRelativeTo,
- rulePaths = [],
- specificConfigPath = null,
- useEslintrc = true
- } = {}) {
- const configArrayFactory = new ConfigArrayFactory({
- additionalPluginPool,
- cwd,
- resolvePluginsRelativeTo
- });
-
- internalSlotsMap.set(this, {
- baseConfigArray: createBaseConfigArray({
- baseConfigData,
- configArrayFactory,
- cwd,
- rulePaths
- }),
- baseConfigData,
- cliConfigArray: createCLIConfigArray({
- cliConfigData,
- configArrayFactory,
- cwd,
- ignorePath,
- specificConfigPath
- }),
- cliConfigData,
- configArrayFactory,
- configCache: new Map(),
- cwd,
- finalizeCache: new WeakMap(),
- ignorePath,
- rulePaths,
- specificConfigPath,
- useEslintrc
- });
- }
-
- /**
- * The path to the current working directory.
- * This is used by tests.
- * @type {string}
- */
- get cwd() {
- const { cwd } = internalSlotsMap.get(this);
-
- return cwd;
- }
-
- /**
- * Get the config array of a given file.
- * If `filePath` was not given, it returns the config which contains only
- * `baseConfigData` and `cliConfigData`.
- * @param {string} [filePath] The file path to a file.
- * @param {Object} [options] The options.
- * @param {boolean} [options.ignoreNotFoundError] If `true` then it doesn't throw `ConfigurationNotFoundError`.
- * @returns {ConfigArray} The config array of the file.
- */
- getConfigArrayForFile(filePath, { ignoreNotFoundError = false } = {}) {
- const {
- baseConfigArray,
- cliConfigArray,
- cwd
- } = internalSlotsMap.get(this);
-
- if (!filePath) {
- return new ConfigArray(...baseConfigArray, ...cliConfigArray);
- }
-
- const directoryPath = path.dirname(path.resolve(cwd, filePath));
-
- debug(`Load config files for ${directoryPath}.`);
-
- return this._finalizeConfigArray(
- this._loadConfigInAncestors(directoryPath),
- directoryPath,
- ignoreNotFoundError
- );
- }
-
- /**
- * Set the config data to override all configs.
- * Require to call `clearCache()` method after this method is called.
- * @param {ConfigData} configData The config data to override all configs.
- * @returns {void}
- */
- setOverrideConfig(configData) {
- const slots = internalSlotsMap.get(this);
-
- slots.cliConfigData = configData;
- }
-
- /**
- * Clear config cache.
- * @returns {void}
- */
- clearCache() {
- const slots = internalSlotsMap.get(this);
-
- slots.baseConfigArray = createBaseConfigArray(slots);
- slots.cliConfigArray = createCLIConfigArray(slots);
- slots.configCache.clear();
- }
-
- /**
- * Load and normalize config files from the ancestor directories.
- * @param {string} directoryPath The path to a leaf directory.
- * @param {boolean} configsExistInSubdirs `true` if configurations exist in subdirectories.
- * @returns {ConfigArray} The loaded config.
- * @private
- */
- _loadConfigInAncestors(directoryPath, configsExistInSubdirs = false) {
- const {
- baseConfigArray,
- configArrayFactory,
- configCache,
- cwd,
- useEslintrc
- } = internalSlotsMap.get(this);
-
- if (!useEslintrc) {
- return baseConfigArray;
- }
-
- let configArray = configCache.get(directoryPath);
-
- // Hit cache.
- if (configArray) {
- debug(`Cache hit: ${directoryPath}.`);
- return configArray;
- }
- debug(`No cache found: ${directoryPath}.`);
-
- const homePath = os.homedir();
-
- // Consider this is root.
- if (directoryPath === homePath && cwd !== homePath) {
- debug("Stop traversing because of considered root.");
- if (configsExistInSubdirs) {
- const filePath = ConfigArrayFactory.getPathToConfigFileInDirectory(directoryPath);
-
- if (filePath) {
- emitDeprecationWarning(
- filePath,
- "ESLINT_PERSONAL_CONFIG_SUPPRESS"
- );
- }
- }
- return this._cacheConfig(directoryPath, baseConfigArray);
- }
-
- // Load the config on this directory.
- try {
- configArray = configArrayFactory.loadInDirectory(directoryPath);
- } catch (error) {
- /* istanbul ignore next */
- if (error.code === "EACCES") {
- debug("Stop traversing because of 'EACCES' error.");
- return this._cacheConfig(directoryPath, baseConfigArray);
- }
- throw error;
- }
-
- if (configArray.length > 0 && configArray.isRoot()) {
- debug("Stop traversing because of 'root:true'.");
- configArray.unshift(...baseConfigArray);
- return this._cacheConfig(directoryPath, configArray);
- }
-
- // Load from the ancestors and merge it.
- const parentPath = path.dirname(directoryPath);
- const parentConfigArray = parentPath && parentPath !== directoryPath
- ? this._loadConfigInAncestors(
- parentPath,
- configsExistInSubdirs || configArray.length > 0
- )
- : baseConfigArray;
-
- if (configArray.length > 0) {
- configArray.unshift(...parentConfigArray);
- } else {
- configArray = parentConfigArray;
- }
-
- // Cache and return.
- return this._cacheConfig(directoryPath, configArray);
- }
-
- /**
- * Freeze and cache a given config.
- * @param {string} directoryPath The path to a directory as a cache key.
- * @param {ConfigArray} configArray The config array as a cache value.
- * @returns {ConfigArray} The `configArray` (frozen).
- */
- _cacheConfig(directoryPath, configArray) {
- const { configCache } = internalSlotsMap.get(this);
-
- Object.freeze(configArray);
- configCache.set(directoryPath, configArray);
-
- return configArray;
- }
-
- /**
- * Finalize a given config array.
- * Concatenate `--config` and other CLI options.
- * @param {ConfigArray} configArray The parent config array.
- * @param {string} directoryPath The path to the leaf directory to find config files.
- * @param {boolean} ignoreNotFoundError If `true` then it doesn't throw `ConfigurationNotFoundError`.
- * @returns {ConfigArray} The loaded config.
- * @private
- */
- _finalizeConfigArray(configArray, directoryPath, ignoreNotFoundError) {
- const {
- cliConfigArray,
- configArrayFactory,
- finalizeCache,
- useEslintrc
- } = internalSlotsMap.get(this);
-
- let finalConfigArray = finalizeCache.get(configArray);
-
- if (!finalConfigArray) {
- finalConfigArray = configArray;
-
- // Load the personal config if there are no regular config files.
- if (
- useEslintrc &&
- configArray.every(c => !c.filePath) &&
- cliConfigArray.every(c => !c.filePath) // `--config` option can be a file.
- ) {
- const homePath = os.homedir();
-
- debug("Loading the config file of the home directory:", homePath);
-
- const personalConfigArray = configArrayFactory.loadInDirectory(
- homePath,
- { name: "PersonalConfig" }
- );
-
- if (
- personalConfigArray.length > 0 &&
- !directoryPath.startsWith(homePath)
- ) {
- const lastElement =
- personalConfigArray[personalConfigArray.length - 1];
-
- emitDeprecationWarning(
- lastElement.filePath,
- "ESLINT_PERSONAL_CONFIG_LOAD"
- );
- }
-
- finalConfigArray = finalConfigArray.concat(personalConfigArray);
- }
-
- // Apply CLI options.
- if (cliConfigArray.length > 0) {
- finalConfigArray = finalConfigArray.concat(cliConfigArray);
- }
-
- // Validate rule settings and environments.
- validateConfigArray(finalConfigArray);
-
- // Cache it.
- Object.freeze(finalConfigArray);
- finalizeCache.set(configArray, finalConfigArray);
-
- debug(
- "Configuration was determined: %o on %s",
- finalConfigArray,
- directoryPath
- );
- }
-
- // At least one element (the default ignore patterns) exists.
- if (!ignoreNotFoundError && useEslintrc && finalConfigArray.length <= 1) {
- throw new ConfigurationNotFoundError(directoryPath);
- }
-
- return finalConfigArray;
- }
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-module.exports = { CascadingConfigArrayFactory };
diff --git a/eslint/lib/cli-engine/cli-engine.js b/eslint/lib/cli-engine/cli-engine.js
index 802a405..9a41406 100644
--- a/eslint/lib/cli-engine/cli-engine.js
+++ b/eslint/lib/cli-engine/cli-engine.js
@@ -19,14 +19,29 @@ const fs = require("fs");
const path = require("path");
const defaultOptions = require("../../conf/default-cli-options");
const pkg = require("../../package.json");
-const ConfigOps = require("../shared/config-ops");
-const naming = require("../shared/naming");
-const ModuleResolver = require("../shared/relative-module-resolver");
+
+
+const {
+ Legacy: {
+ ConfigOps,
+ naming,
+ CascadingConfigArrayFactory,
+ IgnorePattern,
+ getUsedExtractedConfigs
+ }
+} = require("@eslint/eslintrc");
+
+/*
+ * For some reason, ModuleResolver must be included via filepath instead of by
+ * API exports in order to work properly. That's why this is separated out onto
+ * its own require() statement.
+ */
+const ModuleResolver = require("@eslint/eslintrc/lib/shared/relative-module-resolver");
+const { FileEnumerator } = require("./file-enumerator");
+
const { Linter } = require("../linter");
const builtInRules = require("../rules");
-const { CascadingConfigArrayFactory } = require("./cascading-config-array-factory");
-const { IgnorePattern, getUsedExtractedConfigs } = require("./config-array");
-const { FileEnumerator } = require("./file-enumerator");
+const loadRules = require("./load-rules");
const hash = require("./hash");
const LintResultCache = require("./lint-result-cache");
@@ -559,7 +574,11 @@ class CLIEngine {
resolvePluginsRelativeTo: options.resolvePluginsRelativeTo,
rulePaths: options.rulePaths,
specificConfigPath: options.configFile,
- useEslintrc: options.useEslintrc
+ useEslintrc: options.useEslintrc,
+ builtInRules,
+ loadRules,
+ eslintRecommendedPath: path.resolve(__dirname, "../../conf/eslint-recommended.js"),
+ eslintAllPath: path.resolve(__dirname, "../../conf/eslint-all.js")
});
const fileEnumerator = new FileEnumerator({
configArrayFactory,
diff --git a/eslint/lib/cli-engine/config-array-factory.js b/eslint/lib/cli-engine/config-array-factory.js
deleted file mode 100644
index 7c0fba6..0000000
--- a/eslint/lib/cli-engine/config-array-factory.js
+++ /dev/null
@@ -1,1074 +0,0 @@
-/**
- * @fileoverview The factory of `ConfigArray` objects.
- *
- * This class provides methods to create `ConfigArray` instance.
- *
- * - `create(configData, options)`
- * Create a `ConfigArray` instance from a config data. This is to handle CLI
- * options except `--config`.
- * - `loadFile(filePath, options)`
- * Create a `ConfigArray` instance from a config file. This is to handle
- * `--config` option. If the file was not found, throws the following error:
- * - If the filename was `*.js`, a `MODULE_NOT_FOUND` error.
- * - If the filename was `package.json`, an IO error or an
- * `ESLINT_CONFIG_FIELD_NOT_FOUND` error.
- * - Otherwise, an IO error such as `ENOENT`.
- * - `loadInDirectory(directoryPath, options)`
- * Create a `ConfigArray` instance from a config file which is on a given
- * directory. This tries to load `.eslintrc.*` or `package.json`. If not
- * found, returns an empty `ConfigArray`.
- * - `loadESLintIgnore(filePath)`
- * Create a `ConfigArray` instance from a config file that is `.eslintignore`
- * format. This is to handle `--ignore-path` option.
- * - `loadDefaultESLintIgnore()`
- * Create a `ConfigArray` instance from `.eslintignore` or `package.json` in
- * the current working directory.
- *
- * `ConfigArrayFactory` class has the responsibility that loads configuration
- * files, including loading `extends`, `parser`, and `plugins`. The created
- * `ConfigArray` instance has the loaded `extends`, `parser`, and `plugins`.
- *
- * But this class doesn't handle cascading. `CascadingConfigArrayFactory` class
- * handles cascading and hierarchy.
- *
- * @author Toru Nagashima
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const fs = require("fs");
-const path = require("path");
-const importFresh = require("import-fresh");
-const stripComments = require("strip-json-comments");
-const { validateConfigSchema } = require("../shared/config-validator");
-const naming = require("../shared/naming");
-const ModuleResolver = require("../shared/relative-module-resolver");
-const {
- ConfigArray,
- ConfigDependency,
- IgnorePattern,
- OverrideTester
-} = require("./config-array");
-const debug = require("debug")("eslint:config-array-factory");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-const eslintRecommendedPath = path.resolve(__dirname, "../../conf/eslint-recommended.js");
-const eslintAllPath = path.resolve(__dirname, "../../conf/eslint-all.js");
-const configFilenames = [
- ".eslintrc.js",
- ".eslintrc.cjs",
- ".eslintrc.yaml",
- ".eslintrc.yml",
- ".eslintrc.json",
- ".eslintrc",
- "package.json"
-];
-
-// Define types for VSCode IntelliSense.
-/** @typedef {import("../shared/types").ConfigData} ConfigData */
-/** @typedef {import("../shared/types").OverrideConfigData} OverrideConfigData */
-/** @typedef {import("../shared/types").Parser} Parser */
-/** @typedef {import("../shared/types").Plugin} Plugin */
-/** @typedef {import("./config-array/config-dependency").DependentParser} DependentParser */
-/** @typedef {import("./config-array/config-dependency").DependentPlugin} DependentPlugin */
-/** @typedef {ConfigArray[0]} ConfigArrayElement */
-
-/**
- * @typedef {Object} ConfigArrayFactoryOptions
- * @property {Map} [additionalPluginPool] The map for additional plugins.
- * @property {string} [cwd] The path to the current working directory.
- * @property {string} [resolvePluginsRelativeTo] A path to the directory that plugins should be resolved from. Defaults to `cwd`.
- */
-
-/**
- * @typedef {Object} ConfigArrayFactoryInternalSlots
- * @property {Map} additionalPluginPool The map for additional plugins.
- * @property {string} cwd The path to the current working directory.
- * @property {string | undefined} resolvePluginsRelativeTo An absolute path the the directory that plugins should be resolved from.
- */
-
-/**
- * @typedef {Object} ConfigArrayFactoryLoadingContext
- * @property {string} filePath The path to the current configuration.
- * @property {string} matchBasePath The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
- * @property {string} name The name of the current configuration.
- * @property {string} pluginBasePath The base path to resolve plugins.
- * @property {"config" | "ignore" | "implicit-processor"} type The type of the current configuration. This is `"config"` in normal. This is `"ignore"` if it came from `.eslintignore`. This is `"implicit-processor"` if it came from legacy file-extension processors.
- */
-
-/**
- * @typedef {Object} ConfigArrayFactoryLoadingContext
- * @property {string} filePath The path to the current configuration.
- * @property {string} matchBasePath The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
- * @property {string} name The name of the current configuration.
- * @property {"config" | "ignore" | "implicit-processor"} type The type of the current configuration. This is `"config"` in normal. This is `"ignore"` if it came from `.eslintignore`. This is `"implicit-processor"` if it came from legacy file-extension processors.
- */
-
-/** @type {WeakMap} */
-const internalSlotsMap = new WeakMap();
-
-/**
- * Check if a given string is a file path.
- * @param {string} nameOrPath A module name or file path.
- * @returns {boolean} `true` if the `nameOrPath` is a file path.
- */
-function isFilePath(nameOrPath) {
- return (
- /^\.{1,2}[/\\]/u.test(nameOrPath) ||
- path.isAbsolute(nameOrPath)
- );
-}
-
-/**
- * Convenience wrapper for synchronously reading file contents.
- * @param {string} filePath The filename to read.
- * @returns {string} The file contents, with the BOM removed.
- * @private
- */
-function readFile(filePath) {
- return fs.readFileSync(filePath, "utf8").replace(/^\ufeff/u, "");
-}
-
-/**
- * Loads a YAML configuration from a file.
- * @param {string} filePath The filename to load.
- * @returns {ConfigData} The configuration object from the file.
- * @throws {Error} If the file cannot be read.
- * @private
- */
-function loadYAMLConfigFile(filePath) {
- debug(`Loading YAML config file: ${filePath}`);
-
- // lazy load YAML to improve performance when not used
- const yaml = require("js-yaml");
-
- try {
-
- // empty YAML file can be null, so always use
- return yaml.safeLoad(readFile(filePath)) || {};
- } catch (e) {
- debug(`Error reading YAML file: ${filePath}`);
- e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
- throw e;
- }
-}
-
-/**
- * Loads a JSON configuration from a file.
- * @param {string} filePath The filename to load.
- * @returns {ConfigData} The configuration object from the file.
- * @throws {Error} If the file cannot be read.
- * @private
- */
-function loadJSONConfigFile(filePath) {
- debug(`Loading JSON config file: ${filePath}`);
-
- try {
- return JSON.parse(stripComments(readFile(filePath)));
- } catch (e) {
- debug(`Error reading JSON file: ${filePath}`);
- e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
- e.messageTemplate = "failed-to-read-json";
- e.messageData = {
- path: filePath,
- message: e.message
- };
- throw e;
- }
-}
-
-/**
- * Loads a legacy (.eslintrc) configuration from a file.
- * @param {string} filePath The filename to load.
- * @returns {ConfigData} The configuration object from the file.
- * @throws {Error} If the file cannot be read.
- * @private
- */
-function loadLegacyConfigFile(filePath) {
- debug(`Loading legacy config file: ${filePath}`);
-
- // lazy load YAML to improve performance when not used
- const yaml = require("js-yaml");
-
- try {
- return yaml.safeLoad(stripComments(readFile(filePath))) || /* istanbul ignore next */ {};
- } catch (e) {
- debug("Error reading YAML file: %s\n%o", filePath, e);
- e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
- throw e;
- }
-}
-
-/**
- * Loads a JavaScript configuration from a file.
- * @param {string} filePath The filename to load.
- * @returns {ConfigData} The configuration object from the file.
- * @throws {Error} If the file cannot be read.
- * @private
- */
-function loadJSConfigFile(filePath) {
- debug(`Loading JS config file: ${filePath}`);
- try {
- return importFresh(filePath);
- } catch (e) {
- debug(`Error reading JavaScript file: ${filePath}`);
- e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
- throw e;
- }
-}
-
-/**
- * Loads a configuration from a package.json file.
- * @param {string} filePath The filename to load.
- * @returns {ConfigData} The configuration object from the file.
- * @throws {Error} If the file cannot be read.
- * @private
- */
-function loadPackageJSONConfigFile(filePath) {
- debug(`Loading package.json config file: ${filePath}`);
- try {
- const packageData = loadJSONConfigFile(filePath);
-
- if (!Object.hasOwnProperty.call(packageData, "eslintConfig")) {
- throw Object.assign(
- new Error("package.json file doesn't have 'eslintConfig' field."),
- { code: "ESLINT_CONFIG_FIELD_NOT_FOUND" }
- );
- }
-
- return packageData.eslintConfig;
- } catch (e) {
- debug(`Error reading package.json file: ${filePath}`);
- e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
- throw e;
- }
-}
-
-/**
- * Loads a `.eslintignore` from a file.
- * @param {string} filePath The filename to load.
- * @returns {string[]} The ignore patterns from the file.
- * @private
- */
-function loadESLintIgnoreFile(filePath) {
- debug(`Loading .eslintignore file: ${filePath}`);
-
- try {
- return readFile(filePath)
- .split(/\r?\n/gu)
- .filter(line => line.trim() !== "" && !line.startsWith("#"));
- } catch (e) {
- debug(`Error reading .eslintignore file: ${filePath}`);
- e.message = `Cannot read .eslintignore file: ${filePath}\nError: ${e.message}`;
- throw e;
- }
-}
-
-/**
- * Creates an error to notify about a missing config to extend from.
- * @param {string} configName The name of the missing config.
- * @param {string} importerName The name of the config that imported the missing config
- * @returns {Error} The error object to throw
- * @private
- */
-function configMissingError(configName, importerName) {
- return Object.assign(
- new Error(`Failed to load config "${configName}" to extend from.`),
- {
- messageTemplate: "extend-config-missing",
- messageData: { configName, importerName }
- }
- );
-}
-
-/**
- * Loads a configuration file regardless of the source. Inspects the file path
- * to determine the correctly way to load the config file.
- * @param {string} filePath The path to the configuration.
- * @returns {ConfigData|null} The configuration information.
- * @private
- */
-function loadConfigFile(filePath) {
- switch (path.extname(filePath)) {
- case ".js":
- case ".cjs":
- return loadJSConfigFile(filePath);
-
- case ".json":
- if (path.basename(filePath) === "package.json") {
- return loadPackageJSONConfigFile(filePath);
- }
- return loadJSONConfigFile(filePath);
-
- case ".yaml":
- case ".yml":
- return loadYAMLConfigFile(filePath);
-
- default:
- return loadLegacyConfigFile(filePath);
- }
-}
-
-/**
- * Write debug log.
- * @param {string} request The requested module name.
- * @param {string} relativeTo The file path to resolve the request relative to.
- * @param {string} filePath The resolved file path.
- * @returns {void}
- */
-function writeDebugLogForLoading(request, relativeTo, filePath) {
- /* istanbul ignore next */
- if (debug.enabled) {
- let nameAndVersion = null;
-
- try {
- const packageJsonPath = ModuleResolver.resolve(
- `${request}/package.json`,
- relativeTo
- );
- const { version = "unknown" } = require(packageJsonPath);
-
- nameAndVersion = `${request}@${version}`;
- } catch (error) {
- debug("package.json was not found:", error.message);
- nameAndVersion = request;
- }
-
- debug("Loaded: %s (%s)", nameAndVersion, filePath);
- }
-}
-
-/**
- * Create a new context with default values.
- * @param {ConfigArrayFactoryInternalSlots} slots The internal slots.
- * @param {"config" | "ignore" | "implicit-processor" | undefined} providedType The type of the current configuration. Default is `"config"`.
- * @param {string | undefined} providedName The name of the current configuration. Default is the relative path from `cwd` to `filePath`.
- * @param {string | undefined} providedFilePath The path to the current configuration. Default is empty string.
- * @param {string | undefined} providedMatchBasePath The type of the current configuration. Default is the directory of `filePath` or `cwd`.
- * @returns {ConfigArrayFactoryLoadingContext} The created context.
- */
-function createContext(
- { cwd, resolvePluginsRelativeTo },
- providedType,
- providedName,
- providedFilePath,
- providedMatchBasePath
-) {
- const filePath = providedFilePath
- ? path.resolve(cwd, providedFilePath)
- : "";
- const matchBasePath =
- (providedMatchBasePath && path.resolve(cwd, providedMatchBasePath)) ||
- (filePath && path.dirname(filePath)) ||
- cwd;
- const name =
- providedName ||
- (filePath && path.relative(cwd, filePath)) ||
- "";
- const pluginBasePath =
- resolvePluginsRelativeTo ||
- (filePath && path.dirname(filePath)) ||
- cwd;
- const type = providedType || "config";
-
- return { filePath, matchBasePath, name, pluginBasePath, type };
-}
-
-/**
- * Normalize a given plugin.
- * - Ensure the object to have four properties: configs, environments, processors, and rules.
- * - Ensure the object to not have other properties.
- * @param {Plugin} plugin The plugin to normalize.
- * @returns {Plugin} The normalized plugin.
- */
-function normalizePlugin(plugin) {
- return {
- configs: plugin.configs || {},
- environments: plugin.environments || {},
- processors: plugin.processors || {},
- rules: plugin.rules || {}
- };
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-/**
- * The factory of `ConfigArray` objects.
- */
-class ConfigArrayFactory {
-
- /**
- * Initialize this instance.
- * @param {ConfigArrayFactoryOptions} [options] The map for additional plugins.
- */
- constructor({
- additionalPluginPool = new Map(),
- cwd = process.cwd(),
- resolvePluginsRelativeTo
- } = {}) {
- internalSlotsMap.set(this, {
- additionalPluginPool,
- cwd,
- resolvePluginsRelativeTo:
- resolvePluginsRelativeTo &&
- path.resolve(cwd, resolvePluginsRelativeTo)
- });
- }
-
- /**
- * Create `ConfigArray` instance from a config data.
- * @param {ConfigData|null} configData The config data to create.
- * @param {Object} [options] The options.
- * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
- * @param {string} [options.filePath] The path to this config data.
- * @param {string} [options.name] The config name.
- * @returns {ConfigArray} Loaded config.
- */
- create(configData, { basePath, filePath, name } = {}) {
- if (!configData) {
- return new ConfigArray();
- }
-
- const slots = internalSlotsMap.get(this);
- const ctx = createContext(slots, "config", name, filePath, basePath);
- const elements = this._normalizeConfigData(configData, ctx);
-
- return new ConfigArray(...elements);
- }
-
- /**
- * Load a config file.
- * @param {string} filePath The path to a config file.
- * @param {Object} [options] The options.
- * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
- * @param {string} [options.name] The config name.
- * @returns {ConfigArray} Loaded config.
- */
- loadFile(filePath, { basePath, name } = {}) {
- const slots = internalSlotsMap.get(this);
- const ctx = createContext(slots, "config", name, filePath, basePath);
-
- return new ConfigArray(...this._loadConfigData(ctx));
- }
-
- /**
- * Load the config file on a given directory if exists.
- * @param {string} directoryPath The path to a directory.
- * @param {Object} [options] The options.
- * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
- * @param {string} [options.name] The config name.
- * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
- */
- loadInDirectory(directoryPath, { basePath, name } = {}) {
- const slots = internalSlotsMap.get(this);
-
- for (const filename of configFilenames) {
- const ctx = createContext(
- slots,
- "config",
- name,
- path.join(directoryPath, filename),
- basePath
- );
-
- if (fs.existsSync(ctx.filePath)) {
- let configData;
-
- try {
- configData = loadConfigFile(ctx.filePath);
- } catch (error) {
- if (!error || error.code !== "ESLINT_CONFIG_FIELD_NOT_FOUND") {
- throw error;
- }
- }
-
- if (configData) {
- debug(`Config file found: ${ctx.filePath}`);
- return new ConfigArray(
- ...this._normalizeConfigData(configData, ctx)
- );
- }
- }
- }
-
- debug(`Config file not found on ${directoryPath}`);
- return new ConfigArray();
- }
-
- /**
- * Check if a config file on a given directory exists or not.
- * @param {string} directoryPath The path to a directory.
- * @returns {string | null} The path to the found config file. If not found then null.
- */
- static getPathToConfigFileInDirectory(directoryPath) {
- for (const filename of configFilenames) {
- const filePath = path.join(directoryPath, filename);
-
- if (fs.existsSync(filePath)) {
- if (filename === "package.json") {
- try {
- loadPackageJSONConfigFile(filePath);
- return filePath;
- } catch { /* ignore */ }
- } else {
- return filePath;
- }
- }
- }
- return null;
- }
-
- /**
- * Load `.eslintignore` file.
- * @param {string} filePath The path to a `.eslintignore` file to load.
- * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
- */
- loadESLintIgnore(filePath) {
- const slots = internalSlotsMap.get(this);
- const ctx = createContext(
- slots,
- "ignore",
- void 0,
- filePath,
- slots.cwd
- );
- const ignorePatterns = loadESLintIgnoreFile(ctx.filePath);
-
- return new ConfigArray(
- ...this._normalizeESLintIgnoreData(ignorePatterns, ctx)
- );
- }
-
- /**
- * Load `.eslintignore` file in the current working directory.
- * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
- */
- loadDefaultESLintIgnore() {
- const slots = internalSlotsMap.get(this);
- const eslintIgnorePath = path.resolve(slots.cwd, ".eslintignore");
- const packageJsonPath = path.resolve(slots.cwd, "package.json");
-
- if (fs.existsSync(eslintIgnorePath)) {
- return this.loadESLintIgnore(eslintIgnorePath);
- }
- if (fs.existsSync(packageJsonPath)) {
- const data = loadJSONConfigFile(packageJsonPath);
-
- if (Object.hasOwnProperty.call(data, "eslintIgnore")) {
- if (!Array.isArray(data.eslintIgnore)) {
- throw new Error("Package.json eslintIgnore property requires an array of paths");
- }
- const ctx = createContext(
- slots,
- "ignore",
- "eslintIgnore in package.json",
- packageJsonPath,
- slots.cwd
- );
-
- return new ConfigArray(
- ...this._normalizeESLintIgnoreData(data.eslintIgnore, ctx)
- );
- }
- }
-
- return new ConfigArray();
- }
-
- /**
- * Load a given config file.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator} Loaded config.
- * @private
- */
- _loadConfigData(ctx) {
- return this._normalizeConfigData(loadConfigFile(ctx.filePath), ctx);
- }
-
- /**
- * Normalize a given `.eslintignore` data to config array elements.
- * @param {string[]} ignorePatterns The patterns to ignore files.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator} The normalized config.
- * @private
- */
- *_normalizeESLintIgnoreData(ignorePatterns, ctx) {
- const elements = this._normalizeObjectConfigData(
- { ignorePatterns },
- ctx
- );
-
- // Set `ignorePattern.loose` flag for backward compatibility.
- for (const element of elements) {
- if (element.ignorePattern) {
- element.ignorePattern.loose = true;
- }
- yield element;
- }
- }
-
- /**
- * Normalize a given config to an array.
- * @param {ConfigData} configData The config data to normalize.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator} The normalized config.
- * @private
- */
- _normalizeConfigData(configData, ctx) {
- validateConfigSchema(configData, ctx.name || ctx.filePath);
- return this._normalizeObjectConfigData(configData, ctx);
- }
-
- /**
- * Normalize a given config to an array.
- * @param {ConfigData|OverrideConfigData} configData The config data to normalize.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator} The normalized config.
- * @private
- */
- *_normalizeObjectConfigData(configData, ctx) {
- const { files, excludedFiles, ...configBody } = configData;
- const criteria = OverrideTester.create(
- files,
- excludedFiles,
- ctx.matchBasePath
- );
- const elements = this._normalizeObjectConfigDataBody(configBody, ctx);
-
- // Apply the criteria to every element.
- for (const element of elements) {
-
- /*
- * Merge the criteria.
- * This is for the `overrides` entries that came from the
- * configurations of `overrides[].extends`.
- */
- element.criteria = OverrideTester.and(criteria, element.criteria);
-
- /*
- * Remove `root` property to ignore `root` settings which came from
- * `extends` in `overrides`.
- */
- if (element.criteria) {
- element.root = void 0;
- }
-
- yield element;
- }
- }
-
- /**
- * Normalize a given config to an array.
- * @param {ConfigData} configData The config data to normalize.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator} The normalized config.
- * @private
- */
- *_normalizeObjectConfigDataBody(
- {
- env,
- extends: extend,
- globals,
- ignorePatterns,
- noInlineConfig,
- parser: parserName,
- parserOptions,
- plugins: pluginList,
- processor,
- reportUnusedDisableDirectives,
- root,
- rules,
- settings,
- overrides: overrideList = []
- },
- ctx
- ) {
- const extendList = Array.isArray(extend) ? extend : [extend];
- const ignorePattern = ignorePatterns && new IgnorePattern(
- Array.isArray(ignorePatterns) ? ignorePatterns : [ignorePatterns],
- ctx.matchBasePath
- );
-
- // Flatten `extends`.
- for (const extendName of extendList.filter(Boolean)) {
- yield* this._loadExtends(extendName, ctx);
- }
-
- // Load parser & plugins.
- const parser = parserName && this._loadParser(parserName, ctx);
- const plugins = pluginList && this._loadPlugins(pluginList, ctx);
-
- // Yield pseudo config data for file extension processors.
- if (plugins) {
- yield* this._takeFileExtensionProcessors(plugins, ctx);
- }
-
- // Yield the config data except `extends` and `overrides`.
- yield {
-
- // Debug information.
- type: ctx.type,
- name: ctx.name,
- filePath: ctx.filePath,
-
- // Config data.
- criteria: null,
- env,
- globals,
- ignorePattern,
- noInlineConfig,
- parser,
- parserOptions,
- plugins,
- processor,
- reportUnusedDisableDirectives,
- root,
- rules,
- settings
- };
-
- // Flatten `overries`.
- for (let i = 0; i < overrideList.length; ++i) {
- yield* this._normalizeObjectConfigData(
- overrideList[i],
- { ...ctx, name: `${ctx.name}#overrides[${i}]` }
- );
- }
- }
-
- /**
- * Load configs of an element in `extends`.
- * @param {string} extendName The name of a base config.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator} The normalized config.
- * @private
- */
- _loadExtends(extendName, ctx) {
- debug("Loading {extends:%j} relative to %s", extendName, ctx.filePath);
- try {
- if (extendName.startsWith("eslint:")) {
- return this._loadExtendedBuiltInConfig(extendName, ctx);
- }
- if (extendName.startsWith("plugin:")) {
- return this._loadExtendedPluginConfig(extendName, ctx);
- }
- return this._loadExtendedShareableConfig(extendName, ctx);
- } catch (error) {
- error.message += `\nReferenced from: ${ctx.filePath || ctx.name}`;
- throw error;
- }
- }
-
- /**
- * Load configs of an element in `extends`.
- * @param {string} extendName The name of a base config.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator} The normalized config.
- * @private
- */
- _loadExtendedBuiltInConfig(extendName, ctx) {
- if (extendName === "eslint:recommended") {
- return this._loadConfigData({
- ...ctx,
- filePath: eslintRecommendedPath,
- name: `${ctx.name} » ${extendName}`
- });
- }
- if (extendName === "eslint:all") {
- return this._loadConfigData({
- ...ctx,
- filePath: eslintAllPath,
- name: `${ctx.name} » ${extendName}`
- });
- }
-
- throw configMissingError(extendName, ctx.name);
- }
-
- /**
- * Load configs of an element in `extends`.
- * @param {string} extendName The name of a base config.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator} The normalized config.
- * @private
- */
- _loadExtendedPluginConfig(extendName, ctx) {
- const slashIndex = extendName.lastIndexOf("/");
- const pluginName = extendName.slice("plugin:".length, slashIndex);
- const configName = extendName.slice(slashIndex + 1);
-
- if (isFilePath(pluginName)) {
- throw new Error("'extends' cannot use a file path for plugins.");
- }
-
- const plugin = this._loadPlugin(pluginName, ctx);
- const configData =
- plugin.definition &&
- plugin.definition.configs[configName];
-
- if (configData) {
- return this._normalizeConfigData(configData, {
- ...ctx,
- filePath: plugin.filePath || ctx.filePath,
- name: `${ctx.name} » plugin:${plugin.id}/${configName}`
- });
- }
-
- throw plugin.error || configMissingError(extendName, ctx.filePath);
- }
-
- /**
- * Load configs of an element in `extends`.
- * @param {string} extendName The name of a base config.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator} The normalized config.
- * @private
- */
- _loadExtendedShareableConfig(extendName, ctx) {
- const { cwd } = internalSlotsMap.get(this);
- const relativeTo = ctx.filePath || path.join(cwd, "__placeholder__.js");
- let request;
-
- if (isFilePath(extendName)) {
- request = extendName;
- } else if (extendName.startsWith(".")) {
- request = `./${extendName}`; // For backward compatibility. A ton of tests depended on this behavior.
- } else {
- request = naming.normalizePackageName(
- extendName,
- "eslint-config"
- );
- }
-
- let filePath;
-
- try {
- filePath = ModuleResolver.resolve(request, relativeTo);
- } catch (error) {
- /* istanbul ignore else */
- if (error && error.code === "MODULE_NOT_FOUND") {
- throw configMissingError(extendName, ctx.filePath);
- }
- throw error;
- }
-
- writeDebugLogForLoading(request, relativeTo, filePath);
- return this._loadConfigData({
- ...ctx,
- filePath,
- name: `${ctx.name} » ${request}`
- });
- }
-
- /**
- * Load given plugins.
- * @param {string[]} names The plugin names to load.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {Record} The loaded parser.
- * @private
- */
- _loadPlugins(names, ctx) {
- return names.reduce((map, name) => {
- if (isFilePath(name)) {
- throw new Error("Plugins array cannot includes file paths.");
- }
- const plugin = this._loadPlugin(name, ctx);
-
- map[plugin.id] = plugin;
-
- return map;
- }, {});
- }
-
- /**
- * Load a given parser.
- * @param {string} nameOrPath The package name or the path to a parser file.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {DependentParser} The loaded parser.
- */
- _loadParser(nameOrPath, ctx) {
- debug("Loading parser %j from %s", nameOrPath, ctx.filePath);
-
- const { cwd } = internalSlotsMap.get(this);
- const relativeTo = ctx.filePath || path.join(cwd, "__placeholder__.js");
-
- try {
- const filePath = ModuleResolver.resolve(nameOrPath, relativeTo);
-
- writeDebugLogForLoading(nameOrPath, relativeTo, filePath);
-
- return new ConfigDependency({
- definition: require(filePath),
- filePath,
- id: nameOrPath,
- importerName: ctx.name,
- importerPath: ctx.filePath
- });
- } catch (error) {
-
- // If the parser name is "espree", load the espree of ESLint.
- if (nameOrPath === "espree") {
- debug("Fallback espree.");
- return new ConfigDependency({
- definition: require("espree"),
- filePath: require.resolve("espree"),
- id: nameOrPath,
- importerName: ctx.name,
- importerPath: ctx.filePath
- });
- }
-
- debug("Failed to load parser '%s' declared in '%s'.", nameOrPath, ctx.name);
- error.message = `Failed to load parser '${nameOrPath}' declared in '${ctx.name}': ${error.message}`;
-
- return new ConfigDependency({
- error,
- id: nameOrPath,
- importerName: ctx.name,
- importerPath: ctx.filePath
- });
- }
- }
-
- /**
- * Load a given plugin.
- * @param {string} name The plugin name to load.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {DependentPlugin} The loaded plugin.
- * @private
- */
- _loadPlugin(name, ctx) {
- debug("Loading plugin %j from %s", name, ctx.filePath);
-
- const { additionalPluginPool } = internalSlotsMap.get(this);
- const request = naming.normalizePackageName(name, "eslint-plugin");
- const id = naming.getShorthandName(request, "eslint-plugin");
- const relativeTo = path.join(ctx.pluginBasePath, "__placeholder__.js");
-
- if (name.match(/\s+/u)) {
- const error = Object.assign(
- new Error(`Whitespace found in plugin name '${name}'`),
- {
- messageTemplate: "whitespace-found",
- messageData: { pluginName: request }
- }
- );
-
- return new ConfigDependency({
- error,
- id,
- importerName: ctx.name,
- importerPath: ctx.filePath
- });
- }
-
- // Check for additional pool.
- const plugin =
- additionalPluginPool.get(request) ||
- additionalPluginPool.get(id);
-
- if (plugin) {
- return new ConfigDependency({
- definition: normalizePlugin(plugin),
- filePath: "", // It's unknown where the plugin came from.
- id,
- importerName: ctx.name,
- importerPath: ctx.filePath
- });
- }
-
- let filePath;
- let error;
-
- try {
- filePath = ModuleResolver.resolve(request, relativeTo);
- } catch (resolveError) {
- error = resolveError;
- /* istanbul ignore else */
- if (error && error.code === "MODULE_NOT_FOUND") {
- error.messageTemplate = "plugin-missing";
- error.messageData = {
- pluginName: request,
- resolvePluginsRelativeTo: ctx.pluginBasePath,
- importerName: ctx.name
- };
- }
- }
-
- if (filePath) {
- try {
- writeDebugLogForLoading(request, relativeTo, filePath);
-
- const startTime = Date.now();
- const pluginDefinition = require(filePath);
-
- debug(`Plugin ${filePath} loaded in: ${Date.now() - startTime}ms`);
-
- return new ConfigDependency({
- definition: normalizePlugin(pluginDefinition),
- filePath,
- id,
- importerName: ctx.name,
- importerPath: ctx.filePath
- });
- } catch (loadError) {
- error = loadError;
- }
- }
-
- debug("Failed to load plugin '%s' declared in '%s'.", name, ctx.name);
- error.message = `Failed to load plugin '${name}' declared in '${ctx.name}': ${error.message}`;
- return new ConfigDependency({
- error,
- id,
- importerName: ctx.name,
- importerPath: ctx.filePath
- });
- }
-
- /**
- * Take file expression processors as config array elements.
- * @param {Record} plugins The plugin definitions.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator} The config array elements of file expression processors.
- * @private
- */
- *_takeFileExtensionProcessors(plugins, ctx) {
- for (const pluginId of Object.keys(plugins)) {
- const processors =
- plugins[pluginId] &&
- plugins[pluginId].definition &&
- plugins[pluginId].definition.processors;
-
- if (!processors) {
- continue;
- }
-
- for (const processorId of Object.keys(processors)) {
- if (processorId.startsWith(".")) {
- yield* this._normalizeObjectConfigData(
- {
- files: [`*${processorId}`],
- processor: `${pluginId}/${processorId}`
- },
- {
- ...ctx,
- type: "implicit-processor",
- name: `${ctx.name}#processors["${pluginId}/${processorId}"]`
- }
- );
- }
- }
- }
- }
-}
-
-module.exports = { ConfigArrayFactory, createContext };
diff --git a/eslint/lib/cli-engine/config-array/config-array.js b/eslint/lib/cli-engine/config-array/config-array.js
deleted file mode 100644
index 42a7362..0000000
--- a/eslint/lib/cli-engine/config-array/config-array.js
+++ /dev/null
@@ -1,524 +0,0 @@
-/**
- * @fileoverview `ConfigArray` class.
- *
- * `ConfigArray` class expresses the full of a configuration. It has the entry
- * config file, base config files that were extended, loaded parsers, and loaded
- * plugins.
- *
- * `ConfigArray` class provides three properties and two methods.
- *
- * - `pluginEnvironments`
- * - `pluginProcessors`
- * - `pluginRules`
- * The `Map` objects that contain the members of all plugins that this
- * config array contains. Those map objects don't have mutation methods.
- * Those keys are the member ID such as `pluginId/memberName`.
- * - `isRoot()`
- * If `true` then this configuration has `root:true` property.
- * - `extractConfig(filePath)`
- * Extract the final configuration for a given file. This means merging
- * every config array element which that `criteria` property matched. The
- * `filePath` argument must be an absolute path.
- *
- * `ConfigArrayFactory` provides the loading logic of config files.
- *
- * @author Toru Nagashima
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const { ExtractedConfig } = require("./extracted-config");
-const { IgnorePattern } = require("./ignore-pattern");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-// Define types for VSCode IntelliSense.
-/** @typedef {import("../../shared/types").Environment} Environment */
-/** @typedef {import("../../shared/types").GlobalConf} GlobalConf */
-/** @typedef {import("../../shared/types").RuleConf} RuleConf */
-/** @typedef {import("../../shared/types").Rule} Rule */
-/** @typedef {import("../../shared/types").Plugin} Plugin */
-/** @typedef {import("../../shared/types").Processor} Processor */
-/** @typedef {import("./config-dependency").DependentParser} DependentParser */
-/** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */
-/** @typedef {import("./override-tester")["OverrideTester"]} OverrideTester */
-
-/**
- * @typedef {Object} ConfigArrayElement
- * @property {string} name The name of this config element.
- * @property {string} filePath The path to the source file of this config element.
- * @property {InstanceType|null} criteria The tester for the `files` and `excludedFiles` of this config element.
- * @property {Record|undefined} env The environment settings.
- * @property {Record|undefined} globals The global variable settings.
- * @property {IgnorePattern|undefined} ignorePattern The ignore patterns.
- * @property {boolean|undefined} noInlineConfig The flag that disables directive comments.
- * @property {DependentParser|undefined} parser The parser loader.
- * @property {Object|undefined} parserOptions The parser options.
- * @property {Record|undefined} plugins The plugin loaders.
- * @property {string|undefined} processor The processor name to refer plugin's processor.
- * @property {boolean|undefined} reportUnusedDisableDirectives The flag to report unused `eslint-disable` comments.
- * @property {boolean|undefined} root The flag to express root.
- * @property {Record|undefined} rules The rule settings
- * @property {Object|undefined} settings The shared settings.
- * @property {"config" | "ignore" | "implicit-processor"} type The element type.
- */
-
-/**
- * @typedef {Object} ConfigArrayInternalSlots
- * @property {Map} cache The cache to extract configs.
- * @property {ReadonlyMap|null} envMap The map from environment ID to environment definition.
- * @property {ReadonlyMap|null} processorMap The map from processor ID to environment definition.
- * @property {ReadonlyMap|null} ruleMap The map from rule ID to rule definition.
- */
-
-/** @type {WeakMap} */
-const internalSlotsMap = new class extends WeakMap {
- get(key) {
- let value = super.get(key);
-
- if (!value) {
- value = {
- cache: new Map(),
- envMap: null,
- processorMap: null,
- ruleMap: null
- };
- super.set(key, value);
- }
-
- return value;
- }
-}();
-
-/**
- * Get the indices which are matched to a given file.
- * @param {ConfigArrayElement[]} elements The elements.
- * @param {string} filePath The path to a target file.
- * @returns {number[]} The indices.
- */
-function getMatchedIndices(elements, filePath) {
- const indices = [];
-
- for (let i = elements.length - 1; i >= 0; --i) {
- const element = elements[i];
-
- if (!element.criteria || (filePath && element.criteria.test(filePath))) {
- indices.push(i);
- }
- }
-
- return indices;
-}
-
-/**
- * Check if a value is a non-null object.
- * @param {any} x The value to check.
- * @returns {boolean} `true` if the value is a non-null object.
- */
-function isNonNullObject(x) {
- return typeof x === "object" && x !== null;
-}
-
-/**
- * Merge two objects.
- *
- * Assign every property values of `y` to `x` if `x` doesn't have the property.
- * If `x`'s property value is an object, it does recursive.
- * @param {Object} target The destination to merge
- * @param {Object|undefined} source The source to merge.
- * @returns {void}
- */
-function mergeWithoutOverwrite(target, source) {
- if (!isNonNullObject(source)) {
- return;
- }
-
- for (const key of Object.keys(source)) {
- if (key === "__proto__") {
- continue;
- }
-
- if (isNonNullObject(target[key])) {
- mergeWithoutOverwrite(target[key], source[key]);
- } else if (target[key] === void 0) {
- if (isNonNullObject(source[key])) {
- target[key] = Array.isArray(source[key]) ? [] : {};
- mergeWithoutOverwrite(target[key], source[key]);
- } else if (source[key] !== void 0) {
- target[key] = source[key];
- }
- }
- }
-}
-
-/**
- * The error for plugin conflicts.
- */
-class PluginConflictError extends Error {
-
- /**
- * Initialize this error object.
- * @param {string} pluginId The plugin ID.
- * @param {{filePath:string, importerName:string}[]} plugins The resolved plugins.
- */
- constructor(pluginId, plugins) {
- super(`Plugin "${pluginId}" was conflicted between ${plugins.map(p => `"${p.importerName}"`).join(" and ")}.`);
- this.messageTemplate = "plugin-conflict";
- this.messageData = { pluginId, plugins };
- }
-}
-
-/**
- * Merge plugins.
- * `target`'s definition is prior to `source`'s.
- * @param {Record} target The destination to merge
- * @param {Record|undefined} source The source to merge.
- * @returns {void}
- */
-function mergePlugins(target, source) {
- if (!isNonNullObject(source)) {
- return;
- }
-
- for (const key of Object.keys(source)) {
- if (key === "__proto__") {
- continue;
- }
- const targetValue = target[key];
- const sourceValue = source[key];
-
- // Adopt the plugin which was found at first.
- if (targetValue === void 0) {
- if (sourceValue.error) {
- throw sourceValue.error;
- }
- target[key] = sourceValue;
- } else if (sourceValue.filePath !== targetValue.filePath) {
- throw new PluginConflictError(key, [
- {
- filePath: targetValue.filePath,
- importerName: targetValue.importerName
- },
- {
- filePath: sourceValue.filePath,
- importerName: sourceValue.importerName
- }
- ]);
- }
- }
-}
-
-/**
- * Merge rule configs.
- * `target`'s definition is prior to `source`'s.
- * @param {Record} target The destination to merge
- * @param {Record|undefined} source The source to merge.
- * @returns {void}
- */
-function mergeRuleConfigs(target, source) {
- if (!isNonNullObject(source)) {
- return;
- }
-
- for (const key of Object.keys(source)) {
- if (key === "__proto__") {
- continue;
- }
- const targetDef = target[key];
- const sourceDef = source[key];
-
- // Adopt the rule config which was found at first.
- if (targetDef === void 0) {
- if (Array.isArray(sourceDef)) {
- target[key] = [...sourceDef];
- } else {
- target[key] = [sourceDef];
- }
-
- /*
- * If the first found rule config is severity only and the current rule
- * config has options, merge the severity and the options.
- */
- } else if (
- targetDef.length === 1 &&
- Array.isArray(sourceDef) &&
- sourceDef.length >= 2
- ) {
- targetDef.push(...sourceDef.slice(1));
- }
- }
-}
-
-/**
- * Create the extracted config.
- * @param {ConfigArray} instance The config elements.
- * @param {number[]} indices The indices to use.
- * @returns {ExtractedConfig} The extracted config.
- */
-function createConfig(instance, indices) {
- const config = new ExtractedConfig();
- const ignorePatterns = [];
-
- // Merge elements.
- for (const index of indices) {
- const element = instance[index];
-
- // Adopt the parser which was found at first.
- if (!config.parser && element.parser) {
- if (element.parser.error) {
- throw element.parser.error;
- }
- config.parser = element.parser;
- }
-
- // Adopt the processor which was found at first.
- if (!config.processor && element.processor) {
- config.processor = element.processor;
- }
-
- // Adopt the noInlineConfig which was found at first.
- if (config.noInlineConfig === void 0 && element.noInlineConfig !== void 0) {
- config.noInlineConfig = element.noInlineConfig;
- config.configNameOfNoInlineConfig = element.name;
- }
-
- // Adopt the reportUnusedDisableDirectives which was found at first.
- if (config.reportUnusedDisableDirectives === void 0 && element.reportUnusedDisableDirectives !== void 0) {
- config.reportUnusedDisableDirectives = element.reportUnusedDisableDirectives;
- }
-
- // Collect ignorePatterns
- if (element.ignorePattern) {
- ignorePatterns.push(element.ignorePattern);
- }
-
- // Merge others.
- mergeWithoutOverwrite(config.env, element.env);
- mergeWithoutOverwrite(config.globals, element.globals);
- mergeWithoutOverwrite(config.parserOptions, element.parserOptions);
- mergeWithoutOverwrite(config.settings, element.settings);
- mergePlugins(config.plugins, element.plugins);
- mergeRuleConfigs(config.rules, element.rules);
- }
-
- // Create the predicate function for ignore patterns.
- if (ignorePatterns.length > 0) {
- config.ignores = IgnorePattern.createIgnore(ignorePatterns.reverse());
- }
-
- return config;
-}
-
-/**
- * Collect definitions.
- * @template T, U
- * @param {string} pluginId The plugin ID for prefix.
- * @param {Record} defs The definitions to collect.
- * @param {Map} map The map to output.
- * @param {function(T): U} [normalize] The normalize function for each value.
- * @returns {void}
- */
-function collect(pluginId, defs, map, normalize) {
- if (defs) {
- const prefix = pluginId && `${pluginId}/`;
-
- for (const [key, value] of Object.entries(defs)) {
- map.set(
- `${prefix}${key}`,
- normalize ? normalize(value) : value
- );
- }
- }
-}
-
-/**
- * Normalize a rule definition.
- * @param {Function|Rule} rule The rule definition to normalize.
- * @returns {Rule} The normalized rule definition.
- */
-function normalizePluginRule(rule) {
- return typeof rule === "function" ? { create: rule } : rule;
-}
-
-/**
- * Delete the mutation methods from a given map.
- * @param {Map} map The map object to delete.
- * @returns {void}
- */
-function deleteMutationMethods(map) {
- Object.defineProperties(map, {
- clear: { configurable: true, value: void 0 },
- delete: { configurable: true, value: void 0 },
- set: { configurable: true, value: void 0 }
- });
-}
-
-/**
- * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
- * @param {ConfigArrayElement[]} elements The config elements.
- * @param {ConfigArrayInternalSlots} slots The internal slots.
- * @returns {void}
- */
-function initPluginMemberMaps(elements, slots) {
- const processed = new Set();
-
- slots.envMap = new Map();
- slots.processorMap = new Map();
- slots.ruleMap = new Map();
-
- for (const element of elements) {
- if (!element.plugins) {
- continue;
- }
-
- for (const [pluginId, value] of Object.entries(element.plugins)) {
- const plugin = value.definition;
-
- if (!plugin || processed.has(pluginId)) {
- continue;
- }
- processed.add(pluginId);
-
- collect(pluginId, plugin.environments, slots.envMap);
- collect(pluginId, plugin.processors, slots.processorMap);
- collect(pluginId, plugin.rules, slots.ruleMap, normalizePluginRule);
- }
- }
-
- deleteMutationMethods(slots.envMap);
- deleteMutationMethods(slots.processorMap);
- deleteMutationMethods(slots.ruleMap);
-}
-
-/**
- * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
- * @param {ConfigArray} instance The config elements.
- * @returns {ConfigArrayInternalSlots} The extracted config.
- */
-function ensurePluginMemberMaps(instance) {
- const slots = internalSlotsMap.get(instance);
-
- if (!slots.ruleMap) {
- initPluginMemberMaps(instance, slots);
- }
-
- return slots;
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-/**
- * The Config Array.
- *
- * `ConfigArray` instance contains all settings, parsers, and plugins.
- * You need to call `ConfigArray#extractConfig(filePath)` method in order to
- * extract, merge and get only the config data which is related to an arbitrary
- * file.
- * @extends {Array}
- */
-class ConfigArray extends Array {
-
- /**
- * Get the plugin environments.
- * The returned map cannot be mutated.
- * @type {ReadonlyMap} The plugin environments.
- */
- get pluginEnvironments() {
- return ensurePluginMemberMaps(this).envMap;
- }
-
- /**
- * Get the plugin processors.
- * The returned map cannot be mutated.
- * @type {ReadonlyMap} The plugin processors.
- */
- get pluginProcessors() {
- return ensurePluginMemberMaps(this).processorMap;
- }
-
- /**
- * Get the plugin rules.
- * The returned map cannot be mutated.
- * @returns {ReadonlyMap} The plugin rules.
- */
- get pluginRules() {
- return ensurePluginMemberMaps(this).ruleMap;
- }
-
- /**
- * Check if this config has `root` flag.
- * @returns {boolean} `true` if this config array is root.
- */
- isRoot() {
- for (let i = this.length - 1; i >= 0; --i) {
- const root = this[i].root;
-
- if (typeof root === "boolean") {
- return root;
- }
- }
- return false;
- }
-
- /**
- * Extract the config data which is related to a given file.
- * @param {string} filePath The absolute path to the target file.
- * @returns {ExtractedConfig} The extracted config data.
- */
- extractConfig(filePath) {
- const { cache } = internalSlotsMap.get(this);
- const indices = getMatchedIndices(this, filePath);
- const cacheKey = indices.join(",");
-
- if (!cache.has(cacheKey)) {
- cache.set(cacheKey, createConfig(this, indices));
- }
-
- return cache.get(cacheKey);
- }
-
- /**
- * Check if a given path is an additional lint target.
- * @param {string} filePath The absolute path to the target file.
- * @returns {boolean} `true` if the file is an additional lint target.
- */
- isAdditionalTargetPath(filePath) {
- for (const { criteria, type } of this) {
- if (
- type === "config" &&
- criteria &&
- !criteria.endsWithWildcard &&
- criteria.test(filePath)
- ) {
- return true;
- }
- }
- return false;
- }
-}
-
-const exportObject = {
- ConfigArray,
-
- /**
- * Get the used extracted configs.
- * CLIEngine will use this method to collect used deprecated rules.
- * @param {ConfigArray} instance The config array object to get.
- * @returns {ExtractedConfig[]} The used extracted configs.
- * @private
- */
- getUsedExtractedConfigs(instance) {
- const { cache } = internalSlotsMap.get(instance);
-
- return Array.from(cache.values());
- }
-};
-
-module.exports = exportObject;
diff --git a/eslint/lib/cli-engine/config-array/config-dependency.js b/eslint/lib/cli-engine/config-array/config-dependency.js
deleted file mode 100644
index 0d5f6f7..0000000
--- a/eslint/lib/cli-engine/config-array/config-dependency.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * @fileoverview `ConfigDependency` class.
- *
- * `ConfigDependency` class expresses a loaded parser or plugin.
- *
- * If the parser or plugin was loaded successfully, it has `definition` property
- * and `filePath` property. Otherwise, it has `error` property.
- *
- * When `JSON.stringify()` converted a `ConfigDependency` object to a JSON, it
- * omits `definition` property.
- *
- * `ConfigArrayFactory` creates `ConfigDependency` objects when it loads parsers
- * or plugins.
- *
- * @author Toru Nagashima
- */
-"use strict";
-
-const util = require("util");
-
-/**
- * The class is to store parsers or plugins.
- * This class hides the loaded object from `JSON.stringify()` and `console.log`.
- * @template T
- */
-class ConfigDependency {
-
- /**
- * Initialize this instance.
- * @param {Object} data The dependency data.
- * @param {T} [data.definition] The dependency if the loading succeeded.
- * @param {Error} [data.error] The error object if the loading failed.
- * @param {string} [data.filePath] The actual path to the dependency if the loading succeeded.
- * @param {string} data.id The ID of this dependency.
- * @param {string} data.importerName The name of the config file which loads this dependency.
- * @param {string} data.importerPath The path to the config file which loads this dependency.
- */
- constructor({
- definition = null,
- error = null,
- filePath = null,
- id,
- importerName,
- importerPath
- }) {
-
- /**
- * The loaded dependency if the loading succeeded.
- * @type {T|null}
- */
- this.definition = definition;
-
- /**
- * The error object if the loading failed.
- * @type {Error|null}
- */
- this.error = error;
-
- /**
- * The loaded dependency if the loading succeeded.
- * @type {string|null}
- */
- this.filePath = filePath;
-
- /**
- * The ID of this dependency.
- * @type {string}
- */
- this.id = id;
-
- /**
- * The name of the config file which loads this dependency.
- * @type {string}
- */
- this.importerName = importerName;
-
- /**
- * The path to the config file which loads this dependency.
- * @type {string}
- */
- this.importerPath = importerPath;
- }
-
- // eslint-disable-next-line jsdoc/require-description
- /**
- * @returns {Object} a JSON compatible object.
- */
- toJSON() {
- const obj = this[util.inspect.custom]();
-
- // Display `error.message` (`Error#message` is unenumerable).
- if (obj.error instanceof Error) {
- obj.error = { ...obj.error, message: obj.error.message };
- }
-
- return obj;
- }
-
- // eslint-disable-next-line jsdoc/require-description
- /**
- * @returns {Object} an object to display by `console.log()`.
- */
- [util.inspect.custom]() {
- const {
- definition: _ignore, // eslint-disable-line no-unused-vars
- ...obj
- } = this;
-
- return obj;
- }
-}
-
-/** @typedef {ConfigDependency} DependentParser */
-/** @typedef {ConfigDependency} DependentPlugin */
-
-module.exports = { ConfigDependency };
diff --git a/eslint/lib/cli-engine/config-array/extracted-config.js b/eslint/lib/cli-engine/config-array/extracted-config.js
deleted file mode 100644
index b27d6ff..0000000
--- a/eslint/lib/cli-engine/config-array/extracted-config.js
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- * @fileoverview `ExtractedConfig` class.
- *
- * `ExtractedConfig` class expresses a final configuration for a specific file.
- *
- * It provides one method.
- *
- * - `toCompatibleObjectAsConfigFileContent()`
- * Convert this configuration to the compatible object as the content of
- * config files. It converts the loaded parser and plugins to strings.
- * `CLIEngine#getConfigForFile(filePath)` method uses this method.
- *
- * `ConfigArray#extractConfig(filePath)` creates a `ExtractedConfig` instance.
- *
- * @author Toru Nagashima
- */
-"use strict";
-
-const { IgnorePattern } = require("./ignore-pattern");
-
-// For VSCode intellisense
-/** @typedef {import("../../shared/types").ConfigData} ConfigData */
-/** @typedef {import("../../shared/types").GlobalConf} GlobalConf */
-/** @typedef {import("../../shared/types").SeverityConf} SeverityConf */
-/** @typedef {import("./config-dependency").DependentParser} DependentParser */
-/** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */
-
-/**
- * Check if `xs` starts with `ys`.
- * @template T
- * @param {T[]} xs The array to check.
- * @param {T[]} ys The array that may be the first part of `xs`.
- * @returns {boolean} `true` if `xs` starts with `ys`.
- */
-function startsWith(xs, ys) {
- return xs.length >= ys.length && ys.every((y, i) => y === xs[i]);
-}
-
-/**
- * The class for extracted config data.
- */
-class ExtractedConfig {
- constructor() {
-
- /**
- * The config name what `noInlineConfig` setting came from.
- * @type {string}
- */
- this.configNameOfNoInlineConfig = "";
-
- /**
- * Environments.
- * @type {Record}
- */
- this.env = {};
-
- /**
- * Global variables.
- * @type {Record}
- */
- this.globals = {};
-
- /**
- * The glob patterns that ignore to lint.
- * @type {(((filePath:string, dot?:boolean) => boolean) & { basePath:string; patterns:string[] }) | undefined}
- */
- this.ignores = void 0;
-
- /**
- * The flag that disables directive comments.
- * @type {boolean|undefined}
- */
- this.noInlineConfig = void 0;
-
- /**
- * Parser definition.
- * @type {DependentParser|null}
- */
- this.parser = null;
-
- /**
- * Options for the parser.
- * @type {Object}
- */
- this.parserOptions = {};
-
- /**
- * Plugin definitions.
- * @type {Record}
- */
- this.plugins = {};
-
- /**
- * Processor ID.
- * @type {string|null}
- */
- this.processor = null;
-
- /**
- * The flag that reports unused `eslint-disable` directive comments.
- * @type {boolean|undefined}
- */
- this.reportUnusedDisableDirectives = void 0;
-
- /**
- * Rule settings.
- * @type {Record}
- */
- this.rules = {};
-
- /**
- * Shared settings.
- * @type {Object}
- */
- this.settings = {};
- }
-
- /**
- * Convert this config to the compatible object as a config file content.
- * @returns {ConfigData} The converted object.
- */
- toCompatibleObjectAsConfigFileContent() {
- const {
- /* eslint-disable no-unused-vars */
- configNameOfNoInlineConfig: _ignore1,
- processor: _ignore2,
- /* eslint-enable no-unused-vars */
- ignores,
- ...config
- } = this;
-
- config.parser = config.parser && config.parser.filePath;
- config.plugins = Object.keys(config.plugins).filter(Boolean).reverse();
- config.ignorePatterns = ignores ? ignores.patterns : [];
-
- // Strip the default patterns from `ignorePatterns`.
- if (startsWith(config.ignorePatterns, IgnorePattern.DefaultPatterns)) {
- config.ignorePatterns =
- config.ignorePatterns.slice(IgnorePattern.DefaultPatterns.length);
- }
-
- return config;
- }
-}
-
-module.exports = { ExtractedConfig };
diff --git a/eslint/lib/cli-engine/config-array/ignore-pattern.js b/eslint/lib/cli-engine/config-array/ignore-pattern.js
deleted file mode 100644
index 6eaec42..0000000
--- a/eslint/lib/cli-engine/config-array/ignore-pattern.js
+++ /dev/null
@@ -1,237 +0,0 @@
-/**
- * @fileoverview `IgnorePattern` class.
- *
- * `IgnorePattern` class has the set of glob patterns and the base path.
- *
- * It provides two static methods.
- *
- * - `IgnorePattern.createDefaultIgnore(cwd)`
- * Create the default predicate function.
- * - `IgnorePattern.createIgnore(ignorePatterns)`
- * Create the predicate function from multiple `IgnorePattern` objects.
- *
- * It provides two properties and a method.
- *
- * - `patterns`
- * The glob patterns that ignore to lint.
- * - `basePath`
- * The base path of the glob patterns. If absolute paths existed in the
- * glob patterns, those are handled as relative paths to the base path.
- * - `getPatternsRelativeTo(basePath)`
- * Get `patterns` as modified for a given base path. It modifies the
- * absolute paths in the patterns as prepending the difference of two base
- * paths.
- *
- * `ConfigArrayFactory` creates `IgnorePattern` objects when it processes
- * `ignorePatterns` properties.
- *
- * @author Toru Nagashima
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("assert");
-const path = require("path");
-const ignore = require("ignore");
-const debug = require("debug")("eslint:ignore-pattern");
-
-/** @typedef {ReturnType} Ignore */
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Get the path to the common ancestor directory of given paths.
- * @param {string[]} sourcePaths The paths to calculate the common ancestor.
- * @returns {string} The path to the common ancestor directory.
- */
-function getCommonAncestorPath(sourcePaths) {
- let result = sourcePaths[0];
-
- for (let i = 1; i < sourcePaths.length; ++i) {
- const a = result;
- const b = sourcePaths[i];
-
- // Set the shorter one (it's the common ancestor if one includes the other).
- result = a.length < b.length ? a : b;
-
- // Set the common ancestor.
- for (let j = 0, lastSepPos = 0; j < a.length && j < b.length; ++j) {
- if (a[j] !== b[j]) {
- result = a.slice(0, lastSepPos);
- break;
- }
- if (a[j] === path.sep) {
- lastSepPos = j;
- }
- }
- }
-
- let resolvedResult = result || path.sep;
-
- // if Windows common ancestor is root of drive must have trailing slash to be absolute.
- if (resolvedResult && resolvedResult.endsWith(":") && process.platform === "win32") {
- resolvedResult += path.sep;
- }
- return resolvedResult;
-}
-
-/**
- * Make relative path.
- * @param {string} from The source path to get relative path.
- * @param {string} to The destination path to get relative path.
- * @returns {string} The relative path.
- */
-function relative(from, to) {
- const relPath = path.relative(from, to);
-
- if (path.sep === "/") {
- return relPath;
- }
- return relPath.split(path.sep).join("/");
-}
-
-/**
- * Get the trailing slash if existed.
- * @param {string} filePath The path to check.
- * @returns {string} The trailing slash if existed.
- */
-function dirSuffix(filePath) {
- const isDir = (
- filePath.endsWith(path.sep) ||
- (process.platform === "win32" && filePath.endsWith("/"))
- );
-
- return isDir ? "/" : "";
-}
-
-const DefaultPatterns = Object.freeze(["/**/node_modules/*"]);
-const DotPatterns = Object.freeze([".*", "!.eslintrc.*", "!../"]);
-
-//------------------------------------------------------------------------------
-// Public
-//------------------------------------------------------------------------------
-
-class IgnorePattern {
-
- /**
- * The default patterns.
- * @type {string[]}
- */
- static get DefaultPatterns() {
- return DefaultPatterns;
- }
-
- /**
- * Create the default predicate function.
- * @param {string} cwd The current working directory.
- * @returns {((filePath:string, dot:boolean) => boolean) & {basePath:string; patterns:string[]}}
- * The preficate function.
- * The first argument is an absolute path that is checked.
- * The second argument is the flag to not ignore dotfiles.
- * If the predicate function returned `true`, it means the path should be ignored.
- */
- static createDefaultIgnore(cwd) {
- return this.createIgnore([new IgnorePattern(DefaultPatterns, cwd)]);
- }
-
- /**
- * Create the predicate function from multiple `IgnorePattern` objects.
- * @param {IgnorePattern[]} ignorePatterns The list of ignore patterns.
- * @returns {((filePath:string, dot?:boolean) => boolean) & {basePath:string; patterns:string[]}}
- * The preficate function.
- * The first argument is an absolute path that is checked.
- * The second argument is the flag to not ignore dotfiles.
- * If the predicate function returned `true`, it means the path should be ignored.
- */
- static createIgnore(ignorePatterns) {
- debug("Create with: %o", ignorePatterns);
-
- const basePath = getCommonAncestorPath(ignorePatterns.map(p => p.basePath));
- const patterns = [].concat(
- ...ignorePatterns.map(p => p.getPatternsRelativeTo(basePath))
- );
- const ig = ignore().add([...DotPatterns, ...patterns]);
- const dotIg = ignore().add(patterns);
-
- debug(" processed: %o", { basePath, patterns });
-
- return Object.assign(
- (filePath, dot = false) => {
- assert(path.isAbsolute(filePath), "'filePath' should be an absolute path.");
- const relPathRaw = relative(basePath, filePath);
- const relPath = relPathRaw && (relPathRaw + dirSuffix(filePath));
- const adoptedIg = dot ? dotIg : ig;
- const result = relPath !== "" && adoptedIg.ignores(relPath);
-
- debug("Check", { filePath, dot, relativePath: relPath, result });
- return result;
- },
- { basePath, patterns }
- );
- }
-
- /**
- * Initialize a new `IgnorePattern` instance.
- * @param {string[]} patterns The glob patterns that ignore to lint.
- * @param {string} basePath The base path of `patterns`.
- */
- constructor(patterns, basePath) {
- assert(path.isAbsolute(basePath), "'basePath' should be an absolute path.");
-
- /**
- * The glob patterns that ignore to lint.
- * @type {string[]}
- */
- this.patterns = patterns;
-
- /**
- * The base path of `patterns`.
- * @type {string}
- */
- this.basePath = basePath;
-
- /**
- * If `true` then patterns which don't start with `/` will match the paths to the outside of `basePath`. Defaults to `false`.
- *
- * It's set `true` for `.eslintignore`, `package.json`, and `--ignore-path` for backward compatibility.
- * It's `false` as-is for `ignorePatterns` property in config files.
- * @type {boolean}
- */
- this.loose = false;
- }
-
- /**
- * Get `patterns` as modified for a given base path. It modifies the
- * absolute paths in the patterns as prepending the difference of two base
- * paths.
- * @param {string} newBasePath The base path.
- * @returns {string[]} Modifired patterns.
- */
- getPatternsRelativeTo(newBasePath) {
- assert(path.isAbsolute(newBasePath), "'newBasePath' should be an absolute path.");
- const { basePath, loose, patterns } = this;
-
- if (newBasePath === basePath) {
- return patterns;
- }
- const prefix = `/${relative(newBasePath, basePath)}`;
-
- return patterns.map(pattern => {
- const negative = pattern.startsWith("!");
- const head = negative ? "!" : "";
- const body = negative ? pattern.slice(1) : pattern;
-
- if (body.startsWith("/") || body.startsWith("../")) {
- return `${head}${prefix}${body}`;
- }
- return loose ? pattern : `${head}${prefix}/**/${body}`;
- });
- }
-}
-
-module.exports = { IgnorePattern };
diff --git a/eslint/lib/cli-engine/config-array/index.js b/eslint/lib/cli-engine/config-array/index.js
deleted file mode 100644
index 928d76c..0000000
--- a/eslint/lib/cli-engine/config-array/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * @fileoverview `ConfigArray` class.
- * @author Toru Nagashima
- */
-"use strict";
-
-const { ConfigArray, getUsedExtractedConfigs } = require("./config-array");
-const { ConfigDependency } = require("./config-dependency");
-const { ExtractedConfig } = require("./extracted-config");
-const { IgnorePattern } = require("./ignore-pattern");
-const { OverrideTester } = require("./override-tester");
-
-module.exports = {
- ConfigArray,
- ConfigDependency,
- ExtractedConfig,
- IgnorePattern,
- OverrideTester,
- getUsedExtractedConfigs
-};
diff --git a/eslint/lib/cli-engine/config-array/override-tester.js b/eslint/lib/cli-engine/config-array/override-tester.js
deleted file mode 100644
index e7ba120..0000000
--- a/eslint/lib/cli-engine/config-array/override-tester.js
+++ /dev/null
@@ -1,223 +0,0 @@
-/**
- * @fileoverview `OverrideTester` class.
- *
- * `OverrideTester` class handles `files` property and `excludedFiles` property
- * of `overrides` config.
- *
- * It provides one method.
- *
- * - `test(filePath)`
- * Test if a file path matches the pair of `files` property and
- * `excludedFiles` property. The `filePath` argument must be an absolute
- * path.
- *
- * `ConfigArrayFactory` creates `OverrideTester` objects when it processes
- * `overrides` properties.
- *
- * @author Toru Nagashima
- */
-"use strict";
-
-const assert = require("assert");
-const path = require("path");
-const util = require("util");
-const { Minimatch } = require("minimatch");
-const minimatchOpts = { dot: true, matchBase: true };
-
-/**
- * @typedef {Object} Pattern
- * @property {InstanceType[] | null} includes The positive matchers.
- * @property {InstanceType[] | null} excludes The negative matchers.
- */
-
-/**
- * Normalize a given pattern to an array.
- * @param {string|string[]|undefined} patterns A glob pattern or an array of glob patterns.
- * @returns {string[]|null} Normalized patterns.
- * @private
- */
-function normalizePatterns(patterns) {
- if (Array.isArray(patterns)) {
- return patterns.filter(Boolean);
- }
- if (typeof patterns === "string" && patterns) {
- return [patterns];
- }
- return [];
-}
-
-/**
- * Create the matchers of given patterns.
- * @param {string[]} patterns The patterns.
- * @returns {InstanceType[] | null} The matchers.
- */
-function toMatcher(patterns) {
- if (patterns.length === 0) {
- return null;
- }
- return patterns.map(pattern => {
- if (/^\.[/\\]/u.test(pattern)) {
- return new Minimatch(
- pattern.slice(2),
-
- // `./*.js` should not match with `subdir/foo.js`
- { ...minimatchOpts, matchBase: false }
- );
- }
- return new Minimatch(pattern, minimatchOpts);
- });
-}
-
-/**
- * Convert a given matcher to string.
- * @param {Pattern} matchers The matchers.
- * @returns {string} The string expression of the matcher.
- */
-function patternToJson({ includes, excludes }) {
- return {
- includes: includes && includes.map(m => m.pattern),
- excludes: excludes && excludes.map(m => m.pattern)
- };
-}
-
-/**
- * The class to test given paths are matched by the patterns.
- */
-class OverrideTester {
-
- /**
- * Create a tester with given criteria.
- * If there are no criteria, returns `null`.
- * @param {string|string[]} files The glob patterns for included files.
- * @param {string|string[]} excludedFiles The glob patterns for excluded files.
- * @param {string} basePath The path to the base directory to test paths.
- * @returns {OverrideTester|null} The created instance or `null`.
- */
- static create(files, excludedFiles, basePath) {
- const includePatterns = normalizePatterns(files);
- const excludePatterns = normalizePatterns(excludedFiles);
- let endsWithWildcard = false;
-
- if (includePatterns.length === 0) {
- return null;
- }
-
- // Rejects absolute paths or relative paths to parents.
- for (const pattern of includePatterns) {
- if (path.isAbsolute(pattern) || pattern.includes("..")) {
- throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`);
- }
- if (pattern.endsWith("*")) {
- endsWithWildcard = true;
- }
- }
- for (const pattern of excludePatterns) {
- if (path.isAbsolute(pattern) || pattern.includes("..")) {
- throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`);
- }
- }
-
- const includes = toMatcher(includePatterns);
- const excludes = toMatcher(excludePatterns);
-
- return new OverrideTester(
- [{ includes, excludes }],
- basePath,
- endsWithWildcard
- );
- }
-
- /**
- * Combine two testers by logical and.
- * If either of the testers was `null`, returns the other tester.
- * The `basePath` property of the two must be the same value.
- * @param {OverrideTester|null} a A tester.
- * @param {OverrideTester|null} b Another tester.
- * @returns {OverrideTester|null} Combined tester.
- */
- static and(a, b) {
- if (!b) {
- return a && new OverrideTester(
- a.patterns,
- a.basePath,
- a.endsWithWildcard
- );
- }
- if (!a) {
- return new OverrideTester(
- b.patterns,
- b.basePath,
- b.endsWithWildcard
- );
- }
-
- assert.strictEqual(a.basePath, b.basePath);
- return new OverrideTester(
- a.patterns.concat(b.patterns),
- a.basePath,
- a.endsWithWildcard || b.endsWithWildcard
- );
- }
-
- /**
- * Initialize this instance.
- * @param {Pattern[]} patterns The matchers.
- * @param {string} basePath The base path.
- * @param {boolean} endsWithWildcard If `true` then a pattern ends with `*`.
- */
- constructor(patterns, basePath, endsWithWildcard = false) {
-
- /** @type {Pattern[]} */
- this.patterns = patterns;
-
- /** @type {string} */
- this.basePath = basePath;
-
- /** @type {boolean} */
- this.endsWithWildcard = endsWithWildcard;
- }
-
- /**
- * Test if a given path is matched or not.
- * @param {string} filePath The absolute path to the target file.
- * @returns {boolean} `true` if the path was matched.
- */
- test(filePath) {
- if (typeof filePath !== "string" || !path.isAbsolute(filePath)) {
- throw new Error(`'filePath' should be an absolute path, but got ${filePath}.`);
- }
- const relativePath = path.relative(this.basePath, filePath);
-
- return this.patterns.every(({ includes, excludes }) => (
- (!includes || includes.some(m => m.match(relativePath))) &&
- (!excludes || !excludes.some(m => m.match(relativePath)))
- ));
- }
-
- // eslint-disable-next-line jsdoc/require-description
- /**
- * @returns {Object} a JSON compatible object.
- */
- toJSON() {
- if (this.patterns.length === 1) {
- return {
- ...patternToJson(this.patterns[0]),
- basePath: this.basePath
- };
- }
- return {
- AND: this.patterns.map(patternToJson),
- basePath: this.basePath
- };
- }
-
- // eslint-disable-next-line jsdoc/require-description
- /**
- * @returns {Object} an object to display by `console.log()`.
- */
- [util.inspect.custom]() {
- return this.toJSON();
- }
-}
-
-module.exports = { OverrideTester };
diff --git a/eslint/lib/cli-engine/file-enumerator.js b/eslint/lib/cli-engine/file-enumerator.js
index 7c433d3..c2961d7 100644
--- a/eslint/lib/cli-engine/file-enumerator.js
+++ b/eslint/lib/cli-engine/file-enumerator.js
@@ -40,8 +40,13 @@ const getGlobParent = require("glob-parent");
const isGlob = require("is-glob");
const { escapeRegExp } = require("lodash");
const { Minimatch } = require("minimatch");
-const { IgnorePattern } = require("./config-array");
-const { CascadingConfigArrayFactory } = require("./cascading-config-array-factory");
+
+const {
+ Legacy: {
+ IgnorePattern,
+ CascadingConfigArrayFactory
+ }
+} = require("@eslint/eslintrc");
const debug = require("debug")("eslint:file-enumerator");
//------------------------------------------------------------------------------
@@ -208,7 +213,11 @@ class FileEnumerator {
*/
constructor({
cwd = process.cwd(),
- configArrayFactory = new CascadingConfigArrayFactory({ cwd }),
+ configArrayFactory = new CascadingConfigArrayFactory({
+ cwd,
+ eslintRecommendedPath: path.resolve(__dirname, "../../conf/eslint-recommended.js"),
+ eslintAllPath: path.resolve(__dirname, "../../conf/eslint-all.js")
+ }),
extensions = null,
globInputPaths = true,
errorOnUnmatchedPattern = true,
diff --git a/eslint/lib/cli-engine/formatters/checkstyle.js b/eslint/lib/cli-engine/formatters/checkstyle.js
index ba4d1b5..f19b6fc 100644
--- a/eslint/lib/cli-engine/formatters/checkstyle.js
+++ b/eslint/lib/cli-engine/formatters/checkstyle.js
@@ -42,8 +42,8 @@ module.exports = function(results) {
messages.forEach(message => {
output += [
- ``
diff --git a/eslint/lib/eslint/eslint.js b/eslint/lib/eslint/eslint.js
index d195aab..a51ffbf 100644
--- a/eslint/lib/eslint/eslint.js
+++ b/eslint/lib/eslint/eslint.js
@@ -15,7 +15,13 @@ const fs = require("fs");
const { promisify } = require("util");
const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine");
const BuiltinRules = require("../rules");
-const { getRuleSeverity } = require("../shared/config-ops");
+const {
+ Legacy: {
+ ConfigOps: {
+ getRuleSeverity
+ }
+ }
+} = require("@eslint/eslintrc");
const { version } = require("../../package.json");
//------------------------------------------------------------------------------
diff --git a/eslint/lib/init/autoconfig.js b/eslint/lib/init/autoconfig.js
index 2b0aa12..0ace177 100644
--- a/eslint/lib/init/autoconfig.js
+++ b/eslint/lib/init/autoconfig.js
@@ -11,7 +11,7 @@
const lodash = require("lodash"),
recConfig = require("../../conf/eslint-recommended"),
- ConfigOps = require("../shared/config-ops"),
+ ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
{ Linter } = require("../linter"),
configRule = require("./config-rule");
diff --git a/eslint/lib/init/config-initializer.js b/eslint/lib/init/config-initializer.js
index dce3994..f7d4cc7 100644
--- a/eslint/lib/init/config-initializer.js
+++ b/eslint/lib/init/config-initializer.js
@@ -12,14 +12,14 @@
const util = require("util"),
path = require("path"),
- inquirer = require("inquirer"),
+ enquirer = require("enquirer"),
ProgressBar = require("progress"),
semver = require("semver"),
espree = require("espree"),
recConfig = require("../../conf/eslint-recommended"),
- ConfigOps = require("../shared/config-ops"),
+ ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
log = require("../shared/logging"),
- naming = require("../shared/naming"),
+ naming = require("@eslint/eslintrc/lib/shared/naming"),
ModuleResolver = require("../shared/relative-module-resolver"),
autoconfig = require("./autoconfig.js"),
ConfigFile = require("./config-file"),
@@ -146,7 +146,7 @@ function getModulesList(config, installESLint) {
*
* Note: This clones the config object and returns a new config to avoid mutating
* the original config parameter.
- * @param {Object} answers answers received from inquirer
+ * @param {Object} answers answers received from enquirer
* @param {Object} config config object
* @returns {Object} config object with configured rules
*/
@@ -253,7 +253,7 @@ function configureRules(answers, config) {
/**
* process user's answers and create config object
- * @param {Object} answers answers received from inquirer
+ * @param {Object} answers answers received from enquirer
* @returns {Object} config object
*/
function processAnswers(answers) {
@@ -265,7 +265,7 @@ function processAnswers(answers) {
};
config.parserOptions.ecmaVersion = espree.latestEcmaVersion;
- config.env.es2020 = true;
+ config.env.es2021 = true;
// set the module type
if (answers.moduleType === "esm") {
@@ -321,7 +321,6 @@ function processAnswers(answers) {
}
}
if (answers.typescript && config.extends.includes("eslint:recommended")) {
- config.extends.push("plugin:@typescript-eslint/eslint-recommended");
config.extends.push("plugin:@typescript-eslint/recommended");
}
@@ -409,7 +408,7 @@ function installModules(modules) {
npmUtils.installSyncSaveDev(modules);
}
-/* istanbul ignore next: no need to test inquirer */
+/* istanbul ignore next: no need to test enquirer */
/**
* Ask user to install modules.
* @param {string[]} modules Array of modules to be installed.
@@ -425,14 +424,19 @@ function askInstallModules(modules, packageJsonExists) {
log.info("The config that you've selected requires the following dependencies:\n");
log.info(modules.join(" "));
- return inquirer.prompt([
+ return enquirer.prompt([
{
- type: "confirm",
+ type: "toggle",
name: "executeInstallation",
message: "Would you like to install them now with npm?",
- default: true,
- when() {
- return modules.length && packageJsonExists;
+ enabled: "Yes",
+ disabled: "No",
+ initial: 1,
+ skip() {
+ return !(modules.length && packageJsonExists);
+ },
+ result(input) {
+ return this.skipped ? null : input;
}
}
]).then(({ executeInstallation }) => {
@@ -442,114 +446,124 @@ function askInstallModules(modules, packageJsonExists) {
});
}
-/* istanbul ignore next: no need to test inquirer */
+/* istanbul ignore next: no need to test enquirer */
/**
* Ask use a few questions on command prompt
* @returns {Promise} The promise with the result of the prompt
*/
function promptUser() {
- return inquirer.prompt([
+ return enquirer.prompt([
{
- type: "list",
+ type: "select",
name: "purpose",
message: "How would you like to use ESLint?",
- default: "problems",
+
+ // The returned number matches the name value of nth in the choices array.
+ initial: 1,
choices: [
- { name: "To check syntax only", value: "syntax" },
- { name: "To check syntax and find problems", value: "problems" },
- { name: "To check syntax, find problems, and enforce code style", value: "style" }
+ { message: "To check syntax only", name: "syntax" },
+ { message: "To check syntax and find problems", name: "problems" },
+ { message: "To check syntax, find problems, and enforce code style", name: "style" }
]
},
{
- type: "list",
+ type: "select",
name: "moduleType",
message: "What type of modules does your project use?",
- default: "esm",
+ initial: 0,
choices: [
- { name: "JavaScript modules (import/export)", value: "esm" },
- { name: "CommonJS (require/exports)", value: "commonjs" },
- { name: "None of these", value: "none" }
+ { message: "JavaScript modules (import/export)", name: "esm" },
+ { message: "CommonJS (require/exports)", name: "commonjs" },
+ { message: "None of these", name: "none" }
]
},
{
- type: "list",
+ type: "select",
name: "framework",
message: "Which framework does your project use?",
- default: "react",
+ initial: 0,
choices: [
- { name: "React", value: "react" },
- { name: "Vue.js", value: "vue" },
- { name: "None of these", value: "none" }
+ { message: "React", name: "react" },
+ { message: "Vue.js", name: "vue" },
+ { message: "None of these", name: "none" }
]
},
{
- type: "confirm",
+ type: "toggle",
name: "typescript",
message: "Does your project use TypeScript?",
- default: false
+ enabled: "Yes",
+ disabled: "No",
+ initial: 0
},
{
- type: "checkbox",
+ type: "multiselect",
name: "env",
message: "Where does your code run?",
- default: ["browser"],
+ hint: "(Press to select, to toggle all, to invert selection)",
+ initial: 0,
choices: [
- { name: "Browser", value: "browser" },
- { name: "Node", value: "node" }
+ { message: "Browser", name: "browser" },
+ { message: "Node", name: "node" }
]
},
{
- type: "list",
+ type: "select",
name: "source",
message: "How would you like to define a style for your project?",
- default: "guide",
choices: [
- { name: "Use a popular style guide", value: "guide" },
- { name: "Answer questions about your style", value: "prompt" },
- { name: "Inspect your JavaScript file(s)", value: "auto" }
+ { message: "Use a popular style guide", name: "guide" },
+ { message: "Answer questions about your style", name: "prompt" },
+ { message: "Inspect your JavaScript file(s)", name: "auto" }
],
- when(answers) {
- return answers.purpose === "style";
+ skip() {
+ return this.state.answers.purpose !== "style";
+ },
+ result(input) {
+ return this.skipped ? null : input;
}
},
{
- type: "list",
+ type: "select",
name: "styleguide",
message: "Which style guide do you want to follow?",
choices: [
- { name: "Airbnb: https://github.com/airbnb/javascript", value: "airbnb" },
- { name: "Standard: https://github.com/standard/standard", value: "standard" },
- { name: "Google: https://github.com/google/eslint-config-google", value: "google" }
+ { message: "Airbnb: https://github.com/airbnb/javascript", name: "airbnb" },
+ { message: "Standard: https://github.com/standard/standard", name: "standard" },
+ { message: "Google: https://github.com/google/eslint-config-google", name: "google" }
],
- when(answers) {
- answers.packageJsonExists = npmUtils.checkPackageJson();
- return answers.source === "guide" && answers.packageJsonExists;
+ skip() {
+ this.state.answers.packageJsonExists = npmUtils.checkPackageJson();
+ return !(this.state.answers.source === "guide" && this.state.answers.packageJsonExists);
+ },
+ result(input) {
+ return this.skipped ? null : input;
}
},
{
type: "input",
name: "patterns",
message: "Which file(s), path(s), or glob(s) should be examined?",
- when(answers) {
- return (answers.source === "auto");
+ skip() {
+ return this.state.answers.source !== "auto";
},
validate(input) {
- if (input.trim().length === 0 && input.trim() !== ",") {
+ if (!this.skipped && input.trim().length === 0 && input.trim() !== ",") {
return "You must tell us what code to examine. Try again.";
}
return true;
}
},
{
- type: "list",
+ type: "select",
name: "format",
message: "What format do you want your config file to be in?",
- default: "JavaScript",
+ initial: 0,
choices: ["JavaScript", "YAML", "JSON"]
},
{
- type: "confirm",
+ type: "toggle",
name: "installESLint",
message(answers) {
const verb = semver.ltr(answers.localESLintVersion, answers.requiredESLintVersionRange)
@@ -558,9 +572,14 @@ function promptUser() {
return `The style guide "${answers.styleguide}" requires eslint@${answers.requiredESLintVersionRange}. You are currently using eslint@${answers.localESLintVersion}.\n Do you want to ${verb}?`;
},
- default: true,
- when(answers) {
- return answers.source === "guide" && answers.packageJsonExists && hasESLintVersionConflict(answers);
+ enabled: "Yes",
+ disabled: "No",
+ initial: 1,
+ skip() {
+ return !(this.state.answers.source === "guide" && this.state.answers.packageJsonExists && hasESLintVersionConflict(this.state.answers));
+ },
+ result(input) {
+ return this.skipped ? null : input;
}
}
]).then(earlyAnswers => {
@@ -613,33 +632,35 @@ function promptUser() {
}
// continue with the style questions otherwise...
- return inquirer.prompt([
+ return enquirer.prompt([
{
- type: "list",
+ type: "select",
name: "indent",
message: "What style of indentation do you use?",
- default: "tab",
- choices: [{ name: "Tabs", value: "tab" }, { name: "Spaces", value: 4 }]
+ initial: 0,
+ choices: [{ message: "Tabs", name: "tab" }, { message: "Spaces", name: 4 }]
},
{
- type: "list",
+ type: "select",
name: "quotes",
message: "What quotes do you use for strings?",
- default: "double",
- choices: [{ name: "Double", value: "double" }, { name: "Single", value: "single" }]
+ initial: 0,
+ choices: [{ message: "Double", name: "double" }, { message: "Single", name: "single" }]
},
{
- type: "list",
+ type: "select",
name: "linebreak",
message: "What line endings do you use?",
- default: "unix",
- choices: [{ name: "Unix", value: "unix" }, { name: "Windows", value: "windows" }]
+ initial: 0,
+ choices: [{ message: "Unix", name: "unix" }, { message: "Windows", name: "windows" }]
},
{
- type: "confirm",
+ type: "toggle",
name: "semi",
message: "Do you require semicolons?",
- default: true
+ enabled: "Yes",
+ disabled: "No",
+ initial: 1
}
]).then(answers => {
const totalAnswers = Object.assign({}, earlyAnswers, answers);
diff --git a/eslint/lib/linter/code-path-analysis/code-path-analyzer.js b/eslint/lib/linter/code-path-analysis/code-path-analyzer.js
index b612cf4..47427c1 100644
--- a/eslint/lib/linter/code-path-analysis/code-path-analyzer.js
+++ b/eslint/lib/linter/code-path-analysis/code-path-analyzer.js
@@ -39,6 +39,17 @@ function isHandledLogicalOperator(operator) {
return operator === "&&" || operator === "||" || operator === "??";
}
+/**
+ * Checks whether the given assignment operator is a logical assignment operator.
+ * Logical assignments are taken into account for the code path analysis
+ * because of their short-circuiting semantics.
+ * @param {string} operator The operator found in the AssignmentExpression node
+ * @returns {boolean} `true` if the operator is "&&=" or "||=" or "??="
+ */
+function isLogicalAssignmentOperator(operator) {
+ return operator === "&&=" || operator === "||=" || operator === "??=";
+}
+
/**
* Gets the label if the parent node of a given node is a LabeledStatement.
* @param {ASTNode} node A node to get.
@@ -71,6 +82,9 @@ function isForkingByTrueOrFalse(node) {
case "LogicalExpression":
return isHandledLogicalOperator(parent.operator);
+ case "AssignmentExpression":
+ return isLogicalAssignmentOperator(parent.operator);
+
default:
return false;
}
@@ -244,6 +258,19 @@ function preprocess(analyzer, node) {
const parent = node.parent;
switch (parent.type) {
+
+ // The `arguments.length == 0` case is in `postprocess` function.
+ case "CallExpression":
+ if (parent.optional === true && parent.arguments.length >= 1 && parent.arguments[0] === node) {
+ state.makeOptionalRight();
+ }
+ break;
+ case "MemberExpression":
+ if (parent.optional === true && parent.property === node) {
+ state.makeOptionalRight();
+ }
+ break;
+
case "LogicalExpression":
if (
parent.right === node &&
@@ -253,6 +280,15 @@ function preprocess(analyzer, node) {
}
break;
+ case "AssignmentExpression":
+ if (
+ parent.right === node &&
+ isLogicalAssignmentOperator(parent.operator)
+ ) {
+ state.makeLogicalRight();
+ }
+ break;
+
case "ConditionalExpression":
case "IfStatement":
@@ -377,6 +413,20 @@ function processCodePathToEnter(analyzer, node) {
analyzer.emitter.emit("onCodePathStart", codePath, node);
break;
+ case "ChainExpression":
+ state.pushChainContext();
+ break;
+ case "CallExpression":
+ if (node.optional === true) {
+ state.makeOptionalNode();
+ }
+ break;
+ case "MemberExpression":
+ if (node.optional === true) {
+ state.makeOptionalNode();
+ }
+ break;
+
case "LogicalExpression":
if (isHandledLogicalOperator(node.operator)) {
state.pushChoiceContext(
@@ -386,6 +436,15 @@ function processCodePathToEnter(analyzer, node) {
}
break;
+ case "AssignmentExpression":
+ if (isLogicalAssignmentOperator(node.operator)) {
+ state.pushChoiceContext(
+ node.operator.slice(0, -1), // removes `=` from the end
+ isForkingByTrueOrFalse(node)
+ );
+ }
+ break;
+
case "ConditionalExpression":
case "IfStatement":
state.pushChoiceContext("test", false);
@@ -449,6 +508,10 @@ function processCodePathToExit(analyzer, node) {
let dontForward = false;
switch (node.type) {
+ case "ChainExpression":
+ state.popChainContext();
+ break;
+
case "IfStatement":
case "ConditionalExpression":
state.popChoiceContext();
@@ -460,6 +523,12 @@ function processCodePathToExit(analyzer, node) {
}
break;
+ case "AssignmentExpression":
+ if (isLogicalAssignmentOperator(node.operator)) {
+ state.popChoiceContext();
+ }
+ break;
+
case "SwitchStatement":
state.popSwitchContext();
break;
@@ -583,6 +652,13 @@ function postprocess(analyzer, node) {
break;
}
+ // The `arguments.length >= 1` case is in `preprocess` function.
+ case "CallExpression":
+ if (node.optional === true && node.arguments.length === 0) {
+ CodePath.getState(analyzer.codePath).makeOptionalRight();
+ }
+ break;
+
default:
break;
}
diff --git a/eslint/lib/linter/code-path-analysis/code-path-segment.js b/eslint/lib/linter/code-path-analysis/code-path-segment.js
index 6b17b25..ca96ad3 100644
--- a/eslint/lib/linter/code-path-analysis/code-path-segment.js
+++ b/eslint/lib/linter/code-path-analysis/code-path-segment.js
@@ -92,7 +92,6 @@ class CodePathSegment {
/* istanbul ignore if */
if (debug.enabled) {
this.internal.nodes = [];
- this.internal.exitNodes = [];
}
}
diff --git a/eslint/lib/linter/code-path-analysis/code-path-state.js b/eslint/lib/linter/code-path-analysis/code-path-state.js
index 9e76060..f75e60e 100644
--- a/eslint/lib/linter/code-path-analysis/code-path-state.js
+++ b/eslint/lib/linter/code-path-analysis/code-path-state.js
@@ -234,6 +234,7 @@ class CodePathState {
this.tryContext = null;
this.loopContext = null;
this.breakContext = null;
+ this.chainContext = null;
this.currentSegments = [];
this.initialSegment = this.forkContext.head[0];
@@ -316,7 +317,7 @@ class CodePathState {
//--------------------------------------------------------------------------
/**
- * Creates a context for ConditionalExpression, LogicalExpression,
+ * Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only),
* IfStatement, WhileStatement, DoWhileStatement, or ForStatement.
*
* LogicalExpressions have cases that it goes different paths between the
@@ -338,7 +339,7 @@ class CodePathState {
* a -> b -> foo();
* a -> b -> bar();
* @param {string} kind A kind string.
- * If the new context is LogicalExpression's, this is `"&&"` or `"||"`.
+ * If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`.
* If it's IfStatement's or ConditionalExpression's, this is `"test"`.
* Otherwise, this is `"loop"`.
* @param {boolean} isForkingAsResult A flag that shows that goes different
@@ -555,6 +556,64 @@ class CodePathState {
);
}
+ //--------------------------------------------------------------------------
+ // ChainExpression
+ //--------------------------------------------------------------------------
+
+ /**
+ * Push a new `ChainExpression` context to the stack.
+ * This method is called on entering to each `ChainExpression` node.
+ * This context is used to count forking in the optional chain then merge them on the exiting from the `ChainExpression` node.
+ * @returns {void}
+ */
+ pushChainContext() {
+ this.chainContext = {
+ upper: this.chainContext,
+ countChoiceContexts: 0
+ };
+ }
+
+ /**
+ * Pop a `ChainExpression` context from the stack.
+ * This method is called on exiting from each `ChainExpression` node.
+ * This merges all forks of the last optional chaining.
+ * @returns {void}
+ */
+ popChainContext() {
+ const context = this.chainContext;
+
+ this.chainContext = context.upper;
+
+ // pop all choice contexts of this.
+ for (let i = context.countChoiceContexts; i > 0; --i) {
+ this.popChoiceContext();
+ }
+ }
+
+ /**
+ * Create a choice context for optional access.
+ * This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
+ * This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
+ * @returns {void}
+ */
+ makeOptionalNode() {
+ if (this.chainContext) {
+ this.chainContext.countChoiceContexts += 1;
+ this.pushChoiceContext("??", false);
+ }
+ }
+
+ /**
+ * Create a fork.
+ * This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
+ * @returns {void}
+ */
+ makeOptionalRight() {
+ if (this.chainContext) {
+ this.makeLogicalRight();
+ }
+ }
+
//--------------------------------------------------------------------------
// SwitchStatement
//--------------------------------------------------------------------------
diff --git a/eslint/lib/linter/code-path-analysis/debug-helpers.js b/eslint/lib/linter/code-path-analysis/debug-helpers.js
index bde4e0a..a4cb99a 100644
--- a/eslint/lib/linter/code-path-analysis/debug-helpers.js
+++ b/eslint/lib/linter/code-path-analysis/debug-helpers.js
@@ -25,6 +25,22 @@ function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc
return segment.id + (segment.reachable ? "" : "!");
}
+/**
+ * Get string for the given node and operation.
+ * @param {ASTNode} node The node to convert.
+ * @param {"enter" | "exit" | undefined} label The operation label.
+ * @returns {string} The string representation.
+ */
+function nodeToString(node, label) {
+ const suffix = label ? `:${label}` : "";
+
+ switch (node.type) {
+ case "Identifier": return `${node.type}${suffix} (${node.name})`;
+ case "Literal": return `${node.type}${suffix} (${node.value})`;
+ default: return `${node.type}${suffix}`;
+ }
+}
+
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@@ -56,9 +72,15 @@ module.exports = {
const segInternal = state.currentSegments[i].internal;
if (leaving) {
- segInternal.exitNodes.push(node);
+ const last = segInternal.nodes.length - 1;
+
+ if (last >= 0 && segInternal.nodes[last] === nodeToString(node, "enter")) {
+ segInternal.nodes[last] = nodeToString(node, void 0);
+ } else {
+ segInternal.nodes.push(nodeToString(node, "exit"));
+ }
} else {
- segInternal.nodes.push(node);
+ segInternal.nodes.push(nodeToString(node, "enter"));
}
}
@@ -104,23 +126,8 @@ module.exports = {
text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<>\\n";
}
- if (segment.internal.nodes.length > 0 || segment.internal.exitNodes.length > 0) {
- text += [].concat(
- segment.internal.nodes.map(node => {
- switch (node.type) {
- case "Identifier": return `${node.type} (${node.name})`;
- case "Literal": return `${node.type} (${node.value})`;
- default: return node.type;
- }
- }),
- segment.internal.exitNodes.map(node => {
- switch (node.type) {
- case "Identifier": return `${node.type}:exit (${node.name})`;
- case "Literal": return `${node.type}:exit (${node.value})`;
- default: return `${node.type}:exit`;
- }
- })
- ).join("\\n");
+ if (segment.internal.nodes.length > 0) {
+ text += segment.internal.nodes.join("\\n");
} else {
text += "????";
}
diff --git a/eslint/lib/linter/config-comment-parser.js b/eslint/lib/linter/config-comment-parser.js
index 067d024..07bbead 100644
--- a/eslint/lib/linter/config-comment-parser.js
+++ b/eslint/lib/linter/config-comment-parser.js
@@ -11,7 +11,7 @@
//------------------------------------------------------------------------------
const levn = require("levn"),
- ConfigOps = require("../shared/config-ops");
+ ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops");
const debug = require("debug")("eslint:config-comment-parser");
diff --git a/eslint/lib/linter/linter.js b/eslint/lib/linter/linter.js
index f9f3879..5c1a8d7 100644
--- a/eslint/lib/linter/linter.js
+++ b/eslint/lib/linter/linter.js
@@ -16,11 +16,11 @@ const
evk = require("eslint-visitor-keys"),
espree = require("espree"),
lodash = require("lodash"),
- BuiltInEnvironments = require("../../conf/environments"),
+ BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
pkg = require("../../package.json"),
astUtils = require("../shared/ast-utils"),
- ConfigOps = require("../shared/config-ops"),
- validator = require("../shared/config-validator"),
+ ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
+ ConfigValidator = require("@eslint/eslintrc/lib/shared/config-validator"),
Traverser = require("../shared/traverser"),
{ SourceCode } = require("../source-code"),
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
@@ -293,6 +293,9 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
const exportedVariables = {};
const problems = [];
const disableDirectives = [];
+ const validator = new ConfigValidator({
+ builtInRules: Rules
+ });
ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
const trimmedCommentText = stripDirectiveComment(comment.value);
diff --git a/eslint/lib/linter/report-translator.js b/eslint/lib/linter/report-translator.js
index eef5165..bed5af8 100644
--- a/eslint/lib/linter/report-translator.js
+++ b/eslint/lib/linter/report-translator.js
@@ -196,15 +196,19 @@ function mapSuggestions(descriptor, sourceCode, messages) {
return [];
}
- return descriptor.suggest.map(suggestInfo => {
- const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId];
+ return descriptor.suggest
+ .map(suggestInfo => {
+ const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId];
- return {
- ...suggestInfo,
- desc: interpolate(computedDesc, suggestInfo.data),
- fix: normalizeFixes(suggestInfo, sourceCode)
- };
- });
+ return {
+ ...suggestInfo,
+ desc: interpolate(computedDesc, suggestInfo.data),
+ fix: normalizeFixes(suggestInfo, sourceCode)
+ };
+ })
+
+ // Remove suggestions that didn't provide a fix
+ .filter(({ fix }) => fix);
}
/**
diff --git a/eslint/lib/rule-tester/rule-tester.js b/eslint/lib/rule-tester/rule-tester.js
index 77df1de..905f341 100644
--- a/eslint/lib/rule-tester/rule-tester.js
+++ b/eslint/lib/rule-tester/rule-tester.js
@@ -644,6 +644,10 @@ class RuleTester {
assert.ok(item.errors || item.errors === 0,
`Did not specify errors for an invalid test of ${ruleName}`);
+ if (Array.isArray(item.errors) && item.errors.length === 0) {
+ assert.fail("Invalid cases must have at least one error");
+ }
+
const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages");
const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null;
@@ -651,6 +655,11 @@ class RuleTester {
const messages = result.messages;
if (typeof item.errors === "number") {
+
+ if (item.errors === 0) {
+ assert.fail("Invalid cases must have 'error' value greater than 0");
+ }
+
assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
item.errors, item.errors === 1 ? "" : "s", messages.length, util.inspect(messages)));
} else {
@@ -852,6 +861,16 @@ class RuleTester {
);
}
+ // Rules that produce fixes must have `meta.fixable` property.
+ if (result.output !== item.code) {
+ assert.ok(
+ hasOwnProperty(rule, "meta"),
+ "Fixable rules should export a `meta.fixable` property."
+ );
+
+ // Linter throws if a rule that produced a fix has `meta` but doesn't have `meta.fixable`.
+ }
+
assertASTDidntChange(result.beforeAST, result.afterAST);
}
diff --git a/eslint/lib/rules/accessor-pairs.js b/eslint/lib/rules/accessor-pairs.js
index cf994ad..0e0d07a 100644
--- a/eslint/lib/rules/accessor-pairs.js
+++ b/eslint/lib/rules/accessor-pairs.js
@@ -86,16 +86,6 @@ function isAccessorKind(node) {
return node.kind === "get" || node.kind === "set";
}
-/**
- * Checks whether or not a given node is an `Identifier` node which was named a given name.
- * @param {ASTNode} node A node to check.
- * @param {string} name An expected name of the node.
- * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
- */
-function isIdentifier(node, name) {
- return node.type === "Identifier" && node.name === name;
-}
-
/**
* Checks whether or not a given node is an argument of a specified method call.
* @param {ASTNode} node A node to check.
@@ -109,10 +99,7 @@ function isArgumentOfMethodCall(node, index, object, property) {
return (
parent.type === "CallExpression" &&
- parent.callee.type === "MemberExpression" &&
- parent.callee.computed === false &&
- isIdentifier(parent.callee.object, object) &&
- isIdentifier(parent.callee.property, property) &&
+ astUtils.isSpecificMemberAccess(parent.callee, object, property) &&
parent.arguments[index] === node
);
}
diff --git a/eslint/lib/rules/array-callback-return.js b/eslint/lib/rules/array-callback-return.js
index 62ba7b7..7267347 100644
--- a/eslint/lib/rules/array-callback-return.js
+++ b/eslint/lib/rules/array-callback-return.js
@@ -9,8 +9,6 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
-
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
@@ -30,17 +28,27 @@ function isReachable(segment) {
}
/**
- * Checks a given node is a MemberExpression node which has the specified name's
+ * Checks a given node is a member access which has the specified name's
* property.
* @param {ASTNode} node A node to check.
- * @returns {boolean} `true` if the node is a MemberExpression node which has
- * the specified name's property
+ * @returns {boolean} `true` if the node is a member access which has
+ * the specified name's property. The node may be a `(Chain|Member)Expression` node.
*/
function isTargetMethod(node) {
- return (
- node.type === "MemberExpression" &&
- TARGET_METHODS.test(astUtils.getStaticPropertyName(node) || "")
- );
+ return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
+}
+
+/**
+ * Returns a human-legible description of an array method
+ * @param {string} arrayMethodName A method name to fully qualify
+ * @returns {string} the method name prefixed with `Array.` if it is a class method,
+ * or else `Array.prototype.` if it is an instance method.
+ */
+function fullMethodName(arrayMethodName) {
+ if (["from", "of", "isArray"].includes(arrayMethodName)) {
+ return "Array.".concat(arrayMethodName);
+ }
+ return "Array.prototype.".concat(arrayMethodName);
}
/**
@@ -65,6 +73,7 @@ function getArrayMethodName(node) {
*/
case "LogicalExpression":
case "ConditionalExpression":
+ case "ChainExpression":
currentNode = parent;
break;
@@ -153,10 +162,10 @@ module.exports = {
],
messages: {
- expectedAtEnd: "Expected to return a value at the end of {{name}}.",
- expectedInside: "Expected to return a value in {{name}}.",
- expectedReturnValue: "{{name}} expected a return value.",
- expectedNoReturnValue: "{{name}} did not expect a return value."
+ expectedAtEnd: "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.",
+ expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.",
+ expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.",
+ expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}."
}
},
@@ -202,14 +211,13 @@ module.exports = {
}
if (messageId) {
- let name = astUtils.getFunctionNameWithKind(node);
+ const name = astUtils.getFunctionNameWithKind(node);
- name = messageId === "expectedNoReturnValue" ? lodash.upperFirst(name) : name;
context.report({
node,
loc: astUtils.getFunctionHeadLoc(node, sourceCode),
messageId,
- data: { name }
+ data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }
});
}
}
@@ -273,7 +281,8 @@ module.exports = {
node,
messageId,
data: {
- name: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
+ name: astUtils.getFunctionNameWithKind(funcInfo.node),
+ arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
}
});
}
diff --git a/eslint/lib/rules/arrow-body-style.js b/eslint/lib/rules/arrow-body-style.js
index 9d5c77d..7b318ea 100644
--- a/eslint/lib/rules/arrow-body-style.js
+++ b/eslint/lib/rules/arrow-body-style.js
@@ -75,6 +75,7 @@ module.exports = {
const never = options[0] === "never";
const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
const sourceCode = context.getSourceCode();
+ let funcInfo = null;
/**
* Checks whether the given node has ASI problem or not.
@@ -99,6 +100,21 @@ module.exports = {
return sourceCode.getTokenAfter(node);
}
+ /**
+ * Check whether the node is inside of a for loop's init
+ * @param {ASTNode} node node is inside for loop
+ * @returns {boolean} `true` if the node is inside of a for loop, else `false`
+ */
+ function isInsideForLoopInitializer(node) {
+ if (node && node.parent) {
+ if (node.parent.type === "ForStatement" && node.parent.init === node) {
+ return true;
+ }
+ return isInsideForLoopInitializer(node.parent);
+ }
+ return false;
+ }
+
/**
* Determines whether a arrow function body needs braces
* @param {ASTNode} node The arrow function node.
@@ -136,7 +152,7 @@ module.exports = {
context.report({
node,
- loc: arrowBody.loc.start,
+ loc: arrowBody.loc,
messageId,
fix(fixer) {
const fixes = [];
@@ -178,11 +194,13 @@ module.exports = {
* If the first token of the reutrn value is `{` or the return value is a sequence expression,
* enclose the return value by parentheses to avoid syntax error.
*/
- if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression") {
- fixes.push(
- fixer.insertTextBefore(firstValueToken, "("),
- fixer.insertTextAfter(lastValueToken, ")")
- );
+ if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression" || (funcInfo.hasInOperator && isInsideForLoopInitializer(node))) {
+ if (!astUtils.isParenthesised(sourceCode, blockBody[0].argument)) {
+ fixes.push(
+ fixer.insertTextBefore(firstValueToken, "("),
+ fixer.insertTextAfter(lastValueToken, ")")
+ );
+ }
}
/*
@@ -201,7 +219,7 @@ module.exports = {
if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
context.report({
node,
- loc: arrowBody.loc.start,
+ loc: arrowBody.loc,
messageId: "expectedBlock",
fix(fixer) {
const fixes = [];
@@ -245,7 +263,24 @@ module.exports = {
}
return {
- "ArrowFunctionExpression:exit": validate
+ "BinaryExpression[operator='in']"() {
+ let info = funcInfo;
+
+ while (info) {
+ info.hasInOperator = true;
+ info = info.upper;
+ }
+ },
+ ArrowFunctionExpression() {
+ funcInfo = {
+ upper: funcInfo,
+ hasInOperator: false
+ };
+ },
+ "ArrowFunctionExpression:exit"(node) {
+ validate(node);
+ funcInfo = funcInfo.upper;
+ }
};
}
};
diff --git a/eslint/lib/rules/arrow-parens.js b/eslint/lib/rules/arrow-parens.js
index bfd3244..eaa1aab 100644
--- a/eslint/lib/rules/arrow-parens.js
+++ b/eslint/lib/rules/arrow-parens.js
@@ -15,15 +15,12 @@ const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
/**
- * Get location should be reported by AST node.
- * @param {ASTNode} node AST Node.
- * @returns {Location} Location information.
+ * Determines if the given arrow function has block body.
+ * @param {ASTNode} node `ArrowFunctionExpression` node.
+ * @returns {boolean} `true` if the function has block body.
*/
-function getLocation(node) {
- return {
- start: node.params[0].loc.start,
- end: node.params[node.params.length - 1].loc.end
- };
+function hasBlockBody(node) {
+ return node.body.type === "BlockStatement";
}
//------------------------------------------------------------------------------
@@ -75,126 +72,112 @@ module.exports = {
const sourceCode = context.getSourceCode();
/**
- * Determines whether a arrow function argument end with `)`
- * @param {ASTNode} node The arrow function node.
- * @returns {void}
+ * Finds opening paren of parameters for the given arrow function, if it exists.
+ * It is assumed that the given arrow function has exactly one parameter.
+ * @param {ASTNode} node `ArrowFunctionExpression` node.
+ * @returns {Token|null} the opening paren, or `null` if the given arrow function doesn't have parens of parameters.
*/
- function parens(node) {
- const isAsync = node.async;
- const firstTokenOfParam = sourceCode.getFirstToken(node, isAsync ? 1 : 0);
-
- /**
- * Remove the parenthesis around a parameter
- * @param {Fixer} fixer Fixer
- * @returns {string} fixed parameter
- */
- function fixParamsWithParenthesis(fixer) {
- const paramToken = sourceCode.getTokenAfter(firstTokenOfParam);
-
- /*
- * ES8 allows Trailing commas in function parameter lists and calls
- * https://github.com/eslint/eslint/issues/8834
- */
- const closingParenToken = sourceCode.getTokenAfter(paramToken, astUtils.isClosingParenToken);
- const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null;
- const shouldAddSpaceForAsync = asyncToken && (asyncToken.range[1] === firstTokenOfParam.range[0]);
-
- return fixer.replaceTextRange([
- firstTokenOfParam.range[0],
- closingParenToken.range[1]
- ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`);
- }
-
- /**
- * Checks whether there are comments inside the params or not.
- * @returns {boolean} `true` if there are comments inside of parens, else `false`
- */
- function hasCommentsInParens() {
- if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
- const closingParenToken = sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
-
- return closingParenToken && sourceCode.commentsExistBetween(firstTokenOfParam, closingParenToken);
- }
- return false;
-
- }
-
- if (hasCommentsInParens()) {
- return;
- }
-
- // "as-needed", { "requireForBlockBody": true }: x => x
- if (
- requireForBlockBody &&
- node.params[0].type === "Identifier" &&
- !node.params[0].typeAnnotation &&
- node.body.type !== "BlockStatement" &&
- !node.returnType
- ) {
- if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
- context.report({
- node,
- messageId: "unexpectedParensInline",
- loc: getLocation(node),
- fix: fixParamsWithParenthesis
- });
- }
- return;
- }
+ function findOpeningParenOfParams(node) {
+ const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]);
if (
- requireForBlockBody &&
- node.body.type === "BlockStatement"
+ tokenBeforeParams &&
+ astUtils.isOpeningParenToken(tokenBeforeParams) &&
+ node.range[0] <= tokenBeforeParams.range[0]
) {
- if (!astUtils.isOpeningParenToken(firstTokenOfParam)) {
- context.report({
- node,
- messageId: "expectedParensBlock",
- loc: getLocation(node),
- fix(fixer) {
- return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
- }
- });
- }
- return;
+ return tokenBeforeParams;
}
- // "as-needed": x => x
- if (asNeeded &&
- node.params[0].type === "Identifier" &&
- !node.params[0].typeAnnotation &&
- !node.returnType
- ) {
- if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
- context.report({
- node,
- messageId: "unexpectedParens",
- loc: getLocation(node),
- fix: fixParamsWithParenthesis
- });
- }
- return;
- }
+ return null;
+ }
- if (firstTokenOfParam.type === "Identifier") {
- const after = sourceCode.getTokenAfter(firstTokenOfParam);
+ /**
+ * Finds closing paren of parameters for the given arrow function.
+ * It is assumed that the given arrow function has parens of parameters and that it has exactly one parameter.
+ * @param {ASTNode} node `ArrowFunctionExpression` node.
+ * @returns {Token} the closing paren of parameters.
+ */
+ function getClosingParenOfParams(node) {
+ return sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
+ }
- // (x) => x
- if (after.value !== ")") {
- context.report({
- node,
- messageId: "expectedParens",
- loc: getLocation(node),
- fix(fixer) {
- return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
- }
- });
- }
- }
+ /**
+ * Determines whether the given arrow function has comments inside parens of parameters.
+ * It is assumed that the given arrow function has parens of parameters.
+ * @param {ASTNode} node `ArrowFunctionExpression` node.
+ * @param {Token} openingParen Opening paren of parameters.
+ * @returns {boolean} `true` if the function has at least one comment inside of parens of parameters.
+ */
+ function hasCommentsInParensOfParams(node, openingParen) {
+ return sourceCode.commentsExistBetween(openingParen, getClosingParenOfParams(node));
+ }
+
+ /**
+ * Determines whether the given arrow function has unexpected tokens before opening paren of parameters,
+ * in which case it will be assumed that the existing parens of parameters are necessary.
+ * Only tokens within the range of the arrow function (tokens that are part of the arrow function) are taken into account.
+ * Example: (a) => b
+ * @param {ASTNode} node `ArrowFunctionExpression` node.
+ * @param {Token} openingParen Opening paren of parameters.
+ * @returns {boolean} `true` if the function has at least one unexpected token.
+ */
+ function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) {
+ const expectedCount = node.async ? 1 : 0;
+
+ return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen;
}
return {
- "ArrowFunctionExpression[params.length=1]": parens
+ "ArrowFunctionExpression[params.length=1]"(node) {
+ const shouldHaveParens = !asNeeded || requireForBlockBody && hasBlockBody(node);
+ const openingParen = findOpeningParenOfParams(node);
+ const hasParens = openingParen !== null;
+ const [param] = node.params;
+
+ if (shouldHaveParens && !hasParens) {
+ context.report({
+ node,
+ messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens",
+ loc: param.loc,
+ *fix(fixer) {
+ yield fixer.insertTextBefore(param, "(");
+ yield fixer.insertTextAfter(param, ")");
+ }
+ });
+ }
+
+ if (
+ !shouldHaveParens &&
+ hasParens &&
+ param.type === "Identifier" &&
+ !param.typeAnnotation &&
+ !node.returnType &&
+ !hasCommentsInParensOfParams(node, openingParen) &&
+ !hasUnexpectedTokensBeforeOpeningParen(node, openingParen)
+ ) {
+ context.report({
+ node,
+ messageId: requireForBlockBody ? "unexpectedParensInline" : "unexpectedParens",
+ loc: param.loc,
+ *fix(fixer) {
+ const tokenBeforeOpeningParen = sourceCode.getTokenBefore(openingParen);
+ const closingParen = getClosingParenOfParams(node);
+
+ if (
+ tokenBeforeOpeningParen &&
+ tokenBeforeOpeningParen.range[1] === openingParen.range[0] &&
+ !astUtils.canTokensBeAdjacent(tokenBeforeOpeningParen, sourceCode.getFirstToken(param))
+ ) {
+ yield fixer.insertTextBefore(openingParen, " ");
+ }
+
+ // remove parens, whitespace inside parens, and possible trailing comma
+ yield fixer.removeRange([openingParen.range[0], param.range[0]]);
+ yield fixer.removeRange([param.range[1], closingParen.range[1]]);
+ }
+ });
+ }
+ }
};
}
};
diff --git a/eslint/lib/rules/camelcase.js b/eslint/lib/rules/camelcase.js
index 0436083..d34656c 100644
--- a/eslint/lib/rules/camelcase.js
+++ b/eslint/lib/rules/camelcase.js
@@ -32,6 +32,10 @@ module.exports = {
type: "boolean",
default: false
},
+ ignoreGlobals: {
+ type: "boolean",
+ default: false
+ },
properties: {
enum: ["always", "never"]
},
@@ -61,8 +65,11 @@ module.exports = {
let properties = options.properties || "";
const ignoreDestructuring = options.ignoreDestructuring;
const ignoreImports = options.ignoreImports;
+ const ignoreGlobals = options.ignoreGlobals;
const allow = options.allow || [];
+ let globalScope;
+
if (properties !== "always" && properties !== "never") {
properties = "always";
}
@@ -159,6 +166,37 @@ module.exports = {
return false;
}
+ /**
+ * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
+ * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a reference to a global variable.
+ */
+ function isReferenceToGlobalVariable(node) {
+ const variable = globalScope.set.get(node.name);
+
+ return variable && variable.defs.length === 0 &&
+ variable.references.some(ref => ref.identifier === node);
+ }
+
+ /**
+ * Checks whether the given node represents a reference to a property of an object in an object literal expression.
+ * This allows to differentiate between a global variable that is allowed to be used as a reference, and the key
+ * of the expressed object (which shouldn't be allowed).
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a property name of an object literal expression
+ */
+ function isPropertyNameInObjectLiteral(node) {
+ const parent = node.parent;
+
+ return (
+ parent.type === "Property" &&
+ parent.parent.type === "ObjectExpression" &&
+ !parent.computed &&
+ parent.key === node
+ );
+ }
+
/**
* Reports an AST node as a rule violation.
* @param {ASTNode} node The node to report.
@@ -174,6 +212,10 @@ module.exports = {
return {
+ Program() {
+ globalScope = context.getScope();
+ },
+
Identifier(node) {
/*
@@ -189,6 +231,11 @@ module.exports = {
return;
}
+ // Check if it's a global variable
+ if (ignoreGlobals && isReferenceToGlobalVariable(node) && !isPropertyNameInObjectLiteral(node)) {
+ return;
+ }
+
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
diff --git a/eslint/lib/rules/consistent-return.js b/eslint/lib/rules/consistent-return.js
index 22667fa..94db253 100644
--- a/eslint/lib/rules/consistent-return.js
+++ b/eslint/lib/rules/consistent-return.js
@@ -9,23 +9,12 @@
//------------------------------------------------------------------------------
const lodash = require("lodash");
-
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
-/**
- * Checks whether or not a given node is an `Identifier` node which was named a given name.
- * @param {ASTNode} node A node to check.
- * @param {string} name An expected name of the node.
- * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
- */
-function isIdentifier(node, name) {
- return node.type === "Identifier" && node.name === name;
-}
-
/**
* Checks whether or not a given code path segment is unreachable.
* @param {CodePathSegment} segment A CodePathSegment to check.
@@ -165,7 +154,7 @@ module.exports = {
let hasReturnValue = Boolean(argument);
if (treatUndefinedAsUnspecified && hasReturnValue) {
- hasReturnValue = !isIdentifier(argument, "undefined") && argument.operator !== "void";
+ hasReturnValue = !astUtils.isSpecificId(argument, "undefined") && argument.operator !== "void";
}
if (!funcInfo.hasReturn) {
diff --git a/eslint/lib/rules/constructor-super.js b/eslint/lib/rules/constructor-super.js
index 5a848f2..dfec18f 100644
--- a/eslint/lib/rules/constructor-super.js
+++ b/eslint/lib/rules/constructor-super.js
@@ -50,6 +50,7 @@ function isPossibleConstructor(node) {
case "MemberExpression":
case "CallExpression":
case "NewExpression":
+ case "ChainExpression":
case "YieldExpression":
case "TaggedTemplateExpression":
case "MetaProperty":
@@ -59,9 +60,36 @@ function isPossibleConstructor(node) {
return node.name !== "undefined";
case "AssignmentExpression":
- return isPossibleConstructor(node.right);
+ if (["=", "&&="].includes(node.operator)) {
+ return isPossibleConstructor(node.right);
+ }
+
+ if (["||=", "??="].includes(node.operator)) {
+ return (
+ isPossibleConstructor(node.left) ||
+ isPossibleConstructor(node.right)
+ );
+ }
+
+ /**
+ * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
+ * An assignment expression with a mathematical operator can either evaluate to a primitive value,
+ * or throw, depending on the operands. Thus, it cannot evaluate to a constructor function.
+ */
+ return false;
case "LogicalExpression":
+
+ /*
+ * If the && operator short-circuits, the left side was falsy and therefore not a constructor, and if
+ * it doesn't short-circuit, it takes the value from the right side, so the right side must always be a
+ * possible constructor. A future improvement could verify that the left side could be truthy by
+ * excluding falsy literals.
+ */
+ if (node.operator === "&&") {
+ return isPossibleConstructor(node.right);
+ }
+
return (
isPossibleConstructor(node.left) ||
isPossibleConstructor(node.right)
diff --git a/eslint/lib/rules/curly.js b/eslint/lib/rules/curly.js
index 29f00c0..92d31a6 100644
--- a/eslint/lib/rules/curly.js
+++ b/eslint/lib/rules/curly.js
@@ -457,11 +457,18 @@ module.exports = {
return {
IfStatement(node) {
- if (node.parent.type !== "IfStatement") {
+ const parent = node.parent;
+ const isElseIf = parent.type === "IfStatement" && parent.alternate === node;
+
+ if (!isElseIf) {
+
+ // This is a top `if`, check the whole `if-else-if` chain
prepareIfChecks(node).forEach(preparedCheck => {
preparedCheck.check();
});
}
+
+ // Skip `else if`, it's already checked (when the top `if` was visited)
},
WhileStatement(node) {
diff --git a/eslint/lib/rules/dot-location.js b/eslint/lib/rules/dot-location.js
index d483e21..0a739b1 100644
--- a/eslint/lib/rules/dot-location.js
+++ b/eslint/lib/rules/dot-location.js
@@ -52,31 +52,37 @@ module.exports = {
*/
function checkDotLocation(node) {
const property = node.property;
- const dot = sourceCode.getTokenBefore(property);
-
- // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
- const tokenBeforeDot = sourceCode.getTokenBefore(dot);
-
- const textBeforeDot = sourceCode.getText().slice(tokenBeforeDot.range[1], dot.range[0]);
- const textAfterDot = sourceCode.getText().slice(dot.range[1], property.range[0]);
+ const dotToken = sourceCode.getTokenBefore(property);
if (onObject) {
- if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dot)) {
- const neededTextAfterToken = astUtils.isDecimalIntegerNumericToken(tokenBeforeDot) ? " " : "";
+ // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
+ const tokenBeforeDot = sourceCode.getTokenBefore(dotToken);
+
+ if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dotToken)) {
context.report({
node,
- loc: dot.loc,
+ loc: dotToken.loc,
messageId: "expectedDotAfterObject",
- fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], property.range[0]], `${neededTextAfterToken}.${textBeforeDot}${textAfterDot}`)
+ *fix(fixer) {
+ if (dotToken.value.startsWith(".") && astUtils.isDecimalIntegerNumericToken(tokenBeforeDot)) {
+ yield fixer.insertTextAfter(tokenBeforeDot, ` ${dotToken.value}`);
+ } else {
+ yield fixer.insertTextAfter(tokenBeforeDot, dotToken.value);
+ }
+ yield fixer.remove(dotToken);
+ }
});
}
- } else if (!astUtils.isTokenOnSameLine(dot, property)) {
+ } else if (!astUtils.isTokenOnSameLine(dotToken, property)) {
context.report({
node,
- loc: dot.loc,
+ loc: dotToken.loc,
messageId: "expectedDotBeforeProperty",
- fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], property.range[0]], `${textBeforeDot}${textAfterDot}.`)
+ *fix(fixer) {
+ yield fixer.remove(dotToken);
+ yield fixer.insertTextBefore(property, dotToken.value);
+ }
});
}
}
diff --git a/eslint/lib/rules/dot-notation.js b/eslint/lib/rules/dot-notation.js
index 2e8fff8..751b462 100644
--- a/eslint/lib/rules/dot-notation.js
+++ b/eslint/lib/rules/dot-notation.js
@@ -87,28 +87,36 @@ module.exports = {
data: {
key: formattedValue
},
- fix(fixer) {
+ *fix(fixer) {
const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
const rightBracket = sourceCode.getLastToken(node);
+ const nextToken = sourceCode.getTokenAfter(node);
- if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
-
- // Don't perform any fixes if there are comments inside the brackets.
- return null;
+ // Don't perform any fixes if there are comments inside the brackets.
+ if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) {
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
}
- const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket);
- const needsSpaceAfterProperty = tokenAfterProperty &&
- rightBracket.range[1] === tokenAfterProperty.range[0] &&
- !astUtils.canTokensBeAdjacent(String(value), tokenAfterProperty);
-
- const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
- const textAfterProperty = needsSpaceAfterProperty ? " " : "";
-
- return fixer.replaceTextRange(
+ // Replace the brackets by an identifier.
+ if (!node.optional) {
+ yield fixer.insertTextBefore(
+ leftBracket,
+ astUtils.isDecimalInteger(node.object) ? " ." : "."
+ );
+ }
+ yield fixer.replaceTextRange(
[leftBracket.range[0], rightBracket.range[1]],
- `${textBeforeDot}.${value}${textAfterProperty}`
+ value
);
+
+ // Insert a space after the property if it will be connected to the next token.
+ if (
+ nextToken &&
+ rightBracket.range[1] === nextToken.range[0] &&
+ !astUtils.canTokensBeAdjacent(String(value), nextToken)
+ ) {
+ yield fixer.insertTextAfter(node, " ");
+ }
}
});
}
@@ -141,29 +149,24 @@ module.exports = {
data: {
key: node.property.name
},
- fix(fixer) {
- const dot = sourceCode.getTokenBefore(node.property);
- const textAfterDot = sourceCode.text.slice(dot.range[1], node.property.range[0]);
+ *fix(fixer) {
+ const dotToken = sourceCode.getTokenBefore(node.property);
- if (textAfterDot.trim()) {
-
- // Don't perform any fixes if there are comments between the dot and the property name.
- return null;
+ // A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression.
+ if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) {
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
}
- if (node.object.type === "Identifier" && node.object.name === "let") {
-
- /*
- * A statement that starts with `let[` is parsed as a destructuring variable declaration, not
- * a MemberExpression.
- */
- return null;
+ // Don't perform any fixes if there are comments between the dot and the property name.
+ if (sourceCode.commentsExistBetween(dotToken, node.property)) {
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
}
- return fixer.replaceTextRange(
- [dot.range[0], node.property.range[1]],
- `[${textAfterDot}"${node.property.name}"]`
- );
+ // Replace the identifier to brackets.
+ if (!node.optional) {
+ yield fixer.remove(dotToken);
+ }
+ yield fixer.replaceText(node.property, `["${node.property.name}"]`);
}
});
}
diff --git a/eslint/lib/rules/func-call-spacing.js b/eslint/lib/rules/func-call-spacing.js
index 5ecb63e..8fe690d 100644
--- a/eslint/lib/rules/func-call-spacing.js
+++ b/eslint/lib/rules/func-call-spacing.js
@@ -126,15 +126,24 @@ module.exports = {
messageId: "unexpectedWhitespace",
fix(fixer) {
+ // Don't remove comments.
+ if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
+ return null;
+ }
+
+ // If `?.` exsits, it doesn't hide no-undexpected-multiline errors
+ if (node.optional) {
+ return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], "?.");
+ }
+
/*
* Only autofix if there is no newline
* https://github.com/eslint/eslint/issues/7787
*/
- if (!hasNewline) {
- return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
+ if (hasNewline) {
+ return null;
}
-
- return null;
+ return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
}
});
} else if (!never && !hasWhitespace) {
@@ -149,6 +158,9 @@ module.exports = {
},
messageId: "missing",
fix(fixer) {
+ if (node.optional) {
+ return null; // Not sure if inserting a space to either before/after `?.` token.
+ }
return fixer.insertTextBefore(rightToken, " ");
}
});
@@ -161,7 +173,31 @@ module.exports = {
},
messageId: "unexpectedNewline",
fix(fixer) {
- return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
+
+ /*
+ * Only autofix if there is no newline
+ * https://github.com/eslint/eslint/issues/7787
+ * But if `?.` exsits, it doesn't hide no-undexpected-multiline errors
+ */
+ if (!node.optional) {
+ return null;
+ }
+
+ // Don't remove comments.
+ if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
+ return null;
+ }
+
+ const range = [leftToken.range[1], rightToken.range[0]];
+ const qdToken = sourceCode.getTokenAfter(leftToken);
+
+ if (qdToken.range[0] === leftToken.range[1]) {
+ return fixer.replaceTextRange(range, "?. ");
+ }
+ if (qdToken.range[1] === rightToken.range[0]) {
+ return fixer.replaceTextRange(range, " ?.");
+ }
+ return fixer.replaceTextRange(range, " ?. ");
}
});
}
@@ -172,7 +208,7 @@ module.exports = {
const lastToken = sourceCode.getLastToken(node);
const lastCalleeToken = sourceCode.getLastToken(node.callee);
const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
- const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
+ const prevToken = parenToken && sourceCode.getTokenBefore(parenToken, astUtils.isNotQuestionDotToken);
// Parens in NewExpression are optional
if (!(parenToken && parenToken.range[1] < node.range[1])) {
diff --git a/eslint/lib/rules/func-name-matching.js b/eslint/lib/rules/func-name-matching.js
index 83430ff..755c2ee 100644
--- a/eslint/lib/rules/func-name-matching.js
+++ b/eslint/lib/rules/func-name-matching.js
@@ -117,10 +117,7 @@ module.exports = {
if (!node) {
return false;
}
- return node.type === "CallExpression" &&
- node.callee.type === "MemberExpression" &&
- node.callee.object.name === objName &&
- node.callee.property.name === funcName;
+ return node.type === "CallExpression" && astUtils.isSpecificMemberAccess(node.callee, objName, funcName);
}
/**
diff --git a/eslint/lib/rules/function-paren-newline.js b/eslint/lib/rules/function-paren-newline.js
index 894c8e3..9d8d67b 100644
--- a/eslint/lib/rules/function-paren-newline.js
+++ b/eslint/lib/rules/function-paren-newline.js
@@ -218,7 +218,7 @@ module.exports = {
}
case "ArrowFunctionExpression": {
- const firstToken = sourceCode.getFirstToken(node);
+ const firstToken = sourceCode.getFirstToken(node, { skip: (node.async ? 1 : 0) });
if (!astUtils.isOpeningParenToken(firstToken)) {
diff --git a/eslint/lib/rules/global-require.js b/eslint/lib/rules/global-require.js
index 469c017..09d0332 100644
--- a/eslint/lib/rules/global-require.js
+++ b/eslint/lib/rules/global-require.js
@@ -13,7 +13,8 @@ const ACCEPTABLE_PARENTS = [
"CallExpression",
"ConditionalExpression",
"Program",
- "VariableDeclaration"
+ "VariableDeclaration",
+ "ChainExpression"
];
/**
diff --git a/eslint/lib/rules/id-blacklist.js b/eslint/lib/rules/id-blacklist.js
index d77a35d..4fbba90 100644
--- a/eslint/lib/rules/id-blacklist.js
+++ b/eslint/lib/rules/id-blacklist.js
@@ -1,6 +1,6 @@
/**
* @fileoverview Rule that warns when identifier names that are
- * blacklisted in the configuration are used.
+ * specified in the configuration are used.
* @author Keith Cirkel (http://keithcirkel.co.uk)
*/
@@ -111,6 +111,9 @@ function isShorthandPropertyDefinition(node) {
module.exports = {
meta: {
+ deprecated: true,
+ replacedBy: ["id-denylist"],
+
type: "suggestion",
docs: {
@@ -128,25 +131,25 @@ module.exports = {
uniqueItems: true
},
messages: {
- blacklisted: "Identifier '{{name}}' is blacklisted."
+ restricted: "Identifier '{{name}}' is restricted."
}
},
create(context) {
- const blacklist = new Set(context.options);
+ const denyList = new Set(context.options);
const reportedNodes = new Set();
let globalScope;
/**
- * Checks whether the given name is blacklisted.
+ * Checks whether the given name is restricted.
* @param {string} name The name to check.
- * @returns {boolean} `true` if the name is blacklisted.
+ * @returns {boolean} `true` if the name is restricted.
* @private
*/
- function isBlacklisted(name) {
- return blacklist.has(name);
+ function isRestricted(name) {
+ return denyList.has(name);
}
/**
@@ -172,8 +175,8 @@ module.exports = {
/*
* Member access has special rules for checking property names.
- * Read access to a property with a blacklisted name is allowed, because it can be on an object that user has no control over.
- * Write access isn't allowed, because it potentially creates a new property with a blacklisted name.
+ * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over.
+ * Write access isn't allowed, because it potentially creates a new property with a restricted name.
*/
if (
parent.type === "MemberExpression" &&
@@ -205,7 +208,7 @@ module.exports = {
if (!reportedNodes.has(node)) {
context.report({
node,
- messageId: "blacklisted",
+ messageId: "restricted",
data: {
name: node.name
}
@@ -221,7 +224,7 @@ module.exports = {
},
Identifier(node) {
- if (isBlacklisted(node.name) && shouldCheck(node)) {
+ if (isRestricted(node.name) && shouldCheck(node)) {
report(node);
}
}
diff --git a/eslint/lib/rules/id-denylist.js b/eslint/lib/rules/id-denylist.js
new file mode 100644
index 0000000..112fd8a
--- /dev/null
+++ b/eslint/lib/rules/id-denylist.js
@@ -0,0 +1,230 @@
+/**
+ * @fileoverview Rule that warns when identifier names that are
+ * specified in the configuration are used.
+ * @author Keith Cirkel (http://keithcirkel.co.uk)
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether the given node represents assignment target in a normal assignment or destructuring.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is assignment target.
+ */
+function isAssignmentTarget(node) {
+ const parent = node.parent;
+
+ return (
+
+ // normal assignment
+ (
+ parent.type === "AssignmentExpression" &&
+ parent.left === node
+ ) ||
+
+ // destructuring
+ parent.type === "ArrayPattern" ||
+ parent.type === "RestElement" ||
+ (
+ parent.type === "Property" &&
+ parent.value === node &&
+ parent.parent.type === "ObjectPattern"
+ ) ||
+ (
+ parent.type === "AssignmentPattern" &&
+ parent.left === node
+ )
+ );
+}
+
+/**
+ * Checks whether the given node represents an imported name that is renamed in the same import/export specifier.
+ *
+ * Examples:
+ * import { a as b } from 'mod'; // node `a` is renamed import
+ * export { a as b } from 'mod'; // node `a` is renamed import
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a renamed import.
+ */
+function isRenamedImport(node) {
+ const parent = node.parent;
+
+ return (
+ (
+ parent.type === "ImportSpecifier" &&
+ parent.imported !== parent.local &&
+ parent.imported === node
+ ) ||
+ (
+ parent.type === "ExportSpecifier" &&
+ parent.parent.source && // re-export
+ parent.local !== parent.exported &&
+ parent.local === node
+ )
+ );
+}
+
+/**
+ * Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring.
+ *
+ * Examples:
+ * const { a : b } = foo; // node `a` is renamed node.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring.
+ */
+function isRenamedInDestructuring(node) {
+ const parent = node.parent;
+
+ return (
+ (
+ !parent.computed &&
+ parent.type === "Property" &&
+ parent.parent.type === "ObjectPattern" &&
+ parent.value !== node &&
+ parent.key === node
+ )
+ );
+}
+
+/**
+ * Checks whether the given node represents shorthand definition of a property in an object literal.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a shorthand property definition.
+ */
+function isShorthandPropertyDefinition(node) {
+ const parent = node.parent;
+
+ return (
+ parent.type === "Property" &&
+ parent.parent.type === "ObjectExpression" &&
+ parent.shorthand
+ );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "suggestion",
+
+ docs: {
+ description: "disallow specified identifiers",
+ category: "Stylistic Issues",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/id-denylist"
+ },
+
+ schema: {
+ type: "array",
+ items: {
+ type: "string"
+ },
+ uniqueItems: true
+ },
+ messages: {
+ restricted: "Identifier '{{name}}' is restricted."
+ }
+ },
+
+ create(context) {
+
+ const denyList = new Set(context.options);
+ const reportedNodes = new Set();
+
+ let globalScope;
+
+ /**
+ * Checks whether the given name is restricted.
+ * @param {string} name The name to check.
+ * @returns {boolean} `true` if the name is restricted.
+ * @private
+ */
+ function isRestricted(name) {
+ return denyList.has(name);
+ }
+
+ /**
+ * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
+ * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a reference to a global variable.
+ */
+ function isReferenceToGlobalVariable(node) {
+ const variable = globalScope.set.get(node.name);
+
+ return variable && variable.defs.length === 0 &&
+ variable.references.some(ref => ref.identifier === node);
+ }
+
+ /**
+ * Determines whether the given node should be checked.
+ * @param {ASTNode} node `Identifier` node.
+ * @returns {boolean} `true` if the node should be checked.
+ */
+ function shouldCheck(node) {
+ const parent = node.parent;
+
+ /*
+ * Member access has special rules for checking property names.
+ * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over.
+ * Write access isn't allowed, because it potentially creates a new property with a restricted name.
+ */
+ if (
+ parent.type === "MemberExpression" &&
+ parent.property === node &&
+ !parent.computed
+ ) {
+ return isAssignmentTarget(parent);
+ }
+
+ return (
+ parent.type !== "CallExpression" &&
+ parent.type !== "NewExpression" &&
+ !isRenamedImport(node) &&
+ !isRenamedInDestructuring(node) &&
+ !(
+ isReferenceToGlobalVariable(node) &&
+ !isShorthandPropertyDefinition(node)
+ )
+ );
+ }
+
+ /**
+ * Reports an AST node as a rule violation.
+ * @param {ASTNode} node The node to report.
+ * @returns {void}
+ * @private
+ */
+ function report(node) {
+ if (!reportedNodes.has(node)) {
+ context.report({
+ node,
+ messageId: "restricted",
+ data: {
+ name: node.name
+ }
+ });
+ reportedNodes.add(node);
+ }
+ }
+
+ return {
+
+ Program() {
+ globalScope = context.getScope();
+ },
+
+ Identifier(node) {
+ if (isRestricted(node.name) && shouldCheck(node)) {
+ report(node);
+ }
+ }
+ };
+ }
+};
diff --git a/eslint/lib/rules/id-length.js b/eslint/lib/rules/id-length.js
index a68873a..4df081f 100644
--- a/eslint/lib/rules/id-length.js
+++ b/eslint/lib/rules/id-length.js
@@ -39,6 +39,13 @@ module.exports = {
type: "string"
}
},
+ exceptionPatterns: {
+ type: "array",
+ uniqueItems: true,
+ items: {
+ type: "string"
+ }
+ },
properties: {
enum: ["always", "never"]
}
@@ -57,14 +64,20 @@ module.exports = {
const minLength = typeof options.min !== "undefined" ? options.min : 2;
const maxLength = typeof options.max !== "undefined" ? options.max : Infinity;
const properties = options.properties !== "never";
- const exceptions = (options.exceptions ? options.exceptions : [])
- .reduce((obj, item) => {
- obj[item] = true;
-
- return obj;
- }, {});
+ const exceptions = new Set(options.exceptions);
+ const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u"));
const reportedNode = new Set();
+ /**
+ * Checks if a string matches the provided exception patterns
+ * @param {string} name The string to check.
+ * @returns {boolean} if the string is a match
+ * @private
+ */
+ function matchesExceptionPattern(name) {
+ return exceptionPatterns.some(pattern => pattern.test(name));
+ }
+
const SUPPORTED_EXPRESSIONS = {
MemberExpression: properties && function(parent) {
return !parent.computed && (
@@ -112,7 +125,7 @@ module.exports = {
const isShort = name.length < minLength;
const isLong = name.length > maxLength;
- if (!(isShort || isLong) || exceptions[name]) {
+ if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) {
return; // Nothing to report
}
diff --git a/eslint/lib/rules/id-match.js b/eslint/lib/rules/id-match.js
index b97a497..7e400d0 100644
--- a/eslint/lib/rules/id-match.js
+++ b/eslint/lib/rules/id-match.js
@@ -39,7 +39,8 @@ module.exports = {
type: "boolean",
default: false
}
- }
+ },
+ additionalProperties: false
}
],
messages: {
diff --git a/eslint/lib/rules/indent.js b/eslint/lib/rules/indent.js
index d576fde..1c0dccc 100644
--- a/eslint/lib/rules/indent.js
+++ b/eslint/lib/rules/indent.js
@@ -32,6 +32,7 @@ const KNOWN_NODES = new Set([
"BreakStatement",
"CallExpression",
"CatchClause",
+ "ChainExpression",
"ClassBody",
"ClassDeclaration",
"ClassExpression",
@@ -934,6 +935,24 @@ module.exports = {
parameterParens.add(openingParen);
parameterParens.add(closingParen);
+ /*
+ * If `?.` token exists, set desired offset for that.
+ * This logic is copied from `MemberExpression`'s.
+ */
+ if (node.optional) {
+ const dotToken = sourceCode.getTokenAfter(node.callee, astUtils.isQuestionDotToken);
+ const calleeParenCount = sourceCode.getTokensBetween(node.callee, dotToken, { filter: astUtils.isClosingParenToken }).length;
+ const firstTokenOfCallee = calleeParenCount
+ ? sourceCode.getTokenBefore(node.callee, { skip: calleeParenCount - 1 })
+ : sourceCode.getFirstToken(node.callee);
+ const lastTokenOfCallee = sourceCode.getTokenBefore(dotToken);
+ const offsetBase = lastTokenOfCallee.loc.end.line === openingParen.loc.start.line
+ ? lastTokenOfCallee
+ : firstTokenOfCallee;
+
+ offsets.setDesiredOffset(dotToken, offsetBase, 1);
+ }
+
const offsetAfterToken = node.callee.type === "TaggedTemplateExpression" ? sourceCode.getFirstToken(node.callee.quasi) : openingParen;
const offsetToken = sourceCode.getTokenBefore(offsetAfterToken);
@@ -1065,16 +1084,17 @@ module.exports = {
},
ArrowFunctionExpression(node) {
- const firstToken = sourceCode.getFirstToken(node);
+ const maybeOpeningParen = sourceCode.getFirstToken(node, { skip: node.async ? 1 : 0 });
- if (astUtils.isOpeningParenToken(firstToken)) {
- const openingParen = firstToken;
+ if (astUtils.isOpeningParenToken(maybeOpeningParen)) {
+ const openingParen = maybeOpeningParen;
const closingParen = sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken);
parameterParens.add(openingParen);
parameterParens.add(closingParen);
addElementListIndent(node.params, openingParen, closingParen, options.FunctionExpression.parameters);
}
+
addBlocklessNodeIndent(node.body);
},
diff --git a/eslint/lib/rules/index.js b/eslint/lib/rules/index.js
index 9e5571d..3cf26e5 100644
--- a/eslint/lib/rules/index.js
+++ b/eslint/lib/rules/index.js
@@ -57,6 +57,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"guard-for-in": () => require("./guard-for-in"),
"handle-callback-err": () => require("./handle-callback-err"),
"id-blacklist": () => require("./id-blacklist"),
+ "id-denylist": () => require("./id-denylist"),
"id-length": () => require("./id-length"),
"id-match": () => require("./id-match"),
"implicit-arrow-linebreak": () => require("./implicit-arrow-linebreak"),
@@ -176,6 +177,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-plusplus": () => require("./no-plusplus"),
"no-process-env": () => require("./no-process-env"),
"no-process-exit": () => require("./no-process-exit"),
+ "no-promise-executor-return": () => require("./no-promise-executor-return"),
"no-proto": () => require("./no-proto"),
"no-prototype-builtins": () => require("./no-prototype-builtins"),
"no-redeclare": () => require("./no-redeclare"),
@@ -212,6 +214,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-unmodified-loop-condition": () => require("./no-unmodified-loop-condition"),
"no-unneeded-ternary": () => require("./no-unneeded-ternary"),
"no-unreachable": () => require("./no-unreachable"),
+ "no-unreachable-loop": () => require("./no-unreachable-loop"),
"no-unsafe-finally": () => require("./no-unsafe-finally"),
"no-unsafe-negation": () => require("./no-unsafe-negation"),
"no-unused-expressions": () => require("./no-unused-expressions"),
diff --git a/eslint/lib/rules/key-spacing.js b/eslint/lib/rules/key-spacing.js
index 57abb00..fc885a1 100644
--- a/eslint/lib/rules/key-spacing.js
+++ b/eslint/lib/rules/key-spacing.js
@@ -433,11 +433,15 @@ module.exports = {
tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
isKeySide = side === "key",
- locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
isExtra = diff > 0,
diffAbs = Math.abs(diff),
spaces = Array(diffAbs + 1).join(" ");
+ const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
+ const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
+ const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
+ const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;
+
if ((
diff && mode === "strict" ||
diff < 0 && mode === "minimum" ||
@@ -482,7 +486,7 @@ module.exports = {
context.report({
node: property[side],
- loc: locStart,
+ loc,
messageId,
data: {
computed: property.computed ? "computed " : "",
diff --git a/eslint/lib/rules/keyword-spacing.js b/eslint/lib/rules/keyword-spacing.js
index 99979a3..913cf46 100644
--- a/eslint/lib/rules/keyword-spacing.js
+++ b/eslint/lib/rules/keyword-spacing.js
@@ -126,7 +126,7 @@ module.exports = {
!sourceCode.isSpaceBetweenTokens(prevToken, token)
) {
context.report({
- loc: token.loc.start,
+ loc: token.loc,
messageId: "expectedBefore",
data: token,
fix(fixer) {
@@ -178,7 +178,7 @@ module.exports = {
!sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
context.report({
- loc: token.loc.start,
+ loc: token.loc,
messageId: "expectedAfter",
data: token,
fix(fixer) {
diff --git a/eslint/lib/rules/max-len.js b/eslint/lib/rules/max-len.js
index 995e0c5..dd76760 100644
--- a/eslint/lib/rules/max-len.js
+++ b/eslint/lib/rules/max-len.js
@@ -383,11 +383,22 @@ module.exports = {
return;
}
+ const loc = {
+ start: {
+ line: lineNumber,
+ column: 0
+ },
+ end: {
+ line: lineNumber,
+ column: textToMeasure.length
+ }
+ };
+
if (commentLengthApplies) {
if (lineLength > maxCommentLength) {
context.report({
node,
- loc: { line: lineNumber, column: 0 },
+ loc,
messageId: "maxComment",
data: {
lineLength,
@@ -398,7 +409,7 @@ module.exports = {
} else if (lineLength > maxLength) {
context.report({
node,
- loc: { line: lineNumber, column: 0 },
+ loc,
messageId: "max",
data: {
lineLength,
diff --git a/eslint/lib/rules/max-lines.js b/eslint/lib/rules/max-lines.js
index 299377b..0c7e761 100644
--- a/eslint/lib/rules/max-lines.js
+++ b/eslint/lib/rules/max-lines.js
@@ -53,7 +53,8 @@ module.exports = {
}
],
messages: {
- exceed: "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
+ exceed:
+ "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
}
},
@@ -61,7 +62,10 @@ module.exports = {
const option = context.options[0];
let max = 300;
- if (typeof option === "object" && Object.prototype.hasOwnProperty.call(option, "max")) {
+ if (
+ typeof option === "object" &&
+ Object.prototype.hasOwnProperty.call(option, "max")
+ ) {
max = option.max;
} else if (typeof option === "number") {
max = option;
@@ -94,7 +98,9 @@ module.exports = {
token = comment;
do {
- token = sourceCode.getTokenBefore(token, { includeComments: true });
+ token = sourceCode.getTokenBefore(token, {
+ includeComments: true
+ });
} while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(token, comment)) {
@@ -103,7 +109,9 @@ module.exports = {
token = comment;
do {
- token = sourceCode.getTokenAfter(token, { includeComments: true });
+ token = sourceCode.getTokenAfter(token, {
+ includeComments: true
+ });
} while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(comment, token)) {
@@ -118,7 +126,18 @@ module.exports = {
return {
"Program:exit"() {
- let lines = sourceCode.lines.map((text, i) => ({ lineNumber: i + 1, text }));
+ let lines = sourceCode.lines.map((text, i) => ({
+ lineNumber: i + 1,
+ text
+ }));
+
+ /*
+ * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end.
+ * That isn't a real line, so we shouldn't count it.
+ */
+ if (lines.length > 1 && lodash.last(lines).text === "") {
+ lines.pop();
+ }
if (skipBlankLines) {
lines = lines.filter(l => l.text.trim() !== "");
@@ -127,14 +146,29 @@ module.exports = {
if (skipComments) {
const comments = sourceCode.getAllComments();
- const commentLines = lodash.flatten(comments.map(comment => getLinesWithoutCode(comment)));
+ const commentLines = lodash.flatten(
+ comments.map(comment => getLinesWithoutCode(comment))
+ );
- lines = lines.filter(l => !lodash.includes(commentLines, l.lineNumber));
+ lines = lines.filter(
+ l => !lodash.includes(commentLines, l.lineNumber)
+ );
}
if (lines.length > max) {
+ const loc = {
+ start: {
+ line: lines[max].lineNumber,
+ column: 0
+ },
+ end: {
+ line: sourceCode.lines.length,
+ column: lodash.last(sourceCode.lines).length
+ }
+ };
+
context.report({
- loc: { line: 1, column: 0 },
+ loc,
messageId: "exceed",
data: {
max,
diff --git a/eslint/lib/rules/new-cap.js b/eslint/lib/rules/new-cap.js
index 0faf45e..4249a54 100644
--- a/eslint/lib/rules/new-cap.js
+++ b/eslint/lib/rules/new-cap.js
@@ -158,15 +158,9 @@ module.exports = {
* @returns {string} name
*/
function extractNameFromExpression(node) {
-
- let name = "";
-
- if (node.callee.type === "MemberExpression") {
- name = astUtils.getStaticPropertyName(node.callee) || "";
- } else {
- name = node.callee.name;
- }
- return name;
+ return node.callee.type === "Identifier"
+ ? node.callee.name
+ : astUtils.getStaticPropertyName(node.callee) || "";
}
/**
@@ -212,14 +206,16 @@ module.exports = {
return true;
}
- if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
+ const callee = astUtils.skipChainExpression(node.callee);
+
+ if (calleeName === "UTC" && callee.type === "MemberExpression") {
// allow if callee is Date.UTC
- return node.callee.object.type === "Identifier" &&
- node.callee.object.name === "Date";
+ return callee.object.type === "Identifier" &&
+ callee.object.name === "Date";
}
- return skipProperties && node.callee.type === "MemberExpression";
+ return skipProperties && callee.type === "MemberExpression";
}
/**
@@ -229,7 +225,7 @@ module.exports = {
* @returns {void}
*/
function report(node, messageId) {
- let callee = node.callee;
+ let callee = astUtils.skipChainExpression(node.callee);
if (callee.type === "MemberExpression") {
callee = callee.property;
diff --git a/eslint/lib/rules/newline-per-chained-call.js b/eslint/lib/rules/newline-per-chained-call.js
index 4254fec..46c9d6c 100644
--- a/eslint/lib/rules/newline-per-chained-call.js
+++ b/eslint/lib/rules/newline-per-chained-call.js
@@ -57,7 +57,16 @@ module.exports = {
* @returns {string} The prefix of the node.
*/
function getPrefix(node) {
- return node.computed ? "[" : ".";
+ if (node.computed) {
+ if (node.optional) {
+ return "?.[";
+ }
+ return "[";
+ }
+ if (node.optional) {
+ return "?.";
+ }
+ return ".";
}
/**
@@ -76,17 +85,18 @@ module.exports = {
return {
"CallExpression:exit"(node) {
- if (!node.callee || node.callee.type !== "MemberExpression") {
+ const callee = astUtils.skipChainExpression(node.callee);
+
+ if (callee.type !== "MemberExpression") {
return;
}
- const callee = node.callee;
- let parent = callee.object;
+ let parent = astUtils.skipChainExpression(callee.object);
let depth = 1;
while (parent && parent.callee) {
depth += 1;
- parent = parent.callee.object;
+ parent = astUtils.skipChainExpression(astUtils.skipChainExpression(parent.callee).object);
}
if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
diff --git a/eslint/lib/rules/no-alert.js b/eslint/lib/rules/no-alert.js
index 22d0dd5..702b4d2 100644
--- a/eslint/lib/rules/no-alert.js
+++ b/eslint/lib/rules/no-alert.js
@@ -10,7 +10,8 @@
const {
getStaticPropertyName: getPropertyName,
- getVariableByName
+ getVariableByName,
+ skipChainExpression
} = require("./utils/ast-utils");
//------------------------------------------------------------------------------
@@ -64,7 +65,13 @@ function isGlobalThisReferenceOrGlobalWindow(scope, node) {
if (scope.type === "global" && node.type === "ThisExpression") {
return true;
}
- if (node.name === "window" || (node.name === "globalThis" && getVariableByName(scope, "globalThis"))) {
+ if (
+ node.type === "Identifier" &&
+ (
+ node.name === "window" ||
+ (node.name === "globalThis" && getVariableByName(scope, "globalThis"))
+ )
+ ) {
return !isShadowed(scope, node);
}
@@ -96,7 +103,7 @@ module.exports = {
create(context) {
return {
CallExpression(node) {
- const callee = node.callee,
+ const callee = skipChainExpression(node.callee),
currentScope = context.getScope();
// without window.
diff --git a/eslint/lib/rules/no-constant-condition.js b/eslint/lib/rules/no-constant-condition.js
index 5e58386..790d5ea 100644
--- a/eslint/lib/rules/no-constant-condition.js
+++ b/eslint/lib/rules/no-constant-condition.js
@@ -9,9 +9,6 @@
// Helpers
//------------------------------------------------------------------------------
-const EQUALITY_OPERATORS = ["===", "!==", "==", "!="];
-const RELATIONAL_OPERATORS = [">", "<", ">=", "<=", "in", "instanceof"];
-
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -56,6 +53,35 @@ module.exports = {
// Helpers
//--------------------------------------------------------------------------
+ /**
+ * Returns literal's value converted to the Boolean type
+ * @param {ASTNode} node any `Literal` node
+ * @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
+ * `null` when it cannot be determined.
+ */
+ function getBooleanValue(node) {
+ if (node.value === null) {
+
+ /*
+ * it might be a null literal or bigint/regex literal in unsupported environments .
+ * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
+ * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
+ */
+
+ if (node.raw === "null") {
+ return false;
+ }
+
+ // regex is always truthy
+ if (typeof node.regex === "object") {
+ return true;
+ }
+
+ return null;
+ }
+
+ return !!node.value;
+ }
/**
* Checks if a branch node of LogicalExpression short circuits the whole condition
@@ -66,15 +92,23 @@ module.exports = {
function isLogicalIdentity(node, operator) {
switch (node.type) {
case "Literal":
- return (operator === "||" && node.value === true) ||
- (operator === "&&" && node.value === false);
+ return (operator === "||" && getBooleanValue(node) === true) ||
+ (operator === "&&" && getBooleanValue(node) === false);
case "UnaryExpression":
return (operator === "&&" && node.operator === "void");
case "LogicalExpression":
- return isLogicalIdentity(node.left, node.operator) ||
- isLogicalIdentity(node.right, node.operator);
+
+ /*
+ * handles `a && false || b`
+ * `false` is an identity element of `&&` but not `||`
+ */
+ return operator === node.operator &&
+ (
+ isLogicalIdentity(node.left, node.operator) ||
+ isLogicalIdentity(node.right, node.operator)
+ );
// no default
}
@@ -129,21 +163,9 @@ module.exports = {
const isLeftConstant = isConstant(node.left, inBooleanPosition);
const isRightConstant = isConstant(node.right, inBooleanPosition);
const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
- const isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator));
+ const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
return (isLeftConstant && isRightConstant) ||
- (
-
- // in the case of an "OR", we need to know if the right constant value is truthy
- node.operator === "||" &&
- isRightConstant &&
- node.right.value &&
- (
- !node.parent ||
- node.parent.type !== "BinaryExpression" ||
- !(EQUALITY_OPERATORS.includes(node.parent.operator) || RELATIONAL_OPERATORS.includes(node.parent.operator))
- )
- ) ||
isLeftShortCircuit ||
isRightShortCircuit;
}
diff --git a/eslint/lib/rules/no-duplicate-case.js b/eslint/lib/rules/no-duplicate-case.js
index c8a0fa9..e2d9665 100644
--- a/eslint/lib/rules/no-duplicate-case.js
+++ b/eslint/lib/rules/no-duplicate-case.js
@@ -6,6 +6,12 @@
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -31,18 +37,31 @@ module.exports = {
create(context) {
const sourceCode = context.getSourceCode();
+ /**
+ * Determines whether the two given nodes are considered to be equal.
+ * @param {ASTNode} a First node.
+ * @param {ASTNode} b Second node.
+ * @returns {boolean} `true` if the nodes are considered to be equal.
+ */
+ function equal(a, b) {
+ if (a.type !== b.type) {
+ return false;
+ }
+
+ return astUtils.equalTokens(a, b, sourceCode);
+ }
return {
SwitchStatement(node) {
- const previousKeys = new Set();
+ const previousTests = [];
for (const switchCase of node.cases) {
if (switchCase.test) {
- const key = sourceCode.getText(switchCase.test);
+ const test = switchCase.test;
- if (previousKeys.has(key)) {
+ if (previousTests.some(previousTest => equal(previousTest, test))) {
context.report({ node: switchCase, messageId: "unexpected" });
} else {
- previousKeys.add(key);
+ previousTests.push(test);
}
}
}
diff --git a/eslint/lib/rules/no-eval.js b/eslint/lib/rules/no-eval.js
index 811ad4e..a020fde 100644
--- a/eslint/lib/rules/no-eval.js
+++ b/eslint/lib/rules/no-eval.js
@@ -21,38 +21,6 @@ const candidatesOfGlobalObject = Object.freeze([
"globalThis"
]);
-/**
- * Checks a given node is a Identifier node of the specified name.
- * @param {ASTNode} node A node to check.
- * @param {string} name A name to check.
- * @returns {boolean} `true` if the node is a Identifier node of the name.
- */
-function isIdentifier(node, name) {
- return node.type === "Identifier" && node.name === name;
-}
-
-/**
- * Checks a given node is a Literal node of the specified string value.
- * @param {ASTNode} node A node to check.
- * @param {string} name A name to check.
- * @returns {boolean} `true` if the node is a Literal node of the name.
- */
-function isConstant(node, name) {
- switch (node.type) {
- case "Literal":
- return node.value === name;
-
- case "TemplateLiteral":
- return (
- node.expressions.length === 0 &&
- node.quasis[0].value.cooked === name
- );
-
- default:
- return false;
- }
-}
-
/**
* Checks a given node is a MemberExpression node which has the specified name's
* property.
@@ -62,10 +30,7 @@ function isConstant(node, name) {
* the specified name's property
*/
function isMember(node, name) {
- return (
- node.type === "MemberExpression" &&
- (node.computed ? isConstant : isIdentifier)(node.property, name)
- );
+ return astUtils.isSpecificMemberAccess(node, null, name);
}
//------------------------------------------------------------------------------
@@ -230,7 +195,12 @@ module.exports = {
"CallExpression:exit"(node) {
const callee = node.callee;
- if (isIdentifier(callee, "eval")) {
+ /*
+ * Optional call (`eval?.("code")`) is not direct eval.
+ * The direct eval is only step 6.a.vi of https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation
+ * But the optional call is https://tc39.es/ecma262/#sec-optional-chaining-chain-evaluation
+ */
+ if (!node.optional && astUtils.isSpecificId(callee, "eval")) {
report(callee);
}
}
@@ -241,7 +211,7 @@ module.exports = {
"CallExpression:exit"(node) {
const callee = node.callee;
- if (isIdentifier(callee, "eval")) {
+ if (astUtils.isSpecificId(callee, "eval")) {
report(callee);
}
},
diff --git a/eslint/lib/rules/no-extend-native.js b/eslint/lib/rules/no-extend-native.js
index 7ab25ab..db365b5 100644
--- a/eslint/lib/rules/no-extend-native.js
+++ b/eslint/lib/rules/no-extend-native.js
@@ -12,12 +12,6 @@
const astUtils = require("./utils/ast-utils");
const globals = require("globals");
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-const propertyDefinitionMethods = new Set(["defineProperty", "defineProperties"]);
-
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -100,40 +94,30 @@ module.exports = {
}
/**
- * Checks that an identifier is an object of a prototype whose member
- * is being assigned in an AssignmentExpression.
- * Example: Object.prototype.foo = "bar"
- * @param {ASTNode} identifierNode The identifier to check.
- * @returns {boolean} True if the identifier's prototype is modified.
+ * Check if it's an assignment to the property of the given node.
+ * Example: `*.prop = 0` // the `*` is the given node.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} True if an assignment to the property of the node.
*/
- function isInPrototypePropertyAssignment(identifierNode) {
- return Boolean(
- isPrototypePropertyAccessed(identifierNode) &&
- identifierNode.parent.parent.type === "MemberExpression" &&
- identifierNode.parent.parent.parent.type === "AssignmentExpression" &&
- identifierNode.parent.parent.parent.left === identifierNode.parent.parent
+ function isAssigningToPropertyOf(node) {
+ return (
+ node.parent.type === "MemberExpression" &&
+ node.parent.object === node &&
+ node.parent.parent.type === "AssignmentExpression" &&
+ node.parent.parent.left === node.parent
);
}
/**
- * Checks that an identifier is an object of a prototype whose member
- * is being extended via the Object.defineProperty() or
- * Object.defineProperties() methods.
- * Example: Object.defineProperty(Array.prototype, "foo", ...)
- * Example: Object.defineProperties(Array.prototype, ...)
- * @param {ASTNode} identifierNode The identifier to check.
- * @returns {boolean} True if the identifier's prototype is modified.
+ * Checks if the given node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} True if the node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
*/
- function isInDefinePropertyCall(identifierNode) {
- return Boolean(
- isPrototypePropertyAccessed(identifierNode) &&
- identifierNode.parent.parent.type === "CallExpression" &&
- identifierNode.parent.parent.arguments[0] === identifierNode.parent &&
- identifierNode.parent.parent.callee.type === "MemberExpression" &&
- identifierNode.parent.parent.callee.object.type === "Identifier" &&
- identifierNode.parent.parent.callee.object.name === "Object" &&
- identifierNode.parent.parent.callee.property.type === "Identifier" &&
- propertyDefinitionMethods.has(identifierNode.parent.parent.callee.property.name)
+ function isInDefinePropertyCall(node) {
+ return (
+ node.parent.type === "CallExpression" &&
+ node.parent.arguments[0] === node &&
+ astUtils.isSpecificMemberAccess(node.parent.callee, "Object", /^definePropert(?:y|ies)$/u)
);
}
@@ -149,14 +133,27 @@ module.exports = {
* @returns {void}
*/
function checkAndReportPrototypeExtension(identifierNode) {
- if (isInPrototypePropertyAssignment(identifierNode)) {
+ if (!isPrototypePropertyAccessed(identifierNode)) {
+ return; // This is not `*.prototype` access.
+ }
- // Identifier --> MemberExpression --> MemberExpression --> AssignmentExpression
- reportNode(identifierNode.parent.parent.parent, identifierNode.name);
- } else if (isInDefinePropertyCall(identifierNode)) {
+ /*
+ * `identifierNode.parent` is a MamberExpression `*.prototype`.
+ * If it's an optional member access, it may be wrapped by a `ChainExpression` node.
+ */
+ const prototypeNode =
+ identifierNode.parent.parent.type === "ChainExpression"
+ ? identifierNode.parent.parent
+ : identifierNode.parent;
- // Identifier --> MemberExpression --> CallExpression
- reportNode(identifierNode.parent.parent, identifierNode.name);
+ if (isAssigningToPropertyOf(prototypeNode)) {
+
+ // `*.prototype` -> MemberExpression -> AssignmentExpression
+ reportNode(prototypeNode.parent.parent, identifierNode.name);
+ } else if (isInDefinePropertyCall(prototypeNode)) {
+
+ // `*.prototype` -> CallExpression
+ reportNode(prototypeNode.parent, identifierNode.name);
}
}
diff --git a/eslint/lib/rules/no-extra-bind.js b/eslint/lib/rules/no-extra-bind.js
index df69592..2db440d 100644
--- a/eslint/lib/rules/no-extra-bind.js
+++ b/eslint/lib/rules/no-extra-bind.js
@@ -61,24 +61,62 @@ module.exports = {
* @returns {void}
*/
function report(node) {
+ const memberNode = node.parent;
+ const callNode = memberNode.parent.type === "ChainExpression"
+ ? memberNode.parent.parent
+ : memberNode.parent;
+
context.report({
- node: node.parent.parent,
+ node: callNode,
messageId: "unexpected",
- loc: node.parent.property.loc,
+ loc: memberNode.property.loc,
+
fix(fixer) {
- if (node.parent.parent.arguments.length && !isSideEffectFree(node.parent.parent.arguments[0])) {
+ if (!isSideEffectFree(callNode.arguments[0])) {
return null;
}
- const firstTokenToRemove = sourceCode
- .getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
- const lastTokenToRemove = sourceCode.getLastToken(node.parent.parent);
+ /*
+ * The list of the first/last token pair of a removal range.
+ * This is two parts because closing parentheses may exist between the method name and arguments.
+ * E.g. `(function(){}.bind ) (obj)`
+ * ^^^^^ ^^^^^ < removal ranges
+ * E.g. `(function(){}?.['bind'] ) ?.(obj)`
+ * ^^^^^^^^^^ ^^^^^^^ < removal ranges
+ */
+ const tokenPairs = [
+ [
+
+ // `.`, `?.`, or `[` token.
+ sourceCode.getTokenAfter(
+ memberNode.object,
+ astUtils.isNotClosingParenToken
+ ),
+
+ // property name or `]` token.
+ sourceCode.getLastToken(memberNode)
+ ],
+ [
+
+ // `?.` or `(` token of arguments.
+ sourceCode.getTokenAfter(
+ memberNode,
+ astUtils.isNotClosingParenToken
+ ),
+
+ // `)` token of arguments.
+ sourceCode.getLastToken(callNode)
+ ]
+ ];
+ const firstTokenToRemove = tokenPairs[0][0];
+ const lastTokenToRemove = tokenPairs[1][1];
if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
return null;
}
- return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]);
+ return tokenPairs.map(([start, end]) =>
+ fixer.removeRange([start.range[0], end.range[1]]));
}
});
}
@@ -93,18 +131,20 @@ module.exports = {
* @returns {boolean} `true` if the node is the callee of `.bind()` method.
*/
function isCalleeOfBindMethod(node) {
- const parent = node.parent;
- const grandparent = parent.parent;
+ if (!astUtils.isSpecificMemberAccess(node.parent, null, "bind")) {
+ return false;
+ }
+
+ // The node of `*.bind` member access.
+ const bindNode = node.parent.parent.type === "ChainExpression"
+ ? node.parent.parent
+ : node.parent;
return (
- grandparent &&
- grandparent.type === "CallExpression" &&
- grandparent.callee === parent &&
- grandparent.arguments.length === 1 &&
- grandparent.arguments[0].type !== "SpreadElement" &&
- parent.type === "MemberExpression" &&
- parent.object === node &&
- astUtils.getStaticPropertyName(parent) === "bind"
+ bindNode.parent.type === "CallExpression" &&
+ bindNode.parent.callee === bindNode &&
+ bindNode.parent.arguments.length === 1 &&
+ bindNode.parent.arguments[0].type !== "SpreadElement"
);
}
diff --git a/eslint/lib/rules/no-extra-boolean-cast.js b/eslint/lib/rules/no-extra-boolean-cast.js
index b90757b..6ae3ea6 100644
--- a/eslint/lib/rules/no-extra-boolean-cast.js
+++ b/eslint/lib/rules/no-extra-boolean-cast.js
@@ -111,6 +111,10 @@ module.exports = {
* @returns {boolean} If the node is in one of the flagged contexts
*/
function isInFlaggedContext(node) {
+ if (node.parent.type === "ChainExpression") {
+ return isInFlaggedContext(node.parent);
+ }
+
return isInBooleanContext(node) ||
(isLogicalContext(node.parent) &&
@@ -149,6 +153,9 @@ module.exports = {
* @returns {boolean} `true` if the node needs to be parenthesized.
*/
function needsParens(previousNode, node) {
+ if (previousNode.parent.type === "ChainExpression") {
+ return needsParens(previousNode.parent, node);
+ }
if (isParenthesized(previousNode)) {
// parentheses around the previous node will stay, so there is no need for an additional pair
diff --git a/eslint/lib/rules/no-extra-parens.js b/eslint/lib/rules/no-extra-parens.js
index bae1a49..7afe762 100644
--- a/eslint/lib/rules/no-extra-parens.js
+++ b/eslint/lib/rules/no-extra-parens.js
@@ -100,10 +100,18 @@ module.exports = {
* @private
*/
function isImmediateFunctionPrototypeMethodCall(node) {
- return node.type === "CallExpression" &&
- node.callee.type === "MemberExpression" &&
- node.callee.object.type === "FunctionExpression" &&
- ["call", "apply"].includes(astUtils.getStaticPropertyName(node.callee));
+ const callNode = astUtils.skipChainExpression(node);
+
+ if (callNode.type !== "CallExpression") {
+ return false;
+ }
+ const callee = astUtils.skipChainExpression(callNode.callee);
+
+ return (
+ callee.type === "MemberExpression" &&
+ callee.object.type === "FunctionExpression" &&
+ ["call", "apply"].includes(astUtils.getStaticPropertyName(callee))
+ );
}
/**
@@ -360,7 +368,9 @@ module.exports = {
* @returns {boolean} `true` if the given node is an IIFE
*/
function isIIFE(node) {
- return node.type === "CallExpression" && node.callee.type === "FunctionExpression";
+ const maybeCallNode = astUtils.skipChainExpression(node);
+
+ return maybeCallNode.type === "CallExpression" && maybeCallNode.callee.type === "FunctionExpression";
}
/**
@@ -466,13 +476,16 @@ module.exports = {
if (
hasDoubleExcessParens(callee) ||
- !isIIFE(node) && !hasNewParensException && !(
+ !isIIFE(node) &&
+ !hasNewParensException &&
+ !(
// Allow extra parens around a new expression if they are intervening parentheses.
node.type === "NewExpression" &&
callee.type === "MemberExpression" &&
doesMemberExpressionContainCallExpression(callee)
- )
+ ) &&
+ !(!node.optional && callee.type === "ChainExpression")
) {
report(node.callee);
}
@@ -710,6 +723,20 @@ module.exports = {
reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node);
}
+ /**
+ * Checks whether a node is a MemberExpression at NewExpression's callee.
+ * @param {ASTNode} node node to check.
+ * @returns {boolean} True if the node is a MemberExpression at NewExpression's callee. false otherwise.
+ */
+ function isMemberExpInNewCallee(node) {
+ if (node.type === "MemberExpression") {
+ return node.parent.type === "NewExpression" && node.parent.callee === node
+ ? true
+ : node.parent.object === node && isMemberExpInNewCallee(node.parent);
+ }
+ return false;
+ }
+
return {
ArrayExpression(node) {
node.elements
@@ -950,7 +977,11 @@ module.exports = {
LogicalExpression: checkBinaryLogical,
MemberExpression(node) {
- const nodeObjHasExcessParens = hasExcessParens(node.object) &&
+ const shouldAllowWrapOnce = isMemberExpInNewCallee(node) &&
+ doesMemberExpressionContainCallExpression(node);
+ const nodeObjHasExcessParens = shouldAllowWrapOnce
+ ? hasDoubleExcessParens(node.object)
+ : hasExcessParens(node.object) &&
!(
isImmediateFunctionPrototypeMethodCall(node.parent) &&
node.parent.callee === node &&
@@ -974,8 +1005,8 @@ module.exports = {
}
if (nodeObjHasExcessParens &&
- node.object.type === "CallExpression" &&
- node.parent.type !== "NewExpression") {
+ node.object.type === "CallExpression"
+ ) {
report(node.object);
}
@@ -986,6 +1017,13 @@ module.exports = {
report(node.object);
}
+ if (nodeObjHasExcessParens &&
+ node.optional &&
+ node.object.type === "ChainExpression"
+ ) {
+ report(node.object);
+ }
+
if (node.computed && hasExcessParens(node.property)) {
report(node.property);
}
@@ -1071,7 +1109,22 @@ module.exports = {
},
UnaryExpression: checkArgumentWithPrecedence,
- UpdateExpression: checkArgumentWithPrecedence,
+ UpdateExpression(node) {
+ if (node.prefix) {
+ checkArgumentWithPrecedence(node);
+ } else {
+ const { argument } = node;
+ const operatorToken = sourceCode.getLastToken(node);
+
+ if (argument.loc.end.line === operatorToken.loc.start.line) {
+ checkArgumentWithPrecedence(node);
+ } else {
+ if (hasDoubleExcessParens(argument)) {
+ report(argument);
+ }
+ }
+ }
+ },
AwaitExpression: checkArgumentWithPrecedence,
VariableDeclarator(node) {
diff --git a/eslint/lib/rules/no-implicit-coercion.js b/eslint/lib/rules/no-implicit-coercion.js
index 6d5ee61..a639711 100644
--- a/eslint/lib/rules/no-implicit-coercion.js
+++ b/eslint/lib/rules/no-implicit-coercion.js
@@ -47,12 +47,14 @@ function isDoubleLogicalNegating(node) {
* @returns {boolean} Whether or not the node is a binary negating of `.indexOf()` method calling.
*/
function isBinaryNegatingOfIndexOf(node) {
+ if (node.operator !== "~") {
+ return false;
+ }
+ const callNode = astUtils.skipChainExpression(node.argument);
+
return (
- node.operator === "~" &&
- node.argument.type === "CallExpression" &&
- node.argument.callee.type === "MemberExpression" &&
- node.argument.callee.property.type === "Identifier" &&
- INDEX_OF_PATTERN.test(node.argument.callee.property.name)
+ callNode.type === "CallExpression" &&
+ astUtils.isSpecificMemberAccess(callNode.callee, null, INDEX_OF_PATTERN)
);
}
@@ -246,7 +248,10 @@ module.exports = {
// ~foo.indexOf(bar)
operatorAllowed = options.allow.indexOf("~") >= 0;
if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) {
- const recommendation = `${sourceCode.getText(node.argument)} !== -1`;
+
+ // `foo?.indexOf(bar) !== -1` will be true (== found) if the `foo` is nullish. So use `>= 0` in that case.
+ const comparison = node.argument.type === "ChainExpression" ? ">= 0" : "!== -1";
+ const recommendation = `${sourceCode.getText(node.argument)} ${comparison}`;
report(node, recommendation, false);
}
diff --git a/eslint/lib/rules/no-implied-eval.js b/eslint/lib/rules/no-implied-eval.js
index 1668a04..b8120a6 100644
--- a/eslint/lib/rules/no-implied-eval.js
+++ b/eslint/lib/rules/no-implied-eval.js
@@ -35,8 +35,8 @@ module.exports = {
},
create(context) {
- const EVAL_LIKE_FUNCS = Object.freeze(["setTimeout", "execScript", "setInterval"]);
const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]);
+ const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u;
/**
* Checks whether a node is evaluated as a string or not.
@@ -56,28 +56,6 @@ module.exports = {
return false;
}
- /**
- * Checks whether a node is an Identifier node named one of the specified names.
- * @param {ASTNode} node A node to check.
- * @param {string[]} specifiers Array of specified name.
- * @returns {boolean} True if the node is a Identifier node which has specified name.
- */
- function isSpecifiedIdentifier(node, specifiers) {
- return node.type === "Identifier" && specifiers.includes(node.name);
- }
-
- /**
- * Checks a given node is a MemberExpression node which has the specified name's
- * property.
- * @param {ASTNode} node A node to check.
- * @param {string[]} specifiers Array of specified name.
- * @returns {boolean} `true` if the node is a MemberExpression node which has
- * the specified name's property
- */
- function isSpecifiedMember(node, specifiers) {
- return node.type === "MemberExpression" && specifiers.includes(astUtils.getStaticPropertyName(node));
- }
-
/**
* Reports if the `CallExpression` node has evaluated argument.
* @param {ASTNode} node A CallExpression to check.
@@ -114,14 +92,15 @@ module.exports = {
const identifier = ref.identifier;
let node = identifier.parent;
- while (isSpecifiedMember(node, [name])) {
+ while (astUtils.isSpecificMemberAccess(node, null, name)) {
node = node.parent;
}
- if (isSpecifiedMember(node, EVAL_LIKE_FUNCS)) {
- const parent = node.parent;
+ if (astUtils.isSpecificMemberAccess(node, null, EVAL_LIKE_FUNC_PATTERN)) {
+ const calleeNode = node.parent.type === "ChainExpression" ? node.parent : node;
+ const parent = calleeNode.parent;
- if (parent.type === "CallExpression" && parent.callee === node) {
+ if (parent.type === "CallExpression" && parent.callee === calleeNode) {
reportImpliedEvalCallExpression(parent);
}
}
@@ -134,7 +113,7 @@ module.exports = {
return {
CallExpression(node) {
- if (isSpecifiedIdentifier(node.callee, EVAL_LIKE_FUNCS)) {
+ if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) {
reportImpliedEvalCallExpression(node);
}
},
diff --git a/eslint/lib/rules/no-import-assign.js b/eslint/lib/rules/no-import-assign.js
index 32e445f..7a349bb 100644
--- a/eslint/lib/rules/no-import-assign.js
+++ b/eslint/lib/rules/no-import-assign.js
@@ -9,16 +9,12 @@
// Helpers
//------------------------------------------------------------------------------
-const { findVariable, getPropertyName } = require("eslint-utils");
+const { findVariable } = require("eslint-utils");
+const astUtils = require("./utils/ast-utils");
-const MutationMethods = {
- Object: new Set([
- "assign", "defineProperties", "defineProperty", "freeze",
- "setPrototypeOf"
- ]),
- Reflect: new Set([
- "defineProperty", "deleteProperty", "set", "setPrototypeOf"
- ])
+const WellKnownMutationFunctions = {
+ Object: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u,
+ Reflect: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u
};
/**
@@ -56,17 +52,20 @@ function isAssignmentLeft(node) {
* @returns {boolean} `true` if the node is the operand of mutation unary operator.
*/
function isOperandOfMutationUnaryOperator(node) {
- const { parent } = node;
+ const argumentNode = node.parent.type === "ChainExpression"
+ ? node.parent
+ : node;
+ const { parent } = argumentNode;
return (
(
parent.type === "UpdateExpression" &&
- parent.argument === node
+ parent.argument === argumentNode
) ||
(
parent.type === "UnaryExpression" &&
parent.operator === "delete" &&
- parent.argument === node
+ parent.argument === argumentNode
)
);
}
@@ -92,35 +91,37 @@ function isIterationVariable(node) {
}
/**
- * Check if a given node is the iteration variable of `for-in`/`for-of` syntax.
+ * Check if a given node is at the first argument of a well-known mutation function.
+ * - `Object.assign`
+ * - `Object.defineProperty`
+ * - `Object.defineProperties`
+ * - `Object.freeze`
+ * - `Object.setPrototypeOf`
+ * - `Refrect.defineProperty`
+ * - `Refrect.deleteProperty`
+ * - `Refrect.set`
+ * - `Refrect.setPrototypeOf`
* @param {ASTNode} node The node to check.
* @param {Scope} scope A `escope.Scope` object to find variable (whichever).
- * @returns {boolean} `true` if the node is the iteration variable.
+ * @returns {boolean} `true` if the node is at the first argument of a well-known mutation function.
*/
function isArgumentOfWellKnownMutationFunction(node, scope) {
const { parent } = node;
- if (
- parent.type === "CallExpression" &&
- parent.arguments[0] === node &&
- parent.callee.type === "MemberExpression" &&
- parent.callee.object.type === "Identifier"
- ) {
- const { callee } = parent;
- const { object } = callee;
-
- if (Object.keys(MutationMethods).includes(object.name)) {
- const variable = findVariable(scope, object);
-
- return (
- variable !== null &&
- variable.scope.type === "global" &&
- MutationMethods[object.name].has(getPropertyName(callee, scope))
- );
- }
+ if (parent.type !== "CallExpression" || parent.arguments[0] !== node) {
+ return false;
}
+ const callee = astUtils.skipChainExpression(parent.callee);
- return false;
+ if (
+ !astUtils.isSpecificMemberAccess(callee, "Object", WellKnownMutationFunctions.Object) &&
+ !astUtils.isSpecificMemberAccess(callee, "Reflect", WellKnownMutationFunctions.Reflect)
+ ) {
+ return false;
+ }
+ const variable = findVariable(scope, callee.object);
+
+ return variable !== null && variable.scope.type === "global";
}
/**
diff --git a/eslint/lib/rules/no-inline-comments.js b/eslint/lib/rules/no-inline-comments.js
index 41b0f1e..dec2786 100644
--- a/eslint/lib/rules/no-inline-comments.js
+++ b/eslint/lib/rules/no-inline-comments.js
@@ -21,7 +21,17 @@ module.exports = {
url: "https://eslint.org/docs/rules/no-inline-comments"
},
- schema: [],
+ schema: [
+ {
+ type: "object",
+ properties: {
+ ignorePattern: {
+ type: "string"
+ }
+ },
+ additionalProperties: false
+ }
+ ],
messages: {
unexpectedInlineComment: "Unexpected comment inline with code."
@@ -30,6 +40,12 @@ module.exports = {
create(context) {
const sourceCode = context.getSourceCode();
+ const options = context.options[0];
+ let customIgnoreRegExp;
+
+ if (options && options.ignorePattern) {
+ customIgnoreRegExp = new RegExp(options.ignorePattern, "u");
+ }
/**
* Will check that comments are not on lines starting with or ending with code
@@ -51,6 +67,11 @@ module.exports = {
return;
}
+ // Matches the ignore pattern
+ if (customIgnoreRegExp && customIgnoreRegExp.test(node.value)) {
+ return;
+ }
+
// JSX Exception
if (
(isPreambleEmpty || preamble === "{") &&
@@ -80,9 +101,9 @@ module.exports = {
return {
Program() {
- const comments = sourceCode.getAllComments();
-
- comments.filter(token => token.type !== "Shebang").forEach(testCodeAroundComment);
+ sourceCode.getAllComments()
+ .filter(token => token.type !== "Shebang")
+ .forEach(testCodeAroundComment);
}
};
}
diff --git a/eslint/lib/rules/no-irregular-whitespace.js b/eslint/lib/rules/no-irregular-whitespace.js
index 2184233..0bf69b1 100644
--- a/eslint/lib/rules/no-irregular-whitespace.js
+++ b/eslint/lib/rules/no-irregular-whitespace.js
@@ -91,7 +91,7 @@ module.exports = {
const locStart = node.loc.start;
const locEnd = node.loc.end;
- errors = errors.filter(({ loc: errorLoc }) => {
+ errors = errors.filter(({ loc: { start: errorLoc } }) => {
if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) {
if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) {
return false;
@@ -160,15 +160,19 @@ module.exports = {
let match;
while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) {
- const location = {
- line: lineNumber,
- column: match.index
- };
-
errors.push({
node,
messageId: "noIrregularWhitespace",
- loc: location
+ loc: {
+ start: {
+ line: lineNumber,
+ column: match.index
+ },
+ end: {
+ line: lineNumber,
+ column: match.index + match[0].length
+ }
+ }
});
}
});
@@ -189,16 +193,22 @@ module.exports = {
while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) {
const lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0;
- const location = {
- line: lineIndex + 1,
- column: sourceLines[lineIndex].length
- };
errors.push({
node,
messageId: "noIrregularWhitespace",
- loc: location
+ loc: {
+ start: {
+ line: lineIndex + 1,
+ column: sourceLines[lineIndex].length
+ },
+ end: {
+ line: lineIndex + 2,
+ column: 0
+ }
+ }
});
+
lastLineIndex = lineIndex;
}
}
diff --git a/eslint/lib/rules/no-loss-of-precision.js b/eslint/lib/rules/no-loss-of-precision.js
index b95677c..9e28118 100644
--- a/eslint/lib/rules/no-loss-of-precision.js
+++ b/eslint/lib/rules/no-loss-of-precision.js
@@ -36,6 +36,14 @@ module.exports = {
return typeof node.value === "number";
}
+ /**
+ * Gets the source code of the given number literal. Removes `_` numeric separators from the result.
+ * @param {Node} node the number `Literal` node
+ * @returns {string} raw source code of the literal, without numeric separators
+ */
+ function getRaw(node) {
+ return node.raw.replace(/_/gu, "");
+ }
/**
* Checks whether the number is base ten
@@ -55,7 +63,7 @@ module.exports = {
* @returns {boolean} true if they do not match
*/
function notBaseTenLosesPrecision(node) {
- const rawString = node.raw.toUpperCase();
+ const rawString = getRaw(node).toUpperCase();
let base = 0;
if (rawString.startsWith("0B")) {
@@ -161,7 +169,7 @@ module.exports = {
* @returns {boolean} true if they do not match
*/
function baseTenLosesPrecision(node) {
- const normalizedRawNumber = convertNumberToScientificNotation(node.raw);
+ const normalizedRawNumber = convertNumberToScientificNotation(getRaw(node));
const requestedPrecision = normalizedRawNumber.split("e")[0].replace(".", "").length;
if (requestedPrecision > 100) {
diff --git a/eslint/lib/rules/no-magic-numbers.js b/eslint/lib/rules/no-magic-numbers.js
index cd07f5c..510b3f9 100644
--- a/eslint/lib/rules/no-magic-numbers.js
+++ b/eslint/lib/rules/no-magic-numbers.js
@@ -5,7 +5,7 @@
"use strict";
-const { isNumericLiteral } = require("./utils/ast-utils");
+const astUtils = require("./utils/ast-utils");
// Maximum array length by the ECMAScript Specification.
const MAX_ARRAY_LENGTH = 2 ** 32 - 1;
@@ -61,6 +61,10 @@ module.exports = {
ignoreArrayIndexes: {
type: "boolean",
default: false
+ },
+ ignoreDefaultValues: {
+ type: "boolean",
+ default: false
}
},
additionalProperties: false
@@ -77,7 +81,8 @@ module.exports = {
detectObjects = !!config.detectObjects,
enforceConst = !!config.enforceConst,
ignore = (config.ignore || []).map(normalizeIgnoreValue),
- ignoreArrayIndexes = !!config.ignoreArrayIndexes;
+ ignoreArrayIndexes = !!config.ignoreArrayIndexes,
+ ignoreDefaultValues = !!config.ignoreDefaultValues;
const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
@@ -90,6 +95,17 @@ module.exports = {
return ignore.indexOf(value) !== -1;
}
+ /**
+ * Returns whether the number is a default value assignment.
+ * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
+ * @returns {boolean} true if the number is a default value
+ */
+ function isDefaultValue(fullNumberNode) {
+ const parent = fullNumberNode.parent;
+
+ return parent.type === "AssignmentPattern" && parent.right === 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
@@ -100,12 +116,8 @@ module.exports = {
return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] &&
(
- parent.callee.name === "parseInt" ||
- (
- parent.callee.type === "MemberExpression" &&
- parent.callee.object.name === "Number" &&
- parent.callee.property.name === "parseInt"
- )
+ astUtils.isSpecificId(parent.callee, "parseInt") ||
+ astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt")
);
}
@@ -157,7 +169,7 @@ module.exports = {
return {
Literal(node) {
- if (!isNumericLiteral(node)) {
+ if (!astUtils.isNumericLiteral(node)) {
return;
}
@@ -176,9 +188,12 @@ module.exports = {
raw = node.raw;
}
+ const parent = fullNumberNode.parent;
+
// Always allow radix arguments and JSX props
if (
isIgnoredValue(value) ||
+ (ignoreDefaultValues && isDefaultValue(fullNumberNode)) ||
isParseIntRadix(fullNumberNode) ||
isJSXNumber(fullNumberNode) ||
(ignoreArrayIndexes && isArrayIndex(fullNumberNode, value))
@@ -186,8 +201,6 @@ module.exports = {
return;
}
- const parent = fullNumberNode.parent;
-
if (parent.type === "VariableDeclarator") {
if (enforceConst && parent.parent.kind !== "const") {
context.report({
diff --git a/eslint/lib/rules/no-obj-calls.js b/eslint/lib/rules/no-obj-calls.js
index 6139ba2..6eb200c 100644
--- a/eslint/lib/rules/no-obj-calls.js
+++ b/eslint/lib/rules/no-obj-calls.js
@@ -24,10 +24,13 @@ const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"];
* @returns {string} name to report
*/
function getReportNodeName(node) {
- if (node.callee.type === "MemberExpression") {
- return getPropertyName(node.callee);
+ if (node.type === "ChainExpression") {
+ return getReportNodeName(node.expression);
}
- return node.callee.name;
+ if (node.type === "MemberExpression") {
+ return getPropertyName(node);
+ }
+ return node.name;
}
//------------------------------------------------------------------------------
@@ -69,7 +72,7 @@ module.exports = {
}
for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) {
- const name = getReportNodeName(node);
+ const name = getReportNodeName(node.callee);
const ref = path[0];
const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall";
diff --git a/eslint/lib/rules/no-promise-executor-return.js b/eslint/lib/rules/no-promise-executor-return.js
new file mode 100644
index 0000000..32ee6e1
--- /dev/null
+++ b/eslint/lib/rules/no-promise-executor-return.js
@@ -0,0 +1,121 @@
+/**
+ * @fileoverview Rule to disallow returning values from Promise executor functions
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { findVariable } = require("eslint-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const functionTypesToCheck = new Set(["ArrowFunctionExpression", "FunctionExpression"]);
+
+/**
+ * Determines whether the given identifier node is a reference to a global variable.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @returns {boolean} True if the identifier is a reference to a global variable.
+ */
+function isGlobalReference(node, scope) {
+ const variable = findVariable(scope, node);
+
+ return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
+}
+
+/**
+ * Finds function's outer scope.
+ * @param {Scope} scope Function's own scope.
+ * @returns {Scope} Function's outer scope.
+ */
+function getOuterScope(scope) {
+ const upper = scope.upper;
+
+ if (upper.type === "function-expression-name") {
+ return upper.upper;
+ }
+ return upper;
+}
+
+/**
+ * Determines whether the given function node is used as a Promise executor.
+ * @param {ASTNode} node The node to check.
+ * @param {Scope} scope Function's own scope.
+ * @returns {boolean} `true` if the node is a Promise executor.
+ */
+function isPromiseExecutor(node, scope) {
+ const parent = node.parent;
+
+ return parent.type === "NewExpression" &&
+ parent.arguments[0] === node &&
+ parent.callee.type === "Identifier" &&
+ parent.callee.name === "Promise" &&
+ isGlobalReference(parent.callee, getOuterScope(scope));
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "problem",
+
+ docs: {
+ description: "disallow returning values from Promise executor functions",
+ category: "Possible Errors",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/no-promise-executor-return"
+ },
+
+ schema: [],
+
+ messages: {
+ returnsValue: "Return values from promise executor functions cannot be read."
+ }
+ },
+
+ create(context) {
+
+ let funcInfo = null;
+
+ /**
+ * Reports the given node.
+ * @param {ASTNode} node Node to report.
+ * @returns {void}
+ */
+ function report(node) {
+ context.report({ node, messageId: "returnsValue" });
+ }
+
+ return {
+
+ onCodePathStart(_, node) {
+ funcInfo = {
+ upper: funcInfo,
+ shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, context.getScope())
+ };
+
+ if (funcInfo.shouldCheck && node.type === "ArrowFunctionExpression" && node.expression) {
+ report(node.body);
+ }
+ },
+
+ onCodePathEnd() {
+ funcInfo = funcInfo.upper;
+ },
+
+ ReturnStatement(node) {
+ if (funcInfo.shouldCheck && node.argument) {
+ report(node);
+ }
+ }
+ };
+ }
+};
diff --git a/eslint/lib/rules/no-prototype-builtins.js b/eslint/lib/rules/no-prototype-builtins.js
index a00d370..c5e4d49 100644
--- a/eslint/lib/rules/no-prototype-builtins.js
+++ b/eslint/lib/rules/no-prototype-builtins.js
@@ -4,6 +4,12 @@
*/
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -39,15 +45,19 @@ module.exports = {
* @returns {void}
*/
function disallowBuiltIns(node) {
- if (node.callee.type !== "MemberExpression" || node.callee.computed) {
+
+ const callee = astUtils.skipChainExpression(node.callee);
+
+ if (callee.type !== "MemberExpression") {
return;
}
- const propName = node.callee.property.name;
- if (DISALLOWED_PROPS.indexOf(propName) > -1) {
+ const propName = astUtils.getStaticPropertyName(callee);
+
+ if (propName !== null && DISALLOWED_PROPS.indexOf(propName) > -1) {
context.report({
messageId: "prototypeBuildIn",
- loc: node.callee.property.loc,
+ loc: callee.property.loc,
data: { prop: propName },
node
});
diff --git a/eslint/lib/rules/no-script-url.js b/eslint/lib/rules/no-script-url.js
index 2078fc1..0c82052 100644
--- a/eslint/lib/rules/no-script-url.js
+++ b/eslint/lib/rules/no-script-url.js
@@ -7,6 +7,8 @@
"use strict";
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -31,18 +33,30 @@ module.exports = {
create(context) {
- return {
+ /**
+ * Check whether a node's static value starts with "javascript:" or not.
+ * And report an error for unexpected script URL.
+ * @param {ASTNode} node node to check
+ * @returns {void}
+ */
+ function check(node) {
+ const value = astUtils.getStaticStringValue(node);
+ if (typeof value === "string" && value.toLowerCase().indexOf("javascript:") === 0) {
+ context.report({ node, messageId: "unexpectedScriptURL" });
+ }
+ }
+ return {
Literal(node) {
if (node.value && typeof node.value === "string") {
- const value = node.value.toLowerCase();
-
- if (value.indexOf("javascript:") === 0) {
- context.report({ node, messageId: "unexpectedScriptURL" });
- }
+ check(node);
+ }
+ },
+ TemplateLiteral(node) {
+ if (!(node.parent && node.parent.type === "TaggedTemplateExpression")) {
+ check(node);
}
}
};
-
}
};
diff --git a/eslint/lib/rules/no-self-assign.js b/eslint/lib/rules/no-self-assign.js
index 170e46b..705be32 100644
--- a/eslint/lib/rules/no-self-assign.js
+++ b/eslint/lib/rules/no-self-assign.js
@@ -17,56 +17,6 @@ const astUtils = require("./utils/ast-utils");
const SPACES = /\s+/gu;
-/**
- * Checks whether the property of 2 given member expression nodes are the same
- * property or not.
- * @param {ASTNode} left A member expression node to check.
- * @param {ASTNode} right Another member expression node to check.
- * @returns {boolean} `true` if the member expressions have the same property.
- */
-function isSameProperty(left, right) {
- if (left.property.type === "Identifier" &&
- left.property.type === right.property.type &&
- left.property.name === right.property.name &&
- left.computed === right.computed
- ) {
- return true;
- }
-
- const lname = astUtils.getStaticPropertyName(left);
- const rname = astUtils.getStaticPropertyName(right);
-
- return lname !== null && lname === rname;
-}
-
-/**
- * Checks whether 2 given member expression nodes are the reference to the same
- * property or not.
- * @param {ASTNode} left A member expression node to check.
- * @param {ASTNode} right Another member expression node to check.
- * @returns {boolean} `true` if the member expressions are the reference to the
- * same property or not.
- */
-function isSameMember(left, right) {
- if (!isSameProperty(left, right)) {
- return false;
- }
-
- const lobj = left.object;
- const robj = right.object;
-
- if (lobj.type !== robj.type) {
- return false;
- }
- if (lobj.type === "MemberExpression") {
- return isSameMember(lobj, robj);
- }
- if (lobj.type === "ThisExpression") {
- return true;
- }
- return lobj.type === "Identifier" && lobj.name === robj.name;
-}
-
/**
* Traverses 2 Pattern nodes in parallel, then reports self-assignments.
* @param {ASTNode|null} left A left node to traverse. This is a Pattern or
@@ -162,9 +112,9 @@ function eachSelfAssignment(left, right, props, report) {
}
} else if (
props &&
- left.type === "MemberExpression" &&
- right.type === "MemberExpression" &&
- isSameMember(left, right)
+ astUtils.skipChainExpression(left).type === "MemberExpression" &&
+ astUtils.skipChainExpression(right).type === "MemberExpression" &&
+ astUtils.isSameReference(left, right)
) {
report(right);
}
diff --git a/eslint/lib/rules/no-setter-return.js b/eslint/lib/rules/no-setter-return.js
index a558640..9c79240 100644
--- a/eslint/lib/rules/no-setter-return.js
+++ b/eslint/lib/rules/no-setter-return.js
@@ -39,15 +39,12 @@ function isGlobalReference(node, scope) {
* @returns {boolean} `true` if the node is argument at the given position.
*/
function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index) {
- const parent = node.parent;
+ const callNode = node.parent;
- return parent.type === "CallExpression" &&
- parent.arguments[index] === node &&
- parent.callee.type === "MemberExpression" &&
- astUtils.getStaticPropertyName(parent.callee) === methodName &&
- parent.callee.object.type === "Identifier" &&
- parent.callee.object.name === objectName &&
- isGlobalReference(parent.callee.object, scope);
+ return callNode.type === "CallExpression" &&
+ callNode.arguments[index] === node &&
+ astUtils.isSpecificMemberAccess(callNode.callee, objectName, methodName) &&
+ isGlobalReference(astUtils.skipChainExpression(callNode.callee).object, scope);
}
/**
diff --git a/eslint/lib/rules/no-underscore-dangle.js b/eslint/lib/rules/no-underscore-dangle.js
index cac594e..87d2336 100644
--- a/eslint/lib/rules/no-underscore-dangle.js
+++ b/eslint/lib/rules/no-underscore-dangle.js
@@ -1,5 +1,5 @@
/**
- * @fileoverview Rule to flag trailing underscores in variable declarations.
+ * @fileoverview Rule to flag dangling underscores in variable declarations.
* @author Matt DuVall
*/
@@ -45,6 +45,10 @@ module.exports = {
enforceInMethodNames: {
type: "boolean",
default: false
+ },
+ allowFunctionParams: {
+ type: "boolean",
+ default: true
}
},
additionalProperties: false
@@ -64,6 +68,7 @@ module.exports = {
const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false;
const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
+ const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true;
//-------------------------------------------------------------------------
// Helpers
@@ -80,12 +85,12 @@ module.exports = {
}
/**
- * Check if identifier has a underscore at the end
+ * Check if identifier has a dangling underscore
* @param {string} identifier name of the node
* @returns {boolean} true if its is present
* @private
*/
- function hasTrailingUnderscore(identifier) {
+ function hasDanglingUnderscore(identifier) {
const len = identifier.length;
return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_");
@@ -126,16 +131,53 @@ module.exports = {
}
/**
- * Check if function has a underscore at the end
+ * Check if function parameter has a dangling underscore.
+ * @param {ASTNode} node function node to evaluate
+ * @returns {void}
+ * @private
+ */
+ function checkForDanglingUnderscoreInFunctionParameters(node) {
+ if (!allowFunctionParams) {
+ node.params.forEach(param => {
+ const { type } = param;
+ let nodeToCheck;
+
+ if (type === "RestElement") {
+ nodeToCheck = param.argument;
+ } else if (type === "AssignmentPattern") {
+ nodeToCheck = param.left;
+ } else {
+ nodeToCheck = param;
+ }
+
+ if (nodeToCheck.type === "Identifier") {
+ const identifier = nodeToCheck.name;
+
+ if (hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
+ context.report({
+ node: param,
+ messageId: "unexpectedUnderscore",
+ data: {
+ identifier
+ }
+ });
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Check if function has a dangling underscore
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
- function checkForTrailingUnderscoreInFunctionDeclaration(node) {
- if (node.id) {
+ function checkForDanglingUnderscoreInFunction(node) {
+ if (node.type === "FunctionDeclaration" && node.id) {
const identifier = node.id.name;
- if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) {
+ if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
context.report({
node,
messageId: "unexpectedUnderscore",
@@ -145,18 +187,19 @@ module.exports = {
});
}
}
+ checkForDanglingUnderscoreInFunctionParameters(node);
}
/**
- * Check if variable expression has a underscore at the end
+ * Check if variable expression has a dangling underscore
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
- function checkForTrailingUnderscoreInVariableExpression(node) {
+ function checkForDanglingUnderscoreInVariableExpression(node) {
const identifier = node.id.name;
- if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
+ if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
!isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) {
context.report({
node,
@@ -169,18 +212,18 @@ module.exports = {
}
/**
- * Check if member expression has a underscore at the end
+ * Check if member expression has a dangling underscore
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
- function checkForTrailingUnderscoreInMemberExpression(node) {
+ function checkForDanglingUnderscoreInMemberExpression(node) {
const identifier = node.property.name,
isMemberOfThis = node.object.type === "ThisExpression",
isMemberOfSuper = node.object.type === "Super",
isMemberOfThisConstructor = isThisConstructorReference(node);
- if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
+ if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
!(isMemberOfThis && allowAfterThis) &&
!(isMemberOfSuper && allowAfterSuper) &&
!(isMemberOfThisConstructor && allowAfterThisConstructor) &&
@@ -196,16 +239,16 @@ module.exports = {
}
/**
- * Check if method declaration or method property has a underscore at the end
+ * Check if method declaration or method property has a dangling underscore
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
- function checkForTrailingUnderscoreInMethod(node) {
+ function checkForDanglingUnderscoreInMethod(node) {
const identifier = node.key.name;
const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
- if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) {
+ if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
context.report({
node,
messageId: "unexpectedUnderscore",
@@ -221,11 +264,13 @@ module.exports = {
//--------------------------------------------------------------------------
return {
- FunctionDeclaration: checkForTrailingUnderscoreInFunctionDeclaration,
- VariableDeclarator: checkForTrailingUnderscoreInVariableExpression,
- MemberExpression: checkForTrailingUnderscoreInMemberExpression,
- MethodDefinition: checkForTrailingUnderscoreInMethod,
- Property: checkForTrailingUnderscoreInMethod
+ FunctionDeclaration: checkForDanglingUnderscoreInFunction,
+ VariableDeclarator: checkForDanglingUnderscoreInVariableExpression,
+ MemberExpression: checkForDanglingUnderscoreInMemberExpression,
+ MethodDefinition: checkForDanglingUnderscoreInMethod,
+ Property: checkForDanglingUnderscoreInMethod,
+ FunctionExpression: checkForDanglingUnderscoreInFunction,
+ ArrowFunctionExpression: checkForDanglingUnderscoreInFunction
};
}
diff --git a/eslint/lib/rules/no-unexpected-multiline.js b/eslint/lib/rules/no-unexpected-multiline.js
index b5ec20d..7af3fe6 100644
--- a/eslint/lib/rules/no-unexpected-multiline.js
+++ b/eslint/lib/rules/no-unexpected-multiline.js
@@ -68,7 +68,7 @@ module.exports = {
return {
MemberExpression(node) {
- if (!node.computed) {
+ if (!node.computed || node.optional) {
return;
}
checkForBreakAfter(node.object, "property");
@@ -96,7 +96,7 @@ module.exports = {
},
CallExpression(node) {
- if (node.arguments.length === 0) {
+ if (node.arguments.length === 0 || node.optional) {
return;
}
checkForBreakAfter(node.callee, "function");
diff --git a/eslint/lib/rules/no-unneeded-ternary.js b/eslint/lib/rules/no-unneeded-ternary.js
index 0fefc42..06c615f 100644
--- a/eslint/lib/rules/no-unneeded-ternary.js
+++ b/eslint/lib/rules/no-unneeded-ternary.js
@@ -122,7 +122,6 @@ module.exports = {
if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) {
context.report({
node,
- loc: node.consequent.loc.start,
messageId: "unnecessaryConditionalExpression",
fix(fixer) {
if (node.consequent.value === node.alternate.value) {
@@ -144,7 +143,6 @@ module.exports = {
} else if (!defaultAssignment && matchesDefaultAssignment(node)) {
context.report({
node,
- loc: node.consequent.loc.start,
messageId: "unnecessaryConditionalAssignment",
fix: fixer => {
const shouldParenthesizeAlternate =
diff --git a/eslint/lib/rules/no-unreachable-loop.js b/eslint/lib/rules/no-unreachable-loop.js
new file mode 100644
index 0000000..868a6ff
--- /dev/null
+++ b/eslint/lib/rules/no-unreachable-loop.js
@@ -0,0 +1,150 @@
+/**
+ * @fileoverview Rule to disallow loops with a body that allows only one iteration
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"];
+
+/**
+ * Determines whether the given node is the first node in the code path to which a loop statement
+ * 'loops' for the next iteration.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a looping target.
+ */
+function isLoopingTarget(node) {
+ const parent = node.parent;
+
+ if (parent) {
+ switch (parent.type) {
+ case "WhileStatement":
+ return node === parent.test;
+ case "DoWhileStatement":
+ return node === parent.body;
+ case "ForStatement":
+ return node === (parent.update || parent.test || parent.body);
+ case "ForInStatement":
+ case "ForOfStatement":
+ return node === parent.left;
+
+ // no default
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Creates an array with elements from the first given array that are not included in the second given array.
+ * @param {Array} arrA The array to compare from.
+ * @param {Array} arrB The array to compare against.
+ * @returns {Array} a new array that represents `arrA \ arrB`.
+ */
+function getDifference(arrA, arrB) {
+ return arrA.filter(a => !arrB.includes(a));
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "problem",
+
+ docs: {
+ description: "disallow loops with a body that allows only one iteration",
+ category: "Possible Errors",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/no-unreachable-loop"
+ },
+
+ schema: [{
+ type: "object",
+ properties: {
+ ignore: {
+ type: "array",
+ items: {
+ enum: allLoopTypes
+ },
+ uniqueItems: true
+ }
+ },
+ additionalProperties: false
+ }],
+
+ messages: {
+ invalid: "Invalid loop. Its body allows only one iteration."
+ }
+ },
+
+ create(context) {
+ const ignoredLoopTypes = context.options[0] && context.options[0].ignore || [],
+ loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes),
+ loopSelector = loopTypesToCheck.join(","),
+ loopsByTargetSegments = new Map(),
+ loopsToReport = new Set();
+
+ let currentCodePath = null;
+
+ return {
+ onCodePathStart(codePath) {
+ currentCodePath = codePath;
+ },
+
+ onCodePathEnd() {
+ currentCodePath = currentCodePath.upper;
+ },
+
+ [loopSelector](node) {
+
+ /**
+ * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
+ * For unreachable segments, the code path analysis does not raise events required for this implementation.
+ */
+ if (currentCodePath.currentSegments.some(segment => segment.reachable)) {
+ loopsToReport.add(node);
+ }
+ },
+
+ onCodePathSegmentStart(segment, node) {
+ if (isLoopingTarget(node)) {
+ const loop = node.parent;
+
+ loopsByTargetSegments.set(segment, loop);
+ }
+ },
+
+ onCodePathSegmentLoop(_, toSegment, node) {
+ const loop = loopsByTargetSegments.get(toSegment);
+
+ /**
+ * The second iteration is reachable, meaning that the loop is valid by the logic of this rule,
+ * only if there is at least one loop event with the appropriate target (which has been already
+ * determined in the `loopsByTargetSegments` map), raised from either:
+ *
+ * - the end of the loop's body (in which case `node === loop`)
+ * - a `continue` statement
+ *
+ * This condition skips loop events raised from `ForInStatement > .right` and `ForOfStatement > .right` nodes.
+ */
+ if (node === loop || node.type === "ContinueStatement") {
+
+ // Removes loop if it exists in the set. Otherwise, `Set#delete` has no effect and doesn't throw.
+ loopsToReport.delete(loop);
+ }
+ },
+
+ "Program:exit"() {
+ loopsToReport.forEach(
+ node => context.report({ node, messageId: "invalid" })
+ );
+ }
+ };
+ }
+};
diff --git a/eslint/lib/rules/no-unused-expressions.js b/eslint/lib/rules/no-unused-expressions.js
index 8c049f5..882a0fd 100644
--- a/eslint/lib/rules/no-unused-expressions.js
+++ b/eslint/lib/rules/no-unused-expressions.js
@@ -8,6 +8,22 @@
// Rule Definition
//------------------------------------------------------------------------------
+/**
+ * Returns `true`.
+ * @returns {boolean} `true`.
+ */
+function alwaysTrue() {
+ return true;
+}
+
+/**
+ * Returns `false`.
+ * @returns {boolean} `false`.
+ */
+function alwaysFalse() {
+ return false;
+}
+
module.exports = {
meta: {
type: "suggestion",
@@ -101,40 +117,56 @@ module.exports = {
}
/**
- * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags.
- * @param {ASTNode} node any node
- * @returns {boolean} whether the given node is a valid expression
+ * The member functions return `true` if the type has no side-effects.
+ * Unknown nodes are handled as `false`, then this rule ignores those.
*/
- function isValidExpression(node) {
- if (allowTernary) {
+ const Checker = Object.assign(Object.create(null), {
+ isDisallowed(node) {
+ return (Checker[node.type] || alwaysFalse)(node);
+ },
- // Recursive check for ternary and logical expressions
- if (node.type === "ConditionalExpression") {
- return isValidExpression(node.consequent) && isValidExpression(node.alternate);
+ ArrayExpression: alwaysTrue,
+ ArrowFunctionExpression: alwaysTrue,
+ BinaryExpression: alwaysTrue,
+ ChainExpression(node) {
+ return Checker.isDisallowed(node.expression);
+ },
+ ClassExpression: alwaysTrue,
+ ConditionalExpression(node) {
+ if (allowTernary) {
+ return Checker.isDisallowed(node.consequent) || Checker.isDisallowed(node.alternate);
}
- }
-
- if (allowShortCircuit) {
- if (node.type === "LogicalExpression") {
- return isValidExpression(node.right);
- }
- }
-
- if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
return true;
+ },
+ FunctionExpression: alwaysTrue,
+ Identifier: alwaysTrue,
+ Literal: alwaysTrue,
+ LogicalExpression(node) {
+ if (allowShortCircuit) {
+ return Checker.isDisallowed(node.right);
+ }
+ return true;
+ },
+ MemberExpression: alwaysTrue,
+ MetaProperty: alwaysTrue,
+ ObjectExpression: alwaysTrue,
+ SequenceExpression: alwaysTrue,
+ TaggedTemplateExpression() {
+ return !allowTaggedTemplates;
+ },
+ TemplateLiteral: alwaysTrue,
+ ThisExpression: alwaysTrue,
+ UnaryExpression(node) {
+ return node.operator !== "void" && node.operator !== "delete";
}
-
- return /^(?:Assignment|Call|New|Update|Yield|Await|Import)Expression$/u.test(node.type) ||
- (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
- }
+ });
return {
ExpressionStatement(node) {
- if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) {
+ if (Checker.isDisallowed(node.expression) && !isDirective(node, context.getAncestors())) {
context.report({ node, messageId: "unusedExpression" });
}
}
};
-
}
};
diff --git a/eslint/lib/rules/no-unused-vars.js b/eslint/lib/rules/no-unused-vars.js
index 50dcab4..4dc6dc2 100644
--- a/eslint/lib/rules/no-unused-vars.js
+++ b/eslint/lib/rules/no-unused-vars.js
@@ -68,7 +68,8 @@ module.exports = {
caughtErrorsIgnorePattern: {
type: "string"
}
- }
+ },
+ additionalProperties: false
}
]
}
diff --git a/eslint/lib/rules/no-useless-call.js b/eslint/lib/rules/no-useless-call.js
index afc729d..b1382a2 100644
--- a/eslint/lib/rules/no-useless-call.js
+++ b/eslint/lib/rules/no-useless-call.js
@@ -17,13 +17,15 @@ const astUtils = require("./utils/ast-utils");
* @returns {boolean} Whether or not the node is a `.call()`/`.apply()`.
*/
function isCallOrNonVariadicApply(node) {
+ const callee = astUtils.skipChainExpression(node.callee);
+
return (
- node.callee.type === "MemberExpression" &&
- node.callee.property.type === "Identifier" &&
- node.callee.computed === false &&
+ callee.type === "MemberExpression" &&
+ callee.property.type === "Identifier" &&
+ callee.computed === false &&
(
- (node.callee.property.name === "call" && node.arguments.length >= 1) ||
- (node.callee.property.name === "apply" && node.arguments.length === 2 && node.arguments[1].type === "ArrayExpression")
+ (callee.property.name === "call" && node.arguments.length >= 1) ||
+ (callee.property.name === "apply" && node.arguments.length === 2 && node.arguments[1].type === "ArrayExpression")
)
);
}
@@ -74,12 +76,13 @@ module.exports = {
return;
}
- const applied = node.callee.object;
+ const callee = astUtils.skipChainExpression(node.callee);
+ const applied = astUtils.skipChainExpression(callee.object);
const expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
const thisArg = node.arguments[0];
if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
- context.report({ node, messageId: "unnecessaryCall", data: { name: node.callee.property.name } });
+ context.report({ node, messageId: "unnecessaryCall", data: { name: callee.property.name } });
}
}
};
diff --git a/eslint/lib/rules/no-warning-comments.js b/eslint/lib/rules/no-warning-comments.js
index d70bd5d..0691a31 100644
--- a/eslint/lib/rules/no-warning-comments.js
+++ b/eslint/lib/rules/no-warning-comments.js
@@ -8,6 +8,8 @@
const { escapeRegExp } = require("lodash");
const astUtils = require("./utils/ast-utils");
+const CHAR_LIMIT = 40;
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -42,12 +44,11 @@ module.exports = {
],
messages: {
- unexpectedComment: "Unexpected '{{matchedTerm}}' comment."
+ unexpectedComment: "Unexpected '{{matchedTerm}}' comment: '{{comment}}'."
}
},
create(context) {
-
const sourceCode = context.getSourceCode(),
configuration = context.options[0] || {},
warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
@@ -107,7 +108,15 @@ module.exports = {
* \bTERM\b|\bTERM\b, this checks the entire comment
* for the term.
*/
- return new RegExp(prefix + escaped + suffix + eitherOrWordBoundary + term + wordBoundary, "iu");
+ return new RegExp(
+ prefix +
+ escaped +
+ suffix +
+ eitherOrWordBoundary +
+ term +
+ wordBoundary,
+ "iu"
+ );
}
const warningRegExps = warningTerms.map(convertToRegExp);
@@ -135,18 +144,40 @@ module.exports = {
* @returns {void} undefined.
*/
function checkComment(node) {
- if (astUtils.isDirectiveComment(node) && selfConfigRegEx.test(node.value)) {
+ const comment = node.value;
+
+ if (
+ astUtils.isDirectiveComment(node) &&
+ selfConfigRegEx.test(comment)
+ ) {
return;
}
- const matches = commentContainsWarningTerm(node.value);
+ const matches = commentContainsWarningTerm(comment);
matches.forEach(matchedTerm => {
+ let commentToDisplay = "";
+ let truncated = false;
+
+ for (const c of comment.trim().split(/\s+/u)) {
+ const tmp = commentToDisplay ? `${commentToDisplay} ${c}` : c;
+
+ if (tmp.length <= CHAR_LIMIT) {
+ commentToDisplay = tmp;
+ } else {
+ truncated = true;
+ break;
+ }
+ }
+
context.report({
node,
messageId: "unexpectedComment",
data: {
- matchedTerm
+ matchedTerm,
+ comment: `${commentToDisplay}${
+ truncated ? "..." : ""
+ }`
}
});
});
@@ -156,7 +187,9 @@ module.exports = {
Program() {
const comments = sourceCode.getAllComments();
- comments.filter(token => token.type !== "Shebang").forEach(checkComment);
+ comments
+ .filter(token => token.type !== "Shebang")
+ .forEach(checkComment);
}
};
}
diff --git a/eslint/lib/rules/no-whitespace-before-property.js b/eslint/lib/rules/no-whitespace-before-property.js
index ccd0b09..226f873 100644
--- a/eslint/lib/rules/no-whitespace-before-property.js
+++ b/eslint/lib/rules/no-whitespace-before-property.js
@@ -49,8 +49,6 @@ module.exports = {
* @private
*/
function reportError(node, leftToken, rightToken) {
- const replacementText = node.computed ? "" : ".";
-
context.report({
node,
messageId: "unexpectedWhitespace",
@@ -58,7 +56,9 @@ module.exports = {
propName: sourceCode.getText(node.property)
},
fix(fixer) {
- if (!node.computed && astUtils.isDecimalInteger(node.object)) {
+ let replacementText = "";
+
+ if (!node.computed && !node.optional && astUtils.isDecimalInteger(node.object)) {
/*
* If the object is a number literal, fixing it to something like 5.toString() would cause a SyntaxError.
@@ -66,6 +66,18 @@ module.exports = {
*/
return null;
}
+
+ // Don't fix if comments exist.
+ if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
+ return null;
+ }
+
+ if (node.optional) {
+ replacementText = "?.";
+ } else if (!node.computed) {
+ replacementText = ".";
+ }
+
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText);
}
});
@@ -86,7 +98,7 @@ module.exports = {
if (node.computed) {
rightToken = sourceCode.getTokenBefore(node.property, astUtils.isOpeningBracketToken);
- leftToken = sourceCode.getTokenBefore(rightToken);
+ leftToken = sourceCode.getTokenBefore(rightToken, node.optional ? 1 : 0);
} else {
rightToken = sourceCode.getFirstToken(node.property);
leftToken = sourceCode.getTokenBefore(rightToken, 1);
diff --git a/eslint/lib/rules/object-curly-newline.js b/eslint/lib/rules/object-curly-newline.js
index b48b252..616d598 100644
--- a/eslint/lib/rules/object-curly-newline.js
+++ b/eslint/lib/rules/object-curly-newline.js
@@ -224,7 +224,7 @@ module.exports = {
context.report({
messageId: "expectedLinebreakAfterOpeningBrace",
node,
- loc: openBrace.loc.start,
+ loc: openBrace.loc,
fix(fixer) {
if (hasCommentsFirstToken) {
return null;
@@ -238,7 +238,7 @@ module.exports = {
context.report({
messageId: "expectedLinebreakBeforeClosingBrace",
node,
- loc: closeBrace.loc.start,
+ loc: closeBrace.loc,
fix(fixer) {
if (hasCommentsLastToken) {
return null;
@@ -260,7 +260,7 @@ module.exports = {
context.report({
messageId: "unexpectedLinebreakAfterOpeningBrace",
node,
- loc: openBrace.loc.start,
+ loc: openBrace.loc,
fix(fixer) {
if (hasCommentsFirstToken) {
return null;
@@ -280,7 +280,7 @@ module.exports = {
context.report({
messageId: "unexpectedLinebreakBeforeClosingBrace",
node,
- loc: closeBrace.loc.start,
+ loc: closeBrace.loc,
fix(fixer) {
if (hasCommentsLastToken) {
return null;
diff --git a/eslint/lib/rules/object-property-newline.js b/eslint/lib/rules/object-property-newline.js
index 074bc77..0c7f800 100644
--- a/eslint/lib/rules/object-property-newline.js
+++ b/eslint/lib/rules/object-property-newline.js
@@ -77,7 +77,7 @@ module.exports = {
if (lastTokenOfPreviousProperty.loc.end.line === firstTokenOfCurrentProperty.loc.start.line) {
context.report({
node,
- loc: firstTokenOfCurrentProperty.loc.start,
+ loc: firstTokenOfCurrentProperty.loc,
messageId,
fix(fixer) {
const comma = sourceCode.getTokenBefore(firstTokenOfCurrentProperty);
diff --git a/eslint/lib/rules/operator-assignment.js b/eslint/lib/rules/operator-assignment.js
index 6820793..fdb0884 100644
--- a/eslint/lib/rules/operator-assignment.js
+++ b/eslint/lib/rules/operator-assignment.js
@@ -40,45 +40,6 @@ function isNonCommutativeOperatorWithShorthand(operator) {
// Rule Definition
//------------------------------------------------------------------------------
-/**
- * Checks whether two expressions reference the same value. For example:
- * a = a
- * a.b = a.b
- * a[0] = a[0]
- * a['b'] = a['b']
- * @param {ASTNode} a Left side of the comparison.
- * @param {ASTNode} b Right side of the comparison.
- * @returns {boolean} True if both sides match and reference the same value.
- */
-function same(a, b) {
- if (a.type !== b.type) {
- return false;
- }
-
- switch (a.type) {
- case "Identifier":
- return a.name === b.name;
-
- case "Literal":
- return a.value === b.value;
-
- case "MemberExpression":
-
- /*
- * x[0] = x[0]
- * x[y] = x[y]
- * x.y = x.y
- */
- return same(a.object, b.object) && same(a.property, b.property);
-
- case "ThisExpression":
- return true;
-
- default:
- return false;
- }
-}
-
/**
* Determines if the left side of a node can be safely fixed (i.e. if it activates the same getters/setters and)
* toString calls regardless of whether assignment shorthand is used)
@@ -148,12 +109,12 @@ module.exports = {
const operator = expr.operator;
if (isCommutativeOperatorWithShorthand(operator) || isNonCommutativeOperatorWithShorthand(operator)) {
- if (same(left, expr.left)) {
+ if (astUtils.isSameReference(left, expr.left, true)) {
context.report({
node,
messageId: "replaced",
fix(fixer) {
- if (canBeFixed(left)) {
+ if (canBeFixed(left) && canBeFixed(expr.left)) {
const equalsToken = getOperatorToken(node);
const operatorToken = getOperatorToken(expr);
const leftText = sourceCode.getText().slice(node.range[0], equalsToken.range[0]);
@@ -169,7 +130,7 @@ module.exports = {
return null;
}
});
- } else if (same(left, expr.right) && isCommutativeOperatorWithShorthand(operator)) {
+ } else if (astUtils.isSameReference(left, expr.right, true) && isCommutativeOperatorWithShorthand(operator)) {
/*
* This case can't be fixed safely.
@@ -190,7 +151,7 @@ module.exports = {
* @returns {void}
*/
function prohibit(node) {
- if (node.operator !== "=") {
+ if (node.operator !== "=" && !astUtils.isLogicalAssignmentOperator(node.operator)) {
context.report({
node,
messageId: "unexpected",
diff --git a/eslint/lib/rules/operator-linebreak.js b/eslint/lib/rules/operator-linebreak.js
index 3395fea..18da5c5 100644
--- a/eslint/lib/rules/operator-linebreak.js
+++ b/eslint/lib/rules/operator-linebreak.js
@@ -35,11 +35,8 @@ module.exports = {
properties: {
overrides: {
type: "object",
- properties: {
- anyOf: {
- type: "string",
- enum: ["after", "before", "none", "ignore"]
- }
+ additionalProperties: {
+ enum: ["after", "before", "none", "ignore"]
}
}
},
diff --git a/eslint/lib/rules/padded-blocks.js b/eslint/lib/rules/padded-blocks.js
index fa65eae..f8b5bd9 100644
--- a/eslint/lib/rules/padded-blocks.js
+++ b/eslint/lib/rules/padded-blocks.js
@@ -58,7 +58,8 @@ module.exports = {
allowSingleLineBlocks: {
type: "boolean"
}
- }
+ },
+ additionalProperties: false
}
],
diff --git a/eslint/lib/rules/padding-line-between-statements.js b/eslint/lib/rules/padding-line-between-statements.js
index eea19f5..c97b995 100644
--- a/eslint/lib/rules/padding-line-between-statements.js
+++ b/eslint/lib/rules/padding-line-between-statements.js
@@ -85,10 +85,10 @@ function newNodeTypeTester(type) {
*/
function isIIFEStatement(node) {
if (node.type === "ExpressionStatement") {
- let call = node.expression;
+ let call = astUtils.skipChainExpression(node.expression);
if (call.type === "UnaryExpression") {
- call = call.argument;
+ call = astUtils.skipChainExpression(call.argument);
}
return call.type === "CallExpression" && astUtils.isFunction(call.callee);
}
diff --git a/eslint/lib/rules/prefer-arrow-callback.js b/eslint/lib/rules/prefer-arrow-callback.js
index d4e0251..ee5cfe3 100644
--- a/eslint/lib/rules/prefer-arrow-callback.js
+++ b/eslint/lib/rules/prefer-arrow-callback.js
@@ -5,6 +5,8 @@
"use strict";
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
@@ -66,6 +68,7 @@ function getCallbackInfo(node) {
const retv = { isCallback: false, isLexicalThis: false };
let currentNode = node;
let parent = node.parent;
+ let bound = false;
while (currentNode) {
switch (parent.type) {
@@ -73,23 +76,34 @@ function getCallbackInfo(node) {
// Checks parents recursively.
case "LogicalExpression":
+ case "ChainExpression":
case "ConditionalExpression":
break;
// Checks whether the parent node is `.bind(this)` call.
case "MemberExpression":
- if (parent.object === currentNode &&
+ if (
+ parent.object === currentNode &&
!parent.property.computed &&
parent.property.type === "Identifier" &&
- parent.property.name === "bind" &&
- parent.parent.type === "CallExpression" &&
- parent.parent.callee === parent
+ parent.property.name === "bind"
) {
- retv.isLexicalThis = (
- parent.parent.arguments.length === 1 &&
- parent.parent.arguments[0].type === "ThisExpression"
- );
- parent = parent.parent;
+ const maybeCallee = parent.parent.type === "ChainExpression"
+ ? parent.parent
+ : parent;
+
+ if (astUtils.isCallee(maybeCallee)) {
+ if (!bound) {
+ bound = true; // Use only the first `.bind()` to make `isLexicalThis` value.
+ retv.isLexicalThis = (
+ maybeCallee.parent.arguments.length === 1 &&
+ maybeCallee.parent.arguments[0].type === "ThisExpression"
+ );
+ }
+ parent = maybeCallee.parent;
+ } else {
+ return retv;
+ }
} else {
return retv;
}
@@ -272,7 +286,7 @@ module.exports = {
context.report({
node,
messageId: "preferArrowCallback",
- fix(fixer) {
+ *fix(fixer) {
if ((!callbackInfo.isLexicalThis && scopeInfo.this) || hasDuplicateParams(node.params)) {
/*
@@ -281,30 +295,81 @@ module.exports = {
* If the callback function has duplicates in its list of parameters (possible in sloppy mode),
* don't replace it with an arrow function, because this is a SyntaxError with arrow functions.
*/
- return null;
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
}
- const paramsLeftParen = node.params.length ? sourceCode.getTokenBefore(node.params[0]) : sourceCode.getTokenBefore(node.body, 1);
- const paramsRightParen = sourceCode.getTokenBefore(node.body);
- const asyncKeyword = node.async ? "async " : "";
- const paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]);
- const arrowFunctionText = `${asyncKeyword}${paramsFullText} => ${sourceCode.getText(node.body)}`;
+ // Remove `.bind(this)` if exists.
+ if (callbackInfo.isLexicalThis) {
+ const memberNode = node.parent;
- /*
- * If the callback function has `.bind(this)`, replace it with an arrow function and remove the binding.
- * Otherwise, just replace the arrow function itself.
- */
- const replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node;
+ /*
+ * If `.bind(this)` exists but the parent is not `.bind(this)`, don't remove it automatically.
+ * E.g. `(foo || function(){}).bind(this)`
+ */
+ if (memberNode.type !== "MemberExpression") {
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+ }
+
+ const callNode = memberNode.parent;
+ const firstTokenToRemove = sourceCode.getTokenAfter(memberNode.object, astUtils.isNotClosingParenToken);
+ const lastTokenToRemove = sourceCode.getLastToken(callNode);
+
+ /*
+ * If the member expression is parenthesized, don't remove the right paren.
+ * E.g. `(function(){}.bind)(this)`
+ * ^^^^^^^^^^^^
+ */
+ if (astUtils.isParenthesised(sourceCode, memberNode)) {
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+ }
+
+ // If comments exist in the `.bind(this)`, don't remove those.
+ if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+ }
+
+ yield fixer.removeRange([firstTokenToRemove.range[0], lastTokenToRemove.range[1]]);
+ }
+
+ // Convert the function expression to an arrow function.
+ const functionToken = sourceCode.getFirstToken(node, node.async ? 1 : 0);
+ const leftParenToken = sourceCode.getTokenAfter(functionToken, astUtils.isOpeningParenToken);
+
+ if (sourceCode.commentsExistBetween(functionToken, leftParenToken)) {
+
+ // Remove only extra tokens to keep comments.
+ yield fixer.remove(functionToken);
+ if (node.id) {
+ yield fixer.remove(node.id);
+ }
+ } else {
+
+ // Remove extra tokens and spaces.
+ yield fixer.removeRange([functionToken.range[0], leftParenToken.range[0]]);
+ }
+ yield fixer.insertTextBefore(node.body, "=> ");
+
+ // Get the node that will become the new arrow function.
+ let replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node;
+
+ if (replacedNode.type === "ChainExpression") {
+ replacedNode = replacedNode.parent;
+ }
/*
* If the replaced node is part of a BinaryExpression, LogicalExpression, or MemberExpression, then
* the arrow function needs to be parenthesized, because `foo || () => {}` is invalid syntax even
* though `foo || function() {}` is valid.
*/
- const needsParens = replacedNode.parent.type !== "CallExpression" && replacedNode.parent.type !== "ConditionalExpression";
- const replacementText = needsParens ? `(${arrowFunctionText})` : arrowFunctionText;
-
- return fixer.replaceText(replacedNode, replacementText);
+ if (
+ replacedNode.parent.type !== "CallExpression" &&
+ replacedNode.parent.type !== "ConditionalExpression" &&
+ !astUtils.isParenthesised(sourceCode, replacedNode) &&
+ !astUtils.isParenthesised(sourceCode, node)
+ ) {
+ yield fixer.insertTextBefore(replacedNode, "(");
+ yield fixer.insertTextAfter(replacedNode, ")");
+ }
}
});
}
diff --git a/eslint/lib/rules/prefer-destructuring.js b/eslint/lib/rules/prefer-destructuring.js
index 1a51956..b2d3c8a 100644
--- a/eslint/lib/rules/prefer-destructuring.js
+++ b/eslint/lib/rules/prefer-destructuring.js
@@ -4,6 +4,18 @@
*/
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const PRECEDENCE_OF_ASSIGNMENT_EXPR = astUtils.getPrecedence({ type: "AssignmentExpression" });
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -163,6 +175,8 @@ module.exports = {
return node.type === "VariableDeclarator" &&
node.id.type === "Identifier" &&
node.init.type === "MemberExpression" &&
+ !node.init.computed &&
+ node.init.property.type === "Identifier" &&
node.id.name === node.init.property.name;
}
@@ -178,9 +192,20 @@ module.exports = {
const rightNode = node.init;
const sourceCode = context.getSourceCode();
+ // Don't fix if that would remove any comments. Only comments inside `rightNode.object` can be preserved.
+ if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(rightNode.object).length) {
+ return null;
+ }
+
+ let objectText = sourceCode.getText(rightNode.object);
+
+ if (astUtils.getPrecedence(rightNode.object) < PRECEDENCE_OF_ASSIGNMENT_EXPR) {
+ objectText = `(${objectText})`;
+ }
+
return fixer.replaceText(
node,
- `{${rightNode.property.name}} = ${sourceCode.getText(rightNode.object)}`
+ `{${rightNode.property.name}} = ${objectText}`
);
}
diff --git a/eslint/lib/rules/prefer-exponentiation-operator.js b/eslint/lib/rules/prefer-exponentiation-operator.js
index 5e75ef4..d1a00d6 100644
--- a/eslint/lib/rules/prefer-exponentiation-operator.js
+++ b/eslint/lib/rules/prefer-exponentiation-operator.js
@@ -52,7 +52,7 @@ function doesExponentNeedParens(exponent) {
* @returns {boolean} `true` if the expression needs to be parenthesised.
*/
function doesExponentiationExpressionNeedParens(node, sourceCode) {
- const parent = node.parent;
+ const parent = node.parent.type === "ChainExpression" ? node.parent.parent : node.parent;
const needsParens = (
parent.type === "ClassDeclaration" ||
diff --git a/eslint/lib/rules/prefer-numeric-literals.js b/eslint/lib/rules/prefer-numeric-literals.js
index 2a4fb5d..cc82e66 100644
--- a/eslint/lib/rules/prefer-numeric-literals.js
+++ b/eslint/lib/rules/prefer-numeric-literals.js
@@ -29,19 +29,10 @@ const radixMap = new Map([
* false otherwise.
*/
function isParseInt(calleeNode) {
- switch (calleeNode.type) {
- case "Identifier":
- return calleeNode.name === "parseInt";
- case "MemberExpression":
- return calleeNode.object.type === "Identifier" &&
- calleeNode.object.name === "Number" &&
- calleeNode.property.type === "Identifier" &&
- calleeNode.property.name === "parseInt";
-
- // no default
- }
-
- return false;
+ return (
+ astUtils.isSpecificId(calleeNode, "parseInt") ||
+ astUtils.isSpecificMemberAccess(calleeNode, "Number", "parseInt")
+ );
}
//------------------------------------------------------------------------------
@@ -112,6 +103,16 @@ module.exports = {
/*
* If the newly-produced literal would be invalid, (e.g. 0b1234),
* or it would yield an incorrect parseInt result for some other reason, don't make a fix.
+ *
+ * If `str` had numeric separators, `+replacement` will evaluate to `NaN` because unary `+`
+ * per the specification doesn't support numeric separators. Thus, the above condition will be `true`
+ * (`NaN !== anything` is always `true`) regardless of the `parseInt(str, radix)` value.
+ * Consequently, no autofixes will be made. This is correct behavior because `parseInt` also
+ * doesn't support numeric separators, but it does parse part of the string before the first `_`,
+ * so the autofix would be invalid:
+ *
+ * parseInt("1_1", 2) // === 1
+ * 0b1_1 // === 3
*/
return null;
}
diff --git a/eslint/lib/rules/prefer-promise-reject-errors.js b/eslint/lib/rules/prefer-promise-reject-errors.js
index 56911b6..ec16e44 100644
--- a/eslint/lib/rules/prefer-promise-reject-errors.js
+++ b/eslint/lib/rules/prefer-promise-reject-errors.js
@@ -73,9 +73,7 @@ module.exports = {
* @returns {boolean} `true` if the call is a Promise.reject() call
*/
function isPromiseRejectCall(node) {
- return node.callee.type === "MemberExpression" &&
- node.callee.object.type === "Identifier" && node.callee.object.name === "Promise" &&
- node.callee.property.type === "Identifier" && node.callee.property.name === "reject";
+ return astUtils.isSpecificMemberAccess(node.callee, "Promise", "reject");
}
//----------------------------------------------------------------------
diff --git a/eslint/lib/rules/prefer-regex-literals.js b/eslint/lib/rules/prefer-regex-literals.js
index 47b2b09..9e8ce02 100644
--- a/eslint/lib/rules/prefer-regex-literals.js
+++ b/eslint/lib/rules/prefer-regex-literals.js
@@ -25,6 +25,15 @@ function isStringLiteral(node) {
return node.type === "Literal" && typeof node.value === "string";
}
+/**
+ * Determines whether the given node is a regex literal.
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} True if the node is a regex literal.
+ */
+function isRegexLiteral(node) {
+ return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex");
+}
+
/**
* Determines whether the given node is a template literal without expressions.
* @param {ASTNode} node Node to check.
@@ -50,14 +59,28 @@ module.exports = {
url: "https://eslint.org/docs/rules/prefer-regex-literals"
},
- schema: [],
+ schema: [
+ {
+ type: "object",
+ properties: {
+ disallowRedundantWrapping: {
+ type: "boolean",
+ default: false
+ }
+ },
+ additionalProperties: false
+ }
+ ],
messages: {
- unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor."
+ unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.",
+ unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.",
+ unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor."
}
},
create(context) {
+ const [{ disallowRedundantWrapping = false } = {}] = context.options;
/**
* Determines whether the given identifier node is a reference to a global variable.
@@ -79,11 +102,8 @@ module.exports = {
*/
function isStringRawTaggedStaticTemplateLiteral(node) {
return node.type === "TaggedTemplateExpression" &&
- node.tag.type === "MemberExpression" &&
- node.tag.object.type === "Identifier" &&
- node.tag.object.name === "String" &&
- isGlobalReference(node.tag.object) &&
- astUtils.getStaticPropertyName(node.tag) === "raw" &&
+ astUtils.isSpecificMemberAccess(node.tag, "String", "raw") &&
+ isGlobalReference(astUtils.skipChainExpression(node.tag).object) &&
isStaticTemplateLiteral(node.quasi);
}
@@ -98,6 +118,40 @@ module.exports = {
isStringRawTaggedStaticTemplateLiteral(node);
}
+ /**
+ * Determines whether the relevant arguments of the given are all static string literals.
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} True if all arguments are static strings.
+ */
+ function hasOnlyStaticStringArguments(node) {
+ const args = node.arguments;
+
+ if ((args.length === 1 || args.length === 2) && args.every(isStaticString)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines whether the arguments of the given node indicate that a regex literal is unnecessarily wrapped.
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} True if the node already contains a regex literal argument.
+ */
+ function isUnnecessarilyWrappedRegexLiteral(node) {
+ const args = node.arguments;
+
+ if (args.length === 1 && isRegexLiteral(args[0])) {
+ return true;
+ }
+
+ if (args.length === 2 && isRegexLiteral(args[0]) && isStaticString(args[1])) {
+ return true;
+ }
+
+ return false;
+ }
+
return {
Program() {
const scope = context.getScope();
@@ -110,12 +164,13 @@ module.exports = {
};
for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
- const args = node.arguments;
-
- if (
- (args.length === 1 || args.length === 2) &&
- args.every(isStaticString)
- ) {
+ if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) {
+ if (node.arguments.length === 2) {
+ context.report({ node, messageId: "unexpectedRedundantRegExpWithFlags" });
+ } else {
+ context.report({ node, messageId: "unexpectedRedundantRegExp" });
+ }
+ } else if (hasOnlyStaticStringArguments(node)) {
context.report({ node, messageId: "unexpectedRegExp" });
}
}
diff --git a/eslint/lib/rules/prefer-spread.js b/eslint/lib/rules/prefer-spread.js
index bcb0dc0..d3c3c4d 100644
--- a/eslint/lib/rules/prefer-spread.js
+++ b/eslint/lib/rules/prefer-spread.js
@@ -18,17 +18,13 @@ const astUtils = require("./utils/ast-utils");
*/
function isVariadicApplyCalling(node) {
return (
- node.callee.type === "MemberExpression" &&
- node.callee.property.type === "Identifier" &&
- node.callee.property.name === "apply" &&
- node.callee.computed === false &&
+ astUtils.isSpecificMemberAccess(node.callee, null, "apply") &&
node.arguments.length === 2 &&
node.arguments[1].type !== "ArrayExpression" &&
node.arguments[1].type !== "SpreadElement"
);
}
-
/**
* Checks whether or not `thisArg` is not changed by `.apply()`.
* @param {ASTNode|null} expectedThis The node that is the owner of the applied function.
@@ -75,7 +71,7 @@ module.exports = {
return;
}
- const applied = node.callee.object;
+ const applied = astUtils.skipChainExpression(astUtils.skipChainExpression(node.callee).object);
const expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
const thisArg = node.arguments[0];
diff --git a/eslint/lib/rules/prefer-template.js b/eslint/lib/rules/prefer-template.js
index e8f980e..cb96766 100644
--- a/eslint/lib/rules/prefer-template.js
+++ b/eslint/lib/rules/prefer-template.js
@@ -39,33 +39,25 @@ function getTopConcatBinaryExpression(node) {
}
/**
- * Determines whether a given node is a octal escape sequence
+ * Checks whether or not a node contains a string literal with an octal or non-octal decimal escape sequence
* @param {ASTNode} node A node to check
- * @returns {boolean} `true` if the node is an octal escape sequence
+ * @returns {boolean} `true` if at least one string literal within the node contains
+ * an octal or non-octal decimal escape sequence
*/
-function isOctalEscapeSequence(node) {
-
- // No need to check TemplateLiterals – would throw error with octal escape
- const isStringLiteral = node.type === "Literal" && typeof node.value === "string";
-
- if (!isStringLiteral) {
- return false;
- }
-
- return astUtils.hasOctalEscapeSequence(node.raw);
-}
-
-/**
- * Checks whether or not a node contains a octal escape sequence
- * @param {ASTNode} node A node to check
- * @returns {boolean} `true` if the node contains an octal escape sequence
- */
-function hasOctalEscapeSequence(node) {
+function hasOctalOrNonOctalDecimalEscapeSequence(node) {
if (isConcatenation(node)) {
- return hasOctalEscapeSequence(node.left) || hasOctalEscapeSequence(node.right);
+ return (
+ hasOctalOrNonOctalDecimalEscapeSequence(node.left) ||
+ hasOctalOrNonOctalDecimalEscapeSequence(node.right)
+ );
}
- return isOctalEscapeSequence(node);
+ // No need to check TemplateLiterals – would throw parsing error
+ if (node.type === "Literal" && typeof node.value === "string") {
+ return astUtils.hasOctalOrNonOctalDecimalEscapeSequence(node.raw);
+ }
+
+ return false;
}
/**
@@ -237,7 +229,7 @@ module.exports = {
function fixNonStringBinaryExpression(fixer, node) {
const topBinaryExpr = getTopConcatBinaryExpression(node.parent);
- if (hasOctalEscapeSequence(topBinaryExpr)) {
+ if (hasOctalOrNonOctalDecimalEscapeSequence(topBinaryExpr)) {
return null;
}
diff --git a/eslint/lib/rules/quotes.js b/eslint/lib/rules/quotes.js
index d1f4443..da7e127 100644
--- a/eslint/lib/rules/quotes.js
+++ b/eslint/lib/rules/quotes.js
@@ -282,9 +282,12 @@ module.exports = {
description: settings.description
},
fix(fixer) {
- if (quoteOption === "backtick" && astUtils.hasOctalEscapeSequence(rawVal)) {
+ if (quoteOption === "backtick" && astUtils.hasOctalOrNonOctalDecimalEscapeSequence(rawVal)) {
- // An octal escape sequence in a template literal would produce syntax error, even in non-strict mode.
+ /*
+ * An octal or non-octal decimal escape sequence in a template literal would
+ * produce syntax error, even in non-strict mode.
+ */
return null;
}
diff --git a/eslint/lib/rules/radix.js b/eslint/lib/rules/radix.js
index 3903cb2..e322566 100644
--- a/eslint/lib/rules/radix.js
+++ b/eslint/lib/rules/radix.js
@@ -166,9 +166,12 @@ module.exports = {
if (variable && !isShadowed(variable)) {
variable.references.forEach(reference => {
const node = reference.identifier.parent;
+ const maybeCallee = node.parent.type === "ChainExpression"
+ ? node.parent
+ : node;
- if (isParseIntMethod(node) && astUtils.isCallee(node)) {
- checkArguments(node.parent);
+ if (isParseIntMethod(node) && astUtils.isCallee(maybeCallee)) {
+ checkArguments(maybeCallee.parent);
}
});
}
diff --git a/eslint/lib/rules/semi-spacing.js b/eslint/lib/rules/semi-spacing.js
index 936e766..5c546f2 100644
--- a/eslint/lib/rules/semi-spacing.js
+++ b/eslint/lib/rules/semi-spacing.js
@@ -223,6 +223,7 @@ module.exports = {
BreakStatement: checkNode,
ContinueStatement: checkNode,
DebuggerStatement: checkNode,
+ DoWhileStatement: checkNode,
ReturnStatement: checkNode,
ThrowStatement: checkNode,
ImportDeclaration: checkNode,
diff --git a/eslint/lib/rules/sort-imports.js b/eslint/lib/rules/sort-imports.js
index 65ad9a1..4c3ddec 100644
--- a/eslint/lib/rules/sort-imports.js
+++ b/eslint/lib/rules/sort-imports.js
@@ -44,6 +44,10 @@ module.exports = {
ignoreMemberSort: {
type: "boolean",
default: false
+ },
+ allowSeparatedGroups: {
+ type: "boolean",
+ default: false
}
},
additionalProperties: false
@@ -66,6 +70,7 @@ module.exports = {
ignoreDeclarationSort = configuration.ignoreDeclarationSort || false,
ignoreMemberSort = configuration.ignoreMemberSort || false,
memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"],
+ allowSeparatedGroups = configuration.allowSeparatedGroups || false,
sourceCode = context.getSourceCode();
let previousDeclaration = null;
@@ -115,9 +120,32 @@ module.exports = {
}
+ /**
+ * Calculates number of lines between two nodes. It is assumed that the given `left` node appears before
+ * the given `right` node in the source code. Lines are counted from the end of the `left` node till the
+ * start of the `right` node. If the given nodes are on the same line, it returns `0`, same as if they were
+ * on two consecutive lines.
+ * @param {ASTNode} left node that appears before the given `right` node.
+ * @param {ASTNode} right node that appears after the given `left` node.
+ * @returns {number} number of lines between nodes.
+ */
+ function getNumberOfLinesBetween(left, right) {
+ return Math.max(right.loc.start.line - left.loc.end.line - 1, 0);
+ }
+
return {
ImportDeclaration(node) {
if (!ignoreDeclarationSort) {
+ if (
+ previousDeclaration &&
+ allowSeparatedGroups &&
+ getNumberOfLinesBetween(previousDeclaration, node) > 0
+ ) {
+
+ // reset declaration sort
+ previousDeclaration = null;
+ }
+
if (previousDeclaration) {
const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node),
previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration);
diff --git a/eslint/lib/rules/space-before-blocks.js b/eslint/lib/rules/space-before-blocks.js
index 9b56481..87ef9bf 100644
--- a/eslint/lib/rules/space-before-blocks.js
+++ b/eslint/lib/rules/space-before-blocks.js
@@ -5,8 +5,31 @@
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const astUtils = require("./utils/ast-utils");
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether the given node represents the body of a function.
+ * @param {ASTNode} node the node to check.
+ * @returns {boolean} `true` if the node is function body.
+ */
+function isFunctionBody(node) {
+ const parent = node.parent;
+
+ return (
+ node.type === "BlockStatement" &&
+ astUtils.isFunction(parent) &&
+ parent.body === node
+ );
+}
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -82,13 +105,16 @@ module.exports = {
}
/**
- * Checks whether or not a given token is an arrow operator (=>) or a keyword
- * in order to avoid to conflict with `arrow-spacing` and `keyword-spacing`.
- * @param {Token} token A token to check.
- * @returns {boolean} `true` if the token is an arrow operator.
+ * Checks whether the spacing before the given block is already controlled by another rule:
+ * - `arrow-spacing` checks spaces after `=>`.
+ * - `keyword-spacing` checks spaces after keywords in certain contexts.
+ * @param {Token} precedingToken first token before the block.
+ * @param {ASTNode|Token} node `BlockStatement` node or `{` token of a `SwitchStatement` node.
+ * @returns {boolean} `true` if requiring or disallowing spaces before the given block could produce conflicts with other rules.
*/
- function isConflicted(token) {
- return (token.type === "Punctuator" && token.value === "=>") || token.type === "Keyword";
+ function isConflicted(precedingToken, node) {
+ return astUtils.isArrowToken(precedingToken) ||
+ astUtils.isKeywordToken(precedingToken) && !isFunctionBody(node);
}
/**
@@ -99,13 +125,12 @@ module.exports = {
function checkPrecedingSpace(node) {
const precedingToken = sourceCode.getTokenBefore(node);
- if (precedingToken && !isConflicted(precedingToken) && astUtils.isTokenOnSameLine(precedingToken, node)) {
+ if (precedingToken && !isConflicted(precedingToken, node) && astUtils.isTokenOnSameLine(precedingToken, node)) {
const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node);
- const parent = context.getAncestors().pop();
let requireSpace;
let requireNoSpace;
- if (parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") {
+ if (isFunctionBody(node)) {
requireSpace = alwaysFunctions;
requireNoSpace = neverFunctions;
} else if (node.type === "ClassBody") {
diff --git a/eslint/lib/rules/use-isnan.js b/eslint/lib/rules/use-isnan.js
index 7b466be..53ffeb7 100644
--- a/eslint/lib/rules/use-isnan.js
+++ b/eslint/lib/rules/use-isnan.js
@@ -106,7 +106,7 @@ module.exports = {
* @returns {void}
*/
function checkCallExpression(node) {
- const callee = node.callee;
+ const callee = astUtils.skipChainExpression(node.callee);
if (callee.type === "MemberExpression") {
const methodName = astUtils.getStaticPropertyName(callee);
diff --git a/eslint/lib/rules/utils/ast-utils.js b/eslint/lib/rules/utils/ast-utils.js
index ecea694..1fd6340 100644
--- a/eslint/lib/rules/utils/ast-utils.js
+++ b/eslint/lib/rules/utils/ast-utils.js
@@ -37,8 +37,12 @@ const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
// A set of node types that can contain a list of statements
const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
-const DECIMAL_INTEGER_PATTERN = /^(0|[1-9]\d*)$/u;
-const OCTAL_ESCAPE_PATTERN = /^(?:[^\\]|\\[^0-7]|\\0(?![0-9]))*\\(?:[1-7]|0[0-9])/u;
+const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u;
+
+// Tests the presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string
+const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su;
+
+const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]);
/**
* Checks reference if is non initializer and writable.
@@ -143,6 +147,23 @@ function isInLoop(node) {
return false;
}
+/**
+ * Determines whether the given node is a `null` literal.
+ * @param {ASTNode} node The node to check
+ * @returns {boolean} `true` if the node is a `null` literal
+ */
+function isNullLiteral(node) {
+
+ /*
+ * Checking `node.value === null` does not guarantee that a literal is a null literal.
+ * When parsing values that cannot be represented in the current environment (e.g. unicode
+ * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
+ * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
+ * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
+ */
+ return node.type === "Literal" && node.value === null && !node.regex && !node.bigint;
+}
+
/**
* Checks whether or not a node is `null` or `undefined`.
* @param {ASTNode} node A node to check.
@@ -151,7 +172,7 @@ function isInLoop(node) {
*/
function isNullOrUndefined(node) {
return (
- module.exports.isNullLiteral(node) ||
+ isNullLiteral(node) ||
(node.type === "Identifier" && node.name === "undefined") ||
(node.type === "UnaryExpression" && node.operator === "void")
);
@@ -166,20 +187,270 @@ function isCallee(node) {
return node.parent.type === "CallExpression" && node.parent.callee === node;
}
+/**
+ * Returns the result of the string conversion applied to the evaluated value of the given expression node,
+ * if it can be determined statically.
+ *
+ * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only.
+ * In all other cases, this function returns `null`.
+ * @param {ASTNode} node Expression node.
+ * @returns {string|null} String value if it can be determined. Otherwise, `null`.
+ */
+function getStaticStringValue(node) {
+ switch (node.type) {
+ case "Literal":
+ if (node.value === null) {
+ if (isNullLiteral(node)) {
+ return String(node.value); // "null"
+ }
+ if (node.regex) {
+ return `/${node.regex.pattern}/${node.regex.flags}`;
+ }
+ if (node.bigint) {
+ return node.bigint;
+ }
+
+ // Otherwise, this is an unknown literal. The function will return null.
+
+ } else {
+ return String(node.value);
+ }
+ break;
+ case "TemplateLiteral":
+ if (node.expressions.length === 0 && node.quasis.length === 1) {
+ return node.quasis[0].value.cooked;
+ }
+ break;
+
+ // no default
+ }
+
+ return null;
+}
+
+/**
+ * Gets the property name of a given node.
+ * The node can be a MemberExpression, a Property, or a MethodDefinition.
+ *
+ * If the name is dynamic, this returns `null`.
+ *
+ * For examples:
+ *
+ * a.b // => "b"
+ * a["b"] // => "b"
+ * a['b'] // => "b"
+ * a[`b`] // => "b"
+ * a[100] // => "100"
+ * a[b] // => null
+ * a["a" + "b"] // => null
+ * a[tag`b`] // => null
+ * a[`${b}`] // => null
+ *
+ * let a = {b: 1} // => "b"
+ * let a = {["b"]: 1} // => "b"
+ * let a = {['b']: 1} // => "b"
+ * let a = {[`b`]: 1} // => "b"
+ * let a = {[100]: 1} // => "100"
+ * let a = {[b]: 1} // => null
+ * let a = {["a" + "b"]: 1} // => null
+ * let a = {[tag`b`]: 1} // => null
+ * let a = {[`${b}`]: 1} // => null
+ * @param {ASTNode} node The node to get.
+ * @returns {string|null} The property name if static. Otherwise, null.
+ */
+function getStaticPropertyName(node) {
+ let prop;
+
+ switch (node && node.type) {
+ case "ChainExpression":
+ return getStaticPropertyName(node.expression);
+
+ case "Property":
+ case "MethodDefinition":
+ prop = node.key;
+ break;
+
+ case "MemberExpression":
+ prop = node.property;
+ break;
+
+ // no default
+ }
+
+ if (prop) {
+ if (prop.type === "Identifier" && !node.computed) {
+ return prop.name;
+ }
+
+ return getStaticStringValue(prop);
+ }
+
+ return null;
+}
+
+/**
+ * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
+ * @param {ASTNode} node The node to address.
+ * @returns {ASTNode} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node.
+ */
+function skipChainExpression(node) {
+ return node && node.type === "ChainExpression" ? node.expression : node;
+}
+
+/**
+ * Check if the `actual` is an expected value.
+ * @param {string} actual The string value to check.
+ * @param {string | RegExp} expected The expected string value or pattern.
+ * @returns {boolean} `true` if the `actual` is an expected value.
+ */
+function checkText(actual, expected) {
+ return typeof expected === "string"
+ ? actual === expected
+ : expected.test(actual);
+}
+
+/**
+ * Check if a given node is an Identifier node with a given name.
+ * @param {ASTNode} node The node to check.
+ * @param {string | RegExp} name The expected name or the expected pattern of the object name.
+ * @returns {boolean} `true` if the node is an Identifier node with the name.
+ */
+function isSpecificId(node, name) {
+ return node.type === "Identifier" && checkText(node.name, name);
+}
+
+/**
+ * Check if a given node is member access with a given object name and property name pair.
+ * This is regardless of optional or not.
+ * @param {ASTNode} node The node to check.
+ * @param {string | RegExp | null} objectName The expected name or the expected pattern of the object name. If this is nullish, this method doesn't check object.
+ * @param {string | RegExp | null} propertyName The expected name or the expected pattern of the property name. If this is nullish, this method doesn't check property.
+ * @returns {boolean} `true` if the node is member access with the object name and property name pair.
+ * The node is a `MemberExpression` or `ChainExpression`.
+ */
+function isSpecificMemberAccess(node, objectName, propertyName) {
+ const checkNode = skipChainExpression(node);
+
+ if (checkNode.type !== "MemberExpression") {
+ return false;
+ }
+
+ if (objectName && !isSpecificId(checkNode.object, objectName)) {
+ return false;
+ }
+
+ if (propertyName) {
+ const actualPropertyName = getStaticPropertyName(checkNode);
+
+ if (typeof actualPropertyName !== "string" || !checkText(actualPropertyName, propertyName)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Check if two literal nodes are the same value.
+ * @param {ASTNode} left The Literal node to compare.
+ * @param {ASTNode} right The other Literal node to compare.
+ * @returns {boolean} `true` if the two literal nodes are the same value.
+ */
+function equalLiteralValue(left, right) {
+
+ // RegExp literal.
+ if (left.regex || right.regex) {
+ return Boolean(
+ left.regex &&
+ right.regex &&
+ left.regex.pattern === right.regex.pattern &&
+ left.regex.flags === right.regex.flags
+ );
+ }
+
+ // BigInt literal.
+ if (left.bigint || right.bigint) {
+ return left.bigint === right.bigint;
+ }
+
+ return left.value === right.value;
+}
+
+/**
+ * Check if two expressions reference the same value. For example:
+ * a = a
+ * a.b = a.b
+ * a[0] = a[0]
+ * a['b'] = a['b']
+ * @param {ASTNode} left The left side of the comparison.
+ * @param {ASTNode} right The right side of the comparison.
+ * @param {boolean} [disableStaticComputedKey] Don't address `a.b` and `a["b"]` are the same if `true`. For backward compatibility.
+ * @returns {boolean} `true` if both sides match and reference the same value.
+ */
+function isSameReference(left, right, disableStaticComputedKey = false) {
+ if (left.type !== right.type) {
+
+ // Handle `a.b` and `a?.b` are samely.
+ if (left.type === "ChainExpression") {
+ return isSameReference(left.expression, right, disableStaticComputedKey);
+ }
+ if (right.type === "ChainExpression") {
+ return isSameReference(left, right.expression, disableStaticComputedKey);
+ }
+
+ return false;
+ }
+
+ switch (left.type) {
+ case "Super":
+ case "ThisExpression":
+ return true;
+
+ case "Identifier":
+ return left.name === right.name;
+ case "Literal":
+ return equalLiteralValue(left, right);
+
+ case "ChainExpression":
+ return isSameReference(left.expression, right.expression, disableStaticComputedKey);
+
+ case "MemberExpression": {
+ if (!disableStaticComputedKey) {
+ const nameA = getStaticPropertyName(left);
+
+ // x.y = x["y"]
+ if (nameA !== null) {
+ return (
+ isSameReference(left.object, right.object, disableStaticComputedKey) &&
+ nameA === getStaticPropertyName(right)
+ );
+ }
+ }
+
+ /*
+ * x[0] = x[0]
+ * x[y] = x[y]
+ * x.y = x.y
+ */
+ return (
+ left.computed === right.computed &&
+ isSameReference(left.object, right.object, disableStaticComputedKey) &&
+ isSameReference(left.property, right.property, disableStaticComputedKey)
+ );
+ }
+
+ default:
+ return false;
+ }
+}
+
/**
* Checks whether or not a node is `Reflect.apply`.
* @param {ASTNode} node A node to check.
* @returns {boolean} Whether or not the node is a `Reflect.apply`.
*/
function isReflectApply(node) {
- return (
- node.type === "MemberExpression" &&
- node.object.type === "Identifier" &&
- node.object.name === "Reflect" &&
- node.property.type === "Identifier" &&
- node.property.name === "apply" &&
- node.computed === false
- );
+ return isSpecificMemberAccess(node, "Reflect", "apply");
}
/**
@@ -188,14 +459,7 @@ function isReflectApply(node) {
* @returns {boolean} Whether or not the node is a `Array.from`.
*/
function isArrayFromMethod(node) {
- return (
- node.type === "MemberExpression" &&
- node.object.type === "Identifier" &&
- arrayOrTypedArrayPattern.test(node.object.name) &&
- node.property.type === "Identifier" &&
- node.property.name === "from" &&
- node.computed === false
- );
+ return isSpecificMemberAccess(node, arrayOrTypedArrayPattern, "from");
}
/**
@@ -204,17 +468,7 @@ function isArrayFromMethod(node) {
* @returns {boolean} Whether or not the node is a method which has `thisArg`.
*/
function isMethodWhichHasThisArg(node) {
- for (
- let currentNode = node;
- currentNode.type === "MemberExpression" && !currentNode.computed;
- currentNode = currentNode.property
- ) {
- if (currentNode.property.type === "Identifier") {
- return arrayMethodPattern.test(currentNode.property.name);
- }
- }
-
- return false;
+ return isSpecificMemberAccess(node, null, arrayMethodPattern);
}
/**
@@ -289,6 +543,15 @@ function isDotToken(token) {
return token.value === "." && token.type === "Punctuator";
}
+/**
+ * Checks if the given token is a `?.` token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a `?.` token.
+ */
+function isQuestionDotToken(token) {
+ return token.value === "?." && token.type === "Punctuator";
+}
+
/**
* Checks if the given token is a semicolon token or not.
* @param {Token} token The token to check.
@@ -463,6 +726,15 @@ function isMixedLogicalAndCoalesceExpressions(left, right) {
);
}
+/**
+ * Checks if the given operator is a logical assignment operator.
+ * @param {string} operator The operator to check.
+ * @returns {boolean} `true` if the operator is a logical assignment operator.
+ */
+function isLogicalAssignmentOperator(operator) {
+ return LOGICAL_ASSIGNMENT_OPERATORS.has(operator);
+}
+
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@@ -505,6 +777,7 @@ module.exports = {
isCommaToken,
isCommentToken,
isDotToken,
+ isQuestionDotToken,
isKeywordToken,
isNotClosingBraceToken: negate(isClosingBraceToken),
isNotClosingBracketToken: negate(isClosingBracketToken),
@@ -512,6 +785,7 @@ module.exports = {
isNotColonToken: negate(isColonToken),
isNotCommaToken: negate(isCommaToken),
isNotDotToken: negate(isDotToken),
+ isNotQuestionDotToken: negate(isQuestionDotToken),
isNotOpeningBraceToken: negate(isOpeningBraceToken),
isNotOpeningBracketToken: negate(isOpeningBracketToken),
isNotOpeningParenToken: negate(isOpeningParenToken),
@@ -669,6 +943,7 @@ module.exports = {
*/
case "LogicalExpression":
case "ConditionalExpression":
+ case "ChainExpression":
currentNode = parent;
break;
@@ -755,14 +1030,21 @@ module.exports = {
* (function foo() { ... }).apply(obj, []);
*/
case "MemberExpression":
- return (
- parent.object !== currentNode ||
- parent.property.type !== "Identifier" ||
- !bindOrCallOrApplyPattern.test(parent.property.name) ||
- !isCallee(parent) ||
- parent.parent.arguments.length === 0 ||
- isNullOrUndefined(parent.parent.arguments[0])
- );
+ if (
+ parent.object === currentNode &&
+ isSpecificMemberAccess(parent, null, bindOrCallOrApplyPattern)
+ ) {
+ const maybeCalleeNode = parent.parent.type === "ChainExpression"
+ ? parent.parent
+ : parent;
+
+ return !(
+ isCallee(maybeCalleeNode) &&
+ maybeCalleeNode.parent.arguments.length >= 1 &&
+ !isNullOrUndefined(maybeCalleeNode.parent.arguments[0])
+ );
+ }
+ return true;
/*
* e.g.
@@ -884,6 +1166,7 @@ module.exports = {
return 17;
case "CallExpression":
+ case "ChainExpression":
case "ImportExpression":
return 18;
@@ -913,104 +1196,6 @@ module.exports = {
return isFunction(node) && module.exports.isEmptyBlock(node.body);
},
- /**
- * Returns the result of the string conversion applied to the evaluated value of the given expression node,
- * if it can be determined statically.
- *
- * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only.
- * In all other cases, this function returns `null`.
- * @param {ASTNode} node Expression node.
- * @returns {string|null} String value if it can be determined. Otherwise, `null`.
- */
- getStaticStringValue(node) {
- switch (node.type) {
- case "Literal":
- if (node.value === null) {
- if (module.exports.isNullLiteral(node)) {
- return String(node.value); // "null"
- }
- if (node.regex) {
- return `/${node.regex.pattern}/${node.regex.flags}`;
- }
- if (node.bigint) {
- return node.bigint;
- }
-
- // Otherwise, this is an unknown literal. The function will return null.
-
- } else {
- return String(node.value);
- }
- break;
- case "TemplateLiteral":
- if (node.expressions.length === 0 && node.quasis.length === 1) {
- return node.quasis[0].value.cooked;
- }
- break;
-
- // no default
- }
-
- return null;
- },
-
- /**
- * Gets the property name of a given node.
- * The node can be a MemberExpression, a Property, or a MethodDefinition.
- *
- * If the name is dynamic, this returns `null`.
- *
- * For examples:
- *
- * a.b // => "b"
- * a["b"] // => "b"
- * a['b'] // => "b"
- * a[`b`] // => "b"
- * a[100] // => "100"
- * a[b] // => null
- * a["a" + "b"] // => null
- * a[tag`b`] // => null
- * a[`${b}`] // => null
- *
- * let a = {b: 1} // => "b"
- * let a = {["b"]: 1} // => "b"
- * let a = {['b']: 1} // => "b"
- * let a = {[`b`]: 1} // => "b"
- * let a = {[100]: 1} // => "100"
- * let a = {[b]: 1} // => null
- * let a = {["a" + "b"]: 1} // => null
- * let a = {[tag`b`]: 1} // => null
- * let a = {[`${b}`]: 1} // => null
- * @param {ASTNode} node The node to get.
- * @returns {string|null} The property name if static. Otherwise, null.
- */
- getStaticPropertyName(node) {
- let prop;
-
- switch (node && node.type) {
- case "Property":
- case "MethodDefinition":
- prop = node.key;
- break;
-
- case "MemberExpression":
- prop = node.property;
- break;
-
- // no default
- }
-
- if (prop) {
- if (prop.type === "Identifier" && !node.computed) {
- return prop.name;
- }
-
- return module.exports.getStaticStringValue(prop);
- }
-
- return null;
- },
-
/**
* Get directives from directive prologue of a Program or Function node.
* @param {ASTNode} node The node to check.
@@ -1056,16 +1241,27 @@ module.exports = {
* @returns {boolean} `true` if this node is a decimal integer.
* @example
*
- * 5 // true
- * 5. // false
- * 5.0 // false
- * 05 // false
- * 0x5 // false
- * 0b101 // false
- * 0o5 // false
- * 5e0 // false
- * '5' // false
- * 5n // false
+ * 0 // true
+ * 5 // true
+ * 50 // true
+ * 5_000 // true
+ * 1_234_56 // true
+ * 08 // true
+ * 0192 // true
+ * 5. // false
+ * .5 // false
+ * 5.0 // false
+ * 5.00_00 // false
+ * 05 // false
+ * 0x5 // false
+ * 0b101 // false
+ * 0b11_01 // false
+ * 0o5 // false
+ * 5e0 // false
+ * 5e1_000 // false
+ * 5n // false
+ * 1_000n // false
+ * '5' // false
*/
isDecimalInteger(node) {
return node.type === "Literal" && typeof node.value === "number" &&
@@ -1164,7 +1360,7 @@ module.exports = {
if (node.id) {
tokens.push(`'${node.id.name}'`);
} else {
- const name = module.exports.getStaticPropertyName(parent);
+ const name = getStaticPropertyName(parent);
if (name !== null) {
tokens.push(`'${name}'`);
@@ -1391,10 +1587,24 @@ module.exports = {
case "TaggedTemplateExpression":
case "YieldExpression":
case "AwaitExpression":
+ case "ChainExpression":
return true; // possibly an error object.
case "AssignmentExpression":
- return module.exports.couldBeError(node.right);
+ if (["=", "&&="].includes(node.operator)) {
+ return module.exports.couldBeError(node.right);
+ }
+
+ if (["||=", "??="].includes(node.operator)) {
+ return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
+ }
+
+ /**
+ * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
+ * An assignment expression with a mathematical operator can either evaluate to a primitive value,
+ * or throw, depending on the operands. Thus, it cannot evaluate to an `Error` object.
+ */
+ return false;
case "SequenceExpression": {
const exprs = node.expressions;
@@ -1403,6 +1613,17 @@ module.exports = {
}
case "LogicalExpression":
+
+ /*
+ * If the && operator short-circuits, the left side was falsy and therefore not an error, and if it
+ * doesn't short-circuit, it takes the value from the right side, so the right side must always be
+ * a plausible error. A future improvement could verify that the left side could be truthy by
+ * excluding falsy literals.
+ */
+ if (node.operator === "&&") {
+ return module.exports.couldBeError(node.right);
+ }
+
return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
case "ConditionalExpression":
@@ -1413,23 +1634,6 @@ module.exports = {
}
},
- /**
- * Determines whether the given node is a `null` literal.
- * @param {ASTNode} node The node to check
- * @returns {boolean} `true` if the node is a `null` literal
- */
- isNullLiteral(node) {
-
- /*
- * Checking `node.value === null` does not guarantee that a literal is a null literal.
- * When parsing values that cannot be represented in the current environment (e.g. unicode
- * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
- * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
- * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
- */
- return node.type === "Literal" && node.value === null && !node.regex && !node.bigint;
- },
-
/**
* Check if a given node is a numeric literal or not.
* @param {ASTNode} node The node to check.
@@ -1575,20 +1779,31 @@ module.exports = {
},
/**
- * Determines whether the given raw string contains an octal escape sequence.
+ * Determines whether the given raw string contains an octal escape sequence
+ * or a non-octal decimal escape sequence ("\8", "\9").
*
- * "\1", "\2" ... "\7"
- * "\00", "\01" ... "\09"
+ * "\1", "\2" ... "\7", "\8", "\9"
+ * "\00", "\01" ... "\07", "\08", "\09"
*
* "\0", when not followed by a digit, is not an octal escape sequence.
* @param {string} rawString A string in its raw representation.
- * @returns {boolean} `true` if the string contains at least one octal escape sequence.
+ * @returns {boolean} `true` if the string contains at least one octal escape sequence
+ * or at least one non-octal decimal escape sequence.
*/
- hasOctalEscapeSequence(rawString) {
- return OCTAL_ESCAPE_PATTERN.test(rawString);
+ hasOctalOrNonOctalDecimalEscapeSequence(rawString) {
+ return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString);
},
isLogicalExpression,
isCoalesceExpression,
- isMixedLogicalAndCoalesceExpressions
+ isMixedLogicalAndCoalesceExpressions,
+ isNullLiteral,
+ getStaticStringValue,
+ getStaticPropertyName,
+ skipChainExpression,
+ isSpecificId,
+ isSpecificMemberAccess,
+ equalLiteralValue,
+ isSameReference,
+ isLogicalAssignmentOperator
};
diff --git a/eslint/lib/rules/wrap-iife.js b/eslint/lib/rules/wrap-iife.js
index 896aed6..07e5b84 100644
--- a/eslint/lib/rules/wrap-iife.js
+++ b/eslint/lib/rules/wrap-iife.js
@@ -23,7 +23,14 @@ const eslintUtils = require("eslint-utils");
* @private
*/
function isCalleeOfNewExpression(node) {
- return node.parent.type === "NewExpression" && node.parent.callee === node;
+ const maybeCallee = node.parent.type === "ChainExpression"
+ ? node.parent
+ : node;
+
+ return (
+ maybeCallee.parent.type === "NewExpression" &&
+ maybeCallee.parent.callee === maybeCallee
+ );
}
//------------------------------------------------------------------------------
@@ -98,7 +105,7 @@ module.exports = {
* @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist
*/
function getFunctionNodeFromIIFE(node) {
- const callee = node.callee;
+ const callee = astUtils.skipChainExpression(node.callee);
if (callee.type === "FunctionExpression") {
return callee;
diff --git a/eslint/lib/rules/yoda.js b/eslint/lib/rules/yoda.js
index f1159e5..2fca757 100644
--- a/eslint/lib/rules/yoda.js
+++ b/eslint/lib/rules/yoda.js
@@ -111,59 +111,6 @@ function getNormalizedLiteral(node) {
return null;
}
-/**
- * Checks whether two expressions reference the same value. For example:
- * a = a
- * a.b = a.b
- * a[0] = a[0]
- * a['b'] = a['b']
- * @param {ASTNode} a Left side of the comparison.
- * @param {ASTNode} b Right side of the comparison.
- * @returns {boolean} True if both sides match and reference the same value.
- */
-function same(a, b) {
- if (a.type !== b.type) {
- return false;
- }
-
- switch (a.type) {
- case "Identifier":
- return a.name === b.name;
-
- case "Literal":
- return a.value === b.value;
-
- case "MemberExpression": {
- const nameA = astUtils.getStaticPropertyName(a);
-
- // x.y = x["y"]
- if (nameA !== null) {
- return (
- same(a.object, b.object) &&
- nameA === astUtils.getStaticPropertyName(b)
- );
- }
-
- /*
- * x[0] = x[0]
- * x[y] = x[y]
- * x.y = x.y
- */
- return (
- a.computed === b.computed &&
- same(a.object, b.object) &&
- same(a.property, b.property)
- );
- }
-
- case "ThisExpression":
- return true;
-
- default:
- return false;
- }
-}
-
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -236,7 +183,7 @@ module.exports = {
* @returns {boolean} Whether node is a "between" range test.
*/
function isBetweenTest() {
- if (node.operator === "&&" && same(left.right, right.left)) {
+ if (node.operator === "&&" && astUtils.isSameReference(left.right, right.left)) {
const leftLiteral = getNormalizedLiteral(left.left);
const rightLiteral = getNormalizedLiteral(right.right);
@@ -260,7 +207,7 @@ module.exports = {
* @returns {boolean} Whether node is an "outside" range test.
*/
function isOutsideTest() {
- if (node.operator === "||" && same(left.left, right.right)) {
+ if (node.operator === "||" && astUtils.isSameReference(left.left, right.right)) {
const leftLiteral = getNormalizedLiteral(left.right);
const rightLiteral = getNormalizedLiteral(right.left);
@@ -318,36 +265,37 @@ module.exports = {
* @returns {string} A string representation of the node with the sides and operator flipped
*/
function getFlippedString(node) {
- const tokenBefore = sourceCode.getTokenBefore(node);
const operatorToken = sourceCode.getFirstTokenBetween(
node.left,
node.right,
token => token.value === node.operator
);
- const textBeforeOperator = sourceCode
- .getText()
- .slice(
- sourceCode.getTokenBefore(operatorToken).range[1],
- operatorToken.range[0]
- );
- const textAfterOperator = sourceCode
- .getText()
- .slice(
- operatorToken.range[1],
- sourceCode.getTokenAfter(operatorToken).range[0]
- );
- const leftText = sourceCode
- .getText()
- .slice(
- node.range[0],
- sourceCode.getTokenBefore(operatorToken).range[1]
- );
+ const lastLeftToken = sourceCode.getTokenBefore(operatorToken);
const firstRightToken = sourceCode.getTokenAfter(operatorToken);
- const rightText = sourceCode
- .getText()
- .slice(firstRightToken.range[0], node.range[1]);
+ const source = sourceCode.getText();
+
+ const leftText = source.slice(
+ node.range[0],
+ lastLeftToken.range[1]
+ );
+ const textBeforeOperator = source.slice(
+ lastLeftToken.range[1],
+ operatorToken.range[0]
+ );
+ const textAfterOperator = source.slice(
+ operatorToken.range[1],
+ firstRightToken.range[0]
+ );
+ const rightText = source.slice(
+ firstRightToken.range[0],
+ node.range[1]
+ );
+
+ const tokenBefore = sourceCode.getTokenBefore(node);
+ const tokenAfter = sourceCode.getTokenAfter(node);
let prefix = "";
+ let suffix = "";
if (
tokenBefore &&
@@ -357,13 +305,22 @@ module.exports = {
prefix = " ";
}
+ if (
+ tokenAfter &&
+ node.range[1] === tokenAfter.range[0] &&
+ !astUtils.canTokensBeAdjacent(lastLeftToken, tokenAfter)
+ ) {
+ suffix = " ";
+ }
+
return (
prefix +
rightText +
textBeforeOperator +
OPERATOR_FLIP_MAP[operatorToken.value] +
textAfterOperator +
- leftText
+ leftText +
+ suffix
);
}
diff --git a/eslint/lib/shared/config-ops.js b/eslint/lib/shared/config-ops.js
deleted file mode 100644
index 3b4d569..0000000
--- a/eslint/lib/shared/config-ops.js
+++ /dev/null
@@ -1,130 +0,0 @@
-/**
- * @fileoverview Config file operations. This file must be usable in the browser,
- * so no Node-specific code can be here.
- * @author Nicholas C. Zakas
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Private
-//------------------------------------------------------------------------------
-
-const RULE_SEVERITY_STRINGS = ["off", "warn", "error"],
- RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce((map, value, index) => {
- map[value] = index;
- return map;
- }, {}),
- VALID_SEVERITIES = [0, 1, 2, "off", "warn", "error"];
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-module.exports = {
-
- /**
- * Normalizes the severity value of a rule's configuration to a number
- * @param {(number|string|[number, ...*]|[string, ...*])} ruleConfig A rule's configuration value, generally
- * received from the user. A valid config value is either 0, 1, 2, the string "off" (treated the same as 0),
- * the string "warn" (treated the same as 1), the string "error" (treated the same as 2), or an array
- * whose first element is one of the above values. Strings are matched case-insensitively.
- * @returns {(0|1|2)} The numeric severity value if the config value was valid, otherwise 0.
- */
- getRuleSeverity(ruleConfig) {
- const severityValue = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
-
- if (severityValue === 0 || severityValue === 1 || severityValue === 2) {
- return severityValue;
- }
-
- if (typeof severityValue === "string") {
- return RULE_SEVERITY[severityValue.toLowerCase()] || 0;
- }
-
- return 0;
- },
-
- /**
- * Converts old-style severity settings (0, 1, 2) into new-style
- * severity settings (off, warn, error) for all rules. Assumption is that severity
- * values have already been validated as correct.
- * @param {Object} config The config object to normalize.
- * @returns {void}
- */
- normalizeToStrings(config) {
-
- if (config.rules) {
- Object.keys(config.rules).forEach(ruleId => {
- const ruleConfig = config.rules[ruleId];
-
- if (typeof ruleConfig === "number") {
- config.rules[ruleId] = RULE_SEVERITY_STRINGS[ruleConfig] || RULE_SEVERITY_STRINGS[0];
- } else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "number") {
- ruleConfig[0] = RULE_SEVERITY_STRINGS[ruleConfig[0]] || RULE_SEVERITY_STRINGS[0];
- }
- });
- }
- },
-
- /**
- * Determines if the severity for the given rule configuration represents an error.
- * @param {int|string|Array} ruleConfig The configuration for an individual rule.
- * @returns {boolean} True if the rule represents an error, false if not.
- */
- isErrorSeverity(ruleConfig) {
- return module.exports.getRuleSeverity(ruleConfig) === 2;
- },
-
- /**
- * Checks whether a given config has valid severity or not.
- * @param {number|string|Array} ruleConfig The configuration for an individual rule.
- * @returns {boolean} `true` if the configuration has valid severity.
- */
- isValidSeverity(ruleConfig) {
- let severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
-
- if (typeof severity === "string") {
- severity = severity.toLowerCase();
- }
- return VALID_SEVERITIES.indexOf(severity) !== -1;
- },
-
- /**
- * Checks whether every rule of a given config has valid severity or not.
- * @param {Object} config The configuration for rules.
- * @returns {boolean} `true` if the configuration has valid severity.
- */
- isEverySeverityValid(config) {
- return Object.keys(config).every(ruleId => this.isValidSeverity(config[ruleId]));
- },
-
- /**
- * Normalizes a value for a global in a config
- * @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in
- * a global directive comment
- * @returns {("readable"|"writeable"|"off")} The value normalized as a string
- * @throws Error if global value is invalid
- */
- normalizeConfigGlobal(configuredValue) {
- switch (configuredValue) {
- case "off":
- return "off";
-
- case true:
- case "true":
- case "writeable":
- case "writable":
- return "writable";
-
- case null:
- case false:
- case "false":
- case "readable":
- case "readonly":
- return "readonly";
-
- default:
- throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`);
- }
- }
-};
diff --git a/eslint/lib/shared/config-validator.js b/eslint/lib/shared/config-validator.js
index 458bd2a..03b32f1 100644
--- a/eslint/lib/shared/config-validator.js
+++ b/eslint/lib/shared/config-validator.js
@@ -1,3 +1,15 @@
+/*
+ * STOP!!! DO NOT MODIFY.
+ *
+ * This file is part of the ongoing work to move the eslintrc-style config
+ * system into the @eslint/eslintrc package. This file needs to remain
+ * unchanged in order for this work to proceed.
+ *
+ * If you think you need to change this file, please contact @nzakas first.
+ *
+ * Thanks in advance for your cooperation.
+ */
+
/**
* @fileoverview Validates configs.
* @author Brandon Mills
@@ -12,9 +24,9 @@
const
util = require("util"),
configSchema = require("../../conf/config-schema"),
- BuiltInEnvironments = require("../../conf/environments"),
+ BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
BuiltInRules = require("../rules"),
- ConfigOps = require("./config-ops"),
+ ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
{ emitDeprecationWarning } = require("./deprecation-warnings");
const ajv = require("./ajv")();
diff --git a/eslint/lib/shared/naming.js b/eslint/lib/shared/naming.js
deleted file mode 100644
index 32cff94..0000000
--- a/eslint/lib/shared/naming.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * @fileoverview Common helpers for naming of plugins, formatters and configs
- */
-"use strict";
-
-const NAMESPACE_REGEX = /^@.*\//iu;
-
-/**
- * Brings package name to correct format based on prefix
- * @param {string} name The name of the package.
- * @param {string} prefix Can be either "eslint-plugin", "eslint-config" or "eslint-formatter"
- * @returns {string} Normalized name of the package
- * @private
- */
-function normalizePackageName(name, prefix) {
- let normalizedName = name;
-
- /**
- * On Windows, name can come in with Windows slashes instead of Unix slashes.
- * Normalize to Unix first to avoid errors later on.
- * https://github.com/eslint/eslint/issues/5644
- */
- if (normalizedName.includes("\\")) {
- normalizedName = normalizedName.replace(/\\/gu, "/");
- }
-
- if (normalizedName.charAt(0) === "@") {
-
- /**
- * it's a scoped package
- * package name is the prefix, or just a username
- */
- const scopedPackageShortcutRegex = new RegExp(`^(@[^/]+)(?:/(?:${prefix})?)?$`, "u"),
- scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`, "u");
-
- if (scopedPackageShortcutRegex.test(normalizedName)) {
- normalizedName = normalizedName.replace(scopedPackageShortcutRegex, `$1/${prefix}`);
- } else if (!scopedPackageNameRegex.test(normalizedName.split("/")[1])) {
-
- /**
- * for scoped packages, insert the prefix after the first / unless
- * the path is already @scope/eslint or @scope/eslint-xxx-yyy
- */
- normalizedName = normalizedName.replace(/^@([^/]+)\/(.*)$/u, `@$1/${prefix}-$2`);
- }
- } else if (!normalizedName.startsWith(`${prefix}-`)) {
- normalizedName = `${prefix}-${normalizedName}`;
- }
-
- return normalizedName;
-}
-
-/**
- * Removes the prefix from a fullname.
- * @param {string} fullname The term which may have the prefix.
- * @param {string} prefix The prefix to remove.
- * @returns {string} The term without prefix.
- */
-function getShorthandName(fullname, prefix) {
- if (fullname[0] === "@") {
- let matchResult = new RegExp(`^(@[^/]+)/${prefix}$`, "u").exec(fullname);
-
- if (matchResult) {
- return matchResult[1];
- }
-
- matchResult = new RegExp(`^(@[^/]+)/${prefix}-(.+)$`, "u").exec(fullname);
- if (matchResult) {
- return `${matchResult[1]}/${matchResult[2]}`;
- }
- } else if (fullname.startsWith(`${prefix}-`)) {
- return fullname.slice(prefix.length + 1);
- }
-
- return fullname;
-}
-
-/**
- * Gets the scope (namespace) of a term.
- * @param {string} term The term which may have the namespace.
- * @returns {string} The namespace of the term if it has one.
- */
-function getNamespaceFromTerm(term) {
- const match = term.match(NAMESPACE_REGEX);
-
- return match ? match[0] : "";
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-module.exports = {
- normalizePackageName,
- getShorthandName,
- getNamespaceFromTerm
-};
diff --git a/eslint/lib/shared/relative-module-resolver.js b/eslint/lib/shared/relative-module-resolver.js
index 80335c5..cd743f3 100644
--- a/eslint/lib/shared/relative-module-resolver.js
+++ b/eslint/lib/shared/relative-module-resolver.js
@@ -1,3 +1,15 @@
+/*
+ * STOP!!! DO NOT MODIFY.
+ *
+ * This file is part of the ongoing work to move the eslintrc-style config
+ * system into the @eslint/eslintrc package. This file needs to remain
+ * unchanged in order for this work to proceed.
+ *
+ * If you think you need to change this file, please contact @nzakas first.
+ *
+ * Thanks in advance for your cooperation.
+ */
+
/**
* Utility for resolving a module relative to another module
* @author Teddy Katz
diff --git a/eslint/lib/shared/types.js b/eslint/lib/shared/types.js
index bbd95d1..8ad3b1b 100644
--- a/eslint/lib/shared/types.js
+++ b/eslint/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|2015|2016|2017|2018|2019|2020} [ecmaVersion] The ECMAScript version (or revision number).
+ * @property {3|5|6|7|8|9|10|11|12|2015|2016|2017|2018|2019|2020|2021} [ecmaVersion] The ECMAScript version (or revision number).
* @property {"script"|"module"} [sourceType] The source code type.
*/
diff --git a/eslint/messages/extend-config-missing.txt b/eslint/messages/extend-config-missing.txt
index f7c5f71..4defd7a 100644
--- a/eslint/messages/extend-config-missing.txt
+++ b/eslint/messages/extend-config-missing.txt
@@ -2,4 +2,4 @@ ESLint couldn't find the config "<%- configName %>" to extend from. Please check
The config "<%- configName %>" was referenced from the config file in "<%- importerName %>".
-If you still have problems, please stop by https://eslint.org/chat to chat with the team.
+If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/eslint/messages/no-config-found.txt b/eslint/messages/no-config-found.txt
index f1f7beb..b46a7e5 100644
--- a/eslint/messages/no-config-found.txt
+++ b/eslint/messages/no-config-found.txt
@@ -4,4 +4,4 @@ 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
+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
diff --git a/eslint/messages/plugin-conflict.txt b/eslint/messages/plugin-conflict.txt
index f8b6063..3ab4b34 100644
--- a/eslint/messages/plugin-conflict.txt
+++ b/eslint/messages/plugin-conflict.txt
@@ -4,4 +4,4 @@ ESLint couldn't determine the plugin "<%- pluginId %>" uniquely.
Please remove the "plugins" setting from either config or remove either plugin installation.
-If you still can't figure out the problem, please stop by https://eslint.org/chat to chat with the team.
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/eslint/messages/plugin-invalid.txt b/eslint/messages/plugin-invalid.txt
new file mode 100644
index 0000000..3ee2518
--- /dev/null
+++ b/eslint/messages/plugin-invalid.txt
@@ -0,0 +1,8 @@
+"<%- configName %>" is invalid syntax for a config specifier.
+
+* If your intention is to extend from a configuration exported from the plugin, add the configuration name after a slash: e.g. "<%- configName %>/myConfig".
+* If this is the name of a shareable config instead of a plugin, remove the "plugin:" prefix: i.e. "<%- configName.slice("plugin:".length) %>".
+
+"<%- configName %>" was referenced from the config file in "<%- importerName %>".
+
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/eslint/messages/plugin-missing.txt b/eslint/messages/plugin-missing.txt
index 3d37673..aa25f59 100644
--- a/eslint/messages/plugin-missing.txt
+++ b/eslint/messages/plugin-missing.txt
@@ -8,4 +8,4 @@ It's likely that the plugin isn't installed correctly. Try reinstalling by runni
The plugin "<%- pluginName %>" was referenced from the config file in "<%- importerName %>".
-If you still can't figure out the problem, please stop by https://eslint.org/chat to chat with the team.
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/eslint/messages/whitespace-found.txt b/eslint/messages/whitespace-found.txt
index 7d72149..3eed1af 100644
--- a/eslint/messages/whitespace-found.txt
+++ b/eslint/messages/whitespace-found.txt
@@ -1,3 +1,3 @@
ESLint couldn't find the plugin "<%- pluginName %>". because there is whitespace in the name. Please check your configuration and remove all whitespace from the plugin name.
-If you still can't figure out the problem, please stop by https://eslint.org/chat to chat with the team.
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/eslint/package.json b/eslint/package.json
index f9e204c..37c5b51 100644
--- a/eslint/package.json
+++ b/eslint/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint",
- "version": "7.2.0",
+ "version": "7.12.1",
"author": "Nicholas C. Zakas ",
"description": "An AST-based pattern checker for JavaScript.",
"bin": {
@@ -47,15 +47,17 @@
"bugs": "https://github.com/eslint/eslint/issues/",
"dependencies": {
"@babel/code-frame": "^7.0.0",
+ "@eslint/eslintrc": "^0.2.1",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.0.1",
"doctrine": "^3.0.0",
- "eslint-scope": "^5.1.0",
- "eslint-utils": "^2.0.0",
- "eslint-visitor-keys": "^1.2.0",
- "espree": "^7.1.0",
+ "enquirer": "^2.3.5",
+ "eslint-scope": "^5.1.1",
+ "eslint-utils": "^2.1.0",
+ "eslint-visitor-keys": "^2.0.0",
+ "espree": "^7.3.0",
"esquery": "^1.2.0",
"esutils": "^2.0.2",
"file-entry-cache": "^5.0.1",
@@ -65,12 +67,11 @@
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
- "inquirer": "^7.0.0",
"is-glob": "^4.0.0",
"js-yaml": "^3.13.1",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
- "lodash": "^4.17.14",
+ "lodash": "^4.17.19",
"minimatch": "^3.0.4",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
@@ -104,6 +105,7 @@
"eslint-release": "^2.0.0",
"eslump": "^2.0.0",
"esprima": "^4.0.1",
+ "fs-teardown": "^0.1.0",
"glob": "^7.1.6",
"jsdoc": "^3.5.5",
"karma": "^4.0.1",
@@ -111,7 +113,6 @@
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.3",
"karma-webpack": "^4.0.0-rc.6",
- "leche": "^2.2.3",
"lint-staged": "^10.1.2",
"load-perf": "^0.2.0",
"markdownlint": "^0.19.0",
@@ -122,7 +123,7 @@
"npm-license": "^0.3.3",
"nyc": "^15.0.1",
"proxyquire": "^2.0.1",
- "puppeteer": "^2.1.1",
+ "puppeteer": "^4.0.0",
"recast": "^0.19.0",
"regenerator-runtime": "^0.13.2",
"shelljs": "^0.8.2",
diff --git a/eslint/templates/bug-report.md b/eslint/templates/bug-report.md
index d31fcde..bd3035b 100644
--- a/eslint/templates/bug-report.md
+++ b/eslint/templates/bug-report.md
@@ -4,7 +4,7 @@
* **Node Version:**
* **npm Version:**
-**What parser (default, Babel-ESLint, etc.) are you using?**
+**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?**
**Please show your full configuration:**
diff --git a/eslint/tests/_utils/in-memory-fs.js b/eslint/tests/_utils/in-memory-fs.js
index a458209..ddacfc7 100644
--- a/eslint/tests/_utils/in-memory-fs.js
+++ b/eslint/tests/_utils/in-memory-fs.js
@@ -60,11 +60,11 @@ const { Volume, createFsFromVolume } = require("memfs");
const Proxyquire = require("proxyquire/lib/proxyquire");
const CascadingConfigArrayFactoryPath =
- require.resolve("../../lib/cli-engine/cascading-config-array-factory");
+ require.resolve("@eslint/eslintrc/lib/cascading-config-array-factory");
const CLIEnginePath =
require.resolve("../../lib/cli-engine/cli-engine");
const ConfigArrayFactoryPath =
- require.resolve("../../lib/cli-engine/config-array-factory");
+ require.resolve("@eslint/eslintrc/lib/config-array-factory");
const FileEnumeratorPath =
require.resolve("../../lib/cli-engine/file-enumerator");
const LoadRulesPath =
diff --git a/eslint/tests/_utils/index.js b/eslint/tests/_utils/index.js
index 0431e3a..7615613 100644
--- a/eslint/tests/_utils/index.js
+++ b/eslint/tests/_utils/index.js
@@ -1,5 +1,13 @@
+/**
+ * @fileoverview Utilities used in tests
+ */
+
"use strict";
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
const {
defineInMemoryFs,
defineConfigArrayFactoryWithInMemoryFileSystem,
@@ -9,6 +17,11 @@ const {
defineESLintWithInMemoryFileSystem
} = require("./in-memory-fs");
+const { createTeardown, addFile } = require("fs-teardown");
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
/**
* Prevents leading spaces in a multiline template literal from appearing in the resulting string
@@ -27,6 +40,26 @@ function unIndent(strings, ...values) {
return lines.map(line => line.slice(minLineIndent)).join("\n");
}
+/**
+ * Creates a new filesystem volume at the given location with the given files.
+ * @param {Object} desc A description of the filesystem volume to create.
+ * @param {string} desc.cwd The current working directory ESLint is using.
+ * @param {Object} desc.files A map of filename to file contents to create.
+ * @returns {Teardown} An object with prepare(), cleanup(), and getPath()
+ * methods.
+ */
+function createCustomTeardown({ cwd, files }) {
+ const { prepare, cleanup, getPath } = createTeardown(
+ cwd,
+ ...Object.keys(files).map(filename => addFile(filename, files[filename]))
+ );
+
+ return { prepare, cleanup, getPath };
+}
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
module.exports = {
unIndent,
@@ -35,5 +68,6 @@ module.exports = {
defineCascadingConfigArrayFactoryWithInMemoryFileSystem,
defineFileEnumeratorWithInMemoryFileSystem,
defineCLIEngineWithInMemoryFileSystem,
- defineESLintWithInMemoryFileSystem
+ defineESLintWithInMemoryFileSystem,
+ createCustomTeardown
};
diff --git a/eslint/tests/bin/eslint.js b/eslint/tests/bin/eslint.js
index cfc5128..7500d66 100644
--- a/eslint/tests/bin/eslint.js
+++ b/eslint/tests/bin/eslint.js
@@ -179,8 +179,8 @@ 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", "es2020", "--no-eslintrc", "--rule", "semi: [1, never]"]), 0));
- it("has exit code 1 if a linting error is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--env", "es2020", "--no-eslintrc", "--rule", "semi: [2, never]"]), 1));
+ 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));
});
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--do-while-and.js b/eslint/tests/fixtures/code-path-analysis/assignment--do-while-and.js
new file mode 100644
index 0000000..3545211
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--do-while-and.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_2->s1_4;
+s1_3->s1_4->final;
+*/
+do {
+ foo();
+} while (a &&= b);
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nDoWhileStatement:enter"];
+ s1_2[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="DoWhileStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_2->s1_4;
+ s1_3->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--do-while-or.js b/eslint/tests/fixtures/code-path-analysis/assignment--do-while-or.js
new file mode 100644
index 0000000..6762c69
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--do-while-or.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_2->s1_2;
+s1_3->s1_4->final;
+*/
+do {
+ foo();
+} while (a ||= b);
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nDoWhileStatement:enter"];
+ s1_2[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="DoWhileStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_2->s1_2;
+ s1_3->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--do-while-qq.js b/eslint/tests/fixtures/code-path-analysis/assignment--do-while-qq.js
new file mode 100644
index 0000000..f35b620
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--do-while-qq.js
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_2->s1_2;
+s1_3->s1_4;
+s1_2->s1_4->final;
+*/
+do {
+ foo();
+} while (a ??= b);
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nDoWhileStatement:enter"];
+ s1_2[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="DoWhileStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_2->s1_2;
+ s1_3->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-and-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-and-1.js
new file mode 100644
index 0000000..d135f6f
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--for-and-1.js
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_6;
+s1_3->s1_6->final;
+*/
+for (init; a &&= b; update) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="Identifier (update)"];
+ s1_6[label="ForStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_6;
+ s1_3->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-and-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-and-2.js
new file mode 100644
index 0000000..7f373b0
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--for-and-2.js
@@ -0,0 +1,22 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5;
+s1_3->s1_5->final;
+*/
+for (init; a &&= b;) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="ForStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5;
+ s1_3->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-or-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-or-1.js
new file mode 100644
index 0000000..e04977a
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--for-or-1.js
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
+s1_3->s1_6->final;
+*/
+for (init; a ||= b; update) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="Identifier (update)"];
+ s1_6[label="ForStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
+ s1_3->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-or-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-or-2.js
new file mode 100644
index 0000000..58648a4
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--for-or-2.js
@@ -0,0 +1,22 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+s1_3->s1_5->final;
+*/
+for (init; a ||= b;) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="ForStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+ s1_3->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-qq-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-qq-1.js
new file mode 100644
index 0000000..85efdc3
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--for-qq-1.js
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
+s1_3->s1_6;
+s1_2->s1_6->final;
+*/
+for (init; a ??= b; update) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="Identifier (update)"];
+ s1_6[label="ForStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
+ s1_3->s1_6;
+ s1_2->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-qq-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-qq-2.js
new file mode 100644
index 0000000..cf5d31f
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--for-qq-2.js
@@ -0,0 +1,24 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+s1_3->s1_5;
+s1_2->s1_5->final;
+*/
+for (init; a ??= b;) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="ForStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+ s1_3->s1_5;
+ s1_2->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-and-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-and-1.js
new file mode 100644
index 0000000..d16b407
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--if-and-1.js
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+if (a &&= b) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_4[label="IfStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-and-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-and-2.js
new file mode 100644
index 0000000..cbf1892
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--if-and-2.js
@@ -0,0 +1,28 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5;
+s1_1->s1_4->s1_5;
+s1_2->s1_4;
+s1_5->final;
+*/
+if (a &&= b) {
+ foo();
+} else {
+ bar();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="IfStatement:exit\nProgram:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ initial->s1_1->s1_2->s1_3->s1_5;
+ s1_1->s1_4->s1_5;
+ s1_2->s1_4;
+ s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-and-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-and-3.js
new file mode 100644
index 0000000..ad6c187
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--if-and-3.js
@@ -0,0 +1,36 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9;
+s1_1->s1_8->s1_9;
+s1_2->s1_8;
+s1_3->s1_8;
+s1_4->s1_6->s1_7;
+s1_9->final;
+*/
+if ((a &&= b) && c) {
+ d ? foo() : bar();
+} else {
+ baz();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nIfStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nConditionalExpression:enter\nIdentifier (d)"];
+ s1_5[label="CallExpression:enter\nIdentifier (foo)\nCallExpression:exit"];
+ s1_7[label="ConditionalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_9[label="IfStatement:exit\nProgram:exit"];
+ s1_8[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (baz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_6[label="CallExpression:enter\nIdentifier (bar)\nCallExpression:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9;
+ s1_1->s1_8->s1_9;
+ s1_2->s1_8;
+ s1_3->s1_8;
+ s1_4->s1_6->s1_7;
+ s1_9->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-or-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-or-1.js
new file mode 100644
index 0000000..1a2bd7d
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--if-or-1.js
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4->final;
+*/
+if (a ||= b) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_4[label="IfStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_3;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-or-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-or-2.js
new file mode 100644
index 0000000..628ef60
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--if-or-2.js
@@ -0,0 +1,26 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5;
+s1_1->s1_3;
+s1_2->s1_4->s1_5->final;
+*/
+if (a ||= b) {
+ foo();
+} else {
+ bar();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="IfStatement:exit\nProgram:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ initial->s1_1->s1_2->s1_3->s1_5;
+ s1_1->s1_3;
+ s1_2->s1_4->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-or-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-or-3.js
new file mode 100644
index 0000000..cfef916
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--if-or-3.js
@@ -0,0 +1,36 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9;
+s1_1->s1_4;
+s1_2->s1_4;
+s1_3->s1_8->s1_9;
+s1_4->s1_6->s1_7;
+s1_9->final;
+*/
+if ((a ||= b) || c) {
+ d ? foo() : bar();
+} else {
+ baz();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nIfStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nConditionalExpression:enter\nIdentifier (d)"];
+ s1_5[label="CallExpression:enter\nIdentifier (foo)\nCallExpression:exit"];
+ s1_7[label="ConditionalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_9[label="IfStatement:exit\nProgram:exit"];
+ s1_8[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (baz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_6[label="CallExpression:enter\nIdentifier (bar)\nCallExpression:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9;
+ s1_1->s1_4;
+ s1_2->s1_4;
+ s1_3->s1_8->s1_9;
+ s1_4->s1_6->s1_7;
+ s1_9->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-qq-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-qq-1.js
new file mode 100644
index 0000000..847ea79
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--if-qq-1.js
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4;
+s1_1->s1_4->final;
+*/
+if (a ??= b) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_4[label="IfStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_3;
+ s1_2->s1_4;
+ s1_1->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-qq-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-qq-2.js
new file mode 100644
index 0000000..50b2fdb
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--if-qq-2.js
@@ -0,0 +1,30 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5;
+s1_1->s1_3;
+s1_2->s1_4->s1_5;
+s1_1->s1_4;
+s1_5->final;
+*/
+if (a ??= b) {
+ foo();
+} else {
+ bar();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="IfStatement:exit\nProgram:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ initial->s1_1->s1_2->s1_3->s1_5;
+ s1_1->s1_3;
+ s1_2->s1_4->s1_5;
+ s1_1->s1_4;
+ s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-1.js
new file mode 100644
index 0000000..f8d4b44
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-1.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a &&= b && c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-2.js
new file mode 100644
index 0000000..2ad1da1
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-2.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a &&= b || c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-3.js
new file mode 100644
index 0000000..ff80195
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-3.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a &&= b ?? c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-1.js
new file mode 100644
index 0000000..8f51a6e
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-1.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ||= b && c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-2.js
new file mode 100644
index 0000000..7878072
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-2.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ||= b || c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-3.js
new file mode 100644
index 0000000..52498d1
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-3.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ||= b ?? c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-1.js
new file mode 100644
index 0000000..8b2873c
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-1.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ??= b && c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-2.js
new file mode 100644
index 0000000..dc17fb4
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-2.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ??= b || c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-3.js
new file mode 100644
index 0000000..6baaeec
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-3.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ??= b ?? c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-1.js
new file mode 100644
index 0000000..fd7300d
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--multi-1.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a &&= b &&= c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-10.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-10.js
new file mode 100644
index 0000000..dc4785c
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--multi-10.js
@@ -0,0 +1,28 @@
+/*expected
+initial->s1_1->s1_2->s1_4->s1_5->s1_6->s1_8->s1_9;
+s1_1->s1_3->s1_4->s1_9;
+s1_5->s1_7->s1_8;
+s1_9->final;
+*/
+a[b ? c : d] ||= e[f ? g : h] = i;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier (b)"];
+ s1_2[label="Identifier (c)"];
+ s1_4[label="ConditionalExpression:exit\nMemberExpression:exit"];
+ s1_5[label="AssignmentExpression:enter\nMemberExpression:enter\nIdentifier (e)\nConditionalExpression:enter\nIdentifier (f)"];
+ s1_6[label="Identifier (g)"];
+ s1_8[label="ConditionalExpression:exit\nMemberExpression:exit\nIdentifier (i)\nAssignmentExpression:exit"];
+ s1_9[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_3[label="Identifier (d)"];
+ s1_7[label="Identifier (h)"];
+ initial->s1_1->s1_2->s1_4->s1_5->s1_6->s1_8->s1_9;
+ s1_1->s1_3->s1_4->s1_9;
+ s1_5->s1_7->s1_8;
+ s1_9->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-2.js
new file mode 100644
index 0000000..4267d06
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--multi-2.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a &&= b ||= c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-3.js
new file mode 100644
index 0000000..00befa1
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--multi-3.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ||= b ||= c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-4.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-4.js
new file mode 100644
index 0000000..ef6e429
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--multi-4.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ||= b ??= c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-5.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-5.js
new file mode 100644
index 0000000..084fd01
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--multi-5.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ??= b ??= c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-6.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-6.js
new file mode 100644
index 0000000..b884186
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--multi-6.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ??= b &&= c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+ s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-7.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-7.js
new file mode 100644
index 0000000..1deb49c
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--multi-7.js
@@ -0,0 +1,24 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5;
+s1_1->s1_5;
+s1_2->s1_5;
+s1_3->s1_5->final;
+*/
+a ??= b ||= c &&= d;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+ s1_3[label="AssignmentExpression:enter\nIdentifier (c)"];
+ s1_4[label="Identifier (d)\nAssignmentExpression:exit\nAssignmentExpression:exit"];
+ s1_5[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_5;
+ s1_1->s1_5;
+ s1_2->s1_5;
+ s1_3->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-8.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-8.js
new file mode 100644
index 0000000..6dad336
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--multi-8.js
@@ -0,0 +1,14 @@
+/*expected
+initial->s1_1->final;
+*/
+a = b = c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)\nAssignmentExpression:enter\nIdentifier (b)\nIdentifier (c)\nAssignmentExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-9.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-9.js
new file mode 100644
index 0000000..679eecc
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--multi-9.js
@@ -0,0 +1,24 @@
+/*expected
+initial->s1_1->s1_2->s1_4->s1_5->s1_7->s1_8->s1_9;
+s1_1->s1_3->s1_4->s1_6->s1_7->s1_9->final;
+*/
+a[b ? c : d] = e[f ? g : h] ||= i;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier (b)"];
+ s1_2[label="Identifier (c)"];
+ s1_4[label="ConditionalExpression:exit\nMemberExpression:exit\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (e)\nConditionalExpression:enter\nIdentifier (f)"];
+ s1_5[label="Identifier (g)"];
+ s1_7[label="ConditionalExpression:exit\nMemberExpression:exit"];
+ s1_8[label="Identifier (i)"];
+ s1_9[label="AssignmentExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_3[label="Identifier (d)"];
+ s1_6[label="Identifier (h)"];
+ initial->s1_1->s1_2->s1_4->s1_5->s1_7->s1_8->s1_9;
+ s1_1->s1_3->s1_4->s1_6->s1_7->s1_9->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-1.js
new file mode 100644
index 0000000..c80d582
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-1.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+(a &&= b) && c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)"];
+ s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-2.js
new file mode 100644
index 0000000..2cfdcc3
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-2.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4->final;
+*/
+(a &&= b) || c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)"];
+ s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_3;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-3.js
new file mode 100644
index 0000000..8bb52f0
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-3.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+(a &&= b) ?? c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)"];
+ s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-1.js
new file mode 100644
index 0000000..e0ec2e8
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-1.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4->final;
+*/
+(a ||= b) && c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)"];
+ s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_3;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-2.js
new file mode 100644
index 0000000..8398352
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-2.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+(a ||= b) || c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)"];
+ s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-3.js
new file mode 100644
index 0000000..a5cb26c
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-3.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+(a ||= b) ?? c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)"];
+ s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-1.js
new file mode 100644
index 0000000..65d178b
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-1.js
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4;
+s1_1->s1_4->final;
+*/
+(a ??= b) && c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)"];
+ s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_3;
+ s1_2->s1_4;
+ s1_1->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-2.js
new file mode 100644
index 0000000..1e8eeac
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-2.js
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4;
+s1_1->s1_4->final;
+*/
+(a ??= b) || c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)"];
+ s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_3;
+ s1_2->s1_4;
+ s1_1->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-3.js
new file mode 100644
index 0000000..ab94d6b
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-3.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+(a ??= b) ?? c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_3[label="Identifier (c)"];
+ s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_4;
+ s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-1.js
new file mode 100644
index 0000000..90e49db
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-1.js
@@ -0,0 +1,18 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+a &&= b;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)"];
+ s1_3[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3;
+ s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-2.js
new file mode 100644
index 0000000..046559a
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-2.js
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+s1_1->s1_6;
+s1_2->s1_4->s1_5;
+s1_6->final;
+*/
+a &&= b ? c : d;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="ConditionalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)"];
+ s1_5[label="ConditionalExpression:exit"];
+ s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_4[label="Identifier (d)"];
+ initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+ s1_1->s1_6;
+ s1_2->s1_4->s1_5;
+ s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-3.js
new file mode 100644
index 0000000..cd3d0ec
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-3.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+s1_1->s1_3->s1_4->s1_6->final;
+*/
+a[b ? c : d] &&= e;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier (b)"];
+ s1_2[label="Identifier (c)"];
+ s1_4[label="ConditionalExpression:exit\nMemberExpression:exit"];
+ s1_5[label="Identifier (e)"];
+ s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_3[label="Identifier (d)"];
+ initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+ s1_1->s1_3->s1_4->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-bitand.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-bitand.js
new file mode 100644
index 0000000..e769969
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-bitand.js
@@ -0,0 +1,14 @@
+/*expected
+initial->s1_1->final;
+*/
+a &= b;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)\nIdentifier (b)\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-eq.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-eq.js
new file mode 100644
index 0000000..0022ada
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-eq.js
@@ -0,0 +1,14 @@
+/*expected
+initial->s1_1->final;
+*/
+a = b;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)\nIdentifier (b)\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-1.js
new file mode 100644
index 0000000..8737eaa
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-1.js
@@ -0,0 +1,18 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+a ||= b;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)"];
+ s1_3[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3;
+ s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-2.js
new file mode 100644
index 0000000..57f2702
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-2.js
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+s1_1->s1_6;
+s1_2->s1_4->s1_5;
+s1_6->final;
+*/
+a ||= b ? c : d;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="ConditionalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)"];
+ s1_5[label="ConditionalExpression:exit"];
+ s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_4[label="Identifier (d)"];
+ initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+ s1_1->s1_6;
+ s1_2->s1_4->s1_5;
+ s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-3.js
new file mode 100644
index 0000000..9ccc0de
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-3.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+s1_1->s1_3->s1_4->s1_6->final;
+*/
+a[b ? c : d] ||= e;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier (b)"];
+ s1_2[label="Identifier (c)"];
+ s1_4[label="ConditionalExpression:exit\nMemberExpression:exit"];
+ s1_5[label="Identifier (e)"];
+ s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_3[label="Identifier (d)"];
+ initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+ s1_1->s1_3->s1_4->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-plus.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-plus.js
new file mode 100644
index 0000000..1237039
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-plus.js
@@ -0,0 +1,14 @@
+/*expected
+initial->s1_1->final;
+*/
+a += b;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)\nIdentifier (b)\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-1.js
new file mode 100644
index 0000000..8ceede3
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-1.js
@@ -0,0 +1,18 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+a ??= b;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)"];
+ s1_3[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3;
+ s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-2.js
new file mode 100644
index 0000000..be5276a
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-2.js
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+s1_1->s1_6;
+s1_2->s1_4->s1_5;
+s1_6->final;
+*/
+a ??= b ? c : d;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+ s1_2[label="ConditionalExpression:enter\nIdentifier (b)"];
+ s1_3[label="Identifier (c)"];
+ s1_5[label="ConditionalExpression:exit"];
+ s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_4[label="Identifier (d)"];
+ initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+ s1_1->s1_6;
+ s1_2->s1_4->s1_5;
+ s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-3.js
new file mode 100644
index 0000000..bdf7fd8
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-3.js
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+s1_1->s1_3->s1_4->s1_6->final;
+*/
+a[b ? c : d] ??= e;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier (b)"];
+ s1_2[label="Identifier (c)"];
+ s1_4[label="ConditionalExpression:exit\nMemberExpression:exit"];
+ s1_5[label="Identifier (e)"];
+ s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_3[label="Identifier (d)"];
+ initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+ s1_1->s1_3->s1_4->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--while-and.js b/eslint/tests/fixtures/code-path-analysis/assignment--while-and.js
new file mode 100644
index 0000000..8cc5e45
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--while-and.js
@@ -0,0 +1,22 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5;
+s1_3->s1_5->final;
+*/
+while (a &&= b) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nWhileStatement:enter"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="WhileStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5;
+ s1_3->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--while-or.js b/eslint/tests/fixtures/code-path-analysis/assignment--while-or.js
new file mode 100644
index 0000000..cde21d5
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--while-or.js
@@ -0,0 +1,22 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+s1_3->s1_5->final;
+*/
+while (a ||= b) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nWhileStatement:enter"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="WhileStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+ s1_3->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--while-qq.js b/eslint/tests/fixtures/code-path-analysis/assignment--while-qq.js
new file mode 100644
index 0000000..39f3184
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/assignment--while-qq.js
@@ -0,0 +1,24 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+s1_3->s1_5;
+s1_2->s1_5->final;
+*/
+while (a ??= b) {
+ foo();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nWhileStatement:enter"];
+ s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+ s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+ s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_5[label="WhileStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+ s1_3->s1_5;
+ s1_2->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/logical--if-qdot-1.js b/eslint/tests/fixtures/code-path-analysis/logical--if-qdot-1.js
new file mode 100644
index 0000000..4e1d7b0
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/logical--if-qdot-1.js
@@ -0,0 +1,38 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_6->s1_7->s1_9->s1_11;
+s1_1->s1_3->s1_10->s1_11;
+s1_4->s1_6->s1_8->s1_9;
+s1_11->final;
+*/
+if (obj?.foo) {
+ if (obj?.bar) {
+ foo();
+ } else {
+ bar();
+ }
+} else {
+ qiz();
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nIfStatement:enter\nChainExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+ s1_2[label="Identifier (foo)\nMemberExpression:exit"];
+ s1_3[label="ChainExpression:exit"];
+ s1_4[label="BlockStatement:enter\nIfStatement:enter\nChainExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+ s1_5[label="Identifier (bar)\nMemberExpression:exit"];
+ s1_6[label="ChainExpression:exit"];
+ s1_7[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_9[label="IfStatement:exit\nBlockStatement:exit"];
+ s1_11[label="IfStatement:exit\nProgram:exit"];
+ s1_10[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (qiz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s1_8[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_6->s1_7->s1_9->s1_11;
+ s1_1->s1_3->s1_10->s1_11;
+ s1_4->s1_6->s1_8->s1_9;
+ s1_11->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-1.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-1.js
new file mode 100644
index 0000000..5444269
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-1.js
@@ -0,0 +1,38 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_9->s1_10->s1_11->s1_12->s1_13->s1_16;
+s1_1->s1_16;
+s1_2->s1_4->s1_5->s1_16;
+s1_6->s1_8->s1_16;
+s1_9->s1_11->s1_13;
+s1_16->final;
+*/
+
+obj?.[cond ? k1 : k2]?.[k3 || k4]?.(a1 && a2, b1 ?? b2).foo(arg)
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nCallExpression:enter\nMemberExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+ s1_2[label="ConditionalExpression:enter\nIdentifier (cond)"];
+ s1_3[label="Identifier (k1)"];
+ s1_5[label="ConditionalExpression:exit\nMemberExpression:exit"];
+ s1_6[label="LogicalExpression:enter\nIdentifier (k3)"];
+ s1_7[label="Identifier (k4)"];
+ s1_8[label="LogicalExpression:exit\nMemberExpression:exit"];
+ s1_9[label="LogicalExpression:enter\nIdentifier (a1)"];
+ s1_10[label="Identifier (a2)"];
+ s1_11[label="LogicalExpression:exit\nLogicalExpression:enter\nIdentifier (b1)"];
+ s1_12[label="Identifier (b2)"];
+ s1_13[label="LogicalExpression:exit\nCallExpression:exit\nIdentifier (foo)\nMemberExpression:exit\nIdentifier (arg)\nCallExpression:exit"];
+ s1_16[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_4[label="Identifier (k2)"];
+ initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_9->s1_10->s1_11->s1_12->s1_13->s1_16;
+ s1_1->s1_16;
+ s1_2->s1_4->s1_5->s1_16;
+ s1_6->s1_8->s1_16;
+ s1_9->s1_11->s1_13;
+ s1_16->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-2.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-2.js
new file mode 100644
index 0000000..8d53c07
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-2.js
@@ -0,0 +1,38 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_9->s1_10->s1_11->s1_12->s1_13->s1_16;
+s1_1->s1_16;
+s1_2->s1_4->s1_5->s1_16;
+s1_6->s1_8->s1_16;
+s1_9->s1_11->s1_13;
+s1_16->final;
+*/
+
+(obj?.[cond ? k1 : k2]?.[k3 || k4]?.(a1 && a2, b1 ?? b2)).foo(arg)
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nCallExpression:enter\nMemberExpression:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+ s1_2[label="ConditionalExpression:enter\nIdentifier (cond)"];
+ s1_3[label="Identifier (k1)"];
+ s1_5[label="ConditionalExpression:exit\nMemberExpression:exit"];
+ s1_6[label="LogicalExpression:enter\nIdentifier (k3)"];
+ s1_7[label="Identifier (k4)"];
+ s1_8[label="LogicalExpression:exit\nMemberExpression:exit"];
+ s1_9[label="LogicalExpression:enter\nIdentifier (a1)"];
+ s1_10[label="Identifier (a2)"];
+ s1_11[label="LogicalExpression:exit\nLogicalExpression:enter\nIdentifier (b1)"];
+ s1_12[label="Identifier (b2)"];
+ s1_13[label="LogicalExpression:exit\nCallExpression:exit"];
+ s1_16[label="ChainExpression:exit\nIdentifier (foo)\nMemberExpression:exit\nIdentifier (arg)\nCallExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_4[label="Identifier (k2)"];
+ initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_9->s1_10->s1_11->s1_12->s1_13->s1_16;
+ s1_1->s1_16;
+ s1_2->s1_4->s1_5->s1_16;
+ s1_6->s1_8->s1_16;
+ s1_9->s1_11->s1_13;
+ s1_16->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-3.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-3.js
new file mode 100644
index 0000000..0afef1c
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-3.js
@@ -0,0 +1,41 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_10->s1_11->s1_12->s1_13->s1_14->s1_15->s1_16;
+s1_1->s1_10;
+s1_2->s1_4->s1_5->s1_10;
+s1_6->s1_8;
+s1_10->s1_16;
+s1_11->s1_13->s1_15;
+s1_16->final;
+*/
+
+(obj?.[cond ? k1 : k2]?.[k3 || k4])?.(a1 && a2, b1 ?? b2).foo(arg)
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nCallExpression:enter\nChainExpression:enter\nMemberExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+ s1_2[label="ConditionalExpression:enter\nIdentifier (cond)"];
+ s1_3[label="Identifier (k1)"];
+ s1_5[label="ConditionalExpression:exit\nMemberExpression:exit"];
+ s1_6[label="LogicalExpression:enter\nIdentifier (k3)"];
+ s1_7[label="Identifier (k4)"];
+ s1_8[label="LogicalExpression:exit\nMemberExpression:exit"];
+ s1_10[label="ChainExpression:exit"];
+ s1_11[label="LogicalExpression:enter\nIdentifier (a1)"];
+ s1_12[label="Identifier (a2)"];
+ s1_13[label="LogicalExpression:exit\nLogicalExpression:enter\nIdentifier (b1)"];
+ s1_14[label="Identifier (b2)"];
+ s1_15[label="LogicalExpression:exit\nCallExpression:exit\nIdentifier (foo)\nMemberExpression:exit\nIdentifier (arg)\nCallExpression:exit"];
+ s1_16[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_4[label="Identifier (k2)"];
+ initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_10->s1_11->s1_12->s1_13->s1_14->s1_15->s1_16;
+ s1_1->s1_10;
+ s1_2->s1_4->s1_5->s1_10;
+ s1_6->s1_8;
+ s1_10->s1_16;
+ s1_11->s1_13->s1_15;
+ s1_16->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-1.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-1.js
new file mode 100644
index 0000000..d466a4e
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-1.js
@@ -0,0 +1,19 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+
+obj?.aaa.bbb(arg)
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+ s1_2[label="Identifier (aaa)\nMemberExpression:exit\nIdentifier (bbb)\nMemberExpression:exit\nIdentifier (arg)\nCallExpression:exit"];
+ s1_3[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3;
+ s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-2.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-2.js
new file mode 100644
index 0000000..a586752
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-2.js
@@ -0,0 +1,19 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+
+obj.foo?.(arg)
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nIdentifier (obj)\nIdentifier (foo)\nMemberExpression:exit"];
+ s1_2[label="Identifier (arg)\nCallExpression:exit"];
+ s1_3[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3;
+ s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-3.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-3.js
new file mode 100644
index 0000000..fa43af9
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-3.js
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_7;
+s1_1->s1_7;
+s1_2->s1_7;
+s1_3->s1_7->final;
+*/
+
+obj?.aaa?.bbb?.(arg)
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+ s1_2[label="Identifier (aaa)\nMemberExpression:exit"];
+ s1_3[label="Identifier (bbb)\nMemberExpression:exit"];
+ s1_4[label="Identifier (arg)\nCallExpression:exit"];
+ s1_7[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4->s1_7;
+ s1_1->s1_7;
+ s1_2->s1_7;
+ s1_3->s1_7->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-4.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-4.js
new file mode 100644
index 0000000..c101746
--- /dev/null
+++ b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-4.js
@@ -0,0 +1,19 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+
+func?.()(arg)
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nCallExpression:enter\nIdentifier (func)\nCallExpression:exit"];
+ s1_2[label="Identifier (arg)\nCallExpression:exit"];
+ s1_3[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3;
+ s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/parsers/arrow-parens/generics-extends-complex.js b/eslint/tests/fixtures/parsers/arrow-parens/generics-extends-complex.js
new file mode 100644
index 0000000..4a27b49
--- /dev/null
+++ b/eslint/tests/fixtures/parsers/arrow-parens/generics-extends-complex.js
@@ -0,0 +1,249 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * (a) => b
+ */
+
+exports.parse = () => ({
+ type: "Program",
+ body: [
+ {
+ type: "ExpressionStatement",
+ expression: {
+ type: "ArrowFunctionExpression",
+ generator: false,
+ id: null,
+ params: [
+ {
+ type: "Identifier",
+ name: "a",
+ range: [24, 25],
+ loc: {
+ start: { line: 1, column: 24 },
+ end: { line: 1, column: 25 },
+ },
+ },
+ ],
+ body: {
+ type: "Identifier",
+ name: "b",
+ range: [30, 31],
+ loc: { start: { line: 1, column: 30 }, end: { line: 1, column: 31 } },
+ },
+ async: false,
+ expression: true,
+ range: [0, 31],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 31 } },
+ typeParameters: {
+ type: "TSTypeParameterDeclaration",
+ range: [0, 23],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 23 } },
+ params: [
+ {
+ type: "TSTypeParameter",
+ name: {
+ type: "Identifier",
+ name: "T",
+ range: [1, 2],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 2 },
+ },
+ },
+ constraint: {
+ type: "TSIntersectionType",
+ types: [
+ {
+ type: "TSParenthesizedType",
+ typeAnnotation: {
+ type: "TSUnionType",
+ types: [
+ {
+ type: "TSTypeReference",
+ typeName: {
+ type: "Identifier",
+ name: "A",
+ range: [12, 13],
+ loc: {
+ start: { line: 1, column: 12 },
+ end: { line: 1, column: 13 },
+ },
+ },
+ range: [12, 13],
+ loc: {
+ start: { line: 1, column: 12 },
+ end: { line: 1, column: 13 },
+ },
+ },
+ {
+ type: "TSTypeReference",
+ typeName: {
+ type: "Identifier",
+ name: "B",
+ range: [16, 17],
+ loc: {
+ start: { line: 1, column: 16 },
+ end: { line: 1, column: 17 },
+ },
+ },
+ range: [16, 17],
+ loc: {
+ start: { line: 1, column: 16 },
+ end: { line: 1, column: 17 },
+ },
+ },
+ ],
+ range: [12, 17],
+ loc: {
+ start: { line: 1, column: 12 },
+ end: { line: 1, column: 17 },
+ },
+ },
+ range: [11, 18],
+ loc: {
+ start: { line: 1, column: 11 },
+ end: { line: 1, column: 18 },
+ },
+ },
+ {
+ type: "TSTypeReference",
+ typeName: {
+ type: "Identifier",
+ name: "C",
+ range: [21, 22],
+ loc: {
+ start: { line: 1, column: 21 },
+ end: { line: 1, column: 22 },
+ },
+ },
+ range: [21, 22],
+ loc: {
+ start: { line: 1, column: 21 },
+ end: { line: 1, column: 22 },
+ },
+ },
+ ],
+ range: [11, 22],
+ loc: {
+ start: { line: 1, column: 11 },
+ end: { line: 1, column: 22 },
+ },
+ },
+ range: [1, 22],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 22 },
+ },
+ },
+ ],
+ },
+ },
+ range: [0, 31],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 31 } },
+ },
+ ],
+ sourceType: "script",
+ range: [0, 31],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 31 } },
+ tokens: [
+ {
+ type: "Punctuator",
+ value: "<",
+ range: [0, 1],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } },
+ },
+ {
+ type: "Identifier",
+ value: "T",
+ range: [1, 2],
+ loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
+ },
+ {
+ type: "Keyword",
+ value: "extends",
+ range: [3, 10],
+ loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 10 } },
+ },
+ {
+ type: "Punctuator",
+ value: "(",
+ range: [11, 12],
+ loc: { start: { line: 1, column: 11 }, end: { line: 1, column: 12 } },
+ },
+ {
+ type: "Identifier",
+ value: "A",
+ range: [12, 13],
+ loc: { start: { line: 1, column: 12 }, end: { line: 1, column: 13 } },
+ },
+ {
+ type: "Punctuator",
+ value: "|",
+ range: [14, 15],
+ loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 15 } },
+ },
+ {
+ type: "Identifier",
+ value: "B",
+ range: [16, 17],
+ loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 17 } },
+ },
+ {
+ type: "Punctuator",
+ value: ")",
+ range: [17, 18],
+ loc: { start: { line: 1, column: 17 }, end: { line: 1, column: 18 } },
+ },
+ {
+ type: "Punctuator",
+ value: "&",
+ range: [19, 20],
+ loc: { start: { line: 1, column: 19 }, end: { line: 1, column: 20 } },
+ },
+ {
+ type: "Identifier",
+ value: "C",
+ range: [21, 22],
+ loc: { start: { line: 1, column: 21 }, end: { line: 1, column: 22 } },
+ },
+ {
+ type: "Punctuator",
+ value: ">",
+ range: [22, 23],
+ loc: { start: { line: 1, column: 22 }, end: { line: 1, column: 23 } },
+ },
+ {
+ type: "Punctuator",
+ value: "(",
+ range: [23, 24],
+ loc: { start: { line: 1, column: 23 }, end: { line: 1, column: 24 } },
+ },
+ {
+ type: "Identifier",
+ value: "a",
+ range: [24, 25],
+ loc: { start: { line: 1, column: 24 }, end: { line: 1, column: 25 } },
+ },
+ {
+ type: "Punctuator",
+ value: ")",
+ range: [25, 26],
+ loc: { start: { line: 1, column: 25 }, end: { line: 1, column: 26 } },
+ },
+ {
+ type: "Punctuator",
+ value: "=>",
+ range: [27, 29],
+ loc: { start: { line: 1, column: 27 }, end: { line: 1, column: 29 } },
+ },
+ {
+ type: "Identifier",
+ value: "b",
+ range: [30, 31],
+ loc: { start: { line: 1, column: 30 }, end: { line: 1, column: 31 } },
+ },
+ ],
+ comments: [],
+ });
diff --git a/eslint/tests/fixtures/parsers/arrow-parens/generics-extends.js b/eslint/tests/fixtures/parsers/arrow-parens/generics-extends.js
new file mode 100644
index 0000000..ee70b56
--- /dev/null
+++ b/eslint/tests/fixtures/parsers/arrow-parens/generics-extends.js
@@ -0,0 +1,151 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * (a) => b
+ */
+
+exports.parse = () => ({
+ type: "Program",
+ body: [
+ {
+ type: "ExpressionStatement",
+ expression: {
+ type: "ArrowFunctionExpression",
+ generator: false,
+ id: null,
+ params: [
+ {
+ type: "Identifier",
+ name: "a",
+ range: [14, 15],
+ loc: {
+ start: { line: 1, column: 14 },
+ end: { line: 1, column: 15 },
+ },
+ },
+ ],
+ body: {
+ type: "Identifier",
+ name: "b",
+ range: [20, 21],
+ loc: { start: { line: 1, column: 20 }, end: { line: 1, column: 21 } },
+ },
+ async: false,
+ expression: true,
+ range: [0, 21],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 21 } },
+ typeParameters: {
+ type: "TSTypeParameterDeclaration",
+ range: [0, 13],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 13 } },
+ params: [
+ {
+ type: "TSTypeParameter",
+ name: {
+ type: "Identifier",
+ name: "T",
+ range: [1, 2],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 2 },
+ },
+ },
+ constraint: {
+ type: "TSTypeReference",
+ typeName: {
+ type: "Identifier",
+ name: "A",
+ range: [11, 12],
+ loc: {
+ start: { line: 1, column: 11 },
+ end: { line: 1, column: 12 },
+ },
+ },
+ range: [11, 12],
+ loc: {
+ start: { line: 1, column: 11 },
+ end: { line: 1, column: 12 },
+ },
+ },
+ range: [1, 12],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 12 },
+ },
+ },
+ ],
+ },
+ },
+ range: [0, 21],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 21 } },
+ },
+ ],
+ sourceType: "script",
+ range: [0, 21],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 21 } },
+ tokens: [
+ {
+ type: "Punctuator",
+ value: "<",
+ range: [0, 1],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } },
+ },
+ {
+ type: "Identifier",
+ value: "T",
+ range: [1, 2],
+ loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
+ },
+ {
+ type: "Keyword",
+ value: "extends",
+ range: [3, 10],
+ loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 10 } },
+ },
+ {
+ type: "Identifier",
+ value: "A",
+ range: [11, 12],
+ loc: { start: { line: 1, column: 11 }, end: { line: 1, column: 12 } },
+ },
+ {
+ type: "Punctuator",
+ value: ">",
+ range: [12, 13],
+ loc: { start: { line: 1, column: 12 }, end: { line: 1, column: 13 } },
+ },
+ {
+ type: "Punctuator",
+ value: "(",
+ range: [13, 14],
+ loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 14 } },
+ },
+ {
+ type: "Identifier",
+ value: "a",
+ range: [14, 15],
+ loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 15 } },
+ },
+ {
+ type: "Punctuator",
+ value: ")",
+ range: [15, 16],
+ loc: { start: { line: 1, column: 15 }, end: { line: 1, column: 16 } },
+ },
+ {
+ type: "Punctuator",
+ value: "=>",
+ range: [17, 19],
+ loc: { start: { line: 1, column: 17 }, end: { line: 1, column: 19 } },
+ },
+ {
+ type: "Identifier",
+ value: "b",
+ range: [20, 21],
+ loc: { start: { line: 1, column: 20 }, end: { line: 1, column: 21 } },
+ },
+ ],
+ comments: [],
+ });
diff --git a/eslint/tests/fixtures/parsers/arrow-parens/generics-simple-async.js b/eslint/tests/fixtures/parsers/arrow-parens/generics-simple-async.js
new file mode 100644
index 0000000..6892612
--- /dev/null
+++ b/eslint/tests/fixtures/parsers/arrow-parens/generics-simple-async.js
@@ -0,0 +1,128 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * async (a) => b
+ */
+
+exports.parse = () => ({
+ type: "Program",
+ body: [
+ {
+ type: "ExpressionStatement",
+ expression: {
+ type: "ArrowFunctionExpression",
+ generator: false,
+ id: null,
+ params: [
+ {
+ type: "Identifier",
+ name: "a",
+ range: [10, 11],
+ loc: {
+ start: { line: 1, column: 10 },
+ end: { line: 1, column: 11 },
+ },
+ },
+ ],
+ body: {
+ type: "Identifier",
+ name: "b",
+ range: [16, 17],
+ loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 17 } },
+ },
+ async: true,
+ expression: true,
+ range: [0, 17],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } },
+ typeParameters: {
+ type: "TSTypeParameterDeclaration",
+ range: [6, 9],
+ loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 9 } },
+ params: [
+ {
+ type: "TSTypeParameter",
+ name: {
+ type: "Identifier",
+ name: "T",
+ range: [7, 8],
+ loc: {
+ start: { line: 1, column: 7 },
+ end: { line: 1, column: 8 },
+ },
+ },
+ range: [7, 8],
+ loc: {
+ start: { line: 1, column: 7 },
+ end: { line: 1, column: 8 },
+ },
+ },
+ ],
+ },
+ },
+ range: [0, 17],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } },
+ },
+ ],
+ sourceType: "script",
+ range: [0, 17],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } },
+ tokens: [
+ {
+ type: "Identifier",
+ value: "async",
+ range: [0, 5],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 5 } },
+ },
+ {
+ type: "Punctuator",
+ value: "<",
+ range: [6, 7],
+ loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } },
+ },
+ {
+ type: "Identifier",
+ value: "T",
+ range: [7, 8],
+ loc: { start: { line: 1, column: 7 }, end: { line: 1, column: 8 } },
+ },
+ {
+ type: "Punctuator",
+ value: ">",
+ range: [8, 9],
+ loc: { start: { line: 1, column: 8 }, end: { line: 1, column: 9 } },
+ },
+ {
+ type: "Punctuator",
+ value: "(",
+ range: [9, 10],
+ loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 10 } },
+ },
+ {
+ type: "Identifier",
+ value: "a",
+ range: [10, 11],
+ loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } },
+ },
+ {
+ type: "Punctuator",
+ value: ")",
+ range: [11, 12],
+ loc: { start: { line: 1, column: 11 }, end: { line: 1, column: 12 } },
+ },
+ {
+ type: "Punctuator",
+ value: "=>",
+ range: [13, 15],
+ loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 15 } },
+ },
+ {
+ type: "Identifier",
+ value: "b",
+ range: [16, 17],
+ loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 17 } },
+ },
+ ],
+ comments: [],
+ });
diff --git a/eslint/tests/fixtures/parsers/arrow-parens/generics-simple-no-params.js b/eslint/tests/fixtures/parsers/arrow-parens/generics-simple-no-params.js
new file mode 100644
index 0000000..ab17a7d
--- /dev/null
+++ b/eslint/tests/fixtures/parsers/arrow-parens/generics-simple-no-params.js
@@ -0,0 +1,106 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * () => b
+ */
+
+exports.parse = () => ({
+ type: "Program",
+ body: [
+ {
+ type: "ExpressionStatement",
+ expression: {
+ type: "ArrowFunctionExpression",
+ generator: false,
+ id: null,
+ params: [],
+ body: {
+ type: "Identifier",
+ name: "b",
+ range: [9, 10],
+ loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 10 } },
+ },
+ async: false,
+ expression: true,
+ range: [0, 10],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 10 } },
+ typeParameters: {
+ type: "TSTypeParameterDeclaration",
+ range: [0, 3],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 3 } },
+ params: [
+ {
+ type: "TSTypeParameter",
+ name: {
+ type: "Identifier",
+ name: "T",
+ range: [1, 2],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 2 },
+ },
+ },
+ range: [1, 2],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 2 },
+ },
+ },
+ ],
+ },
+ },
+ range: [0, 10],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 10 } },
+ },
+ ],
+ sourceType: "script",
+ range: [0, 10],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 10 } },
+ tokens: [
+ {
+ type: "Punctuator",
+ value: "<",
+ range: [0, 1],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } },
+ },
+ {
+ type: "Identifier",
+ value: "T",
+ range: [1, 2],
+ loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
+ },
+ {
+ type: "Punctuator",
+ value: ">",
+ range: [2, 3],
+ loc: { start: { line: 1, column: 2 }, end: { line: 1, column: 3 } },
+ },
+ {
+ type: "Punctuator",
+ value: "(",
+ range: [3, 4],
+ loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } },
+ },
+ {
+ type: "Punctuator",
+ value: ")",
+ range: [4, 5],
+ loc: { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } },
+ },
+ {
+ type: "Punctuator",
+ value: "=>",
+ range: [6, 8],
+ loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 8 } },
+ },
+ {
+ type: "Identifier",
+ value: "b",
+ range: [9, 10],
+ loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 10 } },
+ },
+ ],
+ comments: [],
+ });
diff --git a/eslint/tests/fixtures/parsers/arrow-parens/generics-simple.js b/eslint/tests/fixtures/parsers/arrow-parens/generics-simple.js
new file mode 100644
index 0000000..92f1a5b
--- /dev/null
+++ b/eslint/tests/fixtures/parsers/arrow-parens/generics-simple.js
@@ -0,0 +1,119 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * (a) => b
+ */
+
+exports.parse = () => ({
+ type: "Program",
+ body: [
+ {
+ type: "ExpressionStatement",
+ expression: {
+ type: "ArrowFunctionExpression",
+ generator: false,
+ id: null,
+ params: [
+ {
+ type: "Identifier",
+ name: "a",
+ range: [4, 5],
+ loc: { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } },
+ },
+ ],
+ body: {
+ type: "Identifier",
+ name: "b",
+ range: [10, 11],
+ loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } },
+ },
+ async: false,
+ expression: true,
+ range: [0, 11],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 11 } },
+ typeParameters: {
+ type: "TSTypeParameterDeclaration",
+ range: [0, 3],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 3 } },
+ params: [
+ {
+ type: "TSTypeParameter",
+ name: {
+ type: "Identifier",
+ name: "T",
+ range: [1, 2],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 2 },
+ },
+ },
+ range: [1, 2],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 2 },
+ },
+ },
+ ],
+ },
+ },
+ range: [0, 11],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 11 } },
+ },
+ ],
+ sourceType: "script",
+ range: [0, 11],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 11 } },
+ tokens: [
+ {
+ type: "Punctuator",
+ value: "<",
+ range: [0, 1],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } },
+ },
+ {
+ type: "Identifier",
+ value: "T",
+ range: [1, 2],
+ loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
+ },
+ {
+ type: "Punctuator",
+ value: ">",
+ range: [2, 3],
+ loc: { start: { line: 1, column: 2 }, end: { line: 1, column: 3 } },
+ },
+ {
+ type: "Punctuator",
+ value: "(",
+ range: [3, 4],
+ loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } },
+ },
+ {
+ type: "Identifier",
+ value: "a",
+ range: [4, 5],
+ loc: { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } },
+ },
+ {
+ type: "Punctuator",
+ value: ")",
+ range: [5, 6],
+ loc: { start: { line: 1, column: 5 }, end: { line: 1, column: 6 } },
+ },
+ {
+ type: "Punctuator",
+ value: "=>",
+ range: [7, 9],
+ loc: { start: { line: 1, column: 7 }, end: { line: 1, column: 9 } },
+ },
+ {
+ type: "Identifier",
+ value: "b",
+ range: [10, 11],
+ loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } },
+ },
+ ],
+ comments: [],
+ });
diff --git a/eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-1.js b/eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-1.js
new file mode 100644
index 0000000..295eb65
--- /dev/null
+++ b/eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-1.js
@@ -0,0 +1,206 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@4.2.0
+ * Source code:
+ * class A { foo(bar: string): void{} }
+ */
+
+exports.parse = () => ({
+ type: "Program",
+ body: [
+ {
+ type: "ClassDeclaration",
+ id: {
+ type: "Identifier",
+ name: "A",
+ range: [6, 7],
+ loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } },
+ },
+ body: {
+ type: "ClassBody",
+ body: [
+ {
+ type: "MethodDefinition",
+ key: {
+ type: "Identifier",
+ name: "foo",
+ range: [10, 13],
+ loc: {
+ start: { line: 1, column: 10 },
+ end: { line: 1, column: 13 },
+ },
+ },
+ value: {
+ type: "FunctionExpression",
+ id: null,
+ generator: false,
+ expression: false,
+ async: false,
+ body: {
+ type: "BlockStatement",
+ body: [],
+ range: [32, 34],
+ loc: {
+ start: { line: 1, column: 32 },
+ end: { line: 1, column: 34 },
+ },
+ },
+ range: [13, 34],
+ params: [
+ {
+ type: "Identifier",
+ name: "bar",
+ range: [14, 25],
+ loc: {
+ start: { line: 1, column: 14 },
+ end: { line: 1, column: 25 },
+ },
+ typeAnnotation: {
+ type: "TSTypeAnnotation",
+ loc: {
+ start: { line: 1, column: 17 },
+ end: { line: 1, column: 25 },
+ },
+ range: [17, 25],
+ typeAnnotation: {
+ type: "TSStringKeyword",
+ range: [19, 25],
+ loc: {
+ start: { line: 1, column: 19 },
+ end: { line: 1, column: 25 },
+ },
+ },
+ },
+ },
+ ],
+ loc: {
+ start: { line: 1, column: 13 },
+ end: { line: 1, column: 34 },
+ },
+ returnType: {
+ type: "TSTypeAnnotation",
+ loc: {
+ start: { line: 1, column: 26 },
+ end: { line: 1, column: 32 },
+ },
+ range: [26, 32],
+ typeAnnotation: {
+ type: "TSVoidKeyword",
+ range: [28, 32],
+ loc: {
+ start: { line: 1, column: 28 },
+ end: { line: 1, column: 32 },
+ },
+ },
+ },
+ },
+ computed: false,
+ static: false,
+ kind: "method",
+ range: [10, 34],
+ loc: {
+ start: { line: 1, column: 10 },
+ end: { line: 1, column: 34 },
+ },
+ },
+ ],
+ range: [8, 36],
+ loc: { start: { line: 1, column: 8 }, end: { line: 1, column: 36 } },
+ },
+ superClass: null,
+ range: [0, 36],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 36 } },
+ },
+ ],
+ sourceType: "script",
+ range: [0, 36],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 36 } },
+ tokens: [
+ {
+ type: "Keyword",
+ value: "class",
+ range: [0, 5],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 5 } },
+ },
+ {
+ type: "Identifier",
+ value: "A",
+ range: [6, 7],
+ loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } },
+ },
+ {
+ type: "Punctuator",
+ value: "{",
+ range: [8, 9],
+ loc: { start: { line: 1, column: 8 }, end: { line: 1, column: 9 } },
+ },
+ {
+ type: "Identifier",
+ value: "foo",
+ range: [10, 13],
+ loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 13 } },
+ },
+ {
+ type: "Punctuator",
+ value: "(",
+ range: [13, 14],
+ loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 14 } },
+ },
+ {
+ type: "Identifier",
+ value: "bar",
+ range: [14, 17],
+ loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 17 } },
+ },
+ {
+ type: "Punctuator",
+ value: ":",
+ range: [17, 18],
+ loc: { start: { line: 1, column: 17 }, end: { line: 1, column: 18 } },
+ },
+ {
+ type: "Identifier",
+ value: "string",
+ range: [19, 25],
+ loc: { start: { line: 1, column: 19 }, end: { line: 1, column: 25 } },
+ },
+ {
+ type: "Punctuator",
+ value: ")",
+ range: [25, 26],
+ loc: { start: { line: 1, column: 25 }, end: { line: 1, column: 26 } },
+ },
+ {
+ type: "Punctuator",
+ value: ":",
+ range: [26, 27],
+ loc: { start: { line: 1, column: 26 }, end: { line: 1, column: 27 } },
+ },
+ {
+ type: "Keyword",
+ value: "void",
+ range: [28, 32],
+ loc: { start: { line: 1, column: 28 }, end: { line: 1, column: 32 } },
+ },
+ {
+ type: "Punctuator",
+ value: "{",
+ range: [32, 33],
+ loc: { start: { line: 1, column: 32 }, end: { line: 1, column: 33 } },
+ },
+ {
+ type: "Punctuator",
+ value: "}",
+ range: [33, 34],
+ loc: { start: { line: 1, column: 33 }, end: { line: 1, column: 34 } },
+ },
+ {
+ type: "Punctuator",
+ value: "}",
+ range: [35, 36],
+ loc: { start: { line: 1, column: 35 }, end: { line: 1, column: 36 } },
+ },
+ ],
+ comments: [],
+ });
diff --git a/eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-2.js b/eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-2.js
new file mode 100644
index 0000000..48c444b
--- /dev/null
+++ b/eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-2.js
@@ -0,0 +1,98 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@4.2.0
+ * Source code:
+ * function foo(): null {}
+ */
+
+exports.parse = () => ({
+ type: "Program",
+ body: [
+ {
+ type: "FunctionDeclaration",
+ id: {
+ type: "Identifier",
+ name: "foo",
+ range: [9, 12],
+ loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 12 } },
+ },
+ generator: false,
+ expression: false,
+ async: false,
+ params: [],
+ body: {
+ type: "BlockStatement",
+ body: [],
+ range: [21, 23],
+ loc: { start: { line: 1, column: 21 }, end: { line: 1, column: 23 } },
+ },
+ range: [0, 23],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 23 } },
+ returnType: {
+ type: "TSTypeAnnotation",
+ loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 20 } },
+ range: [14, 20],
+ typeAnnotation: {
+ type: "TSNullKeyword",
+ range: [16, 20],
+ loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 20 } },
+ },
+ },
+ },
+ ],
+ sourceType: "script",
+ range: [0, 23],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 23 } },
+ tokens: [
+ {
+ type: "Keyword",
+ value: "function",
+ range: [0, 8],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 8 } },
+ },
+ {
+ type: "Identifier",
+ value: "foo",
+ range: [9, 12],
+ loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 12 } },
+ },
+ {
+ type: "Punctuator",
+ value: "(",
+ range: [12, 13],
+ loc: { start: { line: 1, column: 12 }, end: { line: 1, column: 13 } },
+ },
+ {
+ type: "Punctuator",
+ value: ")",
+ range: [13, 14],
+ loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 14 } },
+ },
+ {
+ type: "Punctuator",
+ value: ":",
+ range: [14, 15],
+ loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 15 } },
+ },
+ {
+ type: "Keyword",
+ value: "null",
+ range: [16, 20],
+ loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 20 } },
+ },
+ {
+ type: "Punctuator",
+ value: "{",
+ range: [21, 22],
+ loc: { start: { line: 1, column: 21 }, end: { line: 1, column: 22 } },
+ },
+ {
+ type: "Punctuator",
+ value: "}",
+ range: [22, 23],
+ loc: { start: { line: 1, column: 22 }, end: { line: 1, column: 23 } },
+ },
+ ],
+ comments: [],
+ });
diff --git a/eslint/tests/fixtures/testers/rule-tester/no-var.js b/eslint/tests/fixtures/testers/rule-tester/no-var.js
index 3828856..5841f15 100644
--- a/eslint/tests/fixtures/testers/rule-tester/no-var.js
+++ b/eslint/tests/fixtures/testers/rule-tester/no-var.js
@@ -7,27 +7,31 @@
// Rule Definition
//------------------------------------------------------------------------------
-module.exports = function(context) {
+"use strict";
- "use strict";
+module.exports = {
+ meta: {
+ fixable: "code"
+ },
- var sourceCode = context.getSourceCode();
+ create(context) {
- return {
+ var sourceCode = context.getSourceCode();
- "VariableDeclaration": function(node) {
- if (node.kind === "var") {
- context.report({
- node: node,
- loc: sourceCode.getFirstToken(node).loc,
- message: "Bad var.",
- fix: function(fixer) {
- return fixer.remove(sourceCode.getFirstToken(node));
- }
- })
+ return {
+ "VariableDeclaration": function(node) {
+ if (node.kind === "var") {
+ context.report({
+ node: node,
+ loc: sourceCode.getFirstToken(node).loc,
+ message: "Bad var.",
+ fix: function(fixer) {
+ return fixer.remove(sourceCode.getFirstToken(node));
+ }
+ })
+ }
}
- }
- };
-
+ };
+ }
};
diff --git a/eslint/tests/lib/cli-engine/cascading-config-array-factory.js b/eslint/tests/lib/cli-engine/cascading-config-array-factory.js
deleted file mode 100644
index 8471ac3..0000000
--- a/eslint/tests/lib/cli-engine/cascading-config-array-factory.js
+++ /dev/null
@@ -1,1601 +0,0 @@
-/**
- * @fileoverview Tests for CascadingConfigArrayFactory class.
- * @author Toru Nagashima
- */
-"use strict";
-
-const fs = require("fs");
-const path = require("path");
-const os = require("os");
-const { assert } = require("chai");
-const sh = require("shelljs");
-const sinon = require("sinon");
-const { ConfigArrayFactory } = require("../../../lib/cli-engine/config-array-factory");
-const { ExtractedConfig } = require("../../../lib/cli-engine/config-array/extracted-config");
-const { defineCascadingConfigArrayFactoryWithInMemoryFileSystem } = require("../../_utils");
-
-/** @typedef {InstanceType["CascadingConfigArrayFactory"]>} CascadingConfigArrayFactory */
-/** @typedef {ReturnType} ConfigArray */
-
-const cwdIgnorePatterns = new ConfigArrayFactory()
- .loadDefaultESLintIgnore()[0]
- .ignorePattern
- .patterns;
-
-describe("CascadingConfigArrayFactory", () => {
- describe("'getConfigArrayForFile(filePath)' method should retrieve the proper configuration.", () => {
- describe("with three directories ('lib', 'lib/nested', 'test') that contains 'one.js' and 'two.js'", () => {
- const root = path.join(os.tmpdir(), "eslint/cli-engine/cascading-config-array-factory");
- const files = {
- /* eslint-disable quote-props */
- "lib": {
- "nested": {
- "one.js": "",
- "two.js": "",
- "parser.js": "",
- ".eslintrc.yml": "parser: './parser'"
- },
- "one.js": "",
- "two.js": ""
- },
- "test": {
- "one.js": "",
- "two.js": "",
- ".eslintrc.yml": "env: { mocha: true }"
- },
- ".eslintignore": "/lib/nested/parser.js",
- ".eslintrc.json": JSON.stringify({
- rules: {
- "no-undef": "error",
- "no-unused-vars": "error"
- }
- })
- /* eslint-enable quote-props */
- };
- const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd: () => root, files });
-
- /** @type {CascadingConfigArrayFactory} */
- let factory;
-
- beforeEach(() => {
- factory = new CascadingConfigArrayFactory();
- });
-
- it("should retrieve the config '.eslintrc.json' if 'lib/one.js' was given.", () => {
- const config = factory.getConfigArrayForFile("lib/one.js");
-
- assert.strictEqual(config.length, 3);
- assert.strictEqual(config[0].name, "DefaultIgnorePattern");
- assert.strictEqual(config[1].filePath, path.join(root, ".eslintrc.json"));
- assert.strictEqual(config[2].filePath, path.join(root, ".eslintignore"));
- });
-
- it("should retrieve the merged config of '.eslintrc.json' and 'lib/nested/.eslintrc.yml' if 'lib/nested/one.js' was given.", () => {
- const config = factory.getConfigArrayForFile("lib/nested/one.js");
-
- assert.strictEqual(config.length, 4);
- assert.strictEqual(config[0].name, "DefaultIgnorePattern");
- assert.strictEqual(config[1].filePath, path.join(root, ".eslintrc.json"));
- assert.strictEqual(config[2].filePath, path.join(root, "lib/nested/.eslintrc.yml"));
- assert.strictEqual(config[3].filePath, path.join(root, ".eslintignore"));
- });
-
- it("should retrieve the config '.eslintrc.json' if 'lib/non-exist.js' was given.", () => {
- const config = factory.getConfigArrayForFile("lib/non-exist.js");
-
- assert.strictEqual(config.length, 3);
- assert.strictEqual(config[0].name, "DefaultIgnorePattern");
- assert.strictEqual(config[1].filePath, path.join(root, ".eslintrc.json"));
- assert.strictEqual(config[2].filePath, path.join(root, ".eslintignore"));
- });
- });
-
- describe("deprecation warnings", () => {
- let uid = 0;
- let uniqueHomeDirName = "";
- let homeDir = "";
- let cwd = "";
-
- /** @type {{code:string, message:string}[]} */
- let warnings = [];
-
- /** @type {CascadingConfigArrayFactory} */
- let factory = null;
-
- /** @type {ConfigArray} */
- let config = null;
-
- /**
- * Store a reported warning object if that code starts with `ESLINT_`.
- * @param {{code:string, message:string}} w The warning object to store.
- * @returns {void}
- */
- function onWarning(w) {
- if (w.code.startsWith("ESLINT_")) {
- warnings.push({ code: w.code, message: w.message });
- }
- }
-
- /**
- * Delay to wait for 'warning' events.
- * @returns {Promise} The promise that will be fulfilled after wait a timer.
- */
- function delay() {
- return new Promise(resolve => setTimeout(resolve, 0));
- }
-
- beforeEach(() => {
- uniqueHomeDirName = `home_${++uid}`;
- homeDir = path.join(__dirname, `../../../${uniqueHomeDirName}`);
- warnings = [];
- sinon.stub(os, "homedir").returns(homeDir);
- process.on("warning", onWarning);
- });
- afterEach(() => {
- os.homedir.restore();
- process.removeListener("warning", onWarning);
- });
-
- describe("when '~/.eslintrc.json' exists and CWD is `~/`", () => {
- beforeEach(() => {
- cwd = homeDir;
- const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => cwd,
- files: {
-
- // ~/.eslintrc.json
- ".eslintrc.json": JSON.stringify({ rules: { eqeqeq: "error" } }),
-
- // other files
- "exist-with-root/test.js": "",
- "exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
- "exist/test.js": "",
- "exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
- "not-exist/test.js": ""
- }
- });
-
- factory = new CascadingConfigArrayFactory({ cwd });
- });
-
- // no warning.
- describe("when it lints 'subdir/exist-with-root/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("exist-with-root/test.js");
- await delay();
- });
-
- it("should not raise any warnings.", () => {
- assert.deepStrictEqual(warnings, []);
- });
-
- it("should not load '~/.eslintrc.json'.", () => {
- assert.deepStrictEqual(
- config.extractConfig("a.js").rules,
- { yoda: ["error"] }
- );
- });
- });
-
- // no warning.
- describe("when it lints 'subdir/exist/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("exist/test.js");
- await delay();
- });
-
- it("should not raise any warnings.", () => {
- assert.deepStrictEqual(warnings, []);
- });
-
- it("should load '~/.eslintrc.json'.", () => {
- assert.deepStrictEqual(
- config.extractConfig("a.js").rules,
- { eqeqeq: ["error"], yoda: ["error"] }
- );
- });
- });
-
- // no warning
- describe("when it lints 'subdir/not-exist/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("not-exist/test.js");
- await delay();
- });
-
- it("should not raise any warnings.", () => {
- assert.deepStrictEqual(warnings, []);
- });
-
- it("should load '~/.eslintrc.json'.", () => {
- assert.deepStrictEqual(
- config.extractConfig("a.js").rules,
- { eqeqeq: ["error"] }
- );
- });
- });
- });
-
- describe("when '~/.eslintrc.json' exists and CWD is `~/subdir`", () => {
- beforeEach(() => {
- cwd = path.join(homeDir, "subdir");
- const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => cwd,
- files: {
-
- // ~/.eslintrc.json
- "../.eslintrc.json": JSON.stringify({ rules: { eqeqeq: "error" } }),
-
- // other files
- "exist-with-root/test.js": "",
- "exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
- "exist/test.js": "",
- "exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
- "not-exist/test.js": ""
- }
- });
-
- factory = new CascadingConfigArrayFactory({ cwd });
- });
-
- // Project's config file has `root:true`, then no warning.
- describe("when it lints 'subdir/exist-with-root/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("exist-with-root/test.js");
- await delay();
- });
-
- it("should not raise any warnings.", () => {
- assert.deepStrictEqual(warnings, []);
- });
-
- it("should not load '~/.eslintrc.json'.", () => {
- assert.deepStrictEqual(
- config.extractConfig("a.js").rules,
- { yoda: ["error"] }
- );
- });
- });
-
- // Project's config file doesn't have `root:true` and home is ancestor, then ESLINT_PERSONAL_CONFIG_SUPPRESS.
- describe("when it lints 'subdir/exist/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("exist/test.js");
- await delay();
- });
-
- it("should raise an ESLINT_PERSONAL_CONFIG_SUPPRESS warning.", () => {
- assert.deepStrictEqual(warnings, [
- {
- code: "ESLINT_PERSONAL_CONFIG_SUPPRESS",
- message: `'~/.eslintrc.*' config files have been deprecated. Please remove it or add 'root:true' to the config files in your projects in order to avoid loading '~/.eslintrc.*' accidentally. (found in "${uniqueHomeDirName}${path.sep}.eslintrc.json")`
- }
- ]);
- });
-
- it("should not load '~/.eslintrc.json'.", () => {
- assert.deepStrictEqual(
- config.extractConfig("a.js").rules,
- { yoda: ["error"] }
- );
- });
- });
-
- /*
- * Project's config file doesn't exist and home is ancestor, then no warning.
- * In this case, ESLint will continue to use `~/.eslintrc.json` even if personal config file feature is removed.
- */
- describe("when it lints 'subdir/not-exist/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("not-exist/test.js");
- await delay();
- });
-
- it("should not raise any warnings.", () => {
- assert.deepStrictEqual(warnings, []);
- });
-
- it("should load '~/.eslintrc.json'.", () => {
- assert.deepStrictEqual(
- config.extractConfig("a.js").rules,
- { eqeqeq: ["error"] }
- );
- });
- });
- });
-
- describe("when '~/.eslintrc.json' exists and CWD is `~/../another`", () => {
- beforeEach(() => {
- cwd = path.join(homeDir, "../another");
- const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => cwd,
- files: {
-
- // ~/.eslintrc.json
- [`../${uniqueHomeDirName}/.eslintrc.json`]: JSON.stringify({ rules: { eqeqeq: "error" } }),
-
- // other files
- "exist-with-root/test.js": "",
- "exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
- "exist/test.js": "",
- "exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
- "not-exist/test.js": ""
- }
- });
-
- factory = new CascadingConfigArrayFactory({ cwd });
- });
-
- // Project's config file has `root:true`, then no warning.
- describe("when it lints 'exist-with-root/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("exist-with-root/test.js");
- await delay();
- });
-
- it("should not raise any warnings.", () => {
- assert.deepStrictEqual(warnings, []);
- });
-
- it("should not load '~/.eslintrc.json'.", () => {
- assert.deepStrictEqual(
- config.extractConfig("a.js").rules,
- { yoda: ["error"] }
- );
- });
- });
-
- // Project's config file doesn't have `root:true` but home is not ancestor, then no warning.
- describe("when it lints 'exist/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("exist/test.js");
- await delay();
- });
-
- it("should not raise any warnings.", () => {
- assert.deepStrictEqual(warnings, []);
- });
-
- it("should not load '~/.eslintrc.json'.", () => {
- assert.deepStrictEqual(
- config.extractConfig("a.js").rules,
- { yoda: ["error"] }
- );
- });
- });
-
- // Project's config file doesn't exist and home is not ancestor, then ESLINT_PERSONAL_CONFIG_LOAD.
- describe("when it lints 'not-exist/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("not-exist/test.js");
- await delay();
- });
-
- it("should raise an ESLINT_PERSONAL_CONFIG_LOAD warning.", () => {
- assert.deepStrictEqual(warnings, [
- {
- code: "ESLINT_PERSONAL_CONFIG_LOAD",
- message: `'~/.eslintrc.*' config files have been deprecated. Please use a config file per project or the '--config' option. (found in "${uniqueHomeDirName}${path.sep}.eslintrc.json")`
- }
- ]);
- });
-
- it("should load '~/.eslintrc.json'.", () => {
- assert.deepStrictEqual(
- config.extractConfig("a.js").rules,
- { eqeqeq: ["error"] }
- );
- });
- });
- });
-
- describe("when '~/.eslintrc.json' doesn't exist and CWD is `~/subdir`", () => {
- beforeEach(() => {
- cwd = path.join(homeDir, "subdir");
- const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => cwd,
- files: {
- "exist-with-root/test.js": "",
- "exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
- "exist/test.js": "",
- "exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
- "not-exist/test.js": ""
- }
- });
-
- factory = new CascadingConfigArrayFactory({ cwd });
- });
-
- describe("when it lints 'subdir/exist/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("exist/test.js");
- await delay();
- });
-
- it("should not raise any warnings.", () => {
- assert.deepStrictEqual(warnings, []);
- });
- });
- });
-
- describe("when '~/.eslintrc.json' doesn't exist and CWD is `~/../another`", () => {
- beforeEach(() => {
- cwd = path.join(homeDir, "../another");
- const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => cwd,
- files: {
- "exist-with-root/test.js": "",
- "exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
- "exist/test.js": "",
- "exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
- "not-exist/test.js": ""
- }
- });
-
- factory = new CascadingConfigArrayFactory({ cwd });
- });
-
- describe("when it lints 'not-exist/test.js'", () => {
- beforeEach(async () => {
- config = factory.getConfigArrayForFile("not-exist/test.js", { ignoreNotFoundError: true });
- await delay();
- });
-
- it("should not raise any warnings.", () => {
- assert.deepStrictEqual(warnings, []);
- });
- });
- });
- });
-
- // This group moved from 'tests/lib/config.js' when refactoring to keep the cumulated test cases.
- describe("with 'tests/fixtures/config-hierarchy' files", () => {
- const { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory");
- let fixtureDir;
-
- const DIRECTORY_CONFIG_HIERARCHY = require("../../fixtures/config-hierarchy/file-structure.json");
-
- /**
- * Returns the path inside of the fixture directory.
- * @param {...string} args file path segments.
- * @returns {string} The path inside the fixture directory.
- * @private
- */
- function getFixturePath(...args) {
- return path.join(fixtureDir, "config-hierarchy", ...args);
- }
-
- /**
- * Mocks the current user's home path
- * @param {string} fakeUserHomePath fake user's home path
- * @returns {void}
- * @private
- */
- function mockOsHomedir(fakeUserHomePath) {
- sinon.stub(os, "homedir")
- .returns(fakeUserHomePath);
- }
-
- /**
- * Assert that given two objects have the same properties with the
- * same value for each.
- *
- * The `expected` object is merged with the default values of config
- * data before comparing, so you can specify only the properties you
- * focus on.
- * @param {Object} actual The config object to check.
- * @param {Object} expected What the config object should look like.
- * @returns {void}
- * @private
- */
- function assertConfigsEqual(actual, expected) {
- const defaults = new ExtractedConfig().toCompatibleObjectAsConfigFileContent();
-
- assert.deepStrictEqual(actual, { ...defaults, ...expected });
- }
-
- /**
- * Wait for the next tick.
- * @returns {Promise} -
- */
- function nextTick() {
- return new Promise(resolve => process.nextTick(resolve));
- }
-
- /**
- * Get the config data for a file.
- * @param {CascadingConfigArrayFactory} factory The factory to get config.
- * @param {string} filePath The path to a source code.
- * @returns {Object} The gotten config.
- */
- function getConfig(factory, filePath = "a.js") {
- const { cwd } = factory;
- const absolutePath = path.resolve(cwd, filePath);
-
- return factory
- .getConfigArrayForFile(absolutePath)
- .extractConfig(absolutePath)
- .toCompatibleObjectAsConfigFileContent();
- }
-
- // copy into clean area so as not to get "infected" by this project's .eslintrc files
- before(() => {
- fixtureDir = `${os.tmpdir()}/eslint/fixtures`;
- sh.mkdir("-p", fixtureDir);
- sh.cp("-r", "./tests/fixtures/config-hierarchy", fixtureDir);
- sh.cp("-r", "./tests/fixtures/rules", fixtureDir);
- });
-
- afterEach(() => {
- sinon.verifyAndRestore();
- });
-
- after(() => {
- sh.rm("-r", fixtureDir);
- });
-
- it("should create config object when using baseConfig with extends", () => {
- const customBaseConfig = {
- extends: path.resolve(__dirname, "../../fixtures/config-extends/array/.eslintrc")
- };
- const factory = new CascadingConfigArrayFactory({ baseConfig: customBaseConfig, useEslintrc: false });
- const config = getConfig(factory);
-
- assert.deepStrictEqual(config.env, {
- browser: false,
- es6: true,
- node: true
- });
- assert.deepStrictEqual(config.rules, {
- "no-empty": [1],
- "comma-dangle": [2],
- "no-console": [2]
- });
- });
-
- it("should return the project config when called in current working directory", () => {
- const factory = new CascadingConfigArrayFactory();
- const actual = getConfig(factory);
-
- assert.strictEqual(actual.rules.strict[1], "global");
- });
-
- it("should not retain configs from previous directories when called multiple times", () => {
- const firstpath = path.resolve(__dirname, "../../fixtures/configurations/single-quotes/subdir/.eslintrc");
- const secondpath = path.resolve(__dirname, "../../fixtures/configurations/single-quotes/.eslintrc");
- const factory = new CascadingConfigArrayFactory();
- let config;
-
- config = getConfig(factory, firstpath);
- assert.deepStrictEqual(config.rules["no-new"], [0]);
- config = getConfig(factory, secondpath);
- assert.deepStrictEqual(config.rules["no-new"], [1]);
- });
-
- it("should throw error when a configuration file doesn't exist", () => {
- const configPath = path.resolve(__dirname, "../../fixtures/configurations/.eslintrc");
- const factory = new CascadingConfigArrayFactory();
-
- sinon.stub(fs, "readFileSync").throws(new Error());
-
- assert.throws(() => {
- getConfig(factory, configPath);
- }, "Cannot read config file");
-
- });
-
- it("should throw error when a configuration file is not require-able", () => {
- const configPath = ".eslintrc";
- const factory = new CascadingConfigArrayFactory();
-
- sinon.stub(fs, "readFileSync").throws(new Error());
-
- assert.throws(() => {
- getConfig(factory, configPath);
- }, "Cannot read config file");
-
- });
-
- it("should cache config when the same directory is passed twice", () => {
- const configPath = path.resolve(__dirname, "../../fixtures/configurations/single-quotes/.eslintrc");
- const configArrayFactory = new ConfigArrayFactory();
- const factory = new CascadingConfigArrayFactory({ configArrayFactory });
-
- sinon.spy(configArrayFactory, "loadInDirectory");
-
- // If cached this should be called only once
- getConfig(factory, configPath);
- const callcount = configArrayFactory.loadInDirectory.callcount;
-
- getConfig(factory, configPath);
-
- assert.strictEqual(configArrayFactory.loadInDirectory.callcount, callcount);
- });
-
- // make sure JS-style comments don't throw an error
- it("should load the config file when there are JS-style comments in the text", () => {
- const specificConfigPath = path.resolve(__dirname, "../../fixtures/configurations/comments.json");
- const factory = new CascadingConfigArrayFactory({ specificConfigPath, useEslintrc: false });
- const config = getConfig(factory);
- const { semi, strict } = config.rules;
-
- assert.deepStrictEqual(semi, [1]);
- assert.deepStrictEqual(strict, [0]);
- });
-
- // make sure YAML files work correctly
- it("should load the config file when a YAML file is used", () => {
- const specificConfigPath = path.resolve(__dirname, "../../fixtures/configurations/env-browser.yaml");
- const factory = new CascadingConfigArrayFactory({ specificConfigPath, useEslintrc: false });
- const config = getConfig(factory);
- const { "no-alert": noAlert, "no-undef": noUndef } = config.rules;
-
- assert.deepStrictEqual(noAlert, [0]);
- assert.deepStrictEqual(noUndef, [2]);
- });
-
- it("should contain the correct value for parser when a custom parser is specified", () => {
- const configPath = path.resolve(__dirname, "../../fixtures/configurations/parser/.eslintrc.json");
- const factory = new CascadingConfigArrayFactory();
- const config = getConfig(factory, configPath);
-
- assert.strictEqual(config.parser, path.resolve(path.dirname(configPath), "./custom.js"));
- });
-
- /*
- * Configuration hierarchy ---------------------------------------------
- * https://github.com/eslint/eslint/issues/3915
- */
- it("should correctly merge environment settings", () => {
- const factory = new CascadingConfigArrayFactory({ useEslintrc: true });
- const file = getFixturePath("envs", "sub", "foo.js");
- const expected = {
- rules: {},
- env: {
- browser: true,
- node: false
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Default configuration - blank
- it("should return a blank config when using no .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({ useEslintrc: false });
- const file = getFixturePath("broken", "console-wrong-quotes.js");
- const expected = {
- rules: {},
- globals: {},
- env: {},
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should return a blank config when baseConfig is set to false and no .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({ baseConfig: false, useEslintrc: false });
- const file = getFixturePath("broken", "console-wrong-quotes.js");
- const expected = {
- rules: {},
- globals: {},
- env: {},
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // No default configuration
- it("should return an empty config when not using .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({ useEslintrc: false });
- const file = getFixturePath("broken", "console-wrong-quotes.js");
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, { ignorePatterns: cwdIgnorePatterns });
- });
-
- it("should return a modified config when baseConfig is set to an object and no .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({
- baseConfig: {
- env: {
- node: true
- },
- rules: {
- quotes: [2, "single"]
- }
- },
- useEslintrc: false
- });
- const file = getFixturePath("broken", "console-wrong-quotes.js");
- const expected = {
- env: {
- node: true
- },
- rules: {
- quotes: [2, "single"]
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should return a modified config without plugin rules enabled when baseConfig is set to an object with plugin and no .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({
- baseConfig: {
- env: {
- node: true
- },
- rules: {
- quotes: [2, "single"]
- },
- plugins: ["example-with-rules-config"]
- },
- cwd: getFixturePath("plugins"),
- useEslintrc: false
- });
- const file = getFixturePath("broken", "plugins", "console-wrong-quotes.js");
- const expected = {
- env: {
- node: true
- },
- plugins: ["example-with-rules-config"],
- rules: {
- quotes: [2, "single"]
- }
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Project configuration - second level .eslintrc
- it("should merge configs when local .eslintrc overrides parent .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory();
- const file = getFixturePath("broken", "subbroken", "console-wrong-quotes.js");
- const expected = {
- env: {
- node: true
- },
- rules: {
- "no-console": [1],
- quotes: [2, "single"]
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Project configuration - third level .eslintrc
- it("should merge configs when local .eslintrc overrides parent and grandparent .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory();
- const file = getFixturePath("broken", "subbroken", "subsubbroken", "console-wrong-quotes.js");
- const expected = {
- env: {
- node: true
- },
- rules: {
- "no-console": [0],
- quotes: [1, "double"]
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Project configuration - root set in second level .eslintrc
- it("should not return or traverse configurations in parents of config with root:true", () => {
- const factory = new CascadingConfigArrayFactory();
- const file = getFixturePath("root-true", "parent", "root", "wrong-semi.js");
- const expected = {
- rules: {
- semi: [2, "never"]
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Project configuration - root set in second level .eslintrc
- it("should return project config when called with a relative path from a subdir", () => {
- const factory = new CascadingConfigArrayFactory({ cwd: getFixturePath("root-true", "parent", "root", "subdir") });
- const dir = ".";
- const expected = {
- rules: {
- semi: [2, "never"]
- }
- };
- const actual = getConfig(factory, dir);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Command line configuration - --config with first level .eslintrc
- it("should merge command line config when config file adds to local .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({
- specificConfigPath: getFixturePath("broken", "add-conf.yaml")
- });
- const file = getFixturePath("broken", "console-wrong-quotes.js");
- const expected = {
- env: {
- node: true
- },
- rules: {
- quotes: [2, "double"],
- semi: [1, "never"]
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Command line configuration - --config with first level .eslintrc
- it("should merge command line config when config file overrides local .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({
- specificConfigPath: getFixturePath("broken", "override-conf.yaml")
- });
- const file = getFixturePath("broken", "console-wrong-quotes.js");
- const expected = {
- env: {
- node: true
- },
- rules: {
- quotes: [0, "double"]
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Command line configuration - --config with second level .eslintrc
- it("should merge command line config when config file adds to local and parent .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({
- specificConfigPath: getFixturePath("broken", "add-conf.yaml")
- });
- const file = getFixturePath("broken", "subbroken", "console-wrong-quotes.js");
- const expected = {
- env: {
- node: true
- },
- rules: {
- quotes: [2, "single"],
- "no-console": [1],
- semi: [1, "never"]
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Command line configuration - --config with second level .eslintrc
- it("should merge command line config when config file overrides local and parent .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({
- specificConfigPath: getFixturePath("broken", "override-conf.yaml")
- });
- const file = getFixturePath("broken", "subbroken", "console-wrong-quotes.js");
- const expected = {
- env: {
- node: true
- },
- rules: {
- quotes: [0, "single"],
- "no-console": [1]
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Command line configuration - --rule with --config and first level .eslintrc
- it("should merge command line config and rule when rule and config file overrides local .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({
- cliConfig: {
- rules: {
- quotes: [1, "double"]
- }
- },
- specificConfigPath: getFixturePath("broken", "override-conf.yaml")
- });
- const file = getFixturePath("broken", "console-wrong-quotes.js");
- const expected = {
- env: {
- node: true
- },
- rules: {
- quotes: [1, "double"]
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
- // Command line configuration - --plugin
- it("should merge command line plugin with local .eslintrc", () => {
- const factory = new CascadingConfigArrayFactory({
- cliConfig: {
- plugins: ["another-plugin"]
- },
- cwd: getFixturePath("plugins"),
- resolvePluginsRelativeTo: getFixturePath("plugins")
- });
- const file = getFixturePath("broken", "plugins", "console-wrong-quotes.js");
- const expected = {
- env: {
- node: true
- },
- plugins: [
- "example",
- "another-plugin"
- ],
- rules: {
- quotes: [2, "double"]
- }
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
-
- it("should merge multiple different config file formats", () => {
- const factory = new CascadingConfigArrayFactory();
- const file = getFixturePath("fileexts/subdir/subsubdir/foo.js");
- const expected = {
- env: {
- browser: true
- },
- rules: {
- semi: [2, "always"],
- eqeqeq: [2]
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, file);
-
- assertConfigsEqual(actual, expected);
- });
-
-
- it("should load user config globals", () => {
- const configPath = path.resolve(__dirname, "../../fixtures/globals/conf.yaml");
- const factory = new CascadingConfigArrayFactory({ specificConfigPath: configPath, useEslintrc: false });
- const expected = {
- globals: {
- foo: true
- },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, configPath);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should not load disabled environments", () => {
- const configPath = path.resolve(__dirname, "../../fixtures/environments/disable.yaml");
- const factory = new CascadingConfigArrayFactory({ specificConfigPath: configPath, useEslintrc: false });
- const config = getConfig(factory, configPath);
-
- assert.isUndefined(config.globals.window);
- });
-
- it("should gracefully handle empty files", () => {
- const configPath = path.resolve(__dirname, "../../fixtures/configurations/env-node.json");
- const factory = new CascadingConfigArrayFactory({ specificConfigPath: configPath });
-
- getConfig(factory, path.resolve(__dirname, "../../fixtures/configurations/empty/empty.json"));
- });
-
- // Meaningful stack-traces
- it("should include references to where an `extends` configuration was loaded from", () => {
- const configPath = path.resolve(__dirname, "../../fixtures/config-extends/error.json");
-
- assert.throws(() => {
- const factory = new CascadingConfigArrayFactory({ useEslintrc: false, specificConfigPath: configPath });
-
- getConfig(factory, configPath);
- }, /Referenced from:.*?error\.json/u);
- });
-
- // Keep order with the last array element taking highest precedence
- it("should make the last element in an array take the highest precedence", () => {
- const configPath = path.resolve(__dirname, "../../fixtures/config-extends/array/.eslintrc");
- const factory = new CascadingConfigArrayFactory({ useEslintrc: false, specificConfigPath: configPath });
- const expected = {
- rules: { "no-empty": [1], "comma-dangle": [2], "no-console": [2] },
- env: { browser: false, node: true, es6: true },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, configPath);
-
- assertConfigsEqual(actual, expected);
- });
-
- describe("with env in a child configuration file", () => {
- it("should not overwrite parserOptions of the parent with env of the child", () => {
- const factory = new CascadingConfigArrayFactory();
- const targetPath = getFixturePath("overwrite-ecmaFeatures", "child", "foo.js");
- const expected = {
- rules: {},
- env: { commonjs: true },
- parserOptions: { ecmaFeatures: { globalReturn: false } },
- ignorePatterns: cwdIgnorePatterns
- };
- const actual = getConfig(factory, targetPath);
-
- assertConfigsEqual(actual, expected);
- });
- });
-
- describe("personal config file within home directory", () => {
- const {
- CascadingConfigArrayFactory: StubbedCascadingConfigArrayFactory
- } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "eslint/fixtures/config-hierarchy": DIRECTORY_CONFIG_HIERARCHY
- }
- });
-
- /**
- * Returns the path inside of the fixture directory.
- * @param {...string} args file path segments.
- * @returns {string} The path inside the fixture directory.
- * @private
- */
- function getFakeFixturePath(...args) {
- return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...args);
- }
-
- it("should load the personal config if no local config was found", () => {
- const projectPath = getFakeFixturePath("personal-config", "project-without-config");
- const homePath = getFakeFixturePath("personal-config", "home-folder");
- const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
-
- mockOsHomedir(homePath);
-
- const actual = getConfig(factory, filePath);
- const expected = {
- rules: {
- "home-folder-rule": [2]
- }
- };
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should ignore the personal config if a local config was found", () => {
- const projectPath = getFakeFixturePath("personal-config", "home-folder", "project");
- const homePath = getFakeFixturePath("personal-config", "home-folder");
- const filePath = getFakeFixturePath("personal-config", "home-folder", "project", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
-
- mockOsHomedir(homePath);
-
- const actual = getConfig(factory, filePath);
- const expected = {
- rules: {
- "project-level-rule": [2]
- }
- };
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should ignore the personal config if config is passed through cli", () => {
- const configPath = getFakeFixturePath("quotes-error.json");
- const projectPath = getFakeFixturePath("personal-config", "project-without-config");
- const homePath = getFakeFixturePath("personal-config", "home-folder");
- const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({
- cwd: projectPath,
- specificConfigPath: configPath
- });
-
- mockOsHomedir(homePath);
-
- const actual = getConfig(factory, filePath);
- const expected = {
- rules: {
- quotes: [2, "double"]
- }
- };
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should still load the project config if the current working directory is the same as the home folder", () => {
- const projectPath = getFakeFixturePath("personal-config", "project-with-config");
- const filePath = getFakeFixturePath("personal-config", "project-with-config", "subfolder", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
-
- mockOsHomedir(projectPath);
-
- const actual = getConfig(factory, filePath);
- const expected = {
- rules: {
- "project-level-rule": [2],
- "subfolder-level-rule": [2]
- }
- };
-
- assertConfigsEqual(actual, expected);
- });
- });
-
- describe("when no local or personal config is found", () => {
- const {
- CascadingConfigArrayFactory: StubbedCascadingConfigArrayFactory
- } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "eslint/fixtures/config-hierarchy": DIRECTORY_CONFIG_HIERARCHY
- }
- });
-
- /**
- * Returns the path inside of the fixture directory.
- * @param {...string} args file path segments.
- * @returns {string} The path inside the fixture directory.
- * @private
- */
- function getFakeFixturePath(...args) {
- return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...args);
- }
-
- it("should throw an error if no local config and no personal config was found", () => {
- const projectPath = getFakeFixturePath("personal-config", "project-without-config");
- const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
- const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
-
- mockOsHomedir(homePath);
-
- assert.throws(() => {
- getConfig(factory, filePath);
- }, "No ESLint configuration found");
- });
-
- it("should throw an error if no local config was found and ~/package.json contains no eslintConfig section", () => {
- const projectPath = getFakeFixturePath("personal-config", "project-without-config");
- const homePath = getFakeFixturePath("personal-config", "home-folder-with-packagejson");
- const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
-
- mockOsHomedir(homePath);
-
- assert.throws(() => {
- getConfig(factory, filePath);
- }, "No ESLint configuration found");
- });
-
- it("should not throw an error if no local config and no personal config was found but useEslintrc is false", () => {
- const projectPath = getFakeFixturePath("personal-config", "project-without-config");
- const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
- const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath, useEslintrc: false });
-
- mockOsHomedir(homePath);
-
- getConfig(factory, filePath);
- });
-
- it("should not throw an error if no local config and no personal config was found but rules are specified", () => {
- const projectPath = getFakeFixturePath("personal-config", "project-without-config");
- const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
- const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({
- cliConfig: {
- rules: { quotes: [2, "single"] }
- },
- cwd: projectPath
- });
-
- mockOsHomedir(homePath);
-
- getConfig(factory, filePath);
- });
-
- it("should not throw an error if no local config and no personal config was found but baseConfig is specified", () => {
- const projectPath = getFakeFixturePath("personal-config", "project-without-config");
- const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
- const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({ baseConfig: {}, cwd: projectPath });
-
- mockOsHomedir(homePath);
-
- getConfig(factory, filePath);
- });
- });
-
- describe("with overrides", () => {
- const {
- CascadingConfigArrayFactory: StubbedCascadingConfigArrayFactory
- } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "eslint/fixtures/config-hierarchy": DIRECTORY_CONFIG_HIERARCHY
- }
- });
-
- /**
- * Returns the path inside of the fixture directory.
- * @param {...string} pathSegments One or more path segments, in order of depth, shallowest first
- * @returns {string} The path inside the fixture directory.
- * @private
- */
- function getFakeFixturePath(...pathSegments) {
- return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...pathSegments);
- }
-
- it("should merge override config when the pattern matches the file name", () => {
- const factory = new StubbedCascadingConfigArrayFactory({});
- const targetPath = getFakeFixturePath("overrides", "foo.js");
- const expected = {
- rules: {
- quotes: [2, "single"],
- "no-else-return": [0],
- "no-unused-vars": [1],
- semi: [1, "never"]
- }
- };
- const actual = getConfig(factory, targetPath);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should merge override config when the pattern matches the file path relative to the config file", () => {
- const factory = new StubbedCascadingConfigArrayFactory({});
- const targetPath = getFakeFixturePath("overrides", "child", "child-one.js");
- const expected = {
- rules: {
- curly: ["error", "multi", "consistent"],
- "no-else-return": [0],
- "no-unused-vars": [1],
- quotes: [2, "double"],
- semi: [1, "never"]
- }
- };
- const actual = getConfig(factory, targetPath);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should not merge override config when the pattern matches the absolute file path", () => {
- const resolvedPath = path.resolve(__dirname, "../../fixtures/config-hierarchy/overrides/bar.js");
-
- assert.throws(() => new StubbedCascadingConfigArrayFactory({
- baseConfig: {
- overrides: [{
- files: resolvedPath,
- rules: {
- quotes: [1, "double"]
- }
- }]
- },
- useEslintrc: false
- }), /Invalid override pattern/u);
- });
-
- it("should not merge override config when the pattern traverses up the directory tree", () => {
- const parentPath = "overrides/../**/*.js";
-
- assert.throws(() => new StubbedCascadingConfigArrayFactory({
- baseConfig: {
- overrides: [{
- files: parentPath,
- rules: {
- quotes: [1, "single"]
- }
- }]
- },
- useEslintrc: false
- }), /Invalid override pattern/u);
- });
-
- it("should merge all local configs (override and non-override) before non-local configs", () => {
- const factory = new StubbedCascadingConfigArrayFactory({});
- const targetPath = getFakeFixturePath("overrides", "two", "child-two.js");
- const expected = {
- rules: {
- "no-console": [0],
- "no-else-return": [0],
- "no-unused-vars": [2],
- quotes: [2, "double"],
- semi: [2, "never"]
- }
- };
- const actual = getConfig(factory, targetPath);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should apply overrides in parent .eslintrc over non-override rules in child .eslintrc", () => {
- const targetPath = getFakeFixturePath("overrides", "three", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({
- cwd: getFakeFixturePath("overrides"),
- baseConfig: {
- overrides: [
- {
- files: "three/**/*.js",
- rules: {
- "semi-style": [2, "last"]
- }
- }
- ]
- },
- useEslintrc: false
- });
- const expected = {
- rules: {
- "semi-style": [2, "last"]
- }
- };
- const actual = getConfig(factory, targetPath);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should apply overrides if all glob patterns match", () => {
- const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
- const factory = new StubbedCascadingConfigArrayFactory({
- cwd: getFakeFixturePath("overrides"),
- baseConfig: {
- overrides: [{
- files: ["one/**/*", "*.js"],
- rules: {
- quotes: [2, "single"]
- }
- }]
- },
- useEslintrc: false
- });
- const expected = {
- rules: {
- quotes: [2, "single"]
- }
- };
- const actual = getConfig(factory, targetPath);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should apply overrides even if some glob patterns do not match", () => {
- const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
- const factory = new StubbedCascadingConfigArrayFactory({
- cwd: getFakeFixturePath("overrides"),
- baseConfig: {
- overrides: [{
- files: ["one/**/*", "*two.js"],
- rules: {
- quotes: [2, "single"]
- }
- }]
- },
- useEslintrc: false
- });
- const expected = {
- rules: {
- quotes: [2, "single"]
- }
- };
- const actual = getConfig(factory, targetPath);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should not apply overrides if any excluded glob patterns match", () => {
- const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
- const factory = new StubbedCascadingConfigArrayFactory({
- cwd: getFakeFixturePath("overrides"),
- baseConfig: {
- overrides: [{
- files: "one/**/*",
- excludedFiles: ["two/**/*", "*one.js"],
- rules: {
- quotes: [2, "single"]
- }
- }]
- },
- useEslintrc: false
- });
- const expected = {
- rules: {}
- };
- const actual = getConfig(factory, targetPath);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should apply overrides if all excluded glob patterns fail to match", () => {
- const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
- const factory = new StubbedCascadingConfigArrayFactory({
- cwd: getFakeFixturePath("overrides"),
- baseConfig: {
- overrides: [{
- files: "one/**/*",
- excludedFiles: ["two/**/*", "*two.js"],
- rules: {
- quotes: [2, "single"]
- }
- }]
- },
- useEslintrc: false
- });
- const expected = {
- rules: {
- quotes: [2, "single"]
- }
- };
- const actual = getConfig(factory, targetPath);
-
- assertConfigsEqual(actual, expected);
- });
-
- it("should cascade", () => {
- const targetPath = getFakeFixturePath("overrides", "foo.js");
- const factory = new StubbedCascadingConfigArrayFactory({
- cwd: getFakeFixturePath("overrides"),
- baseConfig: {
- overrides: [
- {
- files: "foo.js",
- rules: {
- semi: [2, "never"],
- quotes: [2, "single"]
- }
- },
- {
- files: "foo.js",
- rules: {
- semi: [2, "never"],
- quotes: [2, "double"]
- }
- }
- ]
- },
- useEslintrc: false
- });
- const expected = {
- rules: {
- semi: [2, "never"],
- quotes: [2, "double"]
- }
- };
- const actual = getConfig(factory, targetPath);
-
- assertConfigsEqual(actual, expected);
- });
- });
-
- describe("deprecation warnings", () => {
- const cwd = path.resolve(__dirname, "../../fixtures/config-file/");
- let warning = null;
-
- /**
- * Store a reported warning object if that code starts with `ESLINT_`.
- * @param {{code:string, message:string}} w The warning object to store.
- * @returns {void}
- */
- function onWarning(w) {
- if (w.code.startsWith("ESLINT_")) {
- warning = w;
- }
- }
-
- /** @type {CascadingConfigArrayFactory} */
- let factory;
-
- beforeEach(() => {
- factory = new CascadingConfigArrayFactory({ cwd });
- warning = null;
- process.on("warning", onWarning);
- });
- afterEach(() => {
- process.removeListener("warning", onWarning);
- });
-
- it("should emit a deprecation warning if 'ecmaFeatures' is given.", async () => {
- getConfig(factory, "ecma-features/test.js");
-
- // Wait for "warning" event.
- await nextTick();
-
- assert.notStrictEqual(warning, null);
- assert.strictEqual(
- warning.message,
- `The 'ecmaFeatures' config file property is deprecated and has no effect. (found in "ecma-features${path.sep}.eslintrc.yml")`
- );
- });
- });
- });
- });
-
- describe("'clearCache()' method should clear cache.", () => {
- describe("with a '.eslintrc.js' file", () => {
- const root = path.join(os.tmpdir(), "eslint/cli-engine/cascading-config-array-factory");
- const files = {
- ".eslintrc.js": ""
- };
- const {
- CascadingConfigArrayFactory
- } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd: () => root, files });
-
- /** @type {Map} */
- let additionalPluginPool;
-
- /** @type {CascadingConfigArrayFactory} */
- let factory;
-
- beforeEach(() => {
- additionalPluginPool = new Map();
- factory = new CascadingConfigArrayFactory({
- additionalPluginPool,
- cliConfig: { plugins: ["test"] }
- });
- });
-
- it("should use cached instance.", () => {
- const one = factory.getConfigArrayForFile("a.js");
- const two = factory.getConfigArrayForFile("a.js");
-
- assert.strictEqual(one, two);
- });
-
- it("should not use cached instance if 'clearCache()' method is called after first config is retrieved", () => {
- const one = factory.getConfigArrayForFile("a.js");
-
- factory.clearCache();
- const two = factory.getConfigArrayForFile("a.js");
-
- assert.notStrictEqual(one, two);
- });
-
- it("should have a loading error in CLI config.", () => {
- const config = factory.getConfigArrayForFile("a.js");
-
- assert.strictEqual(config[2].plugins.test.definition, null);
- });
-
- it("should not have a loading error in CLI config after adding 'test' plugin to the additional plugin pool then calling 'clearCache()'.", () => {
- factory.getConfigArrayForFile("a.js");
-
- additionalPluginPool.set("test", { configs: { name: "test" } });
- factory.clearCache();
-
- // Check.
- const config = factory.getConfigArrayForFile("a.js");
-
- assert.deepStrictEqual(
- config[2].plugins.test.definition,
- {
- configs: { name: "test" },
- environments: {},
- processors: {},
- rules: {}
- }
- );
- });
- });
- });
-});
diff --git a/eslint/tests/lib/cli-engine/cli-engine.js b/eslint/tests/lib/cli-engine/cli-engine.js
index fb3c365..9e7c49c 100644
--- a/eslint/tests/lib/cli-engine/cli-engine.js
+++ b/eslint/tests/lib/cli-engine/cli-engine.js
@@ -12,13 +12,12 @@
const assert = require("chai").assert,
path = require("path"),
sinon = require("sinon"),
- leche = require("leche"),
shell = require("shelljs"),
fs = require("fs"),
os = require("os"),
hash = require("../../../lib/cli-engine/hash"),
- { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory"),
- { unIndent, defineCLIEngineWithInMemoryFileSystem } = require("../../_utils");
+ { CascadingConfigArrayFactory } = require("@eslint/eslintrc/lib/cascading-config-array-factory"),
+ { unIndent, createCustomTeardown } = require("../../_utils");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const fCache = require("file-entry-cache");
@@ -81,7 +80,15 @@ describe("CLIEngine", () => {
}
// copy into clean area so as not to get "infected" by this project's .eslintrc files
- before(() => {
+ before(function() {
+
+ /*
+ * GitHub Actions Windows and macOS runners occasionally exhibit
+ * extremely slow filesystem operations, during which copying fixtures
+ * exceeds the default test timeout, so raise it just for this hook.
+ * Mocha uses `this` to set timeouts on an individual hook level.
+ */
+ this.timeout(60 * 1000); // eslint-disable-line no-invalid-this
shell.mkdir("-p", fixtureDir);
shell.cp("-r", "./tests/fixtures/.", fixtureDir);
});
@@ -822,7 +829,7 @@ describe("CLIEngine", () => {
engine = new CLIEngine({
parser: "espree",
parserOptions: {
- ecmaVersion: 2020
+ ecmaVersion: 2021
},
useEslintrc: false
});
@@ -1286,6 +1293,10 @@ describe("CLIEngine", () => {
it("should throw an error when all given files are ignored", () => {
+ engine = new CLIEngine({
+ ignorePath: getFixturePath(".eslintignore")
+ });
+
assert.throws(() => {
engine.executeOnFiles(["tests/fixtures/cli-engine/"]);
}, "All files matched by 'tests/fixtures/cli-engine/' are ignored.");
@@ -2972,26 +2983,34 @@ describe("CLIEngine", () => {
});
describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => {
- beforeEach(() => {
- ({ CLIEngine } = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => path.join(os.tmpdir(), "cli-engine/11510"),
- files: {
- "no-console-error-in-overrides.json": JSON.stringify({
- overrides: [{
- files: ["*.js"],
- rules: { "no-console": "error" }
- }]
- }),
- ".eslintrc.json": JSON.stringify({
- extends: "./no-console-error-in-overrides.json",
- rules: { "no-console": "off" }
- }),
- "a.js": "console.log();"
- }
- }));
- engine = new CLIEngine();
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: path.join(os.tmpdir(), "cli-engine/11510"),
+ files: {
+ "no-console-error-in-overrides.json": {
+ overrides: [{
+ files: ["*.js"],
+ rules: { "no-console": "error" }
+ }]
+ },
+ ".eslintrc.json": {
+ extends: "./no-console-error-in-overrides.json",
+ rules: { "no-console": "off" }
+ },
+ "a.js": "console.log();"
+ }
});
+ beforeEach(() => {
+ engine = new CLIEngine({
+ cwd: getPath()
+ });
+
+ return prepare();
+ });
+
+ afterEach(cleanup);
+
it("should not report 'no-console' error.", () => {
const { results } = engine.executeOnFiles("a.js");
@@ -3001,35 +3020,42 @@ describe("CLIEngine", () => {
});
describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", () => {
- beforeEach(() => {
- ({ CLIEngine } = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => path.join(os.tmpdir(), "cli-engine/11559"),
- files: {
- "node_modules/eslint-plugin-test/index.js": `
- exports.configs = {
- recommended: { plugins: ["test"] }
- };
- exports.rules = {
- foo: {
- meta: { schema: [{ type: "number" }] },
- create() { return {}; }
- }
- };
- `,
- ".eslintrc.json": JSON.stringify({
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: path.join(os.tmpdir(), "cli-engine/11559"),
+ files: {
+ "node_modules/eslint-plugin-test/index.js": `
+ exports.configs = {
+ recommended: { plugins: ["test"] }
+ };
+ exports.rules = {
+ foo: {
+ meta: { schema: [{ type: "number" }] },
+ create() { return {}; }
+ }
+ };
+ `,
+ ".eslintrc.json": {
- // Import via the recommended config.
- extends: "plugin:test/recommended",
+ // Import via the recommended config.
+ extends: "plugin:test/recommended",
- // Has invalid option.
- rules: { "test/foo": ["error", "invalid-option"] }
- }),
- "a.js": "console.log();"
- }
- }));
- engine = new CLIEngine();
+ // Has invalid option.
+ rules: { "test/foo": ["error", "invalid-option"] }
+ },
+ "a.js": "console.log();"
+ }
});
+ beforeEach(() => {
+ engine = new CLIEngine({
+ cwd: getPath()
+ });
+
+ return prepare();
+ });
+
+ afterEach(cleanup);
+
it("should throw fatal error.", () => {
assert.throws(() => {
engine.executeOnFiles("a.js");
@@ -3038,11 +3064,11 @@ describe("CLIEngine", () => {
});
describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => {
- beforeEach(() => {
- ({ CLIEngine } = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => path.join(os.tmpdir(), "cli-engine/11586"),
- files: {
- "node_modules/eslint-plugin-test/index.js": `
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: path.join(os.tmpdir(), "cli-engine/11586"),
+ files: {
+ "node_modules/eslint-plugin-test/index.js": `
exports.rules = {
"no-example": {
meta: { type: "problem", fixable: "code" },
@@ -3062,16 +3088,27 @@ describe("CLIEngine", () => {
}
};
`,
- ".eslintrc.json": JSON.stringify({
- plugins: ["test"],
- rules: { "test/no-example": "error" }
- }),
- "a.js": "example;"
- }
- }));
- engine = new CLIEngine({ fix: true, fixTypes: ["problem"] });
+ ".eslintrc.json": {
+ plugins: ["test"],
+ rules: { "test/no-example": "error" }
+ },
+ "a.js": "example;"
+ }
});
+ beforeEach(() => {
+ engine = new CLIEngine({
+ cwd: getPath(),
+ fix: true,
+ fixTypes: ["problem"]
+ });
+
+ return prepare();
+ });
+
+ afterEach(cleanup);
+
+
it("should not crash.", () => {
const { results } = engine.executeOnFiles("a.js");
@@ -3122,18 +3159,29 @@ describe("CLIEngine", () => {
`
};
- it("should lint only JavaScript blocks if '--ext' was not given.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ let cleanup;
+
+ beforeEach(() => {
+ cleanup = () => {};
+ });
+
+ afterEach(() => cleanup());
+
+ it("should lint only JavaScript blocks if '--ext' was not given.", async () => {
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" }
- })
+ }
}
- }).CLIEngine;
- engine = new CLIEngine({ cwd: root });
+ });
+
+ cleanup = teardown.cleanup;
+ await teardown.prepare();
+ engine = new CLIEngine({ cwd: teardown.getPath() });
const { results } = engine.executeOnFiles(["test.md"]);
@@ -3143,18 +3191,25 @@ describe("CLIEngine", () => {
assert.strictEqual(results[0].messages[0].line, 2);
});
- it("should fix only JavaScript blocks if '--ext' was not given.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should fix only JavaScript blocks if '--ext' was not given.", async () => {
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" }
- })
+ }
}
- }).CLIEngine;
- engine = new CLIEngine({ cwd: root, fix: true });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({
+ cwd: teardown.getPath(),
+ fix: true
+ });
const { results } = engine.executeOnFiles(["test.md"]);
@@ -3176,18 +3231,24 @@ describe("CLIEngine", () => {
`);
});
- it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => {
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" }
- })
+ }
}
- }).CLIEngine;
- engine = new CLIEngine({ cwd: root, extensions: ["js", "html"] });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({
+ cwd: teardown.getPath(),
+ extensions: ["js", "html"]
+ });
const { results } = engine.executeOnFiles(["test.md"]);
@@ -3199,18 +3260,25 @@ describe("CLIEngine", () => {
assert.strictEqual(results[0].messages[1].line, 7);
});
- it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => {
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" }
- })
+ }
}
- }).CLIEngine;
- engine = new CLIEngine({ cwd: root, extensions: ["js", "html"], fix: true });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({
+ cwd: teardown.getPath(),
+ extensions: ["js", "html"],
+ fix: true
+ });
const { results } = engine.executeOnFiles(["test.md"]);
@@ -3232,12 +3300,13 @@ describe("CLIEngine", () => {
`);
});
- it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => {
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" },
overrides: [
@@ -3246,10 +3315,17 @@ describe("CLIEngine", () => {
processor: "html/non-fixable" // supportsAutofix: false
}
]
- })
+ }
}
- }).CLIEngine;
- engine = new CLIEngine({ cwd: root, extensions: ["js", "html"], fix: true });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({
+ cwd: teardown.getPath(),
+ extensions: ["js", "html"],
+ fix: true
+ });
const { results } = engine.executeOnFiles(["test.md"]);
@@ -3274,12 +3350,13 @@ describe("CLIEngine", () => {
`);
});
- it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => {
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" },
overrides: [
@@ -3300,10 +3377,16 @@ describe("CLIEngine", () => {
}
}
]
- })
+ }
}
- }).CLIEngine;
- engine = new CLIEngine({ cwd: root, extensions: ["js", "html"] });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({
+ cwd: teardown.getPath(),
+ extensions: ["js", "html"]
+ });
const { results } = engine.executeOnFiles(["test.md"]);
@@ -3315,12 +3398,13 @@ describe("CLIEngine", () => {
assert.strictEqual(results[0].messages[1].line, 7);
});
- 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.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ 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 () => {
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" },
overrides: [
@@ -3340,10 +3424,16 @@ describe("CLIEngine", () => {
}
}
]
- })
+ }
}
- }).CLIEngine;
- engine = new CLIEngine({ cwd: root, extensions: ["js", "html"] });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({
+ cwd: teardown.getPath(),
+ extensions: ["js", "html"]
+ });
const { results } = engine.executeOnFiles(["test.md"]);
@@ -3357,30 +3447,37 @@ describe("CLIEngine", () => {
assert.strictEqual(results[0].messages[2].line, 10);
});
- it("should throw an error if invalid processor was specified.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should throw an error if invalid processor was specified.", async () => {
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
processor: "markdown/unknown"
- })
+ }
}
- }).CLIEngine;
- engine = new CLIEngine({ cwd: root });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({
+ cwd: teardown.getPath()
+ });
assert.throws(() => {
engine.executeOnFiles(["test.md"]);
}, /ESLint configuration of processor in '\.eslintrc\.json' is invalid: 'markdown\/unknown' was not found\./u);
});
- it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => {
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" },
overrides: [
@@ -3393,10 +3490,15 @@ describe("CLIEngine", () => {
processor: "markdown/.md"
}
]
- })
+ }
}
- }).CLIEngine;
- engine = new CLIEngine({ cwd: root });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({
+ cwd: teardown.getPath()
+ });
const { results } = engine.executeOnFiles(["test.md"]);
@@ -3508,27 +3610,35 @@ describe("CLIEngine", () => {
});
describe("with '--rulesdir' option", () => {
- it("should use the configured rules which are defined by '--rulesdir' option.", () => {
- const rootPath = getFixturePath("cli-engine/with-rulesdir");
- const StubbedCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => rootPath,
- files: {
- "internal-rules/test.js": `
+
+ const rootPath = getFixturePath("cli-engine/with-rulesdir");
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: rootPath,
+ files: {
+ "internal-rules/test.js": `
module.exports = context => ({
ExpressionStatement(node) {
context.report({ node, message: "ok" })
}
})
`,
- ".eslintrc.json": JSON.stringify({
- root: true,
- rules: { test: "error" }
- }),
- "test.js": "console.log('hello')"
- }
- }).CLIEngine;
+ ".eslintrc.json": {
+ root: true,
+ rules: { test: "error" }
+ },
+ "test.js": "console.log('hello')"
+ }
+ });
- engine = new StubbedCLIEngine({
+ beforeEach(prepare);
+ afterEach(cleanup);
+
+
+ it("should use the configured rules which are defined by '--rulesdir' option.", () => {
+
+ engine = new CLIEngine({
+ cwd: getPath(),
rulePaths: ["internal-rules"]
});
const report = engine.executeOnFiles(["test.js"]);
@@ -3542,9 +3652,18 @@ describe("CLIEngine", () => {
describe("glob pattern '[ab].js'", () => {
const root = getFixturePath("cli-engine/unmatched-glob");
- it("should match '[ab].js' if existed.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ let cleanup;
+
+ beforeEach(() => {
+ cleanup = () => { };
+ });
+
+ afterEach(() => cleanup());
+
+ it("should match '[ab].js' if existed.", async () => {
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"a.js": "",
"b.js": "",
@@ -3552,8 +3671,12 @@ describe("CLIEngine", () => {
"[ab].js": "",
".eslintrc.yml": "root: true"
}
- }).CLIEngine;
- engine = new CLIEngine();
+ });
+
+ engine = new CLIEngine({ cwd: teardown.getPath() });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
const { results } = engine.executeOnFiles(["[ab].js"]);
const filenames = results.map(r => path.basename(r.filePath));
@@ -3561,17 +3684,22 @@ describe("CLIEngine", () => {
assert.deepStrictEqual(filenames, ["[ab].js"]);
});
- it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => {
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"a.js": "",
"b.js": "",
"ab.js": "",
".eslintrc.yml": "root: true"
}
- }).CLIEngine;
- engine = new CLIEngine();
+ });
+
+ engine = new CLIEngine({ cwd: teardown.getPath() });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
const { results } = engine.executeOnFiles(["[ab].js"]);
const filenames = results.map(r => path.basename(r.filePath));
@@ -3583,15 +3711,27 @@ describe("CLIEngine", () => {
describe("with 'noInlineConfig' setting", () => {
const root = getFixturePath("cli-engine/noInlineConfig");
- it("should warn directive comments if 'noInlineConfig' was given.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ let cleanup;
+
+ beforeEach(() => {
+ cleanup = () => { };
+ });
+
+ afterEach(() => cleanup());
+
+ it("should warn directive comments if 'noInlineConfig' was given.", async () => {
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"test.js": "/* globals foo */",
".eslintrc.yml": "noInlineConfig: true"
}
- }).CLIEngine;
- engine = new CLIEngine();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({ cwd: teardown.getPath() });
const { results } = engine.executeOnFiles(["test.js"]);
const messages = results[0].messages;
@@ -3600,16 +3740,20 @@ describe("CLIEngine", () => {
assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml).");
});
- it("should show the config file what the 'noInlineConfig' came from.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should show the config file what the 'noInlineConfig' came from.", async () => {
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"node_modules/eslint-config-foo/index.js": "module.exports = {noInlineConfig: true}",
"test.js": "/* globals foo */",
".eslintrc.yml": "extends: foo"
}
- }).CLIEngine;
- engine = new CLIEngine();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({ cwd: teardown.getPath() });
const { results } = engine.executeOnFiles(["test.js"]);
const messages = results[0].messages;
@@ -3622,15 +3766,26 @@ describe("CLIEngine", () => {
describe("with 'reportUnusedDisableDirectives' setting", () => {
const root = getFixturePath("cli-engine/reportUnusedDisableDirectives");
- it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ let cleanup;
+
+ beforeEach(() => {
+ cleanup = () => { };
+ });
+
+ afterEach(() => cleanup());
+
+ it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => {
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"test.js": "/* eslint-disable eqeqeq */",
".eslintrc.yml": "reportUnusedDisableDirectives: true"
}
- }).CLIEngine;
- engine = new CLIEngine();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({ cwd: teardown.getPath() });
const { results } = engine.executeOnFiles(["test.js"]);
const messages = results[0].messages;
@@ -3641,15 +3796,22 @@ describe("CLIEngine", () => {
});
describe("the runtime option overrides config files.", () => {
- it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => {
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"test.js": "/* eslint-disable eqeqeq */",
".eslintrc.yml": "reportUnusedDisableDirectives: true"
}
- }).CLIEngine;
- engine = new CLIEngine({ reportUnusedDisableDirectives: "off" });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+
+ engine = new CLIEngine({
+ cwd: teardown.getPath(),
+ reportUnusedDisableDirectives: "off"
+ });
const { results } = engine.executeOnFiles(["test.js"]);
const messages = results[0].messages;
@@ -3657,15 +3819,22 @@ describe("CLIEngine", () => {
assert.strictEqual(messages.length, 0);
});
- it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => {
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"test.js": "/* eslint-disable eqeqeq */",
".eslintrc.yml": "reportUnusedDisableDirectives: true"
}
- }).CLIEngine;
- engine = new CLIEngine({ reportUnusedDisableDirectives: "error" });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+
+ engine = new CLIEngine({
+ cwd: teardown.getPath(),
+ reportUnusedDisableDirectives: "error"
+ });
const { results } = engine.executeOnFiles(["test.js"]);
const messages = results[0].messages;
@@ -3680,24 +3849,28 @@ describe("CLIEngine", () => {
describe("with 'overrides[*].extends' setting on deep locations", () => {
const root = getFixturePath("cli-engine/deeply-overrides-i-extends");
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ overrides: [{ files: ["*test*"], extends: "two" }]
+ })}`,
+ "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+ overrides: [{ files: ["*.js"], extends: "three" }]
+ })}`,
+ "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({
+ rules: { "no-console": "error" }
+ })}`,
+ "test.js": "console.log('hello')",
+ ".eslintrc.yml": "extends: one"
+ }
+ });
+
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("should not throw.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- overrides: [{ files: ["*test*"], extends: "two" }]
- })}`,
- "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
- overrides: [{ files: ["*.js"], extends: "three" }]
- })}`,
- "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({
- rules: { "no-console": "error" }
- })}`,
- "test.js": "console.log('hello')",
- ".eslintrc.yml": "extends: one"
- }
- }).CLIEngine;
- engine = new CLIEngine();
+ engine = new CLIEngine({ cwd: getPath() });
const { results } = engine.executeOnFiles(["test.js"]);
const messages = results[0].messages;
@@ -3710,46 +3883,72 @@ describe("CLIEngine", () => {
describe("don't ignore the entry directory.", () => {
const root = getFixturePath("cli-engine/dont-ignore-entry-dir");
- it("'executeOnFiles(\".\")' should not load config files from outside of \".\".", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ let cleanup;
+
+ beforeEach(() => {
+ cleanup = () => {};
+ });
+
+ afterEach(async () => {
+ await cleanup();
+
+ const configFilePath = path.resolve(root, "../.eslintrc.json");
+
+ if (shell.test("-e", configFilePath)) {
+ shell.rm(configFilePath);
+ }
+ });
+
+ it("'executeOnFiles(\".\")' should not load config files from outside of \".\".", async () => {
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"../.eslintrc.json": "BROKEN FILE",
".eslintrc.json": JSON.stringify({ root: true }),
"index.js": "console.log(\"hello\")"
}
- }).CLIEngine;
- engine = new CLIEngine();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({ cwd: teardown.getPath() });
// Don't throw "failed to load config file" error.
engine.executeOnFiles(".");
});
- it("'executeOnFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("'executeOnFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => {
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
- "../.eslintrc.json": JSON.stringify({ ignorePatterns: ["/dont-ignore-entry-dir"] }),
- ".eslintrc.json": JSON.stringify({ root: true }),
+ "../.eslintrc.json": { ignorePatterns: ["/dont-ignore-entry-dir"] },
+ ".eslintrc.json": { root: true },
"index.js": "console.log(\"hello\")"
}
- }).CLIEngine;
- engine = new CLIEngine();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({ cwd: teardown.getPath() });
+
// Don't throw "file not found" error.
engine.executeOnFiles(".");
});
- it("'executeOnFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", () => {
- CLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
+ it("'executeOnFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => {
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
- ".eslintrc.json": JSON.stringify({ ignorePatterns: ["/subdir"] }),
- "subdir/.eslintrc.json": JSON.stringify({ root: true }),
+ ".eslintrc.json": { ignorePatterns: ["/subdir"] },
+ "subdir/.eslintrc.json": { root: true },
"subdir/index.js": "console.log(\"hello\")"
}
- }).CLIEngine;
- engine = new CLIEngine();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ engine = new CLIEngine({ cwd: teardown.getPath() });
// Don't throw "file not found" error.
engine.executeOnFiles("subdir");
@@ -4469,7 +4668,9 @@ describe("CLIEngine", () => {
});
it("should call fs.writeFileSync() for each result with output", () => {
- const fakeFS = leche.fake(fs),
+ const fakeFS = {
+ writeFileSync: () => {}
+ },
localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", {
fs: fakeFS
}).CLIEngine,
@@ -4486,7 +4687,6 @@ describe("CLIEngine", () => {
]
};
- fakeFS.writeFileSync = function() {};
const spy = sinon.spy(fakeFS, "writeFileSync");
localCLIEngine.outputFixes(report);
@@ -4498,7 +4698,9 @@ describe("CLIEngine", () => {
});
it("should call fs.writeFileSync() for each result with output and not at all for a result without output", () => {
- const fakeFS = leche.fake(fs),
+ const fakeFS = {
+ writeFileSync: () => {}
+ },
localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", {
fs: fakeFS
}).CLIEngine,
@@ -4518,7 +4720,6 @@ describe("CLIEngine", () => {
]
};
- fakeFS.writeFileSync = function() {};
const spy = sinon.spy(fakeFS, "writeFileSync");
localCLIEngine.outputFixes(report);
@@ -4555,12 +4756,12 @@ describe("CLIEngine", () => {
describe("resolveFileGlobPatterns", () => {
- leche.withData([
+ [
[".", ["**/*.{js}"]],
["./", ["**/*.{js}"]],
["../", ["../**/*.{js}"]],
["", []]
- ], (input, expected) => {
+ ].forEach(([input, expected]) => {
it(`should correctly resolve ${input} to ${expected}`, () => {
const engine = new CLIEngine();
@@ -4828,41 +5029,40 @@ describe("CLIEngine", () => {
describe("with ignorePatterns config", () => {
const root = getFixturePath("cli-engine/ignore-patterns");
- /** @type {typeof CLIEngine} */
- let InMemoryCLIEngine;
-
describe("ignorePatterns can add an ignore pattern ('foo.js').", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "foo.js"
- }),
- "foo.js": "",
- "bar.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": {
+ ignorePatterns: "foo.js"
+ },
+ "foo.js": "",
+ "bar.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), true);
assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("bar.js"), false);
assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), false);
});
it("'executeOnFiles()' should not verify 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -4876,39 +5076,40 @@ describe("CLIEngine", () => {
});
describe("ignorePatterns can add ignore patterns ('foo.js', '/bar.js').", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: ["foo.js", "/bar.js"]
- }),
- "foo.js": "",
- "bar.js": "",
- "baz.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": "",
- "subdir/baz.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": {
+ ignorePatterns: ["foo.js", "/bar.js"]
+ },
+ "foo.js": "",
+ "bar.js": "",
+ "baz.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": "",
+ "subdir/baz.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), true);
assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true);
});
it("'isPathIgnored()' should return 'true' for '/bar.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("bar.js"), true);
assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), false);
});
it("'executeOnFiles()' should not verify 'foo.js' and '/bar.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -4923,41 +5124,43 @@ describe("CLIEngine", () => {
});
describe("ignorePatterns can unignore '/node_modules/foo'.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "!/node_modules/foo"
- }),
- "node_modules/foo/index.js": "",
- "node_modules/foo/.dot.js": "",
- "node_modules/bar/index.js": "",
- "foo.js": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": {
+ ignorePatterns: "!/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'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("node_modules/foo/index.js"), false);
});
it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("node_modules/foo/.dot.js"), true);
});
it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("node_modules/bar/index.js"), true);
});
it("'executeOnFiles()' should verify 'node_modules/foo/index.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -4971,26 +5174,28 @@ describe("CLIEngine", () => {
});
describe("ignorePatterns can unignore '.eslintrc.js'.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "!.eslintrc.js"
- })}`,
- "foo.js": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "!.eslintrc.js"
+ })}`,
+ "foo.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored(".eslintrc.js"), false);
});
it("'executeOnFiles()' should verify '.eslintrc.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5004,34 +5209,35 @@ describe("CLIEngine", () => {
});
describe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "!.*"
- })}`,
- ".eslintignore": ".foo*",
- ".foo.js": "",
- ".bar.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "!.*"
+ })}`,
+ ".eslintignore": ".foo*",
+ ".foo.js": "",
+ ".bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored(".foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored(".bar.js"), false);
});
it("'executeOnFiles()' should not verify re-ignored '.foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5045,34 +5251,36 @@ describe("CLIEngine", () => {
});
describe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "*.js"
- })}`,
- ".eslintignore": "!foo.js",
- "foo.js": "",
- "bar.js": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "*.js"
+ })}`,
+ ".eslintignore": "!foo.js",
+ "foo.js": "",
+ "bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), false);
});
it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("bar.js"), true);
});
it("'executeOnFiles()' should verify unignored 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5085,28 +5293,29 @@ describe("CLIEngine", () => {
});
describe("ignorePatterns in the config file in a child directory affects to only in the directory.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "foo.js"
- }),
- "subdir/.eslintrc.json": JSON.stringify({
- ignorePatterns: "bar.js"
- }),
- "foo.js": "",
- "bar.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": "",
- "subdir/subsubdir/foo.js": "",
- "subdir/subsubdir/bar.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ ignorePatterns: "foo.js"
+ }),
+ "subdir/.eslintrc.json": JSON.stringify({
+ ignorePatterns: "bar.js"
+ }),
+ "foo.js": "",
+ "bar.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": "",
+ "subdir/subsubdir/foo.js": "",
+ "subdir/subsubdir/bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), true);
assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true);
@@ -5114,20 +5323,20 @@ describe("CLIEngine", () => {
});
it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true);
assert.strictEqual(engine.isPathIgnored("subdir/subsubdir/bar.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("bar.js"), false);
});
it("'executeOnFiles()' should verify 'bar.js' in the outside of 'subdir'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5140,36 +5349,37 @@ describe("CLIEngine", () => {
});
describe("ignorePatterns in the config file in a child directory can unignore the ignored files in the parent directory's config.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "foo.js"
- }),
- "subdir/.eslintrc.json": JSON.stringify({
- ignorePatterns: "!foo.js"
- }),
- "foo.js": "",
- "subdir/foo.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ ignorePatterns: "foo.js"
+ }),
+ "subdir/.eslintrc.json": JSON.stringify({
+ ignorePatterns: "!foo.js"
+ }),
+ "foo.js": "",
+ "subdir/foo.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false);
});
it("'executeOnFiles()' should verify 'foo.js' in the child directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5182,37 +5392,38 @@ describe("CLIEngine", () => {
});
describe(".eslintignore can unignore files that are ignored by ignorePatterns in the config file in the child directory.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({}),
- "subdir/.eslintrc.json": JSON.stringify({
- ignorePatterns: "*.js"
- }),
- ".eslintignore": "!foo.js",
- "foo.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": {},
+ "subdir/.eslintrc.json": {
+ ignorePatterns: "*.js"
+ },
+ ".eslintignore": "!foo.js",
+ "foo.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), false);
assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false);
});
it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true);
});
it("'executeOnFiles()' should verify unignored 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5226,51 +5437,52 @@ describe("CLIEngine", () => {
});
describe("if the config in a child directory has 'root:true', ignorePatterns in the config file in the parent directory should not be used.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "foo.js"
- }),
- "subdir/.eslintrc.json": JSON.stringify({
- root: true,
- ignorePatterns: "bar.js"
- }),
- "foo.js": "",
- "bar.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": {
+ ignorePatterns: "foo.js"
+ },
+ "subdir/.eslintrc.json": {
+ root: true,
+ ignorePatterns: "bar.js"
+ },
+ "foo.js": "",
+ "bar.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("bar.js"), false);
});
it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false);
});
it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true);
});
it("'executeOnFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5284,45 +5496,47 @@ describe("CLIEngine", () => {
});
describe("even if the config in a child directory has 'root:true', .eslintignore should be used.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({}),
- "subdir/.eslintrc.json": JSON.stringify({
- root: true,
- ignorePatterns: "bar.js"
- }),
- ".eslintignore": "foo.js",
- "foo.js": "",
- "bar.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({}),
+ "subdir/.eslintrc.json": JSON.stringify({
+ root: true,
+ ignorePatterns: "bar.js"
+ }),
+ ".eslintignore": "foo.js",
+ "foo.js": "",
+ "bar.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), true);
assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("bar.js"), false);
});
it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true);
});
it("'executeOnFiles()' should verify 'bar.js' in the root directory.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5335,36 +5549,37 @@ describe("CLIEngine", () => {
});
describe("ignorePatterns in the shareable config should be used.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "foo.js"
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: "one"
- }),
- "foo.js": "",
- "bar.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "foo.js"
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: "one"
+ }),
+ "foo.js": "",
+ "bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("bar.js"), false);
});
it("'executeOnFiles()' should verify 'bar.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5377,36 +5592,39 @@ describe("CLIEngine", () => {
});
describe("ignorePatterns in the shareable config should be relative to the entry config file.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "/foo.js"
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: "one"
- }),
- "foo.js": "",
- "subdir/foo.js": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "/foo.js"
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: "one"
+ }),
+ "foo.js": "",
+ "subdir/foo.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false);
});
it("'executeOnFiles()' should verify 'subdir/foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5419,37 +5637,39 @@ describe("CLIEngine", () => {
});
describe("ignorePatterns in a config file can unignore the files which are ignored by ignorePatterns in the shareable config.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "*.js"
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: "one",
- ignorePatterns: "!bar.js"
- }),
- "foo.js": "",
- "bar.js": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "*.js"
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: "one",
+ ignorePatterns: "!bar.js"
+ }),
+ "foo.js": "",
+ "bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
assert.strictEqual(engine.isPathIgnored("bar.js"), false);
});
it("'executeOnFiles()' should verify 'bar.js'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5462,26 +5682,27 @@ describe("CLIEngine", () => {
});
describe("ignorePatterns in a config file should not be used if --no-ignore option was given.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "*.js"
- }),
- "foo.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ ignorePatterns: "*.js"
+ }),
+ "foo.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'false' for 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine({ ignore: false });
+ const engine = new CLIEngine({ cwd: getPath(), ignore: false });
assert.strictEqual(engine.isPathIgnored("foo.js"), false);
});
it("'executeOnFiles()' should verify 'foo.js'.", () => {
- const engine = new InMemoryCLIEngine({ ignore: false });
+ const engine = new CLIEngine({ cwd: getPath(), ignore: false });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5494,26 +5715,28 @@ describe("CLIEngine", () => {
});
describe("ignorePatterns in overrides section is not allowed.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.js": `module.exports = ${JSON.stringify({
- overrides: [
- {
- files: "*.js",
- ignorePatterns: "foo.js"
- }
- ]
- })}`,
- "foo.js": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.js": `module.exports = ${JSON.stringify({
+ overrides: [
+ {
+ files: "*.js",
+ ignorePatterns: "foo.js"
+ }
+ ]
+ })}`,
+ "foo.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("should throw a configuration error.", () => {
assert.throws(() => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
engine.executeOnFiles("*.js");
}, "Unexpected top-level property \"overrides[0].ignorePatterns\"");
@@ -5524,37 +5747,38 @@ describe("CLIEngine", () => {
describe("'overrides[].files' adds lint targets", () => {
const root = getFixturePath("cli-engine/additional-lint-targets");
- let InMemoryCLIEngine;
describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- overrides: [
- {
- files: "foo/*.txt",
- excludedFiles: "**/ignore.txt"
- }
- ]
- }),
- "foo/nested/test.txt": "",
- "foo/test.js": "",
- "foo/test.txt": "",
- "foo/ignore.txt": "",
- "bar/test.js": "",
- "bar/test.txt": "",
- "bar/ignore.txt": "",
- "test.js": "",
- "test.txt": "",
- "ignore.txt": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ overrides: [
+ {
+ files: "foo/*.txt",
+ excludedFiles: "**/ignore.txt"
+ }
+ ]
+ }),
+ "foo/nested/test.txt": "",
+ "foo/test.js": "",
+ "foo/test.txt": "",
+ "foo/ignore.txt": "",
+ "bar/test.js": "",
+ "bar/test.txt": "",
+ "bar/ignore.txt": "",
+ "test.js": "",
+ "test.txt": "",
+ "ignore.txt": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' with a directory path should contain 'foo/test.txt'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles(".")
.results
.map(r => r.filePath)
@@ -5569,7 +5793,7 @@ describe("CLIEngine", () => {
});
it("'executeOnFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles("**/*.js")
.results
.map(r => r.filePath)
@@ -5584,30 +5808,32 @@ describe("CLIEngine", () => {
});
describe("if { files: 'foo/**/*.txt' } is present,", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- overrides: [
- {
- files: "foo/**/*.txt"
- }
- ]
- }),
- "foo/nested/test.txt": "",
- "foo/test.js": "",
- "foo/test.txt": "",
- "bar/test.js": "",
- "bar/test.txt": "",
- "test.js": "",
- "test.txt": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ overrides: [
+ {
+ files: "foo/**/*.txt"
+ }
+ ]
+ }),
+ "foo/nested/test.txt": "",
+ "foo/test.js": "",
+ "foo/test.txt": "",
+ "bar/test.js": "",
+ "bar/test.txt": "",
+ "test.js": "",
+ "test.txt": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles(".")
.results
.map(r => r.filePath)
@@ -5624,30 +5850,31 @@ describe("CLIEngine", () => {
});
describe("if { files: 'foo/**/*' } is present,", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- overrides: [
- {
- files: "foo/**/*"
- }
- ]
- }),
- "foo/nested/test.txt": "",
- "foo/test.js": "",
- "foo/test.txt": "",
- "bar/test.js": "",
- "bar/test.txt": "",
- "test.js": "",
- "test.txt": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ overrides: [
+ {
+ files: "foo/**/*"
+ }
+ ]
+ }),
+ "foo/nested/test.txt": "",
+ "foo/test.js": "",
+ "foo/test.txt": "",
+ "bar/test.js": "",
+ "bar/test.txt": "",
+ "test.js": "",
+ "test.txt": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles(".")
.results
.map(r => r.filePath)
@@ -5662,33 +5889,34 @@ describe("CLIEngine", () => {
});
describe("if { files: 'foo/**/*.txt' } is present in a shareable config,", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({
- overrides: [
- {
- files: "foo/**/*.txt"
- }
- ]
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: "foo"
- }),
- "foo/nested/test.txt": "",
- "foo/test.js": "",
- "foo/test.txt": "",
- "bar/test.js": "",
- "bar/test.txt": "",
- "test.js": "",
- "test.txt": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({
+ overrides: [
+ {
+ files: "foo/**/*.txt"
+ }
+ ]
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: "foo"
+ }),
+ "foo/nested/test.txt": "",
+ "foo/test.js": "",
+ "foo/test.txt": "",
+ "bar/test.js": "",
+ "bar/test.txt": "",
+ "test.js": "",
+ "test.txt": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles(".")
.results
.map(r => r.filePath)
@@ -5705,35 +5933,36 @@ describe("CLIEngine", () => {
});
describe("if { files: 'foo/**/*.txt' } is present in a plugin config,", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({
- bar: {
- overrides: [
- {
- files: "foo/**/*.txt"
- }
- ]
- }
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: "plugin:foo/bar"
- }),
- "foo/nested/test.txt": "",
- "foo/test.js": "",
- "foo/test.txt": "",
- "bar/test.js": "",
- "bar/test.txt": "",
- "test.js": "",
- "test.txt": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({
+ bar: {
+ overrides: [
+ {
+ files: "foo/**/*.txt"
+ }
+ ]
+ }
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: "plugin:foo/bar"
+ }),
+ "foo/nested/test.txt": "",
+ "foo/test.js": "",
+ "foo/test.txt": "",
+ "bar/test.js": "",
+ "bar/test.txt": "",
+ "test.js": "",
+ "test.txt": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => {
- const engine = new InMemoryCLIEngine();
+ const engine = new CLIEngine({ cwd: getPath() });
const filePaths = engine.executeOnFiles(".")
.results
.map(r => r.filePath)
@@ -5753,34 +5982,32 @@ describe("CLIEngine", () => {
describe("'ignorePatterns', 'overrides[].files', and 'overrides[].excludedFiles' of the configuration that the '--config' option provided should be resolved from CWD.", () => {
const root = getFixturePath("cli-engine/config-and-overrides-files");
- /** @type {CLIEngine} */
- let InMemoryCLIEngine;
-
describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/myconf/.eslintrc.json": JSON.stringify({
- overrides: [
- {
- files: "foo/*.js",
- rules: {
- eqeqeq: "error"
- }
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/myconf/.eslintrc.json": JSON.stringify({
+ overrides: [
+ {
+ files: "foo/*.js",
+ rules: {
+ eqeqeq: "error"
}
- ]
- }),
- "node_modules/myconf/foo/test.js": "a == b",
- "foo/test.js": "a == b"
- }
- }).CLIEngine;
+ }
+ ]
+ }),
+ "node_modules/myconf/foo/test.js": "a == b",
+ "foo/test.js": "a == b"
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' with 'foo/test.js' should use the override entry.", () => {
- const engine = new InMemoryCLIEngine({
+ const engine = new CLIEngine({
configFile: "node_modules/myconf/.eslintrc.json",
- cwd: root,
+ cwd: getPath(),
ignore: false,
useEslintrc: false
});
@@ -5813,9 +6040,9 @@ describe("CLIEngine", () => {
});
it("'executeOnFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", () => {
- const engine = new InMemoryCLIEngine({
+ const engine = new CLIEngine({
configFile: "node_modules/myconf/.eslintrc.json",
- cwd: root,
+ cwd: getPath(),
ignore: false,
useEslintrc: false
});
@@ -5836,31 +6063,32 @@ describe("CLIEngine", () => {
});
describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/myconf/.eslintrc.json": JSON.stringify({
- overrides: [
- {
- files: "*",
- excludedFiles: "foo/*.js",
- rules: {
- eqeqeq: "error"
- }
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/myconf/.eslintrc.json": JSON.stringify({
+ overrides: [
+ {
+ files: "*",
+ excludedFiles: "foo/*.js",
+ rules: {
+ eqeqeq: "error"
}
- ]
- }),
- "node_modules/myconf/foo/test.js": "a == b",
- "foo/test.js": "a == b"
- }
- }).CLIEngine;
+ }
+ ]
+ }),
+ "node_modules/myconf/foo/test.js": "a == b",
+ "foo/test.js": "a == b"
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' with 'foo/test.js' should NOT use the override entry.", () => {
- const engine = new InMemoryCLIEngine({
+ const engine = new CLIEngine({
configFile: "node_modules/myconf/.eslintrc.json",
- cwd: root,
+ cwd: getPath(),
ignore: false,
useEslintrc: false
});
@@ -5880,9 +6108,9 @@ describe("CLIEngine", () => {
});
it("'executeOnFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", () => {
- const engine = new InMemoryCLIEngine({
+ const engine = new CLIEngine({
configFile: "node_modules/myconf/.eslintrc.json",
- cwd: root,
+ cwd: getPath(),
ignore: false,
useEslintrc: false
});
@@ -5916,26 +6144,27 @@ describe("CLIEngine", () => {
});
describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/myconf/.eslintrc.json": JSON.stringify({
- ignorePatterns: ["!/node_modules/myconf", "foo/*.js"],
- rules: {
- eqeqeq: "error"
- }
- }),
- "node_modules/myconf/foo/test.js": "a == b",
- "foo/test.js": "a == b"
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/myconf/.eslintrc.json": JSON.stringify({
+ ignorePatterns: ["!/node_modules/myconf", "foo/*.js"],
+ rules: {
+ eqeqeq: "error"
+ }
+ }),
+ "node_modules/myconf/foo/test.js": "a == b",
+ "foo/test.js": "a == b"
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", () => {
- const engine = new InMemoryCLIEngine({
+ const engine = new CLIEngine({
configFile: "node_modules/myconf/.eslintrc.json",
- cwd: root,
+ cwd: getPath(),
useEslintrc: false
});
const files = engine.executeOnFiles("**/*.js")
@@ -5952,14 +6181,7 @@ describe("CLIEngine", () => {
describe("plugin conflicts", () => {
let uid = 0;
- let root = "";
-
- beforeEach(() => {
- root = getFixturePath(`cli-engine/plugin-conflicts-${++uid}`);
- });
-
- /** @type {typeof CLIEngine} */
- let InMemoryCLIEngine;
+ const root = getFixturePath("cli-engine/plugin-conflicts-");
/**
* Verify thrown errors.
@@ -5981,110 +6203,115 @@ describe("CLIEngine", () => {
}
describe("between a config file and linear extendees.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- extends: ["two"],
- plugins: ["foo"]
- })}`,
- "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
- plugins: ["foo"]
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: ["one"],
- plugins: ["foo"]
- }),
- "test.js": ""
- }
- }).CLIEngine;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ extends: ["two"],
+ plugins: ["foo"]
+ })}`,
+ "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+ plugins: ["foo"]
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: ["one"],
+ plugins: ["foo"]
+ }),
+ "test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", () => {
- const engine = new InMemoryCLIEngine({ cwd: root });
+ const engine = new CLIEngine({ cwd: getPath() });
engine.executeOnFiles("test.js");
});
});
describe("between a config file and same-depth extendees.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- plugins: ["foo"]
- })}`,
- "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
- plugins: ["foo"]
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: ["one", "two"],
- plugins: ["foo"]
- }),
- "test.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ plugins: ["foo"]
+ })}`,
+ "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+ plugins: ["foo"]
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: ["one", "two"],
+ plugins: ["foo"]
+ }),
+ "test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", () => {
- const engine = new InMemoryCLIEngine({ cwd: root });
+ const engine = new CLIEngine({ cwd: getPath() });
engine.executeOnFiles("test.js");
});
});
describe("between two config files in different directories, with single node_modules.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- ".eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/test.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ ".eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", () => {
- const engine = new InMemoryCLIEngine({ cwd: root });
+ const engine = new CLIEngine({ cwd: getPath() });
engine.executeOnFiles("subdir/test.js");
});
});
describe("between two config files in different directories, with multiple node_modules.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- ".eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/node_modules/eslint-plugin-foo/index.js": "",
- "subdir/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/test.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ ".eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/node_modules/eslint-plugin-foo/index.js": "",
+ "subdir/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", () => {
- const engine = new InMemoryCLIEngine({ cwd: root });
+ const engine = new CLIEngine({ cwd: getPath() });
assertThrows(
() => engine.executeOnFiles("subdir/test.js"),
@@ -6095,11 +6322,11 @@ describe("CLIEngine", () => {
pluginId: "foo",
plugins: [
{
- filePath: path.join(root, "subdir/node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"),
importerName: `subdir${path.sep}.eslintrc.json`
},
{
- filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
importerName: ".eslintrc.json"
}
]
@@ -6110,25 +6337,26 @@ describe("CLIEngine", () => {
});
describe("between '--config' option and a regular config file, with single node_modules.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/mine/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- ".eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "test.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/mine/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ ".eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", () => {
- const engine = new InMemoryCLIEngine({
- cwd: root,
+ const engine = new CLIEngine({
+ cwd: getPath(),
configFile: "node_modules/mine/.eslintrc.json"
});
@@ -6137,26 +6365,27 @@ describe("CLIEngine", () => {
});
describe("between '--config' option and a regular config file, with multiple node_modules.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/mine/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- ".eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "test.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/mine/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ ".eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", () => {
- const engine = new InMemoryCLIEngine({
- cwd: root,
+ const engine = new CLIEngine({
+ cwd: getPath(),
configFile: "node_modules/mine/.eslintrc.json"
});
@@ -6169,11 +6398,11 @@ describe("CLIEngine", () => {
pluginId: "foo",
plugins: [
{
- filePath: path.join(root, "node_modules/mine/node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "node_modules/mine/node_modules/eslint-plugin-foo/index.js"),
importerName: "--config"
},
{
- filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
importerName: ".eslintrc.json"
}
]
@@ -6184,22 +6413,23 @@ describe("CLIEngine", () => {
});
describe("between '--plugin' option and a regular config file, with single node_modules.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "subdir/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/test.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "subdir/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file, but node_modules directory is unique.)", () => {
- const engine = new InMemoryCLIEngine({
- cwd: root,
+ const engine = new CLIEngine({
+ cwd: getPath(),
plugins: ["foo"]
});
@@ -6208,23 +6438,24 @@ describe("CLIEngine", () => {
});
describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "subdir/node_modules/eslint-plugin-foo/index.js": "",
- "subdir/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/test.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "subdir/node_modules/eslint-plugin-foo/index.js": "",
+ "subdir/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", () => {
- const engine = new InMemoryCLIEngine({
- cwd: root,
+ const engine = new CLIEngine({
+ cwd: getPath(),
plugins: ["foo"]
});
@@ -6237,11 +6468,11 @@ describe("CLIEngine", () => {
pluginId: "foo",
plugins: [
{
- filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
importerName: "CLIOptions"
},
{
- filePath: path.join(root, "subdir/node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"),
importerName: `subdir${path.sep}.eslintrc.json`
}
]
@@ -6252,27 +6483,28 @@ describe("CLIEngine", () => {
});
describe("'--resolve-plugins-relative-to' option overrides the location that ESLint load plugins from.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- ".eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/node_modules/eslint-plugin-foo/index.js": "",
- "subdir/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/test.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ ".eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/node_modules/eslint-plugin-foo/index.js": "",
+ "subdir/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", () => {
- const engine = new InMemoryCLIEngine({
- cwd: root,
- resolvePluginsRelativeTo: root
+ const engine = new CLIEngine({
+ cwd: getPath(),
+ resolvePluginsRelativeTo: getPath()
});
engine.executeOnFiles("subdir/test.js");
@@ -6280,26 +6512,27 @@ describe("CLIEngine", () => {
});
describe("between two config files with different target files.", () => {
- beforeEach(() => {
- InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "one/node_modules/eslint-plugin-foo/index.js": "",
- "one/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "one/test.js": "",
- "two/node_modules/eslint-plugin-foo/index.js": "",
- "two/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "two/test.js": ""
- }
- }).CLIEngine;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "one/node_modules/eslint-plugin-foo/index.js": "",
+ "one/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "one/test.js": "",
+ "two/node_modules/eslint-plugin-foo/index.js": "",
+ "two/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "two/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file for each target file. Not related to each other.)", () => {
- const engine = new InMemoryCLIEngine({ cwd: root });
+ const engine = new CLIEngine({ cwd: getPath() });
const { results } = engine.executeOnFiles("*/test.js");
assert.strictEqual(results.length, 2);
diff --git a/eslint/tests/lib/cli-engine/config-array-factory.js b/eslint/tests/lib/cli-engine/config-array-factory.js
deleted file mode 100644
index 3b979c1..0000000
--- a/eslint/tests/lib/cli-engine/config-array-factory.js
+++ /dev/null
@@ -1,2417 +0,0 @@
-/**
- * @fileoverview Tests for ConfigArrayFactory class.
- * @author Toru Nagashima
- */
-"use strict";
-
-const os = require("os");
-const path = require("path");
-const { assert } = require("chai");
-const { spy } = require("sinon");
-const { ConfigArray } = require("../../../lib/cli-engine/config-array");
-const { OverrideTester } = require("../../../lib/cli-engine/config-array");
-const { createContext } = require("../../../lib/cli-engine/config-array-factory");
-const { defineConfigArrayFactoryWithInMemoryFileSystem } = require("../../_utils");
-
-const tempDir = path.join(os.tmpdir(), "eslint/config-array-factory");
-
-// For VSCode intellisense.
-/** @typedef {InstanceType["ConfigArrayFactory"]>} ConfigArrayFactory */
-
-/**
- * Assert a config array element.
- * @param {Object} actual The actual value.
- * @param {Object} providedExpected The expected value.
- * @returns {void}
- */
-function assertConfigArrayElement(actual, providedExpected) {
- const expected = {
- name: "",
- filePath: "",
- criteria: null,
- env: void 0,
- globals: void 0,
- ignorePattern: void 0,
- noInlineConfig: void 0,
- parser: void 0,
- parserOptions: void 0,
- plugins: void 0,
- processor: void 0,
- reportUnusedDisableDirectives: void 0,
- root: void 0,
- rules: void 0,
- settings: void 0,
- type: "config",
- ...providedExpected
- };
-
- assert.deepStrictEqual(actual, expected);
-}
-
-/**
- * Assert a config array element.
- * @param {Object} actual The actual value.
- * @param {Object} providedExpected The expected value.
- * @returns {void}
- */
-function assertConfig(actual, providedExpected) {
- const expected = {
- env: {},
- globals: {},
- ignorePatterns: [],
- noInlineConfig: void 0,
- parser: null,
- parserOptions: {},
- plugins: [],
- reportUnusedDisableDirectives: void 0,
- rules: {},
- settings: {},
- ...providedExpected
- };
-
- assert.deepStrictEqual(actual, expected);
-}
-
-/**
- * Assert a plugin definition.
- * @param {Object} actual The actual value.
- * @param {Object} providedExpected The expected value.
- * @returns {void}
- */
-function assertPluginDefinition(actual, providedExpected) {
- const expected = {
- configs: {},
- environments: {},
- processors: {},
- rules: {},
- ...providedExpected
- };
-
- assert.deepStrictEqual(actual, expected);
-}
-
-describe("ConfigArrayFactory", () => {
- describe("'create(configData, options)' method should normalize the config data.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir
- });
-
- /** @type {ConfigArrayFactory} */
- let factory;
-
- beforeEach(() => {
- factory = new ConfigArrayFactory();
- });
-
- it("should return an empty config array if 'configData' is null.", () => {
- assert.strictEqual(factory.create(null).length, 0);
- });
-
- it("should throw an error if the config data had invalid properties,", () => {
- assert.throws(() => {
- factory.create({ files: true });
- }, /Unexpected top-level property "files"/u);
- });
-
- it("should call '_normalizeConfigData(configData, ctx)' with given arguments.", () => {
- const configData = {};
- const basePath = tempDir;
- const filePath = __filename;
- const name = "example";
- const normalizeConfigData = spy(factory, "_normalizeConfigData");
-
- factory.create(configData, { basePath, filePath, name });
-
- assert.strictEqual(normalizeConfigData.callCount, 1);
- assert.deepStrictEqual(normalizeConfigData.args[0], [
- configData,
- createContext({ cwd: tempDir }, void 0, name, filePath, basePath)
- ]);
- });
-
- it("should return a config array that contains the yielded elements from '_normalizeConfigData(configData, ctx)'.", () => {
- const elements = [{}, {}];
-
- factory._normalizeConfigData = () => elements; // eslint-disable-line no-underscore-dangle
-
- const configArray = factory.create({});
-
- assert.strictEqual(configArray.length, 2);
- assert.strictEqual(configArray[0], elements[0]);
- assert.strictEqual(configArray[1], elements[1]);
- });
- });
-
- describe("'loadFile(filePath, options)' method should load a config file.", () => {
- const basicFiles = {
- "js/.eslintrc.js": "exports.settings = { name: 'js/.eslintrc.js' }",
- "cjs/.eslintrc.cjs": "exports.settings = { name: 'cjs/.eslintrc.cjs' }",
- "json/.eslintrc.json": "{ \"settings\": { \"name\": \"json/.eslintrc.json\" } }",
- "legacy-json/.eslintrc": "{ \"settings\": { \"name\": \"legacy-json/.eslintrc\" } }",
- "legacy-yml/.eslintrc": "settings:\n name: legacy-yml/.eslintrc",
- "package-json/package.json": "{ \"eslintConfig\": { \"settings\": { \"name\": \"package-json/package.json\" } } }",
- "yml/.eslintrc.yml": "settings:\n name: yml/.eslintrc.yml",
- "yaml/.eslintrc.yaml": "settings:\n name: yaml/.eslintrc.yaml"
- };
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir,
- files: {
- ...basicFiles,
- "invalid-property.json": "{ \"files\": \"*.js\" }",
- "package-json-no-config/package.json": "{ \"name\": \"foo\" }"
- }
- });
-
- /** @type {ConfigArrayFactory} */
- let factory;
-
- beforeEach(() => {
- factory = new ConfigArrayFactory();
- });
-
- it("should throw an error if 'filePath' is null.", () => {
- assert.throws(() => factory.loadFile(null));
- });
-
- it("should throw an error if 'filePath' doesn't exist.", () => {
- assert.throws(() => {
- factory.loadFile("non-exist");
- }, /Cannot read config file:.*non-exist/su);
- });
-
- it("should throw an error if 'filePath' was 'package.json' and it doesn't have 'eslintConfig' field.", () => {
- assert.throws(() => {
- factory.loadFile("package-json-no-config/package.json");
- }, /Cannot read config file:.*package.json/su);
- });
-
- it("should throw an error if the config data had invalid properties,", () => {
- assert.throws(() => {
- factory.loadFile("invalid-property.json");
- }, /Unexpected top-level property "files"/u);
- });
-
- for (const filePath of Object.keys(basicFiles)) {
- it(`should load '${filePath}' then return a config array what contains that file content.`, () => { // eslint-disable-line no-loop-func
- const configArray = factory.loadFile(filePath);
-
- assert.strictEqual(configArray.length, 1);
- assertConfigArrayElement(configArray[0], {
- filePath: path.resolve(tempDir, filePath),
- name: path.relative(tempDir, path.resolve(tempDir, filePath)),
- settings: { name: filePath }
- });
- });
- }
-
- it("should call '_normalizeConfigData(configData, ctx)' with the loaded config data and given options.", () => {
- const basePath = tempDir;
- const filePath = "js/.eslintrc.js";
- const name = "example";
- const normalizeConfigData = spy(factory, "_normalizeConfigData");
-
- factory.loadFile(filePath, { basePath, name });
-
- assert.strictEqual(normalizeConfigData.callCount, 1);
- assert.deepStrictEqual(normalizeConfigData.args[0], [
- { settings: { name: filePath } },
- createContext({ cwd: tempDir }, void 0, name, filePath, basePath)
- ]);
- });
-
- it("should return a config array that contains the yielded elements from '_normalizeConfigData(configData, ctx)'.", () => {
- const elements = [{}, {}];
-
- factory._normalizeConfigData = () => elements; // eslint-disable-line no-underscore-dangle
-
- const configArray = factory.loadFile("js/.eslintrc.js");
-
- assert.strictEqual(configArray.length, 2);
- assert.strictEqual(configArray[0], elements[0]);
- assert.strictEqual(configArray[1], elements[1]);
- });
- });
-
- describe("'loadInDirectory(directoryPath, options)' method should load the config file of a directory.", () => {
- const basicFiles = {
- "js/.eslintrc.js": "exports.settings = { name: 'js/.eslintrc.js' }",
- "cjs/.eslintrc.cjs": "exports.settings = { name: 'cjs/.eslintrc.cjs' }",
- "json/.eslintrc.json": "{ \"settings\": { \"name\": \"json/.eslintrc.json\" } }",
- "legacy-json/.eslintrc": "{ \"settings\": { \"name\": \"legacy-json/.eslintrc\" } }",
- "legacy-yml/.eslintrc": "settings:\n name: legacy-yml/.eslintrc",
- "package-json/package.json": "{ \"eslintConfig\": { \"settings\": { \"name\": \"package-json/package.json\" } } }",
- "yml/.eslintrc.yml": "settings:\n name: yml/.eslintrc.yml",
- "yaml/.eslintrc.yaml": "settings:\n name: yaml/.eslintrc.yaml"
- };
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir,
- files: {
- ...basicFiles,
- "invalid-property/.eslintrc.json": "{ \"files\": \"*.js\" }",
- "package-json-no-config/package.json": "{ \"name\": \"foo\" }"
- }
- });
-
- /** @type {ConfigArrayFactory} */
- let factory;
-
- beforeEach(() => {
- factory = new ConfigArrayFactory();
- });
-
- it("should throw an error if 'directoryPath' is null.", () => {
- assert.throws(() => factory.loadInDirectory(null));
- });
-
- it("should return an empty config array if the config file of 'directoryPath' doesn't exist.", () => {
- assert.strictEqual(factory.loadInDirectory("non-exist").length, 0);
- });
-
- it("should return an empty config array if the config file of 'directoryPath' was package.json and it didn't have 'eslintConfig' field.", () => {
- assert.strictEqual(factory.loadInDirectory("package-json-no-config").length, 0);
- });
-
- it("should throw an error if the config data had invalid properties,", () => {
- assert.throws(() => {
- factory.loadInDirectory("invalid-property");
- }, /Unexpected top-level property "files"/u);
- });
-
- for (const filePath of Object.keys(basicFiles)) {
- const directoryPath = filePath.split("/")[0];
-
- it(`should load '${directoryPath}' then return a config array what contains the config file of that directory.`, () => { // eslint-disable-line no-loop-func
- const configArray = factory.loadInDirectory(directoryPath);
-
- assert.strictEqual(configArray.length, 1);
- assertConfigArrayElement(configArray[0], {
- filePath: path.resolve(tempDir, filePath),
- name: path.relative(tempDir, path.resolve(tempDir, filePath)),
- settings: { name: filePath }
- });
- });
- }
-
- it("should call '_normalizeConfigData(configData, ctx)' with the loaded config data and given options.", () => {
- const basePath = tempDir;
- const directoryPath = "js";
- const name = "example";
- const normalizeConfigData = spy(factory, "_normalizeConfigData");
-
- factory.loadInDirectory(directoryPath, { basePath, name });
-
- assert.strictEqual(normalizeConfigData.callCount, 1);
- assert.deepStrictEqual(normalizeConfigData.args[0], [
- { settings: { name: `${directoryPath}/.eslintrc.js` } },
- createContext({ cwd: tempDir }, void 0, name, path.join(directoryPath, ".eslintrc.js"), basePath)
- ]);
- });
-
- it("should return a config array that contains the yielded elements from '_normalizeConfigData(configData, ctx)'.", () => {
- const elements = [{}, {}];
-
- factory._normalizeConfigData = () => elements; // eslint-disable-line no-underscore-dangle
-
- const configArray = factory.loadInDirectory("js");
-
- assert.strictEqual(configArray.length, 2);
- assert.strictEqual(configArray[0], elements[0]);
- assert.strictEqual(configArray[1], elements[1]);
- });
- });
-
- /*
- * All of `create`, `loadFile`, and `loadInDirectory` call this method.
- * So this section tests the common part of the three.
- */
- describe("'_normalizeConfigData(configData, ctx)' method should normalize the config data.", () => {
-
- /** @type {ConfigArrayFactory} */
- let factory = null;
-
- /**
- * Call `_normalizeConfigData` method with given arguments.
- * @param {ConfigData} configData The config data to normalize.
- * @param {Object} [options] The options.
- * @param {string} [options.filePath] The path to the config file of the config data.
- * @param {string} [options.name] The name of the config file of the config data.
- * @returns {ConfigArray} The created config array.
- */
- function create(configData, { filePath, name } = {}) {
- const ctx = createContext({ cwd: tempDir }, void 0, name, filePath, void 0);
-
- return new ConfigArray(...factory._normalizeConfigData(configData, ctx)); // eslint-disable-line no-underscore-dangle
- }
-
- describe("misc", () => {
- before(() => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir
- });
-
- factory = new ConfigArrayFactory();
- });
-
- describe("if the config data was empty, the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create({});
- });
-
- it("should have an element.", () => {
- assert.strictEqual(configArray.length, 1);
- });
-
- it("should have the default values in the element.", () => {
- assertConfigArrayElement(configArray[0], {});
- });
- });
-
- describe("if the config data had 'env' property, the returned value", () => {
- const env = { node: true };
- let configArray;
-
- beforeEach(() => {
- configArray = create({ env });
- });
-
- it("should have an element.", () => {
- assert.strictEqual(configArray.length, 1);
- });
-
- it("should have the 'env' value in the element.", () => {
- assertConfigArrayElement(configArray[0], { env });
- });
- });
-
- describe("if the config data had 'globals' property, the returned value", () => {
- const globals = { window: "readonly" };
- let configArray;
-
- beforeEach(() => {
- configArray = create({ globals });
- });
-
- it("should have an element.", () => {
- assert.strictEqual(configArray.length, 1);
- });
-
- it("should have the 'globals' value in the element.", () => {
- assertConfigArrayElement(configArray[0], { globals });
- });
- });
-
- describe("if the config data had 'parser' property, the returned value", () => {
- const parser = "espree";
- let configArray;
-
- beforeEach(() => {
- configArray = create({ parser });
- });
-
- it("should have an element.", () => {
- assert.strictEqual(configArray.length, 1);
- });
-
- it("should have the 'parser' value in the element.", () => {
- assert.strictEqual(configArray[0].parser.id, parser);
- });
- });
-
- describe("if the config data had 'parserOptions' property, the returned value", () => {
- const parserOptions = { ecmaVersion: 2015 };
- let configArray;
-
- beforeEach(() => {
- configArray = create({ parserOptions });
- });
-
- it("should have an element.", () => {
- assert.strictEqual(configArray.length, 1);
- });
-
- it("should have the 'parserOptions' value in the element.", () => {
- assertConfigArrayElement(configArray[0], { parserOptions });
- });
- });
-
- describe("if the config data had 'plugins' property, the returned value", () => {
- const plugins = [];
- let configArray;
-
- beforeEach(() => {
- configArray = create({ plugins });
- });
-
- it("should have an element.", () => {
- assert.strictEqual(configArray.length, 1);
- });
-
- it("should have the 'plugins' value in the element.", () => {
- assertConfigArrayElement(configArray[0], { plugins: {} });
- });
- });
-
- describe("if the config data had 'root' property, the returned value", () => {
- const root = true;
- let configArray;
-
- beforeEach(() => {
- configArray = create({ root });
- });
-
- it("should have an element.", () => {
- assert.strictEqual(configArray.length, 1);
- });
-
- it("should have the 'root' value in the element.", () => {
- assertConfigArrayElement(configArray[0], { root });
- });
- });
-
- describe("if the config data had 'rules' property, the returned value", () => {
- const rules = { eqeqeq: "error" };
- let configArray;
-
- beforeEach(() => {
- configArray = create({ rules });
- });
-
- it("should have an element.", () => {
- assert.strictEqual(configArray.length, 1);
- });
-
- it("should have the 'rules' value in the element.", () => {
- assertConfigArrayElement(configArray[0], { rules });
- });
- });
-
- describe("if the config data had 'settings' property, the returned value", () => {
- const settings = { foo: 777 };
- let configArray;
-
- beforeEach(() => {
- configArray = create({ settings });
- });
-
- it("should have an element.", () => {
- assert.strictEqual(configArray.length, 1);
- });
-
- it("should have the 'settings' value in the element.", () => {
- assertConfigArrayElement(configArray[0], { settings });
- });
- });
- });
-
- describe("'parser' details", () => {
- before(() => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir,
- files: {
- "node_modules/xxx-parser/index.js": "exports.name = 'xxx-parser';",
- "subdir/node_modules/xxx-parser/index.js": "exports.name = 'subdir/xxx-parser';",
- "parser.js": "exports.name = './parser.js';"
- }
- });
-
- factory = new ConfigArrayFactory();
- });
-
- describe("if the 'parser' property was a valid package, the first config array element", () => {
- let element;
-
- beforeEach(() => {
- element = create({ parser: "xxx-parser" })[0];
- });
-
- it("should have the package ID at 'parser.id' property.", () => {
- assert.strictEqual(element.parser.id, "xxx-parser");
- });
-
- it("should have the package object at 'parser.definition' property.", () => {
- assert.deepStrictEqual(element.parser.definition, { name: "xxx-parser" });
- });
-
- it("should have the path to the package at 'parser.filePath' property.", () => {
- assert.strictEqual(element.parser.filePath, path.join(tempDir, "node_modules/xxx-parser/index.js"));
- });
- });
-
- describe("if the 'parser' property was an invalid package, the first config array element", () => {
- let element;
-
- beforeEach(() => {
- element = create({ parser: "invalid-parser" })[0];
- });
-
- it("should have the package ID at 'parser.id' property.", () => {
- assert.strictEqual(element.parser.id, "invalid-parser");
- });
-
- it("should have the loading error at 'parser.error' property.", () => {
- assert.match(element.parser.error.message, /Cannot find module 'invalid-parser'/u);
- });
- });
-
- describe("if the 'parser' property was a valid relative path, the first config array element", () => {
- let element;
-
- beforeEach(() => {
- element = create({ parser: "./parser" })[0];
- });
-
- it("should have the given path at 'parser.id' property.", () => {
- assert.strictEqual(element.parser.id, "./parser");
- });
-
- it("should have the file's object at 'parser.definition' property.", () => {
- assert.deepStrictEqual(element.parser.definition, { name: "./parser.js" });
- });
-
- it("should have the absolute path to the file at 'parser.filePath' property.", () => {
- assert.strictEqual(element.parser.filePath, path.join(tempDir, "./parser.js"));
- });
- });
-
- describe("if the 'parser' property was an invalid relative path, the first config array element", () => {
- let element;
-
- beforeEach(() => {
- element = create({ parser: "./invalid-parser" })[0];
- });
-
- it("should have the given path at 'parser.id' property.", () => {
- assert.strictEqual(element.parser.id, "./invalid-parser");
- });
-
- it("should have the loading error at 'parser.error' property.", () => {
- assert.match(element.parser.error.message, /Cannot find module '.\/invalid-parser'/u);
- });
- });
-
- describe("if 'parser' property was given and 'filePath' option was given, the parser", () => {
- let element;
-
- beforeEach(() => {
- element = create(
- { parser: "xxx-parser" },
- { filePath: path.join(tempDir, "subdir/.eslintrc") }
- )[0];
- });
-
- it("should be resolved relative to the 'filePath' option.", () => {
- assert.strictEqual(
- element.parser.filePath,
-
- // rather than "xxx-parser" at the project root.
- path.join(tempDir, "subdir/node_modules/xxx-parser/index.js")
- );
- });
- });
- });
-
- describe("'plugins' details", () => {
- before(() => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir,
- files: {
- "node_modules/eslint-plugin-ext/index.js": "exports.processors = { '.abc': {}, '.xyz': {}, other: {} };",
- "node_modules/eslint-plugin-subdir/index.js": "",
- "node_modules/eslint-plugin-xxx/index.js": "exports.configs = { name: 'eslint-plugin-xxx' };",
- "subdir/node_modules/eslint-plugin-subdir/index.js": "",
- "parser.js": ""
- }
- });
-
- factory = new ConfigArrayFactory();
- });
-
- it("should throw an error if a 'plugins' value is a file path.", () => {
- assert.throws(() => {
- create({ plugins: ["./path/to/plugin"] });
- }, /Plugins array cannot includes file paths/u);
- });
-
- describe("if the 'plugins' property was a valid package, the first config array element", () => {
- let element;
-
- beforeEach(() => {
- element = create({ plugins: ["xxx"] })[0];
- });
-
- it("should have 'plugins[id]' property.", () => {
- assert.notStrictEqual(element.plugins.xxx, void 0);
- });
-
- it("should have the package ID at 'plugins[id].id' property.", () => {
- assert.strictEqual(element.plugins.xxx.id, "xxx");
- });
-
- it("should have the package object at 'plugins[id].definition' property.", () => {
- assertPluginDefinition(
- element.plugins.xxx.definition,
- { configs: { name: "eslint-plugin-xxx" } }
- );
- });
-
- it("should have the path to the package at 'plugins[id].filePath' property.", () => {
- assert.strictEqual(element.plugins.xxx.filePath, path.join(tempDir, "node_modules/eslint-plugin-xxx/index.js"));
- });
- });
-
- describe("if the 'plugins' property was an invalid package, the first config array element", () => {
- let element;
-
- beforeEach(() => {
- element = create({ plugins: ["invalid"] })[0];
- });
-
- it("should have 'plugins[id]' property.", () => {
- assert.notStrictEqual(element.plugins.invalid, void 0);
- });
-
- it("should have the package ID at 'plugins[id].id' property.", () => {
- assert.strictEqual(element.plugins.invalid.id, "invalid");
- });
-
- it("should have the loading error at 'plugins[id].error' property.", () => {
- assert.match(element.plugins.invalid.error.message, /Cannot find module 'eslint-plugin-invalid'/u);
- });
- });
-
- describe("even if 'plugins' property was given and 'filePath' option was given,", () => {
- it("should load the plugin from 'subdir'.", () => {
- const configArray = create(
- { plugins: ["subdir"] },
- { filePath: path.resolve(tempDir, "subdir/a.js") }
- );
-
- assert.strictEqual(
- configArray[0].plugins.subdir.filePath,
- path.resolve(tempDir, "subdir/node_modules/eslint-plugin-subdir/index.js")
- );
- });
- });
-
- describe("if 'plugins' property was given and the plugin has two file extension processors, the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create({ plugins: ["ext"] });
- });
-
- it("should have three elements.", () => {
- assert.strictEqual(configArray.length, 3);
- });
-
- describe("the first element", () => {
- let element;
-
- beforeEach(() => {
- element = configArray[0];
- });
-
- it("should be named '#processors[\"ext/.abc\"]'.", () => {
- assert.strictEqual(element.name, "#processors[\"ext/.abc\"]");
- });
-
- it("should not have 'plugins' property.", () => {
- assert.strictEqual(element.plugins, void 0);
- });
-
- it("should have 'processor' property.", () => {
- assert.strictEqual(element.processor, "ext/.abc");
- });
-
- it("should have 'criteria' property which matches '.abc'.", () => {
- assert.strictEqual(element.criteria.test(path.join(tempDir, "1234.abc")), true);
- assert.strictEqual(element.criteria.test(path.join(tempDir, "1234.xyz")), false);
- });
- });
-
- describe("the second element", () => {
- let element;
-
- beforeEach(() => {
- element = configArray[1];
- });
-
- it("should be named '#processors[\"ext/.xyz\"]'.", () => {
- assert.strictEqual(element.name, "#processors[\"ext/.xyz\"]");
- });
-
- it("should not have 'plugins' property.", () => {
- assert.strictEqual(element.plugins, void 0);
- });
-
- it("should have 'processor' property.", () => {
- assert.strictEqual(element.processor, "ext/.xyz");
- });
-
- it("should have 'criteria' property which matches '.xyz'.", () => {
- assert.strictEqual(element.criteria.test(path.join(tempDir, "1234.abc")), false);
- assert.strictEqual(element.criteria.test(path.join(tempDir, "1234.xyz")), true);
- });
- });
-
- describe("the third element", () => {
- let element;
-
- beforeEach(() => {
- element = configArray[2];
- });
-
- it("should have 'plugins' property.", () => {
- assert.strictEqual(element.plugins.ext.id, "ext");
- });
-
- it("should not have 'processor' property.", () => {
- assert.strictEqual(element.processor, void 0);
- });
- });
- });
- });
-
- describe("'extends' details", () => {
- before(() => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir,
- files: {
- "node_modules/eslint-config-foo/index.js": "exports.env = { browser: true }",
- "node_modules/eslint-config-one/index.js": "module.exports = { extends: 'two', env: { browser: true } }",
- "node_modules/eslint-config-two/index.js": "module.exports = { env: { node: true } }",
- "node_modules/eslint-config-override/index.js": `
- module.exports = {
- rules: { regular: 1 },
- overrides: [
- { files: '*.xxx', rules: { override: 1 } },
- { files: '*.yyy', rules: { override: 2 } }
- ]
- }
- `,
- "node_modules/eslint-plugin-foo/index.js": "exports.configs = { bar: { env: { es6: true } } }",
- "node_modules/eslint-plugin-invalid-config/index.js": "exports.configs = { foo: {} }",
- "node_modules/eslint-plugin-error/index.js": "throw new Error('xxx error')",
- "base.js": "module.exports = { rules: { semi: [2, 'always'] } };"
- }
- });
-
- factory = new ConfigArrayFactory();
- });
-
- it("should throw an error when extends config module is not found", () => {
- assert.throws(() => {
- create({
- extends: "not-exist",
- rules: { eqeqeq: 2 }
- });
- }, /Failed to load config "not-exist" to extend from./u);
- });
-
- it("should throw an error when an eslint config is not found", () => {
- assert.throws(() => {
- create({
- extends: "eslint:foo",
- rules: { eqeqeq: 2 }
- });
- }, /Failed to load config "eslint:foo" to extend from./u);
- });
-
- it("should throw an error when a plugin threw while loading.", () => {
- assert.throws(() => {
- create({
- extends: "plugin:error/foo",
- rules: { eqeqeq: 2 }
- });
- }, /xxx error/u);
- });
-
- it("should throw an error when a plugin extend is a file path.", () => {
- assert.throws(() => {
- create({
- extends: "plugin:./path/to/foo",
- rules: { eqeqeq: 2 }
- });
- }, /'extends' cannot use a file path for plugins/u);
- });
-
- it("should throw an error when an eslint config is not found", () => {
- assert.throws(() => {
- create({
- extends: "eslint:foo",
- rules: { eqeqeq: 2 }
- });
- }, /Failed to load config "eslint:foo" to extend from./u);
- });
-
- describe("if 'extends' property was 'eslint:all', the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create(
- { extends: "eslint:all", rules: { eqeqeq: 1 } },
- { name: ".eslintrc" }
- );
- });
-
- it("should have two elements.", () => {
- assert.strictEqual(configArray.length, 2);
- });
-
- it("should have the config data of 'eslint:all' at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- name: ".eslintrc » eslint:all",
- filePath: require.resolve("../../../conf/eslint-all.js"),
- ...require("../../../conf/eslint-all.js")
- });
- });
-
- it("should have the given config data at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: ".eslintrc",
- rules: { eqeqeq: 1 }
- });
- });
- });
-
- describe("if 'extends' property was 'eslint:recommended', the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create(
- { extends: "eslint:recommended", rules: { eqeqeq: 1 } },
- { name: ".eslintrc" }
- );
- });
-
- it("should have two elements.", () => {
- assert.strictEqual(configArray.length, 2);
- });
-
- it("should have the config data of 'eslint:recommended' at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- name: ".eslintrc » eslint:recommended",
- filePath: require.resolve("../../../conf/eslint-recommended.js"),
- ...require("../../../conf/eslint-recommended.js")
- });
- });
-
- it("should have the given config data at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: ".eslintrc",
- rules: { eqeqeq: 1 }
- });
- });
- });
-
- describe("if 'extends' property was 'foo', the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create(
- { extends: "foo", rules: { eqeqeq: 1 } },
- { name: ".eslintrc" }
- );
- });
-
- it("should have two elements.", () => {
- assert.strictEqual(configArray.length, 2);
- });
-
- it("should have the config data of 'eslint-config-foo' at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- name: ".eslintrc » eslint-config-foo",
- filePath: path.join(tempDir, "node_modules/eslint-config-foo/index.js"),
- env: { browser: true }
- });
- });
-
- it("should have the given config data at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: ".eslintrc",
- rules: { eqeqeq: 1 }
- });
- });
- });
-
- describe("if 'extends' property was 'plugin:foo/bar', the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create(
- { extends: "plugin:foo/bar", rules: { eqeqeq: 1 } },
- { name: ".eslintrc" }
- );
- });
-
- it("should have two elements.", () => {
- assert.strictEqual(configArray.length, 2);
- });
-
- it("should have the config data of 'plugin:foo/bar' at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- name: ".eslintrc » plugin:foo/bar",
- filePath: path.join(tempDir, "node_modules/eslint-plugin-foo/index.js"),
- env: { es6: true }
- });
- });
-
- it("should have the given config data at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: ".eslintrc",
- rules: { eqeqeq: 1 }
- });
- });
- });
-
- describe("if 'extends' property was './base', the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create(
- { extends: "./base", rules: { eqeqeq: 1 } },
- { name: ".eslintrc" }
- );
- });
-
- it("should have two elements.", () => {
- assert.strictEqual(configArray.length, 2);
- });
-
- it("should have the config data of './base' at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- name: ".eslintrc » ./base",
- filePath: path.join(tempDir, "base.js"),
- rules: { semi: [2, "always"] }
- });
- });
-
- it("should have the given config data at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: ".eslintrc",
- rules: { eqeqeq: 1 }
- });
- });
- });
-
- describe("if 'extends' property was 'one' and the 'one' extends 'two', the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create(
- { extends: "one", rules: { eqeqeq: 1 } },
- { name: ".eslintrc" }
- );
- });
-
- it("should have three elements.", () => {
- assert.strictEqual(configArray.length, 3);
- });
-
- it("should have the config data of 'eslint-config-two' at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- name: ".eslintrc » eslint-config-one » eslint-config-two",
- filePath: path.join(tempDir, "node_modules/eslint-config-two/index.js"),
- env: { node: true }
- });
- });
-
- it("should have the config data of 'eslint-config-one' at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: ".eslintrc » eslint-config-one",
- filePath: path.join(tempDir, "node_modules/eslint-config-one/index.js"),
- env: { browser: true }
- });
- });
-
- it("should have the given config data at the third element.", () => {
- assertConfigArrayElement(configArray[2], {
- name: ".eslintrc",
- rules: { eqeqeq: 1 }
- });
- });
- });
-
- describe("if 'extends' property was 'override' and the 'override' has 'overrides' property, the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create(
- { extends: "override", rules: { eqeqeq: 1 } },
- { name: ".eslintrc" }
- );
- });
-
- it("should have four elements.", () => {
- assert.strictEqual(configArray.length, 4);
- });
-
- it("should have the config data of 'eslint-config-override' at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- name: ".eslintrc » eslint-config-override",
- filePath: path.join(tempDir, "node_modules/eslint-config-override/index.js"),
- rules: { regular: 1 }
- });
- });
-
- it("should have the 'overrides[0]' config data of 'eslint-config-override' at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: ".eslintrc » eslint-config-override#overrides[0]",
- filePath: path.join(tempDir, "node_modules/eslint-config-override/index.js"),
- criteria: OverrideTester.create(["*.xxx"], [], tempDir),
- rules: { override: 1 }
- });
- });
-
- it("should have the 'overrides[1]' config data of 'eslint-config-override' at the third element.", () => {
- assertConfigArrayElement(configArray[2], {
- name: ".eslintrc » eslint-config-override#overrides[1]",
- filePath: path.join(tempDir, "node_modules/eslint-config-override/index.js"),
- criteria: OverrideTester.create(["*.yyy"], [], tempDir),
- rules: { override: 2 }
- });
- });
-
- it("should have the given config data at the fourth element.", () => {
- assertConfigArrayElement(configArray[3], {
- name: ".eslintrc",
- rules: { eqeqeq: 1 }
- });
- });
- });
- });
-
- describe("'overrides' details", () => {
- before(() => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir,
- files: {
- "node_modules/eslint-config-foo/index.js": `
- module.exports = {
- rules: { eqeqeq: "error" }
- }
- `,
- "node_modules/eslint-config-has-overrides/index.js": `
- module.exports = {
- rules: { eqeqeq: "error" },
- overrides: [
- {
- files: ["**/foo/**/*.js"],
- rules: { eqeqeq: "off" }
- }
- ]
- }
- `,
- "node_modules/eslint-config-root/index.js": `
- module.exports = {
- root: true,
- rules: { eqeqeq: "error" }
- }
- `
- }
- });
-
- factory = new ConfigArrayFactory();
- });
-
- describe("if 'overrides' property was given, the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create({
- rules: { regular: 1 },
- overrides: [
- { files: "*.xxx", rules: { override: 1 } },
- { files: "*.yyy", rules: { override: 2 } }
- ]
- });
- });
-
- it("should have three elements.", () => {
- assert.strictEqual(configArray.length, 3);
- });
-
- it("should have the given config data at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- rules: { regular: 1 }
- });
- });
-
- it("should have the config data of 'overrides[0]' at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: "#overrides[0]",
- criteria: OverrideTester.create(["*.xxx"], [], tempDir),
- rules: { override: 1 }
- });
- });
-
- it("should have the config data of 'overrides[1]' at the third element.", () => {
- assertConfigArrayElement(configArray[2], {
- name: "#overrides[1]",
- criteria: OverrideTester.create(["*.yyy"], [], tempDir),
- rules: { override: 2 }
- });
- });
- });
-
- describe("if a config in 'overrides' property had 'extends' property, the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create({
- rules: { regular: 1 },
- overrides: [
- {
- files: "*.xxx",
- extends: "foo",
- rules: { override: 1 }
- }
- ]
- });
- });
-
- it("should have three elements.", () => {
- assert.strictEqual(configArray.length, 3);
- });
-
- it("should have the given config data at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- rules: { regular: 1 }
- });
- });
-
- it("should have the config data of 'overrides[0] » eslint-config-foo' at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: "#overrides[0] » eslint-config-foo",
- filePath: path.join(tempDir, "node_modules/eslint-config-foo/index.js"),
- criteria: OverrideTester.create(["*.xxx"], [], tempDir),
- rules: { eqeqeq: "error" }
- });
- });
-
- it("should have the config data of 'overrides[0]' at the third element.", () => {
- assertConfigArrayElement(configArray[2], {
- name: "#overrides[0]",
- criteria: OverrideTester.create(["*.xxx"], [], tempDir),
- rules: { override: 1 }
- });
- });
- });
-
- describe("if a config in 'overrides' property had 'extends' property and the shareable config has 'overrides' property, the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create({
- rules: { regular: 1 },
- overrides: [
- {
- files: "*.xxx",
- extends: "has-overrides",
- rules: { override: 1 }
- }
- ]
- });
- });
-
- it("should have four elements.", () => {
- assert.strictEqual(configArray.length, 4);
- });
-
- it("should have the given config data at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- rules: { regular: 1 }
- });
- });
-
- it("should have the config data of 'overrides[0] » eslint-config-has-overrides' at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: "#overrides[0] » eslint-config-has-overrides",
- filePath: path.join(tempDir, "node_modules/eslint-config-has-overrides/index.js"),
- criteria: OverrideTester.create(["*.xxx"], [], tempDir),
- rules: { eqeqeq: "error" }
- });
- });
-
- it("should have the config data of 'overrides[0] » eslint-config-has-overrides#overrides[0]' at the third element.", () => {
- assertConfigArrayElement(configArray[2], {
- name: "#overrides[0] » eslint-config-has-overrides#overrides[0]",
- filePath: path.join(tempDir, "node_modules/eslint-config-has-overrides/index.js"),
- criteria: OverrideTester.and(
- OverrideTester.create(["*.xxx"], [], tempDir),
- OverrideTester.create(["**/foo/**/*.js"], [], tempDir)
- ),
- rules: { eqeqeq: "off" }
- });
- });
-
- it("should have the config data of 'overrides[0]' at the fourth element.", () => {
- assertConfigArrayElement(configArray[3], {
- name: "#overrides[0]",
- criteria: OverrideTester.create(["*.xxx"], [], tempDir),
- rules: { override: 1 }
- });
- });
- });
-
- describe("if a config in 'overrides' property had 'overrides' property, the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create({
- rules: { regular: 1 },
- overrides: [
- {
- files: "*.xxx",
- rules: { override: 1 },
- overrides: [
- {
- files: "*.yyy",
- rules: { override: 2 }
- }
- ]
- }
- ]
- });
- });
-
- it("should have three elements.", () => {
- assert.strictEqual(configArray.length, 3);
- });
-
- it("should have the given config data at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- rules: { regular: 1 }
- });
- });
-
- it("should have the config data of 'overrides[0]' at the second element.", () => {
- assertConfigArrayElement(configArray[1], {
- name: "#overrides[0]",
- criteria: OverrideTester.create(["*.xxx"], [], tempDir),
- rules: { override: 1 }
- });
- });
-
- it("should have the config data of 'overrides[0].overrides[0]' at the third element.", () => {
- assertConfigArrayElement(configArray[2], {
- name: "#overrides[0]#overrides[0]",
- criteria: OverrideTester.and(
- OverrideTester.create(["*.xxx"], [], tempDir),
- OverrideTester.create(["*.yyy"], [], tempDir)
- ),
- rules: { override: 2 }
- });
- });
- });
-
- describe("if a config in 'overrides' property had 'root' property, the returned value", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = create({
- rules: { regular: 1 },
- overrides: [
- {
- files: "*.xxx",
- extends: "root",
- rules: { override: 1 }
- }
- ]
- });
- });
-
- it("should have three elements.", () => {
- assert.strictEqual(configArray.length, 3);
- });
-
- it("should have the given config data at the first element.", () => {
- assertConfigArrayElement(configArray[0], {
- rules: { regular: 1 }
- });
- });
-
- it("should have the config data of 'overrides[0] » eslint-config-root' at the second element; it doesn't have 'root' property.", () => {
- assertConfigArrayElement(configArray[1], {
- name: "#overrides[0] » eslint-config-root",
- filePath: path.join(tempDir, "node_modules/eslint-config-root/index.js"),
- criteria: OverrideTester.create(["*.xxx"], [], tempDir),
- rules: { eqeqeq: "error" }
- });
- });
-
- it("should have the config data of 'overrides[0]' at the third element.", () => {
- assertConfigArrayElement(configArray[2], {
- name: "#overrides[0]",
- criteria: OverrideTester.create(["*.xxx"], [], tempDir),
- rules: { override: 1 }
- });
- });
- });
- });
-
- describe("additional plugin pool", () => {
- beforeEach(() => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir
- });
-
- factory = new ConfigArrayFactory({
- additionalPluginPool: new Map([
- ["abc", { configs: { name: "abc" } }],
- ["eslint-plugin-def", { configs: { name: "def" } }]
- ])
- });
- });
-
- it("should use the matched plugin in the additional plugin pool; short to short", () => {
- const configArray = create({ plugins: ["abc"] });
-
- assert.strictEqual(configArray[0].plugins.abc.id, "abc");
- assertPluginDefinition(
- configArray[0].plugins.abc.definition,
- { configs: { name: "abc" } }
- );
- });
-
- it("should use the matched plugin in the additional plugin pool; long to short", () => {
- const configArray = create({ plugins: ["eslint-plugin-abc"] });
-
- assert.strictEqual(configArray[0].plugins.abc.id, "abc");
- assertPluginDefinition(
- configArray[0].plugins.abc.definition,
- { configs: { name: "abc" } }
- );
- });
-
- it("should use the matched plugin in the additional plugin pool; short to long", () => {
- const configArray = create({ plugins: ["def"] });
-
- assert.strictEqual(configArray[0].plugins.def.id, "def");
- assertPluginDefinition(
- configArray[0].plugins.def.definition,
- { configs: { name: "def" } }
- );
- });
-
- it("should use the matched plugin in the additional plugin pool; long to long", () => {
- const configArray = create({ plugins: ["eslint-plugin-def"] });
-
- assert.strictEqual(configArray[0].plugins.def.id, "def");
- assertPluginDefinition(
- configArray[0].plugins.def.definition,
- { configs: { name: "def" } }
- );
- });
- });
- });
-
- // This group moved from 'tests/lib/config/config-file.js' when refactoring to keep the cumulated test cases.
- describe("'extends' property should handle the content of extended configs properly.", () => {
- const files = {
- "node_modules/eslint-config-foo/index.js": "exports.env = { browser: true }",
- "node_modules/eslint-config-one/index.js": "module.exports = { extends: 'two', env: { browser: true } }",
- "node_modules/eslint-config-two/index.js": "module.exports = { env: { node: true } }",
- "node_modules/eslint-plugin-invalid-parser/index.js": "exports.configs = { foo: { parser: 'nonexistent-parser' } }",
- "node_modules/eslint-plugin-invalid-config/index.js": "exports.configs = { foo: {} }",
- "js/.eslintrc.js": "module.exports = { rules: { semi: [2, 'always'] } };",
- "cjs/.eslintrc.cjs": "module.exports = { rules: { semi: [2, 'always'] } };",
- "json/.eslintrc.json": "{ \"rules\": { \"quotes\": [2, \"double\"] } }",
- "package-json/package.json": "{ \"eslintConfig\": { \"env\": { \"es6\": true } } }",
- "yaml/.eslintrc.yaml": "env:\n browser: true"
- };
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({ files });
- const factory = new ConfigArrayFactory();
-
- /**
- * Apply `extends` property.
- * @param {Object} configData The config that has `extends` property.
- * @param {string} [filePath] The path to the config data.
- * @returns {Object} The applied config data.
- */
- function applyExtends(configData, filePath = "whatever") {
- return factory
- .create(configData, { filePath })
- .extractConfig(filePath)
- .toCompatibleObjectAsConfigFileContent();
- }
-
- it("should apply extension 'foo' when specified from root directory config", () => {
- const config = applyExtends({
- extends: "foo",
- rules: { eqeqeq: 2 }
- });
-
- assertConfig(config, {
- env: { browser: true },
- rules: { eqeqeq: [2] }
- });
- });
-
- it("should apply all rules when extends config includes 'eslint:all'", () => {
- const config = applyExtends({
- extends: "eslint:all"
- });
-
- assert.strictEqual(config.rules.eqeqeq[0], "error");
- assert.strictEqual(config.rules.curly[0], "error");
- });
-
- it("should throw an error when extends config module is not found", () => {
- assert.throws(() => {
- applyExtends({
- extends: "not-exist",
- rules: { eqeqeq: 2 }
- });
- }, /Failed to load config "not-exist" to extend from./u);
- });
-
- it("should throw an error when an eslint config is not found", () => {
- assert.throws(() => {
- applyExtends({
- extends: "eslint:foo",
- rules: { eqeqeq: 2 }
- });
- }, /Failed to load config "eslint:foo" to extend from./u);
- });
-
- it("should throw an error when a parser in a plugin config is not found", () => {
- assert.throws(() => {
- applyExtends({
- extends: "plugin:invalid-parser/foo",
- rules: { eqeqeq: 2 }
- });
- }, /Failed to load parser 'nonexistent-parser' declared in 'whatever » plugin:invalid-parser\/foo'/u);
- });
-
- it("should fall back to default parser when a parser called 'espree' is not found", () => {
- const config = applyExtends({ parser: "espree" });
-
- assertConfig(config, {
- parser: require.resolve("espree")
- });
- });
-
- it("should throw an error when a plugin config is not found", () => {
- assert.throws(() => {
- applyExtends({
- extends: "plugin:invalid-config/bar",
- rules: { eqeqeq: 2 }
- });
- }, /Failed to load config "plugin:invalid-config\/bar" to extend from./u);
- });
-
- it("should throw an error with a message template when a plugin referenced for a plugin config is not found", () => {
- try {
- applyExtends({
- extends: "plugin:nonexistent-plugin/baz",
- rules: { eqeqeq: 2 }
- });
- } catch (err) {
- assert.strictEqual(err.messageTemplate, "plugin-missing");
- assert.deepStrictEqual(err.messageData, {
- pluginName: "eslint-plugin-nonexistent-plugin",
- resolvePluginsRelativeTo: process.cwd(),
- importerName: "whatever"
- });
- return;
- }
- assert.fail("Expected to throw an error");
- });
-
- it("should throw an error with a message template when a plugin in the plugins list is not found", () => {
- try {
- applyExtends({
- plugins: ["nonexistent-plugin"]
- });
- } catch (err) {
- assert.strictEqual(err.messageTemplate, "plugin-missing");
- assert.deepStrictEqual(err.messageData, {
- pluginName: "eslint-plugin-nonexistent-plugin",
- resolvePluginsRelativeTo: process.cwd(),
- importerName: "whatever"
- });
- return;
- }
- assert.fail("Expected to throw an error");
- });
-
- it("should apply extensions recursively when specified from package", () => {
- const config = applyExtends({
- extends: "one",
- rules: { eqeqeq: 2 }
- });
-
- assertConfig(config, {
- env: { browser: true, node: true },
- rules: { eqeqeq: [2] }
- });
- });
-
- it("should apply extensions when specified from a JavaScript file", () => {
- const config = applyExtends({
- extends: ".eslintrc.js",
- rules: { eqeqeq: 2 }
- }, "js/foo.js");
-
- assertConfig(config, {
- rules: {
- semi: [2, "always"],
- eqeqeq: [2]
- }
- });
- });
-
- it("should apply extensions when specified from a YAML file", () => {
- const config = applyExtends({
- extends: ".eslintrc.yaml",
- rules: { eqeqeq: 2 }
- }, "yaml/foo.js");
-
- assertConfig(config, {
- env: { browser: true },
- rules: {
- eqeqeq: [2]
- }
- });
- });
-
- it("should apply extensions when specified from a JSON file", () => {
- const config = applyExtends({
- extends: ".eslintrc.json",
- rules: { eqeqeq: 2 }
- }, "json/foo.js");
-
- assertConfig(config, {
- rules: {
- eqeqeq: [2],
- quotes: [2, "double"]
- }
- });
- });
-
- it("should apply extensions when specified from a package.json file in a sibling directory", () => {
- const config = applyExtends({
- extends: "../package-json/package.json",
- rules: { eqeqeq: 2 }
- }, "json/foo.js");
-
- assertConfig(config, {
- env: { es6: true },
- rules: {
- eqeqeq: [2]
- }
- });
- });
- });
-
- // This group moved from 'tests/lib/config/config-file.js' when refactoring to keep the cumulated test cases.
- describe("loading config files should work properly.", () => {
-
- /**
- * Load a given config file.
- * @param {ConfigArrayFactory} factory The factory to load.
- * @param {string} filePath The path to a config file.
- * @returns {Object} The applied config data.
- */
- function load(factory, filePath) {
- return factory
- .loadFile(filePath)
- .extractConfig(filePath)
- .toCompatibleObjectAsConfigFileContent();
- }
-
- it("should throw error if file doesn't exist", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem();
- const factory = new ConfigArrayFactory();
-
- assert.throws(() => {
- load(factory, "legacy/nofile.js");
- });
-
- assert.throws(() => {
- load(factory, "legacy/package.json");
- });
- });
-
- it("should load information from a legacy file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "legacy/.eslintrc": "{ rules: { eqeqeq: 2 } }"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "legacy/.eslintrc");
-
- assertConfig(config, {
- rules: {
- eqeqeq: [2]
- }
- });
- });
-
- it("should load information from a JavaScript file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "js/.eslintrc.js": "module.exports = { rules: { semi: [2, 'always'] } };"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "js/.eslintrc.js");
-
- assertConfig(config, {
- rules: {
- semi: [2, "always"]
- }
- });
- });
-
- it("should load information from a JavaScript file with a .cjs extension", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "cjs/.eslintrc.cjs": "module.exports = { rules: { semi: [2, 'always'] } };"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "cjs/.eslintrc.cjs");
-
- assertConfig(config, {
- rules: {
- semi: [2, "always"]
- }
- });
- });
-
- it("should throw error when loading invalid JavaScript file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "js/.eslintrc.broken.js": "module.exports = { rules: { semi: [2, 'always'] }"
- }
- });
- const factory = new ConfigArrayFactory();
-
- assert.throws(() => {
- load(factory, "js/.eslintrc.broken.js");
- }, /Cannot read config file/u);
- });
-
- it("should interpret parser module name when present in a JavaScript file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "node_modules/foo/index.js": "",
- "js/node_modules/foo/index.js": "",
- "js/.eslintrc.parser.js": `module.exports = {
- parser: 'foo',
- rules: { semi: [2, 'always'] }
- };`
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "js/.eslintrc.parser.js");
-
- assertConfig(config, {
- parser: path.resolve("js/node_modules/foo/index.js"),
- rules: {
- semi: [2, "always"]
- }
- });
- });
-
- it("should interpret parser path when present in a JavaScript file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "js/.eslintrc.parser2.js": `module.exports = {
- parser: './not-a-config.js',
- rules: { semi: [2, 'always'] }
- };`,
- "js/not-a-config.js": ""
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "js/.eslintrc.parser2.js");
-
- assertConfig(config, {
- parser: path.resolve("js/not-a-config.js"),
- rules: {
- semi: [2, "always"]
- }
- });
- });
-
- it("should interpret parser module name or path when parser is set to default parser in a JavaScript file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "js/.eslintrc.parser3.js": `module.exports = {
- parser: 'espree',
- rules: { semi: [2, 'always'] }
- };`
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "js/.eslintrc.parser3.js");
-
- assertConfig(config, {
- parser: require.resolve("espree"),
- rules: {
- semi: [2, "always"]
- }
- });
- });
-
- it("should load information from a JSON file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "json/.eslintrc.json": "{ \"rules\": { \"quotes\": [2, \"double\"] } }"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "json/.eslintrc.json");
-
- assertConfig(config, {
- rules: {
- quotes: [2, "double"]
- }
- });
- });
-
- it("should load fresh information from a JSON file", () => {
- const { fs, ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem();
- const factory = new ConfigArrayFactory();
- const initialConfig = {
- rules: {
- quotes: [2, "double"]
- }
- };
- const updatedConfig = {
- rules: {
- quotes: [0]
- }
- };
- let config;
-
- fs.writeFileSync("fresh-test.json", JSON.stringify(initialConfig));
- config = load(factory, "fresh-test.json");
- assertConfig(config, initialConfig);
-
- fs.writeFileSync("fresh-test.json", JSON.stringify(updatedConfig));
- config = load(factory, "fresh-test.json");
- assertConfig(config, updatedConfig);
- });
-
- it("should load information from a package.json file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "package-json/package.json": "{ \"eslintConfig\": { \"env\": { \"es6\": true } } }"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "package-json/package.json");
-
- assertConfig(config, {
- env: { es6: true }
- });
- });
-
- it("should throw error when loading invalid package.json file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "broken-package-json/package.json": "{ \"eslintConfig\": { \"env\": { \"es6\": true } }"
- }
- });
- const factory = new ConfigArrayFactory();
-
- assert.throws(() => {
- try {
- load(factory, "broken-package-json/package.json");
- } catch (error) {
- assert.strictEqual(error.messageTemplate, "failed-to-read-json");
- throw error;
- }
- }, /Cannot read config file/u);
- });
-
- it("should load fresh information from a package.json file", () => {
- const { fs, ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem();
- const factory = new ConfigArrayFactory();
- const initialConfig = {
- eslintConfig: {
- rules: {
- quotes: [2, "double"]
- }
- }
- };
- const updatedConfig = {
- eslintConfig: {
- rules: {
- quotes: [0]
- }
- }
- };
- let config;
-
- fs.writeFileSync("package.json", JSON.stringify(initialConfig));
- config = load(factory, "package.json");
- assertConfig(config, initialConfig.eslintConfig);
-
- fs.writeFileSync("package.json", JSON.stringify(updatedConfig));
- config = load(factory, "package.json");
- assertConfig(config, updatedConfig.eslintConfig);
- });
-
- it("should load fresh information from a .eslintrc.js file", () => {
- const { fs, ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem();
- const factory = new ConfigArrayFactory();
- const initialConfig = {
- rules: {
- quotes: [2, "double"]
- }
- };
- const updatedConfig = {
- rules: {
- quotes: [0]
- }
- };
- let config;
-
- fs.writeFileSync(".eslintrc.js", `module.exports = ${JSON.stringify(initialConfig)}`);
- config = load(factory, ".eslintrc.js");
- assertConfig(config, initialConfig);
-
- fs.writeFileSync(".eslintrc.js", `module.exports = ${JSON.stringify(updatedConfig)}`);
- config = load(factory, ".eslintrc.js");
- assertConfig(config, updatedConfig);
- });
-
- it("should load information from a YAML file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "yaml/.eslintrc.yaml": "env:\n browser: true"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "yaml/.eslintrc.yaml");
-
- assertConfig(config, {
- env: { browser: true }
- });
- });
-
- it("should load information from an empty YAML file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "yaml/.eslintrc.empty.yaml": "{}"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "yaml/.eslintrc.empty.yaml");
-
- assertConfig(config, {});
- });
-
- it("should load information from a YML file", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "yml/.eslintrc.yml": "env:\n node: true"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "yml/.eslintrc.yml");
-
- assertConfig(config, {
- env: { node: true }
- });
- });
-
- it("should load information from a YML file and apply extensions", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "extends/.eslintrc.yml": "extends: ../package-json/package.json\nrules:\n booya: 2",
- "package-json/package.json": "{ \"eslintConfig\": { \"env\": { \"es6\": true } } }"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "extends/.eslintrc.yml");
-
- assertConfig(config, {
- env: { es6: true },
- rules: { booya: [2] }
- });
- });
-
- it("should load information from `extends` chain.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "extends-chain": {
- "node_modules/eslint-config-a": {
- "node_modules/eslint-config-b": {
- "node_modules/eslint-config-c": {
- "index.js": "module.exports = { rules: { c: 2 } };"
- },
- "index.js": "module.exports = { extends: 'c', rules: { b: 2 } };"
- },
- "index.js": "module.exports = { extends: 'b', rules: { a: 2 } };"
- },
- ".eslintrc.json": "{ \"extends\": \"a\" }"
- }
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "extends-chain/.eslintrc.json");
-
- assertConfig(config, {
- rules: {
- a: [2], // from node_modules/eslint-config-a
- b: [2], // from node_modules/eslint-config-a/node_modules/eslint-config-b
- c: [2] // from node_modules/eslint-config-a/node_modules/eslint-config-b/node_modules/eslint-config-c
- }
- });
- });
-
- it("should load information from `extends` chain with relative path.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "extends-chain-2": {
- "node_modules/eslint-config-a/index.js": "module.exports = { extends: './relative.js', rules: { a: 2 } };",
- "node_modules/eslint-config-a/relative.js": "module.exports = { rules: { relative: 2 } };",
- ".eslintrc.json": "{ \"extends\": \"a\" }"
- }
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "extends-chain-2/.eslintrc.json");
-
- assertConfig(config, {
- rules: {
- a: [2], // from node_modules/eslint-config-a/index.js
- relative: [2] // from node_modules/eslint-config-a/relative.js
- }
- });
- });
-
- it("should load information from `extends` chain in .eslintrc with relative path.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "extends-chain-2": {
- "node_modules/eslint-config-a/index.js": "module.exports = { extends: './relative.js', rules: { a: 2 } };",
- "node_modules/eslint-config-a/relative.js": "module.exports = { rules: { relative: 2 } };",
- "relative.eslintrc.json": "{ \"extends\": \"./node_modules/eslint-config-a/index.js\" }"
- }
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "extends-chain-2/relative.eslintrc.json");
-
- assertConfig(config, {
- rules: {
- a: [2], // from node_modules/eslint-config-a/index.js
- relative: [2] // from node_modules/eslint-config-a/relative.js
- }
- });
- });
-
- it("should load information from `parser` in .eslintrc with relative path.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "extends-chain-2": {
- "parser.eslintrc.json": "{ \"parser\": \"./parser.js\" }",
- "parser.js": ""
- }
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "extends-chain-2/parser.eslintrc.json");
-
- assertConfig(config, {
- parser: path.resolve("extends-chain-2/parser.js")
- });
- });
-
- describe("Plugins", () => {
- it("should load information from a YML file and load plugins", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "node_modules/eslint-plugin-test/index.js": `
- module.exports = {
- environments: {
- bar: { globals: { bar: true } }
- }
- }
- `,
- "plugins/.eslintrc.yml": `
- plugins:
- - test
- rules:
- test/foo: 2
- env:
- test/bar: true
- `
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "plugins/.eslintrc.yml");
-
- assertConfig(config, {
- env: { "test/bar": true },
- plugins: ["test"],
- rules: {
- "test/foo": [2]
- }
- });
- });
-
- it("should load two separate configs from a plugin", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "node_modules/eslint-plugin-test/index.js": `
- module.exports = {
- configs: {
- foo: { rules: { semi: 2, quotes: 1 } },
- bar: { rules: { quotes: 2, yoda: 2 } }
- }
- }
- `,
- "plugins/.eslintrc.yml": `
- extends:
- - plugin:test/foo
- - plugin:test/bar
- `
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "plugins/.eslintrc.yml");
-
- assertConfig(config, {
- rules: {
- semi: [2],
- quotes: [2],
- yoda: [2]
- }
- });
- });
- });
-
- describe("even if config files have Unicode BOM,", () => {
- it("should read the JSON config file correctly.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "bom/.eslintrc.json": "\uFEFF{ \"rules\": { \"semi\": \"error\" } }"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "bom/.eslintrc.json");
-
- assertConfig(config, {
- rules: {
- semi: ["error"]
- }
- });
- });
-
- it("should read the YAML config file correctly.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "bom/.eslintrc.yaml": "\uFEFFrules:\n semi: error"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "bom/.eslintrc.yaml");
-
- assertConfig(config, {
- rules: {
- semi: ["error"]
- }
- });
- });
-
- it("should read the config in package.json correctly.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "bom/package.json": "\uFEFF{ \"eslintConfig\": { \"rules\": { \"semi\": \"error\" } } }"
- }
- });
- const factory = new ConfigArrayFactory();
- const config = load(factory, "bom/package.json");
-
- assertConfig(config, {
- rules: {
- semi: ["error"]
- }
- });
- });
- });
-
- it("throws an error including the config file name if the config file is invalid", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- files: {
- "invalid/invalid-top-level-property.yml": "invalidProperty: 3"
- }
- });
- const factory = new ConfigArrayFactory();
-
- try {
- load(factory, "invalid/invalid-top-level-property.yml");
- } catch (err) {
- assert.include(err.message, `ESLint configuration in ${`invalid${path.sep}invalid-top-level-property.yml`} is invalid`);
- return;
- }
- assert.fail();
- });
- });
-
- // This group moved from 'tests/lib/config/config-file.js' when refactoring to keep the cumulated test cases.
- describe("'extends' property should resolve the location of configs properly.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir,
- files: {
- "node_modules/eslint-config-foo/index.js": "",
- "node_modules/eslint-config-foo/bar.js": "",
- "node_modules/eslint-config-eslint-configfoo/index.js": "",
- "node_modules/@foo/eslint-config/index.js": "",
- "node_modules/@foo/eslint-config-bar/index.js": "",
- "node_modules/eslint-plugin-foo/index.js": "exports.configs = { bar: {} }",
- "node_modules/@foo/eslint-plugin/index.js": "exports.configs = { bar: {} }",
- "node_modules/@foo/eslint-plugin-bar/index.js": "exports.configs = { baz: {} }",
- "foo/bar/.eslintrc": "",
- ".eslintrc": ""
- }
- });
- const factory = new ConfigArrayFactory();
-
- /**
- * Resolve `extends` module.
- * @param {string} request The module name to resolve.
- * @param {string} [relativeTo] The importer path to resolve.
- * @returns {string} The resolved path.
- */
- function resolve(request, relativeTo) {
- return factory.create(
- { extends: request },
- { filePath: relativeTo }
- )[0];
- }
-
- describe("Relative to CWD", () => {
- for (const { input, expected } of [
- { input: ".eslintrc", expected: path.resolve(tempDir, ".eslintrc") },
- { input: "eslint-config-foo", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/index.js") },
- { input: "eslint-config-foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/bar.js") },
- { input: "foo", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/index.js") },
- { input: "foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/bar.js") },
- { input: "eslint-configfoo", expected: path.resolve(tempDir, "node_modules/eslint-config-eslint-configfoo/index.js") },
- { input: "@foo/eslint-config", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config/index.js") },
- { input: "@foo", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config/index.js") },
- { input: "@foo/bar", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config-bar/index.js") },
- { input: "plugin:foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-plugin-foo/index.js") },
- { input: "plugin:@foo/bar", expected: path.resolve(tempDir, "node_modules/@foo/eslint-plugin/index.js") },
- { input: "plugin:@foo/bar/baz", expected: path.resolve(tempDir, "node_modules/@foo/eslint-plugin-bar/index.js") }
- ]) {
- it(`should return ${expected} when passed ${input}`, () => {
- const result = resolve(input);
-
- assert.strictEqual(result.filePath, expected);
- });
- }
- });
-
- describe("Relative to config file", () => {
- const relativePath = path.resolve(tempDir, "./foo/bar/.eslintrc");
-
- for (const { input, expected } of [
- { input: ".eslintrc", expected: path.join(path.dirname(relativePath), ".eslintrc") },
- { input: "eslint-config-foo", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/index.js") },
- { input: "eslint-config-foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/bar.js") },
- { input: "foo", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/index.js") },
- { input: "foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/bar.js") },
- { input: "eslint-configfoo", expected: path.resolve(tempDir, "node_modules/eslint-config-eslint-configfoo/index.js") },
- { input: "@foo/eslint-config", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config/index.js") },
- { input: "@foo", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config/index.js") },
- { input: "@foo/bar", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config-bar/index.js") },
- { input: "plugin:foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-plugin-foo/index.js") },
- { input: "plugin:@foo/bar", expected: path.resolve(tempDir, "node_modules/@foo/eslint-plugin/index.js") },
- { input: "plugin:@foo/bar/baz", expected: path.resolve(tempDir, "node_modules/@foo/eslint-plugin-bar/index.js") }
- ]) {
- it(`should return ${expected} when passed ${input}`, () => {
- const result = resolve(input, relativePath);
-
- assert.strictEqual(result.filePath, expected);
- });
- }
- });
- });
-
- // This group moved from 'tests/lib/config/plugins.js' when refactoring to keep the cumulated test cases.
- describe("'plugins' property should load a correct plugin.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir,
- files: {
- "node_modules/@scope/eslint-plugin-example/index.js": "exports.configs = { name: '@scope/eslint-plugin-example' };",
- "node_modules/eslint-plugin-example/index.js": "exports.configs = { name: 'eslint-plugin-example' };",
- "node_modules/eslint-plugin-throws-on-load/index.js": "throw new Error('error thrown while loading this module')"
- }
- });
- const factory = new ConfigArrayFactory();
-
- /**
- * Load a plugin.
- * @param {string} request A request to load a plugin.
- * @param {ConfigArrayFactory} [configArrayFactory] The factory to use
- * @returns {Map} The loaded plugins.
- */
- function load(request, configArrayFactory = factory) {
- const config = configArrayFactory.create({ plugins: [request] });
-
- return new Map(
- Object
- .entries(config[0].plugins)
- .map(([id, entry]) => {
- if (entry.error) {
- throw entry.error;
- }
- return [id, entry.definition];
- })
- );
- }
-
- it("should load a plugin when referenced by short name", () => {
- const loadedPlugins = load("example");
-
- assertPluginDefinition(
- loadedPlugins.get("example"),
- { configs: { name: "eslint-plugin-example" } }
- );
- });
-
- it("should load a plugin when referenced by short name, even when using a custom loadPluginsRelativeTo value", () => {
- const { ConfigArrayFactory: FactoryWithPluginsInSubdir } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir,
- files: {
- "subdir/node_modules/eslint-plugin-example/index.js": "exports.configs = { name: 'eslint-plugin-example' };"
- }
- });
-
- const factoryWithCustomPluginPath = new FactoryWithPluginsInSubdir({ resolvePluginsRelativeTo: "subdir" });
-
- const loadedPlugins = load("example", factoryWithCustomPluginPath);
-
- assertPluginDefinition(
- loadedPlugins.get("example"),
- { configs: { name: "eslint-plugin-example" } }
- );
- });
-
- it("should load a plugin when referenced by long name", () => {
- const loadedPlugins = load("eslint-plugin-example");
-
- assertPluginDefinition(
- loadedPlugins.get("example"),
- { configs: { name: "eslint-plugin-example" } }
- );
- });
-
- it("should throw an error when a plugin has whitespace", () => {
- assert.throws(() => {
- load("whitespace ");
- }, /Whitespace found in plugin name 'whitespace '/u);
- assert.throws(() => {
- load("whitespace\t");
- }, /Whitespace found in plugin name/u);
- assert.throws(() => {
- load("whitespace\n");
- }, /Whitespace found in plugin name/u);
- assert.throws(() => {
- load("whitespace\r");
- }, /Whitespace found in plugin name/u);
- });
-
- it("should throw an error when a plugin doesn't exist", () => {
- assert.throws(() => {
- load("nonexistentplugin");
- }, /Failed to load plugin/u);
- });
-
- it("should rethrow an error that a plugin throws on load", () => {
- assert.throws(() => {
- load("throws-on-load");
- }, /error thrown while loading this module/u);
- });
-
- it("should load a scoped plugin when referenced by short name", () => {
- const loadedPlugins = load("@scope/example");
-
- assertPluginDefinition(
- loadedPlugins.get("@scope/example"),
- { configs: { name: "@scope/eslint-plugin-example" } }
- );
- });
-
- it("should load a scoped plugin when referenced by long name", () => {
- const loadedPlugins = load("@scope/eslint-plugin-example");
-
- assertPluginDefinition(
- loadedPlugins.get("@scope/example"),
- { configs: { name: "@scope/eslint-plugin-example" } }
- );
- });
-
- describe("when referencing a scope plugin and omitting @scope/", () => {
- it("should load a scoped plugin when referenced by short name, but should not get the plugin if '@scope/' is omitted", () => {
- const loadedPlugins = load("@scope/example");
-
- assert.strictEqual(loadedPlugins.get("example"), void 0);
- });
-
- it("should load a scoped plugin when referenced by long name, but should not get the plugin if '@scope/' is omitted", () => {
- const loadedPlugins = load("@scope/eslint-plugin-example");
-
- assert.strictEqual(loadedPlugins.get("example"), void 0);
- });
- });
- });
-
- // This group moved from 'tests/lib/config/plugins.js' when refactoring to keep the cumulated test cases.
- describe("'plugins' property should load some correct plugins.", () => {
- const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd: () => tempDir,
- files: {
- "node_modules/eslint-plugin-example1/index.js": "exports.configs = { name: 'eslint-plugin-example1' };",
- "node_modules/eslint-plugin-example2/index.js": "exports.configs = { name: 'eslint-plugin-example2' };"
- }
- });
- const factory = new ConfigArrayFactory();
-
- /**
- * Load a plugin.
- * @param {string[]} request A request to load a plugin.
- * @returns {Map} The loaded plugins.
- */
- function loadAll(request) {
- const config = factory.create({ plugins: request });
-
- return new Map(
- Object
- .entries(config[0].plugins)
- .map(([id, entry]) => {
- if (entry.error) {
- throw entry.error;
- }
- return [id, entry.definition];
- })
- );
- }
-
- it("should load plugins when passed multiple plugins", () => {
- const loadedPlugins = loadAll(["example1", "example2"]);
-
- assertPluginDefinition(
- loadedPlugins.get("example1"),
- { configs: { name: "eslint-plugin-example1" } }
- );
- assertPluginDefinition(
- loadedPlugins.get("example2"),
- { configs: { name: "eslint-plugin-example2" } }
- );
- });
- });
-});
diff --git a/eslint/tests/lib/cli-engine/config-array/config-array.js b/eslint/tests/lib/cli-engine/config-array/config-array.js
deleted file mode 100644
index fafb688..0000000
--- a/eslint/tests/lib/cli-engine/config-array/config-array.js
+++ /dev/null
@@ -1,739 +0,0 @@
-/**
- * @fileoverview Tests for ConfigArray class.
- * @author Toru Nagashima
- */
-"use strict";
-
-const path = require("path");
-const { assert } = require("chai");
-const { ConfigArray, OverrideTester, getUsedExtractedConfigs } = require("../../../../lib/cli-engine/config-array");
-
-describe("ConfigArray", () => {
- it("should be a sub class of Array.", () => {
- assert(new ConfigArray() instanceof Array);
- });
-
- describe("'constructor(...elements)' should adopt the elements as array elements.", () => {
- const patterns = [
- { elements: [] },
- { elements: [{ value: 1 }] },
- { elements: [{ value: 2 }, { value: 3 }] },
- { elements: [{ value: 4 }, { value: 5 }, { value: 6 }] }
- ];
-
- for (const { elements } of patterns) {
- describe(`if it gave ${JSON.stringify(elements)} then`, () => {
- let configArray;
-
- beforeEach(() => {
- configArray = new ConfigArray(...elements);
- });
-
- it(`should have ${elements.length} as the length.`, () => {
- assert.strictEqual(configArray.length, elements.length);
- });
-
- for (let i = 0; i < elements.length; ++i) {
- it(`should have ${JSON.stringify(elements[i])} at configArray[${i}].`, () => { // eslint-disable-line no-loop-func
- assert.strictEqual(configArray[i], elements[i]);
- });
- }
- });
- }
- });
-
- describe("'isRoot()' method should be the value of the last element which has 'root' property.", () => {
- const patterns = [
- { elements: [], expected: false },
- { elements: [{}], expected: false },
- { elements: [{}, {}], expected: false },
- { elements: [{ root: false }], expected: false },
- { elements: [{ root: true }], expected: true },
- { elements: [{ root: true }, { root: false }], expected: false },
- { elements: [{ root: false }, { root: true }], expected: true },
- { elements: [{ root: false }, { root: true }, { rules: {} }], expected: true }, // ignore undefined.
- { elements: [{ root: true }, { root: 1 }], expected: true } // ignore non-boolean value
- ];
-
- for (const { elements, expected } of patterns) {
- it(`should be ${expected} if the elements are ${JSON.stringify(elements)}.`, () => {
- assert.strictEqual(new ConfigArray(...elements).isRoot(), expected);
- });
- }
- });
-
- describe("'pluginEnvironments' property should be the environments of all plugins.", () => {
- const env = {
- "aaa/xxx": {},
- "bbb/xxx": {}
- };
- let configArray;
-
- beforeEach(() => {
- configArray = new ConfigArray(
- {
- plugins: {
- aaa: {
- definition: {
- environments: {
- xxx: env["aaa/xxx"]
- }
- }
- }
- }
- },
- {
- criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
- plugins: {
- bbb: {
- definition: {
- environments: {
- xxx: env["bbb/xxx"]
- }
- }
- }
- }
- }
- );
- });
-
- it("should return null for built-in env", () => {
- assert.strictEqual(configArray.pluginEnvironments.get("node"), void 0);
- });
-
- it("should return 'aaa/xxx' if it exists.", () => {
- assert.strictEqual(configArray.pluginEnvironments.get("aaa/xxx"), env["aaa/xxx"]);
- });
-
- it("should return 'bbb/xxx' if it exists.", () => {
- assert.strictEqual(configArray.pluginEnvironments.get("bbb/xxx"), env["bbb/xxx"]);
- });
-
- it("should throw an error if it tried to mutate.", () => {
- assert.throws(() => {
- configArray.pluginEnvironments.set("ccc/xxx", {});
- });
- });
- });
-
- describe("'pluginProcessors' property should be the processors of all plugins.", () => {
- const processors = {
- "aaa/.xxx": {},
- "bbb/.xxx": {}
- };
- let configArray;
-
- beforeEach(() => {
- configArray = new ConfigArray(
- {
- plugins: {
- aaa: {
- definition: {
- processors: {
- ".xxx": processors["aaa/.xxx"]
- }
- }
- }
- }
- },
- {
- criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
- plugins: {
- bbb: {
- definition: {
- processors: {
- ".xxx": processors["bbb/.xxx"]
- }
- }
- }
- }
- }
- );
- });
-
- it("should return 'aaa/.xxx' if it exists.", () => {
- assert.strictEqual(configArray.pluginProcessors.get("aaa/.xxx"), processors["aaa/.xxx"]);
- });
-
- it("should return 'bbb/.xxx' if it exists.", () => {
- assert.strictEqual(configArray.pluginProcessors.get("bbb/.xxx"), processors["bbb/.xxx"]);
- });
-
- it("should throw an error if it tried to mutate.", () => {
- assert.throws(() => {
- configArray.pluginProcessors.set("ccc/.xxx", {});
- });
- });
- });
-
- describe("'pluginRules' property should be the rules of all plugins.", () => {
- const rules = {
- "aaa/xxx": {},
- "bbb/xxx": {}
- };
- let configArray;
-
- beforeEach(() => {
- configArray = new ConfigArray(
- {
- plugins: {
- aaa: {
- definition: {
- rules: {
- xxx: rules["aaa/xxx"]
- }
- }
- }
- }
- },
- {
- criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
- plugins: {
- bbb: {
- definition: {
- rules: {
- xxx: rules["bbb/xxx"]
- }
- }
- }
- }
- }
- );
- });
-
- it("should return null for built-in rules", () => {
- assert.strictEqual(configArray.pluginRules.get("eqeqeq"), void 0);
- });
-
- it("should return 'aaa/xxx' if it exists.", () => {
- assert.strictEqual(configArray.pluginRules.get("aaa/xxx"), rules["aaa/xxx"]);
- });
-
- it("should return 'bbb/xxx' if it exists.", () => {
- assert.strictEqual(configArray.pluginRules.get("bbb/xxx"), rules["bbb/xxx"]);
- });
-
- it("should throw an error if it tried to mutate.", () => {
- assert.throws(() => {
- configArray.pluginRules.set("ccc/xxx", {});
- });
- });
- });
-
- describe("'extractConfig(filePath)' method should retrieve the merged config for a given file.", () => {
- it("should throw an error if a 'parser' has the loading error.", () => {
- assert.throws(() => {
- new ConfigArray(
- {
- parser: { error: new Error("Failed to load a parser.") }
- }
- ).extractConfig(__filename);
- }, "Failed to load a parser.");
- });
-
- it("should not throw if the errored 'parser' was not used; overwriten", () => {
- const parser = { id: "a parser" };
- const config = new ConfigArray(
- {
- parser: { error: new Error("Failed to load a parser.") }
- },
- {
- parser
- }
- ).extractConfig(__filename);
-
- assert.strictEqual(config.parser, parser);
- });
-
- it("should not throw if the errored 'parser' was not used; not matched", () => {
- const config = new ConfigArray(
- {
- criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
- parser: { error: new Error("Failed to load a parser.") }
- }
- ).extractConfig(__filename);
-
- assert.strictEqual(config.parser, null);
- });
-
- it("should throw an error if a 'plugins' value has the loading error.", () => {
- assert.throws(() => {
- new ConfigArray(
- {
- plugins: {
- foo: { error: new Error("Failed to load a plugin.") }
- }
- }
- ).extractConfig(__filename);
- }, "Failed to load a plugin.");
- });
-
- it("should not throw if the errored 'plugins' value was not used; not matched", () => {
- const config = new ConfigArray(
- {
- criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
- plugins: {
- foo: { error: new Error("Failed to load a plugin.") }
- }
- }
- ).extractConfig(__filename);
-
- assert.deepStrictEqual(config.plugins, {});
- });
-
- it("should not merge the elements which were not matched.", () => {
- const config = new ConfigArray(
- {
- rules: {
- "no-redeclare": "error"
- }
- },
- {
- criteria: OverrideTester.create(["*.js"], [], process.cwd()),
- rules: {
- "no-undef": "error"
- }
- },
- {
- criteria: OverrideTester.create(["*.js"], [path.basename(__filename)], process.cwd()),
- rules: {
- "no-use-before-define": "error"
- }
- },
- {
- criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
- rules: {
- "no-unused-vars": "error"
- }
- }
- ).extractConfig(__filename);
-
- assert.deepStrictEqual(config.rules, {
- "no-redeclare": ["error"],
- "no-undef": ["error"]
- });
- });
-
- it("should return the same instance for every the same matching.", () => {
- const configArray = new ConfigArray(
- {
- rules: {
- "no-redeclare": "error"
- }
- },
- {
- criteria: OverrideTester.create(["*.js"], [], process.cwd()),
- rules: {
- "no-undef": "error"
- }
- },
- {
- criteria: OverrideTester.create(["*.js"], [path.basename(__filename)], process.cwd()),
- rules: {
- "no-use-before-define": "error"
- }
- },
- {
- criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
- rules: {
- "no-unused-vars": "error"
- }
- }
- );
-
- assert.strictEqual(
- configArray.extractConfig(path.join(__dirname, "a.js")),
- configArray.extractConfig(path.join(__dirname, "b.js"))
- );
- });
-
- /**
- * Merge two config data.
- *
- * The test cases which depend on this function were moved from
- * 'tests/lib/config/config-ops.js' when refactoring to keep the
- * cumulated test cases.
- *
- * Previously, the merging logic of multiple config data had been
- * implemented in `ConfigOps.merge()` function. But currently, it's
- * implemented in `ConfigArray#extractConfig()` method.
- * @param {Object} target A config data.
- * @param {Object} source Another config data.
- * @returns {Object} The merged config data.
- */
- function merge(target, source) {
- return new ConfigArray(target, source).extractConfig(__filename);
- }
-
- it("should combine two objects when passed two objects with different top-level properties", () => {
- const config = [
- { env: { browser: true } },
- { globals: { foo: "bar" } }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.strictEqual(result.globals.foo, "bar");
- assert.isTrue(result.env.browser);
- });
-
- it("should combine without blowing up on null values", () => {
- const config = [
- { env: { browser: true } },
- { env: { node: null } }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.strictEqual(result.env.node, null);
- assert.isTrue(result.env.browser);
- });
-
- it("should combine two objects with parser when passed two objects with different top-level properties", () => {
- const config = [
- { env: { browser: true }, parser: "espree" },
- { globals: { foo: "bar" } }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.strictEqual(result.parser, "espree");
- });
-
- it("should combine configs and override rules when passed configs with the same rules", () => {
- const config = [
- { rules: { "no-mixed-requires": [0, false] } },
- { rules: { "no-mixed-requires": [1, true] } }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.isArray(result.rules["no-mixed-requires"]);
- assert.strictEqual(result.rules["no-mixed-requires"][0], 1);
- assert.strictEqual(result.rules["no-mixed-requires"][1], true);
- });
-
- it("should combine configs when passed configs with parserOptions", () => {
- const config = [
- { parserOptions: { ecmaFeatures: { jsx: true } } },
- { parserOptions: { ecmaFeatures: { globalReturn: true } } }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.deepStrictEqual(result, {
- configNameOfNoInlineConfig: "",
- env: {},
- globals: {},
- ignores: void 0,
- noInlineConfig: void 0,
- parser: null,
- parserOptions: {
- ecmaFeatures: {
- jsx: true,
- globalReturn: true
- }
- },
- plugins: {},
- processor: null,
- reportUnusedDisableDirectives: void 0,
- rules: {},
- settings: {}
- });
-
- // double-check that originals were not changed
- assert.deepStrictEqual(config[0], { parserOptions: { ecmaFeatures: { jsx: true } } });
- assert.deepStrictEqual(config[1], { parserOptions: { ecmaFeatures: { globalReturn: true } } });
- });
-
- it("should override configs when passed configs with the same ecmaFeatures", () => {
- const config = [
- { parserOptions: { ecmaFeatures: { globalReturn: false } } },
- { parserOptions: { ecmaFeatures: { globalReturn: true } } }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.deepStrictEqual(result, {
- configNameOfNoInlineConfig: "",
- env: {},
- globals: {},
- ignores: void 0,
- noInlineConfig: void 0,
- parser: null,
- parserOptions: {
- ecmaFeatures: {
- globalReturn: true
- }
- },
- plugins: {},
- processor: null,
- reportUnusedDisableDirectives: void 0,
- rules: {},
- settings: {}
- });
- });
-
- it("should combine configs and override rules when merging two configs with arrays and int", () => {
-
- const config = [
- { rules: { "no-mixed-requires": [0, false] } },
- { rules: { "no-mixed-requires": 1 } }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.isArray(result.rules["no-mixed-requires"]);
- assert.strictEqual(result.rules["no-mixed-requires"][0], 1);
- assert.strictEqual(result.rules["no-mixed-requires"][1], false);
- assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires": [0, false] } });
- assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires": 1 } });
- });
-
- it("should combine configs and override rules options completely", () => {
-
- const config = [
- { rules: { "no-mixed-requires1": [1, { event: ["evt", "e"] }] } },
- { rules: { "no-mixed-requires1": [1, { err: ["error", "e"] }] } }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.isArray(result.rules["no-mixed-requires1"]);
- assert.deepStrictEqual(result.rules["no-mixed-requires1"][1], { err: ["error", "e"] });
- assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires1": [1, { event: ["evt", "e"] }] } });
- assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires1": [1, { err: ["error", "e"] }] } });
- });
-
- it("should combine configs and override rules options without array or object", () => {
-
- const config = [
- { rules: { "no-mixed-requires1": ["warn", "nconf", "underscore"] } },
- { rules: { "no-mixed-requires1": [2, "requirejs"] } }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.strictEqual(result.rules["no-mixed-requires1"][0], 2);
- assert.strictEqual(result.rules["no-mixed-requires1"][1], "requirejs");
- assert.isUndefined(result.rules["no-mixed-requires1"][2]);
- assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires1": ["warn", "nconf", "underscore"] } });
- assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires1": [2, "requirejs"] } });
- });
-
- it("should combine configs and override rules options without array or object but special case", () => {
-
- const config = [
- { rules: { "no-mixed-requires1": [1, "nconf", "underscore"] } },
- { rules: { "no-mixed-requires1": "error" } }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.strictEqual(result.rules["no-mixed-requires1"][0], "error");
- assert.strictEqual(result.rules["no-mixed-requires1"][1], "nconf");
- assert.strictEqual(result.rules["no-mixed-requires1"][2], "underscore");
- assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires1": [1, "nconf", "underscore"] } });
- assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires1": "error" } });
- });
-
- it("should combine configs correctly", () => {
-
- const config = [
- {
- rules: {
- "no-mixed-requires1": [1, { event: ["evt", "e"] }],
- "valid-jsdoc": 1,
- semi: 1,
- quotes1: [2, { exception: ["hi"] }],
- smile: [1, ["hi", "bye"]]
- },
- parserOptions: {
- ecmaFeatures: { jsx: true }
- },
- env: { browser: true },
- globals: { foo: false }
- },
- {
- rules: {
- "no-mixed-requires1": [1, { err: ["error", "e"] }],
- "valid-jsdoc": 2,
- test: 1,
- smile: [1, ["xxx", "yyy"]]
- },
- parserOptions: {
- ecmaFeatures: { globalReturn: true }
- },
- env: { browser: false },
- globals: { foo: true }
- }
- ];
-
- const result = merge(config[0], config[1]);
-
- assert.deepStrictEqual(result, {
- configNameOfNoInlineConfig: "",
- parser: null,
- parserOptions: {
- ecmaFeatures: {
- jsx: true,
- globalReturn: true
- }
- },
- plugins: {},
- env: {
- browser: false
- },
- globals: {
- foo: true
- },
- rules: {
- "no-mixed-requires1": [1,
- {
- err: [
- "error",
- "e"
- ]
- }
- ],
- quotes1: [2,
- {
- exception: [
- "hi"
- ]
- }
- ],
- semi: [1],
- smile: [1, ["xxx", "yyy"]],
- test: [1],
- "valid-jsdoc": [2]
- },
- settings: {},
- processor: null,
- noInlineConfig: void 0,
- reportUnusedDisableDirectives: void 0,
- ignores: void 0
- });
- assert.deepStrictEqual(config[0], {
- rules: {
- "no-mixed-requires1": [1, { event: ["evt", "e"] }],
- "valid-jsdoc": 1,
- semi: 1,
- quotes1: [2, { exception: ["hi"] }],
- smile: [1, ["hi", "bye"]]
- },
- parserOptions: {
- ecmaFeatures: { jsx: true }
- },
- env: { browser: true },
- globals: { foo: false }
- });
- assert.deepStrictEqual(config[1], {
- rules: {
- "no-mixed-requires1": [1, { err: ["error", "e"] }],
- "valid-jsdoc": 2,
- test: 1,
- smile: [1, ["xxx", "yyy"]]
- },
- parserOptions: {
- ecmaFeatures: { globalReturn: true }
- },
- env: { browser: false },
- globals: { foo: true }
- });
- });
-
- it("should copy deeply if there is not the destination's property", () => {
- const a = {};
- const b = { settings: { bar: 1 } };
-
- const result = merge(a, b);
-
- assert(a.settings === void 0);
- assert(b.settings.bar === 1);
- assert(result.settings.bar === 1);
-
- result.settings.bar = 2;
- assert(b.settings.bar === 1);
- assert(result.settings.bar === 2);
- });
- });
-
- describe("'getUsedExtractedConfigs(instance)' function should retrieve used extracted configs from the instance's internal cache.", () => {
- let configArray;
-
- beforeEach(() => {
- configArray = new ConfigArray(
- {
- rules: {
- "no-redeclare": "error"
- }
- },
- {
- criteria: OverrideTester.create(["*.js"], [], process.cwd()),
- rules: {
- "no-undef": "error"
- }
- },
- {
- criteria: OverrideTester.create(["*.js"], [path.basename(__filename)], process.cwd()),
- rules: {
- "no-use-before-define": "error"
- }
- },
- {
- criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
- rules: {
- "no-unused-vars": "error"
- }
- }
- );
- });
-
- it("should return empty array before it called 'extractConfig(filePath)'.", () => {
- assert.deepStrictEqual(getUsedExtractedConfigs(configArray), []);
- });
-
- for (const { filePaths } of [
- { filePaths: [__filename] },
- { filePaths: [__filename, `${__filename}.ts`] },
- { filePaths: [__filename, `${__filename}.ts`, path.join(__dirname, "foo.js")] }
- ]) {
- describe(`after it called 'extractConfig(filePath)' ${filePaths.length} time(s) with ${JSON.stringify(filePaths, null, 4)}, the returned array`, () => { // eslint-disable-line no-loop-func
- let configs;
- let usedConfigs;
-
- beforeEach(() => {
- configs = filePaths.map(filePath => configArray.extractConfig(filePath));
- usedConfigs = getUsedExtractedConfigs(configArray);
- });
-
- it(`should have ${filePaths.length} as the length.`, () => {
- assert.strictEqual(usedConfigs.length, configs.length);
- });
-
- for (let i = 0; i < filePaths.length; ++i) {
- it(`should contain 'configs[${i}]'.`, () => { // eslint-disable-line no-loop-func
- assert(usedConfigs.includes(configs[i]));
- });
- }
- });
- }
-
- it("should not contain duplicate values.", () => {
-
- // Call some times, including with the same arguments.
- configArray.extractConfig(__filename);
- configArray.extractConfig(`${__filename}.ts`);
- configArray.extractConfig(path.join(__dirname, "foo.js"));
- configArray.extractConfig(__filename);
- configArray.extractConfig(path.join(__dirname, "foo.js"));
- configArray.extractConfig(path.join(__dirname, "bar.js"));
- configArray.extractConfig(path.join(__dirname, "baz.js"));
-
- const usedConfigs = getUsedExtractedConfigs(configArray);
-
- assert.strictEqual(new Set(usedConfigs).size, usedConfigs.length);
- });
- });
-});
diff --git a/eslint/tests/lib/cli-engine/config-array/config-dependency.js b/eslint/tests/lib/cli-engine/config-array/config-dependency.js
deleted file mode 100644
index fc008fa..0000000
--- a/eslint/tests/lib/cli-engine/config-array/config-dependency.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/**
- * @fileoverview Tests for ConfigDependency class.
- * @author Toru Nagashima
- */
-"use strict";
-
-const assert = require("assert");
-const { Console } = require("console");
-const { Writable } = require("stream");
-const { ConfigDependency } = require("../../../../lib/cli-engine/config-array/config-dependency");
-
-describe("ConfigDependency", () => {
- describe("'constructor(data)' should initialize properties.", () => {
-
- /** @type {ConfigDependency} */
- let dep;
-
- beforeEach(() => {
- dep = new ConfigDependency({
- definition: { name: "definition?" },
- error: new Error("error?"),
- filePath: "filePath?",
- id: "id?",
- importerName: "importerName?",
- importerPath: "importerPath?"
- });
- });
-
- it("should set 'data.definition' to 'definition' property.", () => {
- assert.deepStrictEqual(dep.definition, { name: "definition?" });
- });
-
- it("should set 'data.error' to 'error' property.", () => {
- assert.deepStrictEqual(dep.error.message, "error?");
- });
-
- it("should set 'data.filePath' to 'filePath' property.", () => {
- assert.deepStrictEqual(dep.filePath, "filePath?");
- });
-
- it("should set 'data.id' to 'id' property.", () => {
- assert.deepStrictEqual(dep.id, "id?");
- });
-
- it("should set 'data.importerName' to 'importerName' property.", () => {
- assert.deepStrictEqual(dep.importerName, "importerName?");
- });
-
- it("should set 'data.importerPath' to 'importerPath' property.", () => {
- assert.deepStrictEqual(dep.importerPath, "importerPath?");
- });
- });
-
- describe("'JSON.stringify(...)' should return readable JSON; not include 'definition' property", () => {
- it("should not print 'definition' property.", () => {
- const dep = new ConfigDependency({
- definition: { name: "definition?" },
- error: new Error("error?"),
- filePath: "filePath?",
- id: "id?",
- importerName: "importerName?",
- importerPath: "importerPath?"
- });
-
- assert.deepStrictEqual(
- JSON.parse(JSON.stringify(dep)),
- {
- error: { message: "error?" },
- filePath: "filePath?",
- id: "id?",
- importerName: "importerName?",
- importerPath: "importerPath?"
- }
- );
- });
- });
-
- describe("'console.log(...)' should print readable string; not include 'defininition' property", () => {
-
- // Record the written strings to `output` variable.
- let output = "";
- const localConsole = new Console(
- new class extends Writable {
- write(chunk) { // eslint-disable-line class-methods-use-this
- output += chunk;
- }
- }()
- );
-
- it("should not print 'definition' property.", () => {
- const error = new Error("error?"); // reuse error object to use the same stacktrace.
- const dep = new ConfigDependency({
- definition: { name: "definition?" },
- error,
- filePath: "filePath?",
- id: "id?",
- importerName: "importerName?",
- importerPath: "importerPath?"
- });
-
- // Make actual output.
- output = "";
- localConsole.log(dep);
- const actual = output;
-
- // Make expected output; no `definition` property.
- output = "";
- localConsole.log({
- error,
- filePath: "filePath?",
- id: "id?",
- importerName: "importerName?",
- importerPath: "importerPath?"
- });
- const expected = output;
-
- assert.strictEqual(actual, expected);
- });
- });
-});
diff --git a/eslint/tests/lib/cli-engine/config-array/extracted-config.js b/eslint/tests/lib/cli-engine/config-array/extracted-config.js
deleted file mode 100644
index 9d70047..0000000
--- a/eslint/tests/lib/cli-engine/config-array/extracted-config.js
+++ /dev/null
@@ -1,139 +0,0 @@
-/**
- * @fileoverview Tests for ExtractedConfig class.
- * @author Toru Nagashima
- */
-"use strict";
-
-const assert = require("assert");
-const { ExtractedConfig } = require("../../../../lib/cli-engine/config-array/extracted-config");
-
-describe("'ExtractedConfig' class", () => {
- describe("'constructor()' should create an instance.", () => {
-
- /** @type {ExtractedConfig} */
- let config;
-
- beforeEach(() => {
- config = new ExtractedConfig();
- });
-
- it("should have 'env' property.", () => {
- assert.deepStrictEqual(config.env, {});
- });
-
- it("should have 'globals' property.", () => {
- assert.deepStrictEqual(config.globals, {});
- });
-
- it("should have 'parser' property.", () => {
- assert.deepStrictEqual(config.parser, null);
- });
-
- it("should have 'parserOptions' property.", () => {
- assert.deepStrictEqual(config.parserOptions, {});
- });
-
- it("should have 'plugins' property.", () => {
- assert.deepStrictEqual(config.plugins, {});
- });
-
- it("should have 'processor' property.", () => {
- assert.deepStrictEqual(config.processor, null);
- });
-
- it("should have 'rules' property.", () => {
- assert.deepStrictEqual(config.rules, {});
- });
-
- it("should have 'settings' property.", () => {
- assert.deepStrictEqual(config.settings, {});
- });
- });
-
- describe("'toCompatibleObjectAsConfigFileContent()' method should return a valid config data.", () => {
-
- /** @type {ExtractedConfig} */
- let config;
-
- beforeEach(() => {
- config = new ExtractedConfig();
- });
-
- it("should use 'env' property as is.", () => {
- config.env = { a: true };
-
- const data = config.toCompatibleObjectAsConfigFileContent();
-
- assert.deepStrictEqual(data.env, { a: true });
- });
-
- it("should use 'globals' as is.", () => {
- config.globals = { a: true };
-
- const data = config.toCompatibleObjectAsConfigFileContent();
-
- assert.deepStrictEqual(data.globals, { a: true });
- });
-
- it("should use 'parser.filePath' for 'parser' property.", () => {
- config.parser = {
- definition: {},
- error: null,
- filePath: "/path/to/a/parser",
- id: "parser",
- importerName: "importer name",
- importerPath: "importer path"
- };
-
- const data = config.toCompatibleObjectAsConfigFileContent();
-
- assert.deepStrictEqual(data.parser, "/path/to/a/parser");
- });
-
- it("should use 'null' for 'parser' property if 'parser' property is 'null'.", () => {
- const data = config.toCompatibleObjectAsConfigFileContent();
-
- assert.deepStrictEqual(data.parser, null);
- });
-
- it("should use 'parserOptions' property as is.", () => {
- config.parserOptions = { a: true };
-
- const data = config.toCompatibleObjectAsConfigFileContent();
-
- assert.deepStrictEqual(data.parserOptions, { a: true });
- });
-
- it("should use the keys of 'plugins' property for 'plugins' property.", () => {
- config.plugins = { a: {}, b: {} };
-
- const data = config.toCompatibleObjectAsConfigFileContent();
-
- assert.deepStrictEqual(data.plugins, ["b", "a"]);
- });
-
- it("should not use 'processor' property.", () => {
- config.processor = "foo/.md";
-
- const data = config.toCompatibleObjectAsConfigFileContent();
-
- assert.deepStrictEqual(data.processor, void 0);
- });
-
- it("should use 'rules' property as is.", () => {
- config.rules = { a: 1, b: 2 };
-
- const data = config.toCompatibleObjectAsConfigFileContent();
-
- assert.deepStrictEqual(data.rules, { a: 1, b: 2 });
- });
-
- it("should use 'settings' property as is.", () => {
- config.settings = { a: 1 };
-
- const data = config.toCompatibleObjectAsConfigFileContent();
-
- assert.deepStrictEqual(data.settings, { a: 1 });
- });
- });
-});
diff --git a/eslint/tests/lib/cli-engine/config-array/ignore-pattern.js b/eslint/tests/lib/cli-engine/config-array/ignore-pattern.js
deleted file mode 100644
index 9d652f3..0000000
--- a/eslint/tests/lib/cli-engine/config-array/ignore-pattern.js
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * @fileoverview Tests for IgnorePattern class.
- * @author Toru Nagashima
- */
-"use strict";
-
-const assert = require("assert");
-const path = require("path");
-const sinon = require("sinon");
-const { IgnorePattern } = require("../../../../lib/cli-engine/config-array/ignore-pattern");
-
-describe("IgnorePattern", () => {
- describe("constructor(patterns, basePath)", () => {
- it("should bind the first argument to 'patterns' property.", () => {
- const p = new IgnorePattern(["a.js"], process.cwd());
-
- assert.deepStrictEqual(p.patterns, ["a.js"]);
- });
-
- it("should bind the second argument to 'basePath' property.", () => {
- const p = new IgnorePattern(["a.js"], process.cwd());
-
- assert.strictEqual(p.basePath, process.cwd());
- });
-
- it("should throw an error if the second argument was not an absolute path.", () => {
- assert.throws(() => new IgnorePattern([], "a.js"), "");
- });
- });
-
- describe("getPatternsRelativeTo(newBasePath)", () => {
- it("should return 'patterns' as-is if the argument is the same as 'basePath'.", () => {
- const basePath1 = path.join(process.cwd(), "foo/bar");
- const p = new IgnorePattern(["a.js", "/b.js", "!c.js", "!/d.js"], basePath1);
-
- assert.deepStrictEqual(
- p.getPatternsRelativeTo(basePath1),
- ["a.js", "/b.js", "!c.js", "!/d.js"]
- );
- });
-
- it("should return modified 'patterns' if the argument is different from 'basePath'.", () => {
- const basePath1 = path.join(process.cwd(), "foo/bar");
- const basePath2 = process.cwd();
- const p = new IgnorePattern(["a.js", "/b.js", "!c.js", "!/d.js"], basePath1);
-
- assert.deepStrictEqual(
- p.getPatternsRelativeTo(basePath2),
- ["/foo/bar/**/a.js", "/foo/bar/b.js", "!/foo/bar/**/c.js", "!/foo/bar/d.js"]
- );
- });
- });
-
- describe("static createIgnore(ignorePatterns)", () => {
- describe("with two patterns should return a function, and the function", () => {
-
- /**
- * performs static createIgnre assertions against the cwd.
- * @param {string} cwd cwd to be the base path for assertions
- * @returns {void}
- */
- function assertions(cwd) {
- const basePath1 = path.join(cwd, "foo/bar");
- const basePath2 = path.join(cwd, "abc/");
- const ignores = IgnorePattern.createIgnore([
- new IgnorePattern(["*.js", "/*.ts", "!a.*", "!/b.*"], basePath1),
- new IgnorePattern(["*.js", "/*.ts", "!a.*", "!/b.*"], basePath2)
- ]);
- const patterns = [
- ["a.js", false],
- ["a.ts", false],
- ["b.js", false],
- ["b.ts", false],
- ["c.js", false],
- ["c.ts", false],
- ["dir/a.js", false],
- ["dir/a.ts", false],
- ["dir/b.js", false],
- ["dir/b.ts", false],
- ["dir/c.js", false],
- ["dir/c.ts", false],
- ["foo/bar/a.js", false],
- ["foo/bar/a.ts", false],
- ["foo/bar/b.js", false],
- ["foo/bar/b.ts", false],
- ["foo/bar/c.js", true],
- ["foo/bar/c.ts", true],
- ["foo/bar/dir/a.js", false],
- ["foo/bar/dir/a.ts", false],
- ["foo/bar/dir/b.js", true],
- ["foo/bar/dir/b.ts", false],
- ["foo/bar/dir/c.js", true],
- ["foo/bar/dir/c.ts", false],
- ["abc/a.js", false],
- ["abc/a.ts", false],
- ["abc/b.js", false],
- ["abc/b.ts", false],
- ["abc/c.js", true],
- ["abc/c.ts", true],
- ["abc/dir/a.js", false],
- ["abc/dir/a.ts", false],
- ["abc/dir/b.js", true],
- ["abc/dir/b.ts", false],
- ["abc/dir/c.js", true],
- ["abc/dir/c.ts", false]
- ];
-
- for (const [filename, expected] of patterns) {
- it(`should return ${expected} if '${filename}' was given.`, () => {
- assert.strictEqual(ignores(path.join(cwd, filename)), expected);
- });
- }
-
- it("should return false if '.dot.js' and false was given.", () => {
- assert.strictEqual(ignores(path.join(cwd, ".dot.js"), false), true);
- });
-
- it("should return true if '.dot.js' and true were given.", () => {
- assert.strictEqual(ignores(path.join(cwd, ".dot.js"), true), false);
- });
-
- it("should return false if '.dot/foo.js' and false was given.", () => {
- assert.strictEqual(ignores(path.join(cwd, ".dot/foo.js"), false), true);
- });
-
- it("should return true if '.dot/foo.js' and true were given.", () => {
- assert.strictEqual(ignores(path.join(cwd, ".dot/foo.js"), true), false);
- });
- }
-
- assertions(process.cwd());
-
- /*
- * This will catch regressions of Windows specific issue #12850 when run on CI nodes.
- * This runs the full set of assertions for the function returned from IgnorePattern.createIgnore.
- * When run on Windows CI nodes the .root drive i.e C:\ will be supplied
- * forcing getCommonAncestors to resolve to the root of the drive thus catching any regrssion of 12850.
- * When run on *nix CI nodes provides additional coverage on this OS too.
- * assertions when run on Windows CI nodes and / on *nix OS
- */
- assertions(path.parse(process.cwd()).root);
- });
- });
-
- describe("static createIgnore(ignorePatterns)", () => {
-
- /*
- * This test will catch regressions of Windows specific issue #12850 when run on your local dev box
- * irrespective of if you are running a Windows or *nix style OS.
- * When running on *nix sinon is used to emulate Windows behaviors of path and platform APIs
- * thus ensuring that the Windows specific fix is exercised and any regression is caught.
- */
- it("with common ancestor of drive root on windows should not throw", () => {
- try {
-
- /*
- * When not on Windows return win32 values so local runs on *nix hit the same code path as on Windows
- * thus enabling developers with *nix style OS to catch and debug any future regression of #12850 without
- * requiring a Windows based OS.
- */
- if (process.platform !== "win32") {
- sinon.stub(process, "platform").value("win32");
- sinon.stub(path, "sep").value(path.win32.sep);
- sinon.replace(path, "isAbsolute", path.win32.isAbsolute);
- }
-
- const ignores = IgnorePattern.createIgnore([
- new IgnorePattern(["*.js"], "C:\\foo\\bar"),
- new IgnorePattern(["*.js"], "C:\\abc\\")
- ]);
-
- // calls to this should not throw when getCommonAncestor returns root of drive
- ignores("C:\\abc\\contract.d.ts");
- } finally {
- sinon.restore();
- }
- });
- });
-});
diff --git a/eslint/tests/lib/cli-engine/config-array/override-tester.js b/eslint/tests/lib/cli-engine/config-array/override-tester.js
deleted file mode 100644
index 4920c44..0000000
--- a/eslint/tests/lib/cli-engine/config-array/override-tester.js
+++ /dev/null
@@ -1,286 +0,0 @@
-/**
- * @fileoverview Tests for OverrideTester class.
- * @author Toru Nagashima
- */
-"use strict";
-
-const assert = require("assert");
-const { Console } = require("console");
-const path = require("path");
-const { Writable } = require("stream");
-const { OverrideTester } = require("../../../../lib/cli-engine/config-array/override-tester");
-
-describe("OverrideTester", () => {
- describe("'create(files, excludedFiles, basePath)' should create a tester.", () => {
- for (const { files, excludedFiles, basePath } of [
- { files: void 0, excludedFiles: void 0, basePath: process.cwd() },
- { files: [], excludedFiles: [], basePath: process.cwd() }
- ]) {
- it(`should return null if ${JSON.stringify({ files, excludedFiles, basePath })} was given.`, () => {
- assert.strictEqual(
- OverrideTester.create(files, excludedFiles, basePath),
- null
- );
- });
- }
-
- it("should return an 'OverrideTester' instance that has given parameters if strings were given.", () => {
- const files = "*.js";
- const excludedFiles = "ignore/*";
- const basePath = process.cwd();
- const tester = OverrideTester.create(files, excludedFiles, basePath);
-
- assert.strictEqual(tester.patterns.length, 1);
- assert.strictEqual(tester.patterns[0].includes.length, 1);
- assert.strictEqual(tester.patterns[0].excludes.length, 1);
- assert.strictEqual(tester.patterns[0].includes[0].pattern, files);
- assert.strictEqual(tester.patterns[0].excludes[0].pattern, excludedFiles);
- assert.strictEqual(tester.basePath, basePath);
- });
-
- it("should return an 'OverrideTester' instance that has given parameters if arrays were given.", () => {
- const files = ["*.js"];
- const excludedFiles = ["ignore/*"];
- const basePath = process.cwd();
- const tester = OverrideTester.create(files, excludedFiles, basePath);
-
- assert.strictEqual(tester.patterns.length, 1);
- assert.strictEqual(tester.patterns[0].includes.length, 1);
- assert.strictEqual(tester.patterns[0].excludes.length, 1);
- assert.strictEqual(tester.patterns[0].includes[0].pattern, files[0]);
- assert.strictEqual(tester.patterns[0].excludes[0].pattern, excludedFiles[0]);
- assert.strictEqual(tester.basePath, basePath);
- });
- });
-
- describe("'and(a, b)' should return either or create another tester what includes both.", () => {
- it("should return null if both were null.", () => {
- assert.strictEqual(OverrideTester.and(null, null), null);
- });
-
- it("should return a new tester with the the first one's properties if the second one was null.", () => {
- const tester = OverrideTester.create("*.js", null, process.cwd());
- const result = OverrideTester.and(tester, null);
-
- assert.notStrictEqual(result, tester);
- assert.strictEqual(result.patterns, tester.patterns);
- assert.strictEqual(result.basePath, tester.basePath);
- });
-
- it("should return a new tester with the the second one's properties if the first one was null.", () => {
- const tester = OverrideTester.create("*.js", null, process.cwd());
- const result = OverrideTester.and(null, tester);
-
- assert.notStrictEqual(result, tester);
- assert.strictEqual(result.patterns, tester.patterns);
- assert.strictEqual(result.basePath, tester.basePath);
- });
-
- it("should return another one what includes both patterns if both are testers.", () => {
- const tester1 = OverrideTester.create("*.js");
- const tester2 = OverrideTester.create("*.ts");
- const tester3 = OverrideTester.and(tester1, tester2);
-
- assert.strictEqual(tester3.patterns.length, 2);
- assert.strictEqual(tester3.patterns[0], tester1.patterns[0]);
- assert.strictEqual(tester3.patterns[1], tester2.patterns[0]);
- });
- });
-
- describe("'test(filePath)' method", () => {
- it("should throw an error if no arguments were given.", () => {
- assert.throws(() => {
- OverrideTester.create(["*.js"], [], process.cwd()).test();
- }, /'filePath' should be an absolute path, but got undefined/u);
- });
-
- it("should throw an error if a non-string value was given.", () => {
- assert.throws(() => {
- OverrideTester.create(["*.js"], [], process.cwd()).test(100);
- }, /'filePath' should be an absolute path, but got 100/u);
- });
-
- it("should throw an error if a relative path was given.", () => {
- assert.throws(() => {
- OverrideTester.create(["*.js"], [], process.cwd()).test("foo/bar.js");
- }, /'filePath' should be an absolute path, but got foo\/bar\.js/u);
- });
-
- it("should return true only when both conditions are matched if the tester was created by 'and' factory function.", () => {
- const tester = OverrideTester.and(
- OverrideTester.create(["*.js"], [], process.cwd()),
- OverrideTester.create(["test/**"], [], process.cwd())
- );
-
- assert.strictEqual(tester.test(path.resolve("test/a.js")), true);
- assert.strictEqual(tester.test(path.resolve("lib/a.js")), false);
- assert.strictEqual(tester.test(path.resolve("test/a.ts")), false);
- });
-
- /**
- * Test if a given file path matches to the given condition.
- *
- * The test cases which depend on this function were moved from
- * 'tests/lib/config/config-ops.js' when refactoring to keep the
- * cumulated test cases.
- *
- * Previously, the testing logic of `overrides` properties had been
- * implemented in `ConfigOps.pathMatchesGlobs()` function. But
- * currently, it's implemented in `OverrideTester` class.
- * @param {string} filePath The file path to test patterns against
- * @param {string|string[]} files One or more glob patterns
- * @param {string|string[]} [excludedFiles] One or more glob patterns
- * @returns {boolean} The result.
- */
- function test(filePath, files, excludedFiles) {
- const basePath = process.cwd();
- const tester = OverrideTester.create(files, excludedFiles, basePath);
-
- return tester.test(path.resolve(basePath, filePath));
- }
-
- /**
- * Emits a test that confirms the specified file path matches the specified combination of patterns.
- * @param {string} filePath The file path to test patterns against
- * @param {string|string[]} patterns One or more glob patterns
- * @param {string|string[]} [excludedPatterns] One or more glob patterns
- * @returns {void}
- */
- function match(filePath, patterns, excludedPatterns) {
- it(`matches ${filePath} given '${patterns.join("','")}' includes and '${excludedPatterns.join("','")}' excludes`, () => {
- const result = test(filePath, patterns, excludedPatterns);
-
- assert.strictEqual(result, true);
- });
- }
-
- /**
- * Emits a test that confirms the specified file path does not match the specified combination of patterns.
- * @param {string} filePath The file path to test patterns against
- * @param {string|string[]} patterns One or more glob patterns
- * @param {string|string[]} [excludedPatterns] One or more glob patterns
- * @returns {void}
- */
- function noMatch(filePath, patterns, excludedPatterns) {
- it(`does not match ${filePath} given '${patterns.join("','")}' includes and '${excludedPatterns.join("','")}' excludes`, () => {
- const result = test(filePath, patterns, excludedPatterns);
-
- assert.strictEqual(result, false);
- });
- }
-
- /**
- * Emits a test that confirms the specified pattern throws an error.
- * @param {string} filePath The file path to test the pattern against
- * @param {string} pattern The glob pattern that should trigger the error condition
- * @param {string} expectedMessage The expected error's message
- * @returns {void}
- */
- function error(filePath, pattern, expectedMessage) {
- it(`emits an error given '${pattern}'`, () => {
- let errorMessage;
-
- try {
- test(filePath, pattern);
- } catch (e) {
- errorMessage = e.message;
- }
-
- assert.strictEqual(errorMessage, expectedMessage);
- });
- }
-
- // files in the project root
- match("foo.js", ["foo.js"], []);
- match("foo.js", ["*"], []);
- match("foo.js", ["*.js"], []);
- match("foo.js", ["**/*.js"], []);
- match("bar.js", ["*.js"], ["foo.js"]);
- match("foo.js", ["./foo.js"], []);
- match("foo.js", ["./*"], []);
- match("foo.js", ["./**"], []);
-
- noMatch("foo.js", ["*"], ["foo.js"]);
- noMatch("foo.js", ["*.js"], ["foo.js"]);
- noMatch("foo.js", ["**/*.js"], ["foo.js"]);
-
- // files in a subdirectory
- match("subdir/foo.js", ["foo.js"], []);
- match("subdir/foo.js", ["*"], []);
- match("subdir/foo.js", ["*.js"], []);
- match("subdir/foo.js", ["**/*.js"], []);
- match("subdir/foo.js", ["subdir/*.js"], []);
- match("subdir/foo.js", ["subdir/foo.js"], []);
- match("subdir/foo.js", ["subdir/*"], []);
- match("subdir/second/foo.js", ["subdir/**"], []);
- match("subdir/foo.js", ["./**"], []);
- match("subdir/foo.js", ["./subdir/**"], []);
- match("subdir/foo.js", ["./subdir/*"], []);
-
- noMatch("subdir/foo.js", ["./foo.js"], []);
- noMatch("subdir/foo.js", ["*"], ["subdir/**"]);
- noMatch("subdir/very/deep/foo.js", ["*.js"], ["subdir/**"]);
- noMatch("subdir/second/foo.js", ["subdir/*"], []);
- noMatch("subdir/second/foo.js", ["subdir/**"], ["subdir/second/*"]);
-
- // error conditions
- error("foo.js", ["/*.js"], "Invalid override pattern (expected relative path not containing '..'): /*.js");
- error("foo.js", ["/foo.js"], "Invalid override pattern (expected relative path not containing '..'): /foo.js");
- error("foo.js", ["../**"], "Invalid override pattern (expected relative path not containing '..'): ../**");
- });
-
- describe("'JSON.stringify(...)' should return readable JSON; not include 'Minimatch' objects", () => {
- it("should return an object that has three properties 'includes', 'excludes', and 'basePath' if that 'patterns' property include one object.", () => {
- const files = "*.js";
- const excludedFiles = "test/*";
- const basePath = process.cwd();
- const tester = OverrideTester.create(files, excludedFiles, basePath);
-
- assert.strictEqual(
- JSON.stringify(tester),
- `{"includes":["${files}"],"excludes":["${excludedFiles}"],"basePath":${JSON.stringify(basePath)}}`
- );
- });
-
- it("should return an object that has two properties 'AND' and 'basePath' if that 'patterns' property include two or more objects.", () => {
- const files1 = "*.js";
- const excludedFiles1 = "test/*";
- const files2 = "*.story.js";
- const excludedFiles2 = "src/*";
- const basePath = process.cwd();
- const tester = OverrideTester.and(
- OverrideTester.create(files1, excludedFiles1, basePath),
- OverrideTester.create(files2, excludedFiles2, basePath)
- );
-
- assert.deepStrictEqual(
- JSON.parse(JSON.stringify(tester)),
- {
- AND: [
- { includes: [files1], excludes: [excludedFiles1] },
- { includes: [files2], excludes: [excludedFiles2] }
- ],
- basePath
- }
- );
- });
- });
-
- describe("'console.log(...)' should print readable string; not include 'Minimatch' objects", () => {
- const localConsole = new Console(new Writable());
-
- it("should use 'toJSON()' method.", () => {
- const tester = OverrideTester.create("*.js", "", process.cwd());
- let called = false;
-
- tester.toJSON = () => {
- called = true;
- return "";
- };
-
- localConsole.log(tester);
-
- assert(called);
- });
- });
-});
diff --git a/eslint/tests/lib/cli-engine/file-enumerator.js b/eslint/tests/lib/cli-engine/file-enumerator.js
index 4372886..3ea51a4 100644
--- a/eslint/tests/lib/cli-engine/file-enumerator.js
+++ b/eslint/tests/lib/cli-engine/file-enumerator.js
@@ -10,30 +10,24 @@ const os = require("os");
const { assert } = require("chai");
const sh = require("shelljs");
const { CascadingConfigArrayFactory } =
- require("../../../lib/cli-engine/cascading-config-array-factory");
-const { defineFileEnumeratorWithInMemoryFileSystem } = require("../../_utils");
+ require("@eslint/eslintrc/lib/cascading-config-array-factory");
+const { createCustomTeardown } = require("../../_utils");
+const { FileEnumerator } = require("../../../lib/cli-engine/file-enumerator");
describe("FileEnumerator", () => {
describe("'iterateFiles(patterns)' method should iterate files and configs.", () => {
describe("with three directories ('lib', 'lib/nested', 'test') that contains 'one.js' and 'two.js'", () => {
const root = path.join(os.tmpdir(), "eslint/file-enumerator");
const files = {
- /* eslint-disable quote-props */
- "lib": {
- "nested": {
- "one.js": "",
- "two.js": "",
- "parser.js": "",
- ".eslintrc.yml": "parser: './parser'"
- },
- "one.js": "",
- "two.js": ""
- },
- "test": {
- "one.js": "",
- "two.js": "",
- ".eslintrc.yml": "env: { mocha: true }"
- },
+ "lib/nested/one.js": "",
+ "lib/nested/two.js": "",
+ "lib/nested/parser.js": "",
+ "lib/nested/.eslintrc.yml": "parser: './parser'",
+ "lib/one.js": "",
+ "lib/two.js": "",
+ "test/one.js": "",
+ "test/two.js": "",
+ "test/.eslintrc.yml": "env: { mocha: true }",
".eslintignore": "/lib/nested/parser.js",
".eslintrc.json": JSON.stringify({
rules: {
@@ -41,17 +35,19 @@ describe("FileEnumerator", () => {
"no-unused-vars": "error"
}
})
- /* eslint-enable quote-props */
};
- const { FileEnumerator } = defineFileEnumeratorWithInMemoryFileSystem({ cwd: () => root, files });
+ const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: root, files });
/** @type {FileEnumerator} */
let enumerator;
- beforeEach(() => {
- enumerator = new FileEnumerator();
+ beforeEach(async () => {
+ await prepare();
+ enumerator = new FileEnumerator({ cwd: getPath() });
});
+ afterEach(cleanup);
+
it("should ignore empty strings.", () => {
Array.from(enumerator.iterateFiles(["lib/*.js", ""])); // don't throw "file not found" error.
});
@@ -177,7 +173,6 @@ describe("FileEnumerator", () => {
// 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", () => {
- const { FileEnumerator } = require("../../../lib/cli-engine/file-enumerator");
let fixtureDir;
/**
@@ -211,7 +206,16 @@ describe("FileEnumerator", () => {
);
}
- before(() => {
+ before(function() {
+
+ /*
+ * GitHub Actions Windows and macOS runners occasionally
+ * exhibit extremely slow filesystem operations, during which
+ * copying fixtures exceeds the default test timeout, so raise
+ * it just for this hook. Mocha uses `this` to set timeouts on
+ * an individual hook level.
+ */
+ this.timeout(60 * 1000); // eslint-disable-line no-invalid-this
fixtureDir = `${os.tmpdir()}/eslint/tests/fixtures/`;
sh.mkdir("-p", fixtureDir);
sh.cp("-r", "./tests/fixtures/*", fixtureDir);
@@ -484,4 +488,31 @@ describe("FileEnumerator", () => {
});
});
});
+
+ // https://github.com/eslint/eslint/issues/13789
+ describe("constructor default values when config extends eslint:recommended", () => {
+ const root = path.join(os.tmpdir(), "eslint/file-enumerator");
+ const files = {
+ "file.js": "",
+ ".eslintrc.json": JSON.stringify({
+ extends: ["eslint:recommended"]
+ })
+ };
+ const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: root, files });
+
+
+ /** @type {FileEnumerator} */
+ let enumerator;
+
+ beforeEach(async () => {
+ await prepare();
+ enumerator = new FileEnumerator({ cwd: getPath() });
+ });
+
+ afterEach(cleanup);
+
+ it("should not throw an exception iterating files", () => {
+ Array.from(enumerator.iterateFiles(["."]));
+ });
+ });
});
diff --git a/eslint/tests/lib/cli-engine/formatters/checkstyle.js b/eslint/tests/lib/cli-engine/formatters/checkstyle.js
index 2a72da1..218b153 100644
--- a/eslint/tests/lib/cli-engine/formatters/checkstyle.js
+++ b/eslint/tests/lib/cli-engine/formatters/checkstyle.js
@@ -151,4 +151,21 @@ describe("formatter:checkstyle", () => {
assert.strictEqual(result, "");
});
});
+
+ describe("when passing single message without line and column", () => {
+ const code = [{
+ filePath: "foo.js",
+ messages: [{
+ message: "Unexpected foo.",
+ severity: 2,
+ ruleId: "foo"
+ }]
+ }];
+
+ it("should return line and column as 0 instead of undefined", () => {
+ const result = formatter(code);
+
+ assert.strictEqual(result, "");
+ });
+ });
});
diff --git a/eslint/tests/lib/cli.js b/eslint/tests/lib/cli.js
index a1d9a23..0f8a24c 100644
--- a/eslint/tests/lib/cli.js
+++ b/eslint/tests/lib/cli.js
@@ -82,7 +82,15 @@ describe("cli", () => {
}
// copy into clean area so as not to get "infected" by this project's .eslintrc files
- before(() => {
+ before(function() {
+
+ /*
+ * GitHub Actions Windows and macOS runners occasionally exhibit
+ * extremely slow filesystem operations, during which copying fixtures
+ * exceeds the default test timeout, so raise it just for this hook.
+ * Mocha uses `this` to set timeouts on an individual hook level.
+ */
+ this.timeout(60 * 1000); // eslint-disable-line no-invalid-this
fixtureDir = `${os.tmpdir()}/eslint/fixtures`;
sh.mkdir("-p", fixtureDir);
sh.cp("-r", "./tests/fixtures/.", fixtureDir);
diff --git a/eslint/tests/lib/eslint/eslint.js b/eslint/tests/lib/eslint/eslint.js
index 6807a57..e4f37f4 100644
--- a/eslint/tests/lib/eslint/eslint.js
+++ b/eslint/tests/lib/eslint/eslint.js
@@ -16,13 +16,12 @@ const os = require("os");
const path = require("path");
const escapeStringRegExp = require("escape-string-regexp");
const fCache = require("file-entry-cache");
-const leche = require("leche");
const sinon = require("sinon");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const shell = require("shelljs");
-const { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory");
+const { CascadingConfigArrayFactory } = require("@eslint/eslintrc/lib/cascading-config-array-factory");
const hash = require("../../../lib/cli-engine/hash");
-const { unIndent, defineESLintWithInMemoryFileSystem } = require("../../_utils");
+const { unIndent, createCustomTeardown } = require("../../_utils");
//------------------------------------------------------------------------------
// Tests
@@ -87,7 +86,15 @@ describe("ESLint", () => {
}
// copy into clean area so as not to get "infected" by this project's .eslintrc files
- before(() => {
+ before(function() {
+
+ /*
+ * GitHub Actions Windows and macOS runners occasionally exhibit
+ * extremely slow filesystem operations, during which copying fixtures
+ * exceeds the default test timeout, so raise it just for this hook.
+ * Mocha uses `this` to set timeouts on an individual hook level.
+ */
+ this.timeout(60 * 1000); // eslint-disable-line no-invalid-this
shell.mkdir("-p", fixtureDir);
shell.cp("-r", "./tests/fixtures/.", fixtureDir);
});
@@ -900,7 +907,7 @@ describe("ESLint", () => {
overrideConfig: {
parser: "espree",
parserOptions: {
- ecmaVersion: 2020
+ ecmaVersion: 2021
}
},
useEslintrc: false
@@ -2938,26 +2945,31 @@ describe("ESLint", () => {
});
describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => {
- beforeEach(() => {
- ({ ESLint } = defineESLintWithInMemoryFileSystem({
- cwd: () => path.join(os.tmpdir(), "eslint/11510"),
- files: {
- "no-console-error-in-overrides.json": JSON.stringify({
- overrides: [{
- files: ["*.js"],
- rules: { "no-console": "error" }
- }]
- }),
- ".eslintrc.json": JSON.stringify({
- extends: "./no-console-error-in-overrides.json",
- rules: { "no-console": "off" }
- }),
- "a.js": "console.log();"
- }
- }));
- eslint = new ESLint();
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: path.join(os.tmpdir(), "eslint/11510"),
+ files: {
+ "no-console-error-in-overrides.json": JSON.stringify({
+ overrides: [{
+ files: ["*.js"],
+ rules: { "no-console": "error" }
+ }]
+ }),
+ ".eslintrc.json": JSON.stringify({
+ extends: "./no-console-error-in-overrides.json",
+ rules: { "no-console": "off" }
+ }),
+ "a.js": "console.log();"
+ }
});
+ beforeEach(() => {
+ eslint = new ESLint({ cwd: getPath() });
+ return prepare();
+ });
+
+ afterEach(cleanup);
+
it("should not report 'no-console' error.", async () => {
const results = await eslint.lintFiles("a.js");
@@ -2967,11 +2979,11 @@ describe("ESLint", () => {
});
describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", () => {
- beforeEach(() => {
- ({ ESLint } = defineESLintWithInMemoryFileSystem({
- cwd: () => path.join(os.tmpdir(), "eslint/11559"),
- files: {
- "node_modules/eslint-plugin-test/index.js": `
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: path.join(os.tmpdir(), "eslint/11559"),
+ files: {
+ "node_modules/eslint-plugin-test/index.js": `
exports.configs = {
recommended: { plugins: ["test"] }
};
@@ -2982,20 +2994,26 @@ describe("ESLint", () => {
}
};
`,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": JSON.stringify({
- // Import via the recommended config.
- extends: "plugin:test/recommended",
+ // Import via the recommended config.
+ extends: "plugin:test/recommended",
- // Has invalid option.
- rules: { "test/foo": ["error", "invalid-option"] }
- }),
- "a.js": "console.log();"
- }
- }));
- eslint = new ESLint();
+ // Has invalid option.
+ rules: { "test/foo": ["error", "invalid-option"] }
+ }),
+ "a.js": "console.log();"
+ }
});
+ beforeEach(() => {
+ eslint = new ESLint({ cwd: getPath() });
+ return prepare();
+ });
+
+ afterEach(cleanup);
+
+
it("should throw fatal error.", async () => {
await assert.rejects(async () => {
await eslint.lintFiles("a.js");
@@ -3004,11 +3022,10 @@ describe("ESLint", () => {
});
describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => {
- beforeEach(() => {
- ({ ESLint } = defineESLintWithInMemoryFileSystem({
- cwd: () => path.join(os.tmpdir(), "eslint/11586"),
- files: {
- "node_modules/eslint-plugin-test/index.js": `
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: path.join(os.tmpdir(), "cli-engine/11586"),
+ files: {
+ "node_modules/eslint-plugin-test/index.js": `
exports.rules = {
"no-example": {
meta: { type: "problem", fixable: "code" },
@@ -3028,16 +3045,26 @@ describe("ESLint", () => {
}
};
`,
- ".eslintrc.json": JSON.stringify({
- plugins: ["test"],
- rules: { "test/no-example": "error" }
- }),
- "a.js": "example;"
- }
- }));
- eslint = new ESLint({ fix: true, fixTypes: ["problem"] });
+ ".eslintrc.json": {
+ plugins: ["test"],
+ rules: { "test/no-example": "error" }
+ },
+ "a.js": "example;"
+ }
});
+ beforeEach(() => {
+ eslint = new ESLint({
+ cwd: getPath(),
+ fix: true,
+ fixTypes: ["problem"]
+ });
+
+ return prepare();
+ });
+
+ afterEach(cleanup);
+
it("should not crash.", async () => {
const results = await eslint.lintFiles("a.js");
@@ -3088,18 +3115,29 @@ describe("ESLint", () => {
`
};
+ let cleanup;
+
+ beforeEach(() => {
+ cleanup = () => { };
+ });
+
+ afterEach(() => cleanup());
+
it("should lint only JavaScript blocks if '--ext' was not given.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" }
- })
+ }
}
- }).ESLint;
- eslint = new ESLint({ cwd: root });
+ });
+
+ cleanup = teardown.cleanup;
+ await teardown.prepare();
+ eslint = new ESLint({ cwd: teardown.getPath() });
const results = await eslint.lintFiles(["test.md"]);
assert.strictEqual(results.length, 1);
@@ -3109,17 +3147,20 @@ describe("ESLint", () => {
});
it("should fix only JavaScript blocks if '--ext' was not given.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" }
- })
+ }
}
- }).ESLint;
- eslint = new ESLint({ cwd: root, fix: true });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath(), fix: true });
const results = await eslint.lintFiles(["test.md"]);
assert.strictEqual(results.length, 1);
@@ -3141,17 +3182,20 @@ describe("ESLint", () => {
});
it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" }
- })
+ }
}
- }).ESLint;
- eslint = new ESLint({ cwd: root, extensions: ["js", "html"] });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] });
const results = await eslint.lintFiles(["test.md"]);
assert.strictEqual(results.length, 1);
@@ -3163,17 +3207,20 @@ describe("ESLint", () => {
});
it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" }
- })
+ }
}
- }).ESLint;
- eslint = new ESLint({ cwd: root, extensions: ["js", "html"], fix: true });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true });
const results = await eslint.lintFiles(["test.md"]);
assert.strictEqual(results.length, 1);
@@ -3194,12 +3241,12 @@ describe("ESLint", () => {
`);
});
- it("should use overriden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => {
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" },
overrides: [
@@ -3208,10 +3255,13 @@ describe("ESLint", () => {
processor: "html/non-fixable" // supportsAutofix: false
}
]
- })
+ }
}
- }).ESLint;
- eslint = new ESLint({ cwd: root, extensions: ["js", "html"], fix: true });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true });
const results = await eslint.lintFiles(["test.md"]);
assert.strictEqual(results.length, 1);
@@ -3236,11 +3286,11 @@ describe("ESLint", () => {
});
it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" },
overrides: [
@@ -3261,10 +3311,13 @@ describe("ESLint", () => {
}
}
]
- })
+ }
}
- }).ESLint;
- eslint = new ESLint({ cwd: root, extensions: ["js", "html"] });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] });
const results = await eslint.lintFiles(["test.md"]);
assert.strictEqual(results.length, 1);
@@ -3276,11 +3329,11 @@ describe("ESLint", () => {
});
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 () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" },
overrides: [
@@ -3300,10 +3353,13 @@ describe("ESLint", () => {
}
}
]
- })
+ }
}
- }).ESLint;
- eslint = new ESLint({ cwd: root, extensions: ["js", "html"] });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] });
const results = await eslint.lintFiles(["test.md"]);
assert.strictEqual(results.length, 1);
@@ -3317,17 +3373,20 @@ describe("ESLint", () => {
});
it("should throw an error if invalid processor was specified.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
processor: "markdown/unknown"
- })
+ }
}
- }).ESLint;
- eslint = new ESLint({ cwd: root });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath() });
await assert.rejects(async () => {
await eslint.lintFiles(["test.md"]);
@@ -3335,11 +3394,11 @@ describe("ESLint", () => {
});
it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
...commonFiles,
- ".eslintrc.json": JSON.stringify({
+ ".eslintrc.json": {
plugins: ["markdown", "html"],
rules: { semi: "error" },
overrides: [
@@ -3352,10 +3411,13 @@ describe("ESLint", () => {
processor: "markdown/.md"
}
]
- })
+ }
}
- }).ESLint;
- eslint = new ESLint({ cwd: root });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath() });
const results = await eslint.lintFiles(["test.md"]);
assert.strictEqual(results.length, 1);
@@ -3466,27 +3528,33 @@ describe("ESLint", () => {
});
describe("with '--rulesdir' option", () => {
- it("should use the configured rules which are defined by '--rulesdir' option.", async () => {
- const rootPath = getFixturePath("cli-engine/with-rulesdir");
- const StubbedESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => rootPath,
- files: {
- "internal-rules/test.js": `
+
+ const rootPath = getFixturePath("cli-engine/with-rulesdir");
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: rootPath,
+ files: {
+ "internal-rules/test.js": `
module.exports = context => ({
ExpressionStatement(node) {
context.report({ node, message: "ok" })
}
})
`,
- ".eslintrc.json": JSON.stringify({
- root: true,
- rules: { test: "error" }
- }),
- "test.js": "console.log('hello')"
- }
- }).ESLint;
+ ".eslintrc.json": {
+ root: true,
+ rules: { test: "error" }
+ },
+ "test.js": "console.log('hello')"
+ }
+ });
- eslint = new StubbedESLint({
+ beforeEach(prepare);
+ afterEach(cleanup);
+
+
+ it("should use the configured rules which are defined by '--rulesdir' option.", async () => {
+ eslint = new ESLint({
+ cwd: getPath(),
rulePaths: ["internal-rules"]
});
const results = await eslint.lintFiles(["test.js"]);
@@ -3500,9 +3568,18 @@ describe("ESLint", () => {
describe("glob pattern '[ab].js'", () => {
const root = getFixturePath("cli-engine/unmatched-glob");
+ let cleanup;
+
+ beforeEach(() => {
+ cleanup = () => { };
+ });
+
+ afterEach(() => cleanup());
+
it("should match '[ab].js' if existed.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"a.js": "",
"b.js": "",
@@ -3510,8 +3587,12 @@ describe("ESLint", () => {
"[ab].js": "",
".eslintrc.yml": "root: true"
}
- }).ESLint;
- eslint = new ESLint();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+
+ eslint = new ESLint({ cwd: teardown.getPath() });
const results = await eslint.lintFiles(["[ab].js"]);
const filenames = results.map(r => path.basename(r.filePath));
@@ -3519,16 +3600,19 @@ describe("ESLint", () => {
});
it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"a.js": "",
"b.js": "",
"ab.js": "",
".eslintrc.yml": "root: true"
}
- }).ESLint;
- eslint = new ESLint();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath() });
const results = await eslint.lintFiles(["[ab].js"]);
const filenames = results.map(r => path.basename(r.filePath));
@@ -3539,15 +3623,27 @@ describe("ESLint", () => {
describe("with 'noInlineConfig' setting", () => {
const root = getFixturePath("cli-engine/noInlineConfig");
+ let cleanup;
+
+ beforeEach(() => {
+ cleanup = () => { };
+ });
+
+ afterEach(() => cleanup());
+
it("should warn directive comments if 'noInlineConfig' was given.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"test.js": "/* globals foo */",
".eslintrc.yml": "noInlineConfig: true"
}
- }).ESLint;
- eslint = new ESLint();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath() });
+
const results = await eslint.lintFiles(["test.js"]);
const messages = results[0].messages;
@@ -3556,15 +3652,19 @@ describe("ESLint", () => {
});
it("should show the config file what the 'noInlineConfig' came from.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"node_modules/eslint-config-foo/index.js": "module.exports = {noInlineConfig: true}",
"test.js": "/* globals foo */",
".eslintrc.yml": "extends: foo"
}
- }).ESLint;
- eslint = new ESLint();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath() });
+
const results = await eslint.lintFiles(["test.js"]);
const messages = results[0].messages;
@@ -3576,15 +3676,28 @@ describe("ESLint", () => {
describe("with 'reportUnusedDisableDirectives' setting", () => {
const root = getFixturePath("cli-engine/reportUnusedDisableDirectives");
+ let cleanup;
+
+ beforeEach(() => {
+ cleanup = () => { };
+ });
+
+ afterEach(() => cleanup());
+
it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"test.js": "/* eslint-disable eqeqeq */",
".eslintrc.yml": "reportUnusedDisableDirectives: true"
}
- }).ESLint;
- eslint = new ESLint();
+ });
+
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath() });
+
const results = await eslint.lintFiles(["test.js"]);
const messages = results[0].messages;
@@ -3595,14 +3708,22 @@ describe("ESLint", () => {
describe("the runtime option overrides config files.", () => {
it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"test.js": "/* eslint-disable eqeqeq */",
".eslintrc.yml": "reportUnusedDisableDirectives: true"
}
- }).ESLint;
- eslint = new ESLint({ reportUnusedDisableDirectives: "off" });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+
+ eslint = new ESLint({
+ cwd: teardown.getPath(),
+ reportUnusedDisableDirectives: "off"
+ });
+
const results = await eslint.lintFiles(["test.js"]);
const messages = results[0].messages;
@@ -3610,14 +3731,22 @@ describe("ESLint", () => {
});
it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"test.js": "/* eslint-disable eqeqeq */",
".eslintrc.yml": "reportUnusedDisableDirectives: true"
}
- }).ESLint;
- eslint = new ESLint({ reportUnusedDisableDirectives: "error" });
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+
+ eslint = new ESLint({
+ cwd: teardown.getPath(),
+ reportUnusedDisableDirectives: "error"
+ });
+
const results = await eslint.lintFiles(["test.js"]);
const messages = results[0].messages;
@@ -3630,25 +3759,28 @@ describe("ESLint", () => {
describe("with 'overrides[*].extends' setting on deep locations", () => {
const root = getFixturePath("cli-engine/deeply-overrides-i-extends");
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ overrides: [{ files: ["*test*"], extends: "two" }]
+ })}`,
+ "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+ overrides: [{ files: ["*.js"], extends: "three" }]
+ })}`,
+ "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({
+ rules: { "no-console": "error" }
+ })}`,
+ "test.js": "console.log('hello')",
+ ".eslintrc.yml": "extends: one"
+ }
+ });
+
+ beforeEach(prepare);
+ afterEach(cleanup);
it("should not throw.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- overrides: [{ files: ["*test*"], extends: "two" }]
- })}`,
- "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
- overrides: [{ files: ["*.js"], extends: "three" }]
- })}`,
- "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({
- rules: { "no-console": "error" }
- })}`,
- "test.js": "console.log('hello')",
- ".eslintrc.yml": "extends: one"
- }
- }).ESLint;
- eslint = new ESLint();
+ eslint = new ESLint({ cwd: getPath() });
const results = await eslint.lintFiles(["test.js"]);
const messages = results[0].messages;
@@ -3660,46 +3792,71 @@ describe("ESLint", () => {
describe("don't ignore the entry directory.", () => {
const root = getFixturePath("cli-engine/dont-ignore-entry-dir");
+ let cleanup;
+
+ beforeEach(() => {
+ cleanup = () => { };
+ });
+
+ afterEach(async () => {
+ await cleanup();
+
+ const configFilePath = path.resolve(root, "../.eslintrc.json");
+
+ if (shell.test("-e", configFilePath)) {
+ shell.rm(configFilePath);
+ }
+ });
+
it("'lintFiles(\".\")' should not load config files from outside of \".\".", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
"../.eslintrc.json": "BROKEN FILE",
".eslintrc.json": JSON.stringify({ root: true }),
"index.js": "console.log(\"hello\")"
}
- }).ESLint;
- eslint = new ESLint();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath() });
// Don't throw "failed to load config file" error.
await eslint.lintFiles(".");
});
it("'lintFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
- "../.eslintrc.json": JSON.stringify({ ignorePatterns: ["/dont-ignore-entry-dir"] }),
- ".eslintrc.json": JSON.stringify({ root: true }),
+ "../.eslintrc.json": { ignorePatterns: ["/dont-ignore-entry-dir"] },
+ ".eslintrc.json": { root: true },
"index.js": "console.log(\"hello\")"
}
- }).ESLint;
- eslint = new ESLint();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath() });
// Don't throw "file not found" error.
await eslint.lintFiles(".");
});
it("'lintFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => {
- ESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
+ const teardown = createCustomTeardown({
+ cwd: root,
files: {
- ".eslintrc.json": JSON.stringify({ ignorePatterns: ["/subdir"] }),
- "subdir/.eslintrc.json": JSON.stringify({ root: true }),
+ ".eslintrc.json": { ignorePatterns: ["/subdir"] },
+ "subdir/.eslintrc.json": { root: true },
"subdir/index.js": "console.log(\"hello\")"
}
- }).ESLint;
- eslint = new ESLint();
+ });
+
+ await teardown.prepare();
+ cleanup = teardown.cleanup;
+ eslint = new ESLint({ cwd: teardown.getPath() });
// Don't throw "file not found" error.
await eslint.lintFiles("subdir");
@@ -3762,6 +3919,26 @@ describe("ESLint", () => {
await assert.rejects(() => eslint.calculateConfigForFile(null), /'filePath' must be a non-empty string/u);
});
+
+ // https://github.com/eslint/eslint/issues/13793
+ it("should throw with an invalid built-in rule config", async () => {
+ const options = {
+ baseConfig: {
+ rules: {
+ "no-alert": ["error", {
+ thisDoesNotExist: true
+ }]
+ }
+ }
+ };
+ const engine = new ESLint(options);
+ const filePath = getFixturePath("single-quoted.js");
+
+ await assert.rejects(
+ () => engine.calculateConfigForFile(filePath),
+ /Configuration for rule "no-alert" is invalid:/u
+ );
+ });
});
describe("isPathIgnored", () => {
@@ -4427,11 +4604,14 @@ describe("ESLint", () => {
});
it("should call fs.writeFile() for each result with output", async () => {
- const fakeFS = leche.fake(fs);
- const spy = fakeFS.writeFile = sinon.spy(callLastArgument);
- const localESLint = proxyquire("../../../lib/eslint/eslint", {
+ const fakeFS = {
+ writeFile: sinon.spy(callLastArgument)
+ };
+ const spy = fakeFS.writeFile;
+ const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", {
fs: fakeFS
- }).ESLint;
+ });
+
const results = [
{
filePath: path.resolve("foo.js"),
@@ -4451,11 +4631,13 @@ describe("ESLint", () => {
});
it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => {
- const fakeFS = leche.fake(fs);
- const spy = fakeFS.writeFile = sinon.spy(callLastArgument);
- const localESLint = proxyquire("../../../lib/eslint/eslint", {
+ const fakeFS = {
+ writeFile: sinon.spy(callLastArgument)
+ };
+ const spy = fakeFS.writeFile;
+ const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", {
fs: fakeFS
- }).ESLint;
+ });
const results = [
{
filePath: path.resolve("foo.js"),
@@ -4627,41 +4809,39 @@ describe("ESLint", () => {
describe("with ignorePatterns config", () => {
const root = getFixturePath("cli-engine/ignore-patterns");
- /** @type {typeof ESLint} */
- let InMemoryESLint;
-
describe("ignorePatterns can add an ignore pattern ('foo.js').", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "foo.js"
- }),
- "foo.js": "",
- "bar.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": {
+ ignorePatterns: "foo.js"
+ },
+ "foo.js": "",
+ "bar.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false);
});
it("'lintFiles()' should not verify 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -4674,39 +4854,40 @@ describe("ESLint", () => {
});
describe("ignorePatterns can add ignore patterns ('foo.js', '/bar.js').", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: ["foo.js", "/bar.js"]
- }),
- "foo.js": "",
- "bar.js": "",
- "baz.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": "",
- "subdir/baz.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": {
+ ignorePatterns: ["foo.js", "/bar.js"]
+ },
+ "foo.js": "",
+ "bar.js": "",
+ "baz.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": "",
+ "subdir/baz.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true);
});
it("'isPathIgnored()' should return 'true' for '/bar.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("bar.js"), true);
assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false);
});
it("'lintFiles()' should not verify 'foo.js' and '/bar.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -4720,41 +4901,43 @@ describe("ESLint", () => {
});
describe("ignorePatterns can unignore '/node_modules/foo'.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "!/node_modules/foo"
- }),
- "node_modules/foo/index.js": "",
- "node_modules/foo/.dot.js": "",
- "node_modules/bar/index.js": "",
- "foo.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": {
+ ignorePatterns: "!/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 InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false);
});
it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), true);
});
it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ 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 InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -4767,26 +4950,28 @@ describe("ESLint", () => {
});
describe("ignorePatterns can unignore '.eslintrc.js'.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "!.eslintrc.js"
- })}`,
- "foo.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "!.eslintrc.js"
+ })}`,
+ "foo.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored(".eslintrc.js"), false);
});
it("'lintFiles()' should verify '.eslintrc.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -4799,34 +4984,35 @@ describe("ESLint", () => {
});
describe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "!.*"
- })}`,
- ".eslintignore": ".foo*",
- ".foo.js": "",
- ".bar.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "!.*"
+ })}`,
+ ".eslintignore": ".foo*",
+ ".foo.js": "",
+ ".bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored(".foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored(".bar.js"), false);
});
it("'lintFiles()' should not verify re-ignored '.foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -4839,34 +5025,35 @@ describe("ESLint", () => {
});
describe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "*.js"
- })}`,
- ".eslintignore": "!foo.js",
- "foo.js": "",
- "bar.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "*.js"
+ })}`,
+ ".eslintignore": "!foo.js",
+ "foo.js": "",
+ "bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), false);
});
it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("bar.js"), true);
});
it("'lintFiles()' should verify unignored 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -4878,28 +5065,30 @@ describe("ESLint", () => {
});
describe("ignorePatterns in the config file in a child directory affects to only in the directory.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "foo.js"
- }),
- "subdir/.eslintrc.json": JSON.stringify({
- ignorePatterns: "bar.js"
- }),
- "foo.js": "",
- "bar.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": "",
- "subdir/subsubdir/foo.js": "",
- "subdir/subsubdir/bar.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ ignorePatterns: "foo.js"
+ }),
+ "subdir/.eslintrc.json": JSON.stringify({
+ ignorePatterns: "bar.js"
+ }),
+ "foo.js": "",
+ "bar.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": "",
+ "subdir/subsubdir/foo.js": "",
+ "subdir/subsubdir/bar.js": ""
+ }
});
+
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true);
@@ -4907,20 +5096,20 @@ describe("ESLint", () => {
});
it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true);
assert.strictEqual(await engine.isPathIgnored("subdir/subsubdir/bar.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
});
it("'lintFiles()' should verify 'bar.js' in the outside of 'subdir'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -4932,36 +5121,37 @@ describe("ESLint", () => {
});
describe("ignorePatterns in the config file in a child directory can unignore the ignored files in the parent directory's config.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "foo.js"
- }),
- "subdir/.eslintrc.json": JSON.stringify({
- ignorePatterns: "!foo.js"
- }),
- "foo.js": "",
- "subdir/foo.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ ignorePatterns: "foo.js"
+ }),
+ "subdir/.eslintrc.json": JSON.stringify({
+ ignorePatterns: "!foo.js"
+ }),
+ "foo.js": "",
+ "subdir/foo.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false);
});
it("'lintFiles()' should verify 'foo.js' in the child directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -4973,37 +5163,38 @@ describe("ESLint", () => {
});
describe(".eslintignore can unignore files that are ignored by ignorePatterns in the config file in the child directory.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({}),
- "subdir/.eslintrc.json": JSON.stringify({
- ignorePatterns: "*.js"
- }),
- ".eslintignore": "!foo.js",
- "foo.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({}),
+ "subdir/.eslintrc.json": JSON.stringify({
+ ignorePatterns: "*.js"
+ }),
+ ".eslintignore": "!foo.js",
+ "foo.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), false);
assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false);
});
it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true);
});
it("'lintFiles()' should verify unignored 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -5016,51 +5207,52 @@ describe("ESLint", () => {
});
describe("if the config in a child directory has 'root:true', ignorePatterns in the config file in the parent directory should not be used.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "foo.js"
- }),
- "subdir/.eslintrc.json": JSON.stringify({
- root: true,
- ignorePatterns: "bar.js"
- }),
- "foo.js": "",
- "bar.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ ignorePatterns: "foo.js"
+ }),
+ "subdir/.eslintrc.json": JSON.stringify({
+ root: true,
+ ignorePatterns: "bar.js"
+ }),
+ "foo.js": "",
+ "bar.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
});
it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false);
});
it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true);
});
it("'lintFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -5073,45 +5265,46 @@ describe("ESLint", () => {
});
describe("even if the config in a child directory has 'root:true', .eslintignore should be used.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({}),
- "subdir/.eslintrc.json": JSON.stringify({
- root: true,
- ignorePatterns: "bar.js"
- }),
- ".eslintignore": "foo.js",
- "foo.js": "",
- "bar.js": "",
- "subdir/foo.js": "",
- "subdir/bar.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({}),
+ "subdir/.eslintrc.json": JSON.stringify({
+ root: true,
+ ignorePatterns: "bar.js"
+ }),
+ ".eslintignore": "foo.js",
+ "foo.js": "",
+ "bar.js": "",
+ "subdir/foo.js": "",
+ "subdir/bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
});
it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true);
});
it("'lintFiles()' should verify 'bar.js' in the root directory.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -5123,36 +5316,37 @@ describe("ESLint", () => {
});
describe("ignorePatterns in the shareable config should be used.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "foo.js"
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: "one"
- }),
- "foo.js": "",
- "bar.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "foo.js"
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: "one"
+ }),
+ "foo.js": "",
+ "bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
});
it("'lintFiles()' should verify 'bar.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -5164,36 +5358,38 @@ describe("ESLint", () => {
});
describe("ignorePatterns in the shareable config should be relative to the entry config file.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "/foo.js"
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: "one"
- }),
- "foo.js": "",
- "subdir/foo.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "/foo.js"
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: "one"
+ }),
+ "foo.js": "",
+ "subdir/foo.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false);
});
it("'lintFiles()' should verify 'subdir/foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -5205,37 +5401,38 @@ describe("ESLint", () => {
});
describe("ignorePatterns in a config file can unignore the files which are ignored by ignorePatterns in the shareable config.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- ignorePatterns: "*.js"
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: "one",
- ignorePatterns: "!bar.js"
- }),
- "foo.js": "",
- "bar.js": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ ignorePatterns: "*.js"
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: "one",
+ ignorePatterns: "!bar.js"
+ }),
+ "foo.js": "",
+ "bar.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
});
it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
});
it("'lintFiles()' should verify 'bar.js'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -5247,26 +5444,28 @@ describe("ESLint", () => {
});
describe("ignorePatterns in a config file should not be used if --no-ignore option was given.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- ignorePatterns: "*.js"
- }),
- "foo.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ ignorePatterns: "*.js"
+ }),
+ "foo.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'isPathIgnored()' should return 'false' for 'foo.js'.", async () => {
- const engine = new InMemoryESLint({ ignore: false });
+ const engine = new ESLint({ cwd: getPath(), ignore: false });
assert.strictEqual(await engine.isPathIgnored("foo.js"), false);
});
it("'lintFiles()' should verify 'foo.js'.", async () => {
- const engine = new InMemoryESLint({ ignore: false });
+ const engine = new ESLint({ cwd: getPath(), ignore: false });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -5278,26 +5477,28 @@ describe("ESLint", () => {
});
describe("ignorePatterns in overrides section is not allowed.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.js": `module.exports = ${JSON.stringify({
- overrides: [
- {
- files: "*.js",
- ignorePatterns: "foo.js"
- }
- ]
- })}`,
- "foo.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.js": `module.exports = ${JSON.stringify({
+ overrides: [
+ {
+ files: "*.js",
+ ignorePatterns: "foo.js"
+ }
+ ]
+ })}`,
+ "foo.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("should throw a configuration error.", async () => {
await assert.rejects(async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
await engine.lintFiles("*.js");
}, /Unexpected top-level property "overrides\[0\]\.ignorePatterns"/u);
@@ -5307,37 +5508,38 @@ describe("ESLint", () => {
describe("'overrides[].files' adds lint targets", () => {
const root = getFixturePath("cli-engine/additional-lint-targets");
- let InMemoryESLint;
+
describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- overrides: [
- {
- files: "foo/*.txt",
- excludedFiles: "**/ignore.txt"
- }
- ]
- }),
- "foo/nested/test.txt": "",
- "foo/test.js": "",
- "foo/test.txt": "",
- "foo/ignore.txt": "",
- "bar/test.js": "",
- "bar/test.txt": "",
- "bar/ignore.txt": "",
- "test.js": "",
- "test.txt": "",
- "ignore.txt": ""
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ overrides: [
+ {
+ files: "foo/*.txt",
+ excludedFiles: "**/ignore.txt"
+ }
+ ]
+ }),
+ "foo/nested/test.txt": "",
+ "foo/test.js": "",
+ "foo/test.txt": "",
+ "foo/ignore.txt": "",
+ "bar/test.js": "",
+ "bar/test.txt": "",
+ "bar/ignore.txt": "",
+ "test.js": "",
+ "test.txt": "",
+ "ignore.txt": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("."))
.map(r => r.filePath)
.sort();
@@ -5351,7 +5553,7 @@ describe("ESLint", () => {
});
it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("**/*.js"))
.map(r => r.filePath)
.sort();
@@ -5365,30 +5567,32 @@ describe("ESLint", () => {
});
describe("if { files: 'foo/**/*.txt' } is present,", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- overrides: [
- {
- files: "foo/**/*.txt"
- }
- ]
- }),
- "foo/nested/test.txt": "",
- "foo/test.js": "",
- "foo/test.txt": "",
- "bar/test.js": "",
- "bar/test.txt": "",
- "test.js": "",
- "test.txt": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ overrides: [
+ {
+ files: "foo/**/*.txt"
+ }
+ ]
+ }),
+ "foo/nested/test.txt": "",
+ "foo/test.js": "",
+ "foo/test.txt": "",
+ "bar/test.js": "",
+ "bar/test.txt": "",
+ "test.js": "",
+ "test.txt": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("."))
.map(r => r.filePath)
.sort();
@@ -5404,30 +5608,32 @@ describe("ESLint", () => {
});
describe("if { files: 'foo/**/*' } is present,", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- ".eslintrc.json": JSON.stringify({
- overrides: [
- {
- files: "foo/**/*"
- }
- ]
- }),
- "foo/nested/test.txt": "",
- "foo/test.js": "",
- "foo/test.txt": "",
- "bar/test.js": "",
- "bar/test.txt": "",
- "test.js": "",
- "test.txt": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ ".eslintrc.json": JSON.stringify({
+ overrides: [
+ {
+ files: "foo/**/*"
+ }
+ ]
+ }),
+ "foo/nested/test.txt": "",
+ "foo/test.js": "",
+ "foo/test.txt": "",
+ "bar/test.js": "",
+ "bar/test.txt": "",
+ "test.js": "",
+ "test.txt": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("."))
.map(r => r.filePath)
.sort();
@@ -5441,33 +5647,35 @@ describe("ESLint", () => {
});
describe("if { files: 'foo/**/*.txt' } is present in a shareable config,", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({
- overrides: [
- {
- files: "foo/**/*.txt"
- }
- ]
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: "foo"
- }),
- "foo/nested/test.txt": "",
- "foo/test.js": "",
- "foo/test.txt": "",
- "bar/test.js": "",
- "bar/test.txt": "",
- "test.js": "",
- "test.txt": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({
+ overrides: [
+ {
+ files: "foo/**/*.txt"
+ }
+ ]
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: "foo"
+ }),
+ "foo/nested/test.txt": "",
+ "foo/test.js": "",
+ "foo/test.txt": "",
+ "bar/test.js": "",
+ "bar/test.txt": "",
+ "test.js": "",
+ "test.txt": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("."))
.map(r => r.filePath)
.sort();
@@ -5483,35 +5691,37 @@ describe("ESLint", () => {
});
describe("if { files: 'foo/**/*.txt' } is present in a plugin config,", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({
- bar: {
- overrides: [
- {
- files: "foo/**/*.txt"
- }
- ]
- }
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: "plugin:foo/bar"
- }),
- "foo/nested/test.txt": "",
- "foo/test.js": "",
- "foo/test.txt": "",
- "bar/test.js": "",
- "bar/test.txt": "",
- "test.js": "",
- "test.txt": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({
+ bar: {
+ overrides: [
+ {
+ files: "foo/**/*.txt"
+ }
+ ]
+ }
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: "plugin:foo/bar"
+ }),
+ "foo/nested/test.txt": "",
+ "foo/test.js": "",
+ "foo/test.txt": "",
+ "bar/test.js": "",
+ "bar/test.txt": "",
+ "test.js": "",
+ "test.txt": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => {
- const engine = new InMemoryESLint();
+ const engine = new ESLint({ cwd: getPath() });
const filePaths = (await engine.lintFiles("."))
.map(r => r.filePath)
.sort();
@@ -5530,34 +5740,32 @@ describe("ESLint", () => {
describe("'ignorePatterns', 'overrides[].files', and 'overrides[].excludedFiles' of the configuration that the '--config' option provided should be resolved from CWD.", () => {
const root = getFixturePath("cli-engine/config-and-overrides-files");
- /** @type {ESLint} */
- let InMemoryESLint;
-
describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/myconf/.eslintrc.json": JSON.stringify({
- overrides: [
- {
- files: "foo/*.js",
- rules: {
- eqeqeq: "error"
- }
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/myconf/.eslintrc.json": {
+ overrides: [
+ {
+ files: "foo/*.js",
+ rules: {
+ eqeqeq: "error"
}
- ]
- }),
- "node_modules/myconf/foo/test.js": "a == b",
- "foo/test.js": "a == b"
- }
- }).ESLint;
+ }
+ ]
+ },
+ "node_modules/myconf/foo/test.js": "a == b",
+ "foo/test.js": "a == b"
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' with 'foo/test.js' should use the override entry.", async () => {
- const engine = new InMemoryESLint({
+ const engine = new ESLint({
overrideConfigFile: "node_modules/myconf/.eslintrc.json",
- cwd: root,
+ cwd: getPath(),
ignore: false,
useEslintrc: false
});
@@ -5567,7 +5775,7 @@ describe("ESLint", () => {
assert.deepStrictEqual(results, [
{
errorCount: 1,
- filePath: path.join(root, "foo/test.js"),
+ filePath: path.join(getPath(), "foo/test.js"),
fixableErrorCount: 0,
fixableWarningCount: 0,
messages: [
@@ -5591,7 +5799,7 @@ describe("ESLint", () => {
});
it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", async () => {
- const engine = new InMemoryESLint({
+ const engine = new ESLint({
overrideConfigFile: "node_modules/myconf/.eslintrc.json",
cwd: root,
ignore: false,
@@ -5603,7 +5811,7 @@ describe("ESLint", () => {
assert.deepStrictEqual(results, [
{
errorCount: 0,
- filePath: path.join(root, "node_modules/myconf/foo/test.js"),
+ filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"),
fixableErrorCount: 0,
fixableWarningCount: 0,
messages: [],
@@ -5615,29 +5823,30 @@ describe("ESLint", () => {
});
describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/myconf/.eslintrc.json": JSON.stringify({
- overrides: [
- {
- files: "*",
- excludedFiles: "foo/*.js",
- rules: {
- eqeqeq: "error"
- }
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/myconf/.eslintrc.json": JSON.stringify({
+ overrides: [
+ {
+ files: "*",
+ excludedFiles: "foo/*.js",
+ rules: {
+ eqeqeq: "error"
}
- ]
- }),
- "node_modules/myconf/foo/test.js": "a == b",
- "foo/test.js": "a == b"
- }
- }).ESLint;
+ }
+ ]
+ }),
+ "node_modules/myconf/foo/test.js": "a == b",
+ "foo/test.js": "a == b"
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' with 'foo/test.js' should NOT use the override entry.", async () => {
- const engine = new InMemoryESLint({
+ const engine = new ESLint({
overrideConfigFile: "node_modules/myconf/.eslintrc.json",
cwd: root,
ignore: false,
@@ -5649,7 +5858,7 @@ describe("ESLint", () => {
assert.deepStrictEqual(results, [
{
errorCount: 0,
- filePath: path.join(root, "foo/test.js"),
+ filePath: path.join(getPath(), "foo/test.js"),
fixableErrorCount: 0,
fixableWarningCount: 0,
messages: [],
@@ -5660,7 +5869,7 @@ describe("ESLint", () => {
});
it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", async () => {
- const engine = new InMemoryESLint({
+ const engine = new ESLint({
overrideConfigFile: "node_modules/myconf/.eslintrc.json",
cwd: root,
ignore: false,
@@ -5672,7 +5881,7 @@ describe("ESLint", () => {
assert.deepStrictEqual(results, [
{
errorCount: 1,
- filePath: path.join(root, "node_modules/myconf/foo/test.js"),
+ filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"),
fixableErrorCount: 0,
fixableWarningCount: 0,
messages: [
@@ -5697,26 +5906,27 @@ describe("ESLint", () => {
});
describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/myconf/.eslintrc.json": JSON.stringify({
- ignorePatterns: ["!/node_modules/myconf", "foo/*.js"],
- rules: {
- eqeqeq: "error"
- }
- }),
- "node_modules/myconf/foo/test.js": "a == b",
- "foo/test.js": "a == b"
- }
- }).ESLint;
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: root,
+ files: {
+ "node_modules/myconf/.eslintrc.json": JSON.stringify({
+ ignorePatterns: ["!/node_modules/myconf", "foo/*.js"],
+ rules: {
+ eqeqeq: "error"
+ }
+ }),
+ "node_modules/myconf/foo/test.js": "a == b",
+ "foo/test.js": "a == b"
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => {
- const engine = new InMemoryESLint({
+ const engine = new ESLint({
overrideConfigFile: "node_modules/myconf/.eslintrc.json",
- cwd: root,
+ cwd: getPath(),
useEslintrc: false
});
const files = (await engine.lintFiles("**/*.js"))
@@ -5732,14 +5942,7 @@ describe("ESLint", () => {
describe("plugin conflicts", () => {
let uid = 0;
- let root = "";
-
- beforeEach(() => {
- root = getFixturePath(`eslint/plugin-conflicts-${++uid}`);
- });
-
- /** @type {typeof ESLint} */
- let InMemoryESLint;
+ const root = getFixturePath("cli-engine/plugin-conflicts-");
/**
* Verify thrown errors.
@@ -5761,110 +5964,118 @@ describe("ESLint", () => {
}
describe("between a config file and linear extendees.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- extends: ["two"],
- plugins: ["foo"]
- })}`,
- "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
- plugins: ["foo"]
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: ["one"],
- plugins: ["foo"]
- }),
- "test.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ extends: ["two"],
+ plugins: ["foo"]
+ })}`,
+ "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+ plugins: ["foo"]
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: ["one"],
+ plugins: ["foo"]
+ }),
+ "test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => {
- const engine = new InMemoryESLint({ cwd: root });
+ const engine = new ESLint({ cwd: getPath() });
await engine.lintFiles("test.js");
});
});
describe("between a config file and same-depth extendees.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
- plugins: ["foo"]
- })}`,
- "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
- plugins: ["foo"]
- })}`,
- ".eslintrc.json": JSON.stringify({
- extends: ["one", "two"],
- plugins: ["foo"]
- }),
- "test.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+ plugins: ["foo"]
+ })}`,
+ "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+ plugins: ["foo"]
+ })}`,
+ ".eslintrc.json": JSON.stringify({
+ extends: ["one", "two"],
+ plugins: ["foo"]
+ }),
+ "test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => {
- const engine = new InMemoryESLint({ cwd: root });
+ const engine = new ESLint({ cwd: getPath() });
await engine.lintFiles("test.js");
});
});
describe("between two config files in different directories, with single node_modules.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- ".eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/test.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ ".eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => {
- const engine = new InMemoryESLint({ cwd: root });
+ const engine = new ESLint({ cwd: getPath() });
await engine.lintFiles("subdir/test.js");
});
});
describe("between two config files in different directories, with multiple node_modules.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- ".eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/node_modules/eslint-plugin-foo/index.js": "",
- "subdir/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/test.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ ".eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/node_modules/eslint-plugin-foo/index.js": "",
+ "subdir/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => {
- const engine = new InMemoryESLint({ cwd: root });
+ const engine = new ESLint({ cwd: getPath() });
await assertThrows(
() => engine.lintFiles("subdir/test.js"),
@@ -5875,11 +6086,11 @@ describe("ESLint", () => {
pluginId: "foo",
plugins: [
{
- filePath: path.join(root, "subdir/node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"),
importerName: `subdir${path.sep}.eslintrc.json`
},
{
- filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
importerName: ".eslintrc.json"
}
]
@@ -5890,25 +6101,27 @@ describe("ESLint", () => {
});
describe("between '--config' option and a regular config file, with single node_modules.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/mine/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- ".eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "test.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/mine/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ ".eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => {
- const engine = new InMemoryESLint({
- cwd: root,
+ const engine = new ESLint({
+ cwd: getPath(),
overrideConfigFile: "node_modules/mine/.eslintrc.json"
});
@@ -5917,26 +6130,28 @@ describe("ESLint", () => {
});
describe("between '--config' option and a regular config file, with multiple node_modules.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "",
- "node_modules/mine/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- ".eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "test.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "",
+ "node_modules/mine/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ ".eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => {
- const engine = new InMemoryESLint({
- cwd: root,
+ const engine = new ESLint({
+ cwd: getPath(),
overrideConfigFile: "node_modules/mine/.eslintrc.json"
});
@@ -5949,11 +6164,11 @@ describe("ESLint", () => {
pluginId: "foo",
plugins: [
{
- filePath: path.join(root, "node_modules/mine/node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "node_modules/mine/node_modules/eslint-plugin-foo/index.js"),
importerName: "--config"
},
{
- filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
importerName: ".eslintrc.json"
}
]
@@ -5964,22 +6179,25 @@ describe("ESLint", () => {
});
describe("between '--plugin' option and a regular config file, with single node_modules.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "subdir/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/test.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "subdir/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/test.js": ""
+ }
});
+
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file, but node_modules directory is unique.)", async () => {
- const engine = new InMemoryESLint({
- cwd: root,
+ const engine = new ESLint({
+ cwd: getPath(),
overrideConfig: { plugins: ["foo"] }
});
@@ -5988,23 +6206,25 @@ describe("ESLint", () => {
});
describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- "subdir/node_modules/eslint-plugin-foo/index.js": "",
- "subdir/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/test.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ "subdir/node_modules/eslint-plugin-foo/index.js": "",
+ "subdir/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", async () => {
- const engine = new InMemoryESLint({
- cwd: root,
+ const engine = new ESLint({
+ cwd: getPath(),
overrideConfig: { plugins: ["foo"] }
});
@@ -6017,11 +6237,11 @@ describe("ESLint", () => {
pluginId: "foo",
plugins: [
{
- filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
importerName: "CLIOptions"
},
{
- filePath: path.join(root, "subdir/node_modules/eslint-plugin-foo/index.js"),
+ filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"),
importerName: `subdir${path.sep}.eslintrc.json`
}
]
@@ -6032,27 +6252,29 @@ describe("ESLint", () => {
});
describe("'--resolve-plugins-relative-to' option overrides the location that ESLint load plugins from.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "node_modules/eslint-plugin-foo/index.js": "",
- ".eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/node_modules/eslint-plugin-foo/index.js": "",
- "subdir/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "subdir/test.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "node_modules/eslint-plugin-foo/index.js": "",
+ ".eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/node_modules/eslint-plugin-foo/index.js": "",
+ "subdir/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "subdir/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", async () => {
- const engine = new InMemoryESLint({
- cwd: root,
- resolvePluginsRelativeTo: root
+ const engine = new ESLint({
+ cwd: getPath(),
+ resolvePluginsRelativeTo: getPath()
});
await engine.lintFiles("subdir/test.js");
@@ -6060,26 +6282,28 @@ describe("ESLint", () => {
});
describe("between two config files with different target files.", () => {
- beforeEach(() => {
- InMemoryESLint = defineESLintWithInMemoryFileSystem({
- cwd: () => root,
- files: {
- "one/node_modules/eslint-plugin-foo/index.js": "",
- "one/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "one/test.js": "",
- "two/node_modules/eslint-plugin-foo/index.js": "",
- "two/.eslintrc.json": JSON.stringify({
- plugins: ["foo"]
- }),
- "two/test.js": ""
- }
- }).ESLint;
+
+ const { prepare, cleanup, getPath } = createCustomTeardown({
+ cwd: `${root}${++uid}`,
+ files: {
+ "one/node_modules/eslint-plugin-foo/index.js": "",
+ "one/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "one/test.js": "",
+ "two/node_modules/eslint-plugin-foo/index.js": "",
+ "two/.eslintrc.json": JSON.stringify({
+ plugins: ["foo"]
+ }),
+ "two/test.js": ""
+ }
});
+ beforeEach(prepare);
+ afterEach(cleanup);
+
it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file for each target file. Not related to each other.)", async () => {
- const engine = new InMemoryESLint({ cwd: root });
+ const engine = new ESLint({ cwd: getPath() });
const results = await engine.lintFiles("*/test.js");
assert.strictEqual(results.length, 2);
diff --git a/eslint/tests/lib/init/config-file.js b/eslint/tests/lib/init/config-file.js
index 69556b4..e9fe62e 100644
--- a/eslint/tests/lib/init/config-file.js
+++ b/eslint/tests/lib/init/config-file.js
@@ -9,10 +9,8 @@
//------------------------------------------------------------------------------
const assert = require("chai").assert,
- leche = require("leche"),
sinon = require("sinon"),
path = require("path"),
- fs = require("fs"),
yaml = require("js-yaml"),
espree = require("espree"),
ConfigFile = require("../../../lib/init/config-file"),
@@ -59,15 +57,17 @@ describe("ConfigFile", () => {
sinon.verifyAndRestore();
});
- leche.withData([
+ [
["JavaScript", "foo.js", espree.parse],
["JSON", "bar.json", JSON.parse],
["YAML", "foo.yaml", yaml.safeLoad],
["YML", "foo.yml", yaml.safeLoad]
- ], (fileType, filename, validate) => {
+ ].forEach(([fileType, filename, validate]) => {
it(`should write a file through fs when a ${fileType} path is passed`, () => {
- const fakeFS = leche.fake(fs);
+ const fakeFS = {
+ writeFileSync: () => {}
+ };
sinon.mock(fakeFS).expects("writeFileSync").withExactArgs(
filename,
@@ -83,7 +83,9 @@ describe("ConfigFile", () => {
});
it("should include a newline character at EOF", () => {
- const fakeFS = leche.fake(fs);
+ const fakeFS = {
+ writeFileSync: () => {}
+ };
sinon.mock(fakeFS).expects("writeFileSync").withExactArgs(
filename,
@@ -100,7 +102,9 @@ describe("ConfigFile", () => {
});
it("should make sure js config files match linting rules", () => {
- const fakeFS = leche.fake(fs);
+ const fakeFS = {
+ writeFileSync: () => {}
+ };
const singleQuoteConfig = {
rules: {
@@ -122,7 +126,9 @@ describe("ConfigFile", () => {
});
it("should still write a js config file even if linting fails", () => {
- const fakeFS = leche.fake(fs);
+ const fakeFS = {
+ writeFileSync: () => {}
+ };
const fakeCLIEngine = sinon.mock().withExactArgs(sinon.match({
baseConfig: config,
fix: true,
diff --git a/eslint/tests/lib/init/config-initializer.js b/eslint/tests/lib/init/config-initializer.js
index 945e185..d607ccf 100644
--- a/eslint/tests/lib/init/config-initializer.js
+++ b/eslint/tests/lib/init/config-initializer.js
@@ -136,7 +136,7 @@ describe("configInitializer", () => {
assert.deepStrictEqual(config.rules.quotes, ["error", "single"]);
assert.deepStrictEqual(config.rules["linebreak-style"], ["error", "unix"]);
assert.deepStrictEqual(config.rules.semi, ["error", "always"]);
- assert.strictEqual(config.env.es2020, true);
+ assert.strictEqual(config.env.es2021, true);
assert.strictEqual(config.parserOptions.ecmaVersion, espree.latestEcmaVersion);
assert.strictEqual(config.parserOptions.sourceType, "module");
assert.strictEqual(config.env.browser, true);
@@ -174,7 +174,7 @@ describe("configInitializer", () => {
assert.strictEqual(config.parser, "@typescript-eslint/parser");
assert.deepStrictEqual(config.plugins, ["@typescript-eslint"]);
- assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended"]);
+ assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:@typescript-eslint/recommended"]);
});
it("should enable typescript parser and plugin with vue", () => {
@@ -182,7 +182,7 @@ describe("configInitializer", () => {
answers.typescript = true;
const config = init.processAnswers(answers);
- assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:vue/essential", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended"]);
+ assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:vue/essential", "plugin:@typescript-eslint/recommended"]);
assert.strictEqual(config.parserOptions.parser, "@typescript-eslint/parser");
assert.deepStrictEqual(config.plugins, ["vue", "@typescript-eslint"]);
});
diff --git a/eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js b/eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js
index ec516c5..c5d0ecf 100644
--- a/eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js
+++ b/eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js
@@ -557,7 +557,7 @@ describe("CodePathAnalyzer", () => {
}
}));
const messages = linter.verify(source, {
- parserOptions: { ecmaVersion: 2020 },
+ parserOptions: { ecmaVersion: 2021 },
rules: { test: 2 }
});
diff --git a/eslint/tests/lib/linter/report-translator.js b/eslint/tests/lib/linter/report-translator.js
index a79400c..4d137b5 100644
--- a/eslint/tests/lib/linter/report-translator.js
+++ b/eslint/tests/lib/linter/report-translator.js
@@ -553,6 +553,224 @@ describe("createReportTranslator", () => {
}
);
});
+
+ it("should remove the whole suggestion if 'fix' function returned `null`.", () => {
+ const reportDescriptor = {
+ node,
+ loc: location,
+ message,
+ suggest: [{
+ desc: "A suggestion for the issue",
+ fix: () => null
+ }]
+ };
+
+ assert.deepStrictEqual(
+ translateReport(reportDescriptor),
+ {
+ ruleId: "foo-rule",
+ severity: 2,
+ message: "foo",
+ line: 2,
+ column: 1,
+ nodeType: "ExpressionStatement"
+ }
+ );
+ });
+
+ it("should remove the whole suggestion if 'fix' function returned an empty array.", () => {
+ const reportDescriptor = {
+ node,
+ loc: location,
+ message,
+ suggest: [{
+ desc: "A suggestion for the issue",
+ fix: () => []
+ }]
+ };
+
+ assert.deepStrictEqual(
+ translateReport(reportDescriptor),
+ {
+ ruleId: "foo-rule",
+ severity: 2,
+ message: "foo",
+ line: 2,
+ column: 1,
+ nodeType: "ExpressionStatement"
+ }
+ );
+ });
+
+ it("should remove the whole suggestion if 'fix' function returned an empty sequence.", () => {
+ const reportDescriptor = {
+ node,
+ loc: location,
+ message,
+ suggest: [{
+ desc: "A suggestion for the issue",
+ *fix() {}
+ }]
+ };
+
+ assert.deepStrictEqual(
+ translateReport(reportDescriptor),
+ {
+ ruleId: "foo-rule",
+ severity: 2,
+ message: "foo",
+ line: 2,
+ column: 1,
+ nodeType: "ExpressionStatement"
+ }
+ );
+ });
+
+ // This isn't offically supported, but autofix works the same way
+ it("should remove the whole suggestion if 'fix' function didn't return anything.", () => {
+ const reportDescriptor = {
+ node,
+ loc: location,
+ message,
+ suggest: [{
+ desc: "A suggestion for the issue",
+ fix() {}
+ }]
+ };
+
+ assert.deepStrictEqual(
+ translateReport(reportDescriptor),
+ {
+ ruleId: "foo-rule",
+ severity: 2,
+ message: "foo",
+ line: 2,
+ column: 1,
+ nodeType: "ExpressionStatement"
+ }
+ );
+ });
+
+ it("should keep suggestion before a removed suggestion.", () => {
+ const reportDescriptor = {
+ node,
+ loc: location,
+ message,
+ suggest: [{
+ desc: "Suggestion with a fix",
+ fix: () => ({ range: [1, 2], text: "foo" })
+ }, {
+ desc: "Suggestion without a fix",
+ fix: () => null
+ }]
+ };
+
+ assert.deepStrictEqual(
+ translateReport(reportDescriptor),
+ {
+ ruleId: "foo-rule",
+ severity: 2,
+ message: "foo",
+ line: 2,
+ column: 1,
+ nodeType: "ExpressionStatement",
+ suggestions: [{
+ desc: "Suggestion with a fix",
+ fix: { range: [1, 2], text: "foo" }
+ }]
+ }
+ );
+ });
+
+ it("should keep suggestion after a removed suggestion.", () => {
+ const reportDescriptor = {
+ node,
+ loc: location,
+ message,
+ suggest: [{
+ desc: "Suggestion without a fix",
+ fix: () => null
+ }, {
+ desc: "Suggestion with a fix",
+ fix: () => ({ range: [1, 2], text: "foo" })
+ }]
+ };
+
+ assert.deepStrictEqual(
+ translateReport(reportDescriptor),
+ {
+ ruleId: "foo-rule",
+ severity: 2,
+ message: "foo",
+ line: 2,
+ column: 1,
+ nodeType: "ExpressionStatement",
+ suggestions: [{
+ desc: "Suggestion with a fix",
+ fix: { range: [1, 2], text: "foo" }
+ }]
+ }
+ );
+ });
+
+ it("should remove multiple suggestions that didn't provide a fix and keep those that did.", () => {
+ const reportDescriptor = {
+ node,
+ loc: location,
+ message,
+ suggest: [{
+ desc: "Keep #1",
+ fix: () => ({ range: [1, 2], text: "foo" })
+ }, {
+ desc: "Remove #1",
+ fix() {
+ return null;
+ }
+ }, {
+ desc: "Keep #2",
+ fix: () => ({ range: [1, 2], text: "bar" })
+ }, {
+ desc: "Remove #2",
+ fix() {
+ return [];
+ }
+ }, {
+ desc: "Keep #3",
+ fix: () => ({ range: [1, 2], text: "baz" })
+ }, {
+ desc: "Remove #3",
+ *fix() {}
+ }, {
+ desc: "Keep #4",
+ fix: () => ({ range: [1, 2], text: "quux" })
+ }]
+ };
+
+ assert.deepStrictEqual(
+ translateReport(reportDescriptor),
+ {
+ ruleId: "foo-rule",
+ severity: 2,
+ message: "foo",
+ line: 2,
+ column: 1,
+ nodeType: "ExpressionStatement",
+ suggestions: [{
+ desc: "Keep #1",
+ fix: { range: [1, 2], text: "foo" }
+ }, {
+ desc: "Keep #2",
+ fix: { range: [1, 2], text: "bar" }
+ }, {
+ desc: "Keep #3",
+ fix: { range: [1, 2], text: "baz" }
+ }, {
+ desc: "Keep #4",
+ fix: { range: [1, 2], text: "quux" }
+ }]
+ }
+ );
+ });
});
describe("message interpolation", () => {
diff --git a/eslint/tests/lib/rule-tester/rule-tester.js b/eslint/tests/lib/rule-tester/rule-tester.js
index fedc237..3f23936 100644
--- a/eslint/tests/lib/rule-tester/rule-tester.js
+++ b/eslint/tests/lib/rule-tester/rule-tester.js
@@ -304,6 +304,10 @@ describe("RuleTester", () => {
it("should use strict equality to compare output", () => {
const replaceProgramWith5Rule = {
+ meta: {
+ fixable: "code"
+ },
+
create: context => ({
Program(node) {
context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
@@ -463,6 +467,37 @@ describe("RuleTester", () => {
}, expectedErrorMessage);
});
+ it("should throw error for empty error array", () => {
+ assert.throws(() => {
+ ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
+ valid: [],
+ invalid: [{
+ code: "var foo;",
+ errors: []
+ }]
+ });
+ }, /Invalid cases must have at least one error/u);
+ });
+
+ it("should throw error for errors : 0", () => {
+ assert.throws(() => {
+ ruleTester.run(
+ "suggestions-messageIds",
+ require("../../fixtures/testers/rule-tester/suggestions")
+ .withMessageIds,
+ {
+ valid: [],
+ invalid: [
+ {
+ code: "var foo;",
+ errors: 0
+ }
+ ]
+ }
+ );
+ }, /Invalid cases must have 'error' value greater than 0/u);
+ });
+
it("should not skip column assertion if column is a falsy value", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
@@ -732,7 +767,7 @@ describe("RuleTester", () => {
{
code: "eval(foo)",
parser: require.resolve("esprima"),
- errors: [{}]
+ errors: [{ line: 1 }]
}
]
});
@@ -1206,6 +1241,73 @@ describe("RuleTester", () => {
}, "Error must specify 'messageId' if 'data' is used.");
});
+ // fixable rules with or without `meta` property
+ it("should not throw an error if a rule that has `meta.fixable` produces fixes", () => {
+ const replaceProgramWith5Rule = {
+ meta: {
+ fixable: "code"
+ },
+ create(context) {
+ return {
+ Program(node) {
+ context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
+ }
+ };
+ }
+ };
+
+ ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
+ valid: [],
+ invalid: [
+ { code: "var foo = bar;", output: "5", errors: 1 }
+ ]
+ });
+ });
+ it("should throw an error if a new-format rule that doesn't have `meta` produces fixes", () => {
+ const replaceProgramWith5Rule = {
+ create(context) {
+ return {
+ Program(node) {
+ context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
+ }
+ };
+ }
+ };
+
+ assert.throws(() => {
+ ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
+ valid: [],
+ invalid: [
+ { code: "var foo = bar;", output: "5", errors: 1 }
+ ]
+ });
+ }, "Fixable rules should export a `meta.fixable` property.");
+ });
+ it("should throw an error if a legacy-format rule produces fixes", () => {
+
+ /**
+ * Legacy-format rule (a function instead of an object with `create` method).
+ * @param {RuleContext} context The ESLint rule context object.
+ * @returns {Object} Listeners.
+ */
+ function replaceProgramWith5Rule(context) {
+ return {
+ Program(node) {
+ context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
+ }
+ };
+ }
+
+ assert.throws(() => {
+ ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
+ valid: [],
+ invalid: [
+ { code: "var foo = bar;", output: "5", errors: 1 }
+ ]
+ });
+ }, "Fixable rules should export a `meta.fixable` property.");
+ });
+
describe("suggestions", () => {
it("should pass with valid suggestions (tested using desc)", () => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
@@ -1893,4 +1995,5 @@ describe("RuleTester", () => {
});
});
+
});
diff --git a/eslint/tests/lib/rules/accessor-pairs.js b/eslint/tests/lib/rules/accessor-pairs.js
index e8f143a..c634132 100644
--- a/eslint/tests/lib/rules/accessor-pairs.js
+++ b/eslint/tests/lib/rules/accessor-pairs.js
@@ -1087,6 +1087,46 @@ ruleTester.run("accessor-pairs", rule, {
code: "Object.create(null, {foo: {set: function(value) {}}});",
errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
},
+ {
+ code: "var o = {d: 1};\n Object?.defineProperty(o, 'c', \n{set: function(value) {\n val = value; \n} \n});",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+ },
+ {
+ code: "Reflect?.defineProperty(obj, 'foo', {set: function(value) {}});",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+ },
+ {
+ code: "Object?.defineProperties(obj, {foo: {set: function(value) {}}});",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+ },
+ {
+ code: "Object?.create(null, {foo: {set: function(value) {}}});",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+ },
+ {
+ code: "var o = {d: 1};\n (Object?.defineProperty)(o, 'c', \n{set: function(value) {\n val = value; \n} \n});",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+ },
+ {
+ code: "(Reflect?.defineProperty)(obj, 'foo', {set: function(value) {}});",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+ },
+ {
+ code: "(Object?.defineProperties)(obj, {foo: {set: function(value) {}}});",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+ },
+ {
+ code: "(Object?.create)(null, {foo: {set: function(value) {}}});",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+ },
//------------------------------------------------------------------------------
// Classes
diff --git a/eslint/tests/lib/rules/array-callback-return.js b/eslint/tests/lib/rules/array-callback-return.js
index 24b40cb..6d6a4d8 100644
--- a/eslint/tests/lib/rules/array-callback-return.js
+++ b/eslint/tests/lib/rules/array-callback-return.js
@@ -115,90 +115,91 @@ ruleTester.run("array-callback-return", rule, {
],
invalid: [
- { code: "Array.from(x, function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "Array.from(x, function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "Int32Array.from(x, function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "Int32Array.from(x, function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.filter(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.filter(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.find(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.find(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.findIndex(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.findIndex(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.flatMap(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.flatMap(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.map(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.map(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.reduce(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.reduce(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.reduceRight(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.reduceRight(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.some(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.some(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.sort(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.sort(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.bar.baz.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.bar.baz.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo[\"every\"](function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo[\"every\"](function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo[`every`](function() {})", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo[`every`](function foo() {})", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.every(() => {})", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected to return a value in arrow function.", column: 14 }] },
- { code: "foo.every(function() { if (a) return true; })", errors: [{ message: "Expected to return a value at the end of function.", column: 11 }] },
- { code: "foo.every(function cb() { if (a) return true; })", errors: [{ message: "Expected to return a value at the end of function 'cb'.", column: 11 }] },
- { code: "foo.every(function() { switch (a) { case 0: break; default: return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function" } }] },
- { code: "foo.every(function foo() { switch (a) { case 0: break; default: return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function 'foo'" } }] },
- { code: "foo.every(function() { try { bar(); } catch (err) { return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function" } }] },
- { code: "foo.every(function foo() { try { bar(); } catch (err) { return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function 'foo'" } }] },
- { code: "foo.every(function() { return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "Function" } }] },
- { code: "foo.every(function foo() { return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "Function 'foo'" } }] },
- { code: "foo.every(function() { if (a) return; })", errors: ["Expected to return a value at the end of function.", { messageId: "expectedReturnValue", data: { name: "Function" } }] },
- { code: "foo.every(function foo() { if (a) return; })", errors: ["Expected to return a value at the end of function 'foo'.", { messageId: "expectedReturnValue", data: { name: "Function 'foo'" } }] },
- { code: "foo.every(function() { if (a) return; else return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "Function" } }, { messageId: "expectedReturnValue", data: { name: "Function" } }] },
- { code: "foo.every(function foo() { if (a) return; else return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "Function 'foo'" } }, { messageId: "expectedReturnValue", data: { name: "Function 'foo'" } }] },
- { code: "foo.every(cb || function() {})", errors: ["Expected to return a value in function."] },
- { code: "foo.every(cb || function foo() {})", errors: ["Expected to return a value in function 'foo'."] },
- { code: "foo.every(a ? function() {} : function() {})", errors: ["Expected to return a value in function.", "Expected to return a value in function."] },
- { code: "foo.every(a ? function foo() {} : function bar() {})", errors: ["Expected to return a value in function 'foo'.", "Expected to return a value in function 'bar'."] },
- { code: "foo.every(function(){ return function() {}; }())", errors: [{ message: "Expected to return a value in function.", column: 30 }] },
- { code: "foo.every(function(){ return function foo() {}; }())", errors: [{ message: "Expected to return a value in function 'foo'.", column: 30 }] },
- { code: "foo.every(() => {})", options: [{ allowImplicit: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected to return a value in arrow function." }] },
- { code: "foo.every(() => {})", options: [{ allowImplicit: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected to return a value in arrow function." }] },
+ { code: "Array.from(x, function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] },
+ { code: "Array.from(x, function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.from" } }] },
+ { code: "Int32Array.from(x, function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] },
+ { code: "Int32Array.from(x, function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.from" } }] },
+ { code: "foo.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.filter(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.filter" } }] },
+ { 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.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.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" } }] },
+ { code: "foo.map(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.map" } }] },
+ { code: "foo.reduce(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduce" } }] },
+ { code: "foo.reduce(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.reduce" } }] },
+ { code: "foo.reduceRight(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduceRight" } }] },
+ { code: "foo.reduceRight(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.reduceRight" } }] },
+ { code: "foo.some(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.some" } }] },
+ { 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.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" } }] },
+ { code: "foo[\"every\"](function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo[`every`](function() {})", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo[`every`](function foo() {})", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(() => {})", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Array.prototype.every() expects a return value from arrow function.", column: 14 }] },
+ { code: "foo.every(function() { if (a) return true; })", errors: [{ message: "Array.prototype.every() expects a value to be returned at the end of function.", column: 11 }] },
+ { code: "foo.every(function cb() { if (a) return true; })", errors: [{ message: "Array.prototype.every() expects a value to be returned at the end of function 'cb'.", column: 11 }] },
+ { code: "foo.every(function() { switch (a) { case 0: break; default: return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(function foo() { switch (a) { case 0: break; default: return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(function() { try { bar(); } catch (err) { return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(function foo() { try { bar(); } catch (err) { return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(function() { return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(function foo() { return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(function() { if (a) return; })", errors: ["Array.prototype.every() expects a value to be returned at the end of function.", { messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(function foo() { if (a) return; })", errors: ["Array.prototype.every() expects a value to be returned at the end of function 'foo'.", { messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(function() { if (a) return; else return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }, { messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(function foo() { if (a) return; else return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }, { messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.every(cb || function() {})", errors: ["Array.prototype.every() expects a return value from function."] },
+ { code: "foo.every(cb || function foo() {})", errors: ["Array.prototype.every() expects a return value from function 'foo'."] },
+ { code: "foo.every(a ? function() {} : function() {})", errors: ["Array.prototype.every() expects a return value from function.", "Array.prototype.every() expects a return value from function."] },
+ { code: "foo.every(a ? function foo() {} : function bar() {})", errors: ["Array.prototype.every() expects a return value from function 'foo'.", "Array.prototype.every() expects a return value from function 'bar'."] },
+ { code: "foo.every(function(){ return function() {}; }())", errors: [{ message: "Array.prototype.every() expects a return value from function.", column: 30 }] },
+ { code: "foo.every(function(){ return function foo() {}; }())", errors: [{ message: "Array.prototype.every() expects a return value from function 'foo'.", column: 30 }] },
+ { code: "foo.every(() => {})", options: [{ allowImplicit: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Array.prototype.every() expects a return value from arrow function." }] },
+ { code: "foo.every(() => {})", options: [{ allowImplicit: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Array.prototype.every() expects a return value from arrow function." }] },
// options: { allowImplicit: true }
- { code: "Array.from(x, function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.every(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.filter(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.find(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.map(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.reduce(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.reduceRight(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.bar.baz.every(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.every(cb || function() {})", options: allowImplicitOptions, errors: ["Expected to return a value in function."] },
- { code: "[\"foo\",\"bar\"].sort(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.forEach(x => x)", options: allowImplicitCheckForEach, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
- { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function" } }] },
- { code: "foo.forEach(function bar(x) { return x;})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function 'bar'" } }] },
+ { code: "Array.from(x, function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] },
+ { code: "foo.every(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.filter(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
+ { code: "foo.find(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.find" } }] },
+ { code: "foo.map(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.map" } }] },
+ { code: "foo.reduce(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduce" } }] },
+ { code: "foo.reduceRight(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduceRight" } }] },
+ { 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.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" } }] },
// // options: { checkForEach: true }
- { code: "foo.forEach(x => x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
- { code: "foo.forEach(val => y += val)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
- { code: "[\"foo\",\"bar\"].forEach(x => ++x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
- { code: "foo.bar().forEach(x => x === y)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
- { code: "foo.forEach(function() {return function() { if (a == b) { return a; }}}())", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function" } }] },
- { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function" } }] },
- { code: "foo.forEach(function(x) { if (a == b) {return undefined;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function" } }] },
- { code: "foo.forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function 'bar'" } }] },
- { code: "foo.bar().forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function 'bar'" } }] },
- { code: "[\"foo\",\"bar\"].forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function 'bar'" } }] },
- { code: "foo.forEach((x) => { return x;})", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
- { code: "Array.from(x, function() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.every(function() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
- { code: "foo.filter(function foo() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
- { code: "foo.filter(function foo() { return; })", options: checkForEachOptions, errors: [{ messageId: "expectedReturnValue", data: { name: "Function 'foo'" } }] },
- { code: "foo.every(cb || function() {})", options: checkForEachOptions, errors: ["Expected to return a value in function."] },
+ { code: "foo.forEach(x => x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "foo.forEach(val => y += val)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "[\"foo\",\"bar\"].forEach(x => ++x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "foo.bar().forEach(x => x === y)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "foo.forEach(function() {return function() { if (a == b) { return a; }}}())", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "foo.forEach(function(x) { if (a == b) {return undefined;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "foo.forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "foo.forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: ["Array.prototype.forEach() expects no useless return value from function 'bar'."] },
+ { code: "foo.bar().forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "[\"foo\",\"bar\"].forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "foo.forEach((x) => { return x;})", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+ { code: "Array.from(x, function() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] },
+ { code: "foo.every(function() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+ { code: "foo.filter(function foo() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
+ { code: "foo.filter(function foo() { return; })", options: checkForEachOptions, errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
+ { code: "foo.every(cb || function() {})", options: checkForEachOptions, errors: ["Array.prototype.every() expects a return value from function."] },
// full location tests
{
@@ -206,7 +207,7 @@ ruleTester.run("array-callback-return", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedInside",
- data: { name: "arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
type: "ArrowFunctionExpression",
line: 1,
column: 16,
@@ -219,7 +220,7 @@ ruleTester.run("array-callback-return", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedInside",
- data: { name: "arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
type: "ArrowFunctionExpression",
line: 2,
column: 4,
@@ -232,7 +233,7 @@ ruleTester.run("array-callback-return", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedInside",
- data: { name: "arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
type: "ArrowFunctionExpression",
line: 1,
column: 26,
@@ -245,7 +246,7 @@ ruleTester.run("array-callback-return", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedReturnValue",
- data: { name: "Arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
type: "ReturnStatement",
line: 1,
column: 21,
@@ -258,7 +259,7 @@ ruleTester.run("array-callback-return", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedInside",
- data: { name: "arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.from" },
type: "ArrowFunctionExpression",
line: 1,
column: 21,
@@ -272,7 +273,7 @@ ruleTester.run("array-callback-return", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedNoReturnValue",
- data: { name: "Arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
type: "ArrowFunctionExpression",
line: 1,
column: 17,
@@ -286,7 +287,7 @@ ruleTester.run("array-callback-return", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedNoReturnValue",
- data: { name: "Arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
type: "ArrowFunctionExpression",
line: 1,
column: 41,
@@ -300,7 +301,7 @@ ruleTester.run("array-callback-return", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedNoReturnValue",
- data: { name: "Arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
type: "ArrowFunctionExpression",
line: 2,
column: 13,
@@ -314,7 +315,7 @@ ruleTester.run("array-callback-return", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedNoReturnValue",
- data: { name: "Arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
type: "ReturnStatement",
line: 1,
column: 52,
@@ -326,7 +327,7 @@ ruleTester.run("array-callback-return", rule, {
code: "foo.filter(function(){})",
errors: [{
messageId: "expectedInside",
- data: { name: "function" },
+ data: { name: "function", arrayMethodName: "Array.prototype.filter" },
type: "FunctionExpression",
line: 1,
column: 12,
@@ -338,7 +339,7 @@ ruleTester.run("array-callback-return", rule, {
code: "foo.filter(function (){})",
errors: [{
messageId: "expectedInside",
- data: { name: "function" },
+ data: { name: "function", arrayMethodName: "Array.prototype.filter" },
type: "FunctionExpression",
line: 1,
column: 12,
@@ -350,7 +351,7 @@ ruleTester.run("array-callback-return", rule, {
code: "foo.filter(function\n(){})",
errors: [{
messageId: "expectedInside",
- data: { name: "function" },
+ data: { name: "function", arrayMethodName: "Array.prototype.filter" },
type: "FunctionExpression",
line: 1,
column: 12,
@@ -362,7 +363,7 @@ ruleTester.run("array-callback-return", rule, {
code: "foo.filter(function bar(){})",
errors: [{
messageId: "expectedInside",
- data: { name: "function 'bar'" },
+ data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" },
type: "FunctionExpression",
line: 1,
column: 12,
@@ -374,7 +375,7 @@ ruleTester.run("array-callback-return", rule, {
code: "foo.filter(function bar (){})",
errors: [{
messageId: "expectedInside",
- data: { name: "function 'bar'" },
+ data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" },
type: "FunctionExpression",
line: 1,
column: 12,
@@ -386,7 +387,7 @@ ruleTester.run("array-callback-return", rule, {
code: "foo.filter(function\n bar() {})",
errors: [{
messageId: "expectedInside",
- data: { name: "function 'bar'" },
+ data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" },
type: "FunctionExpression",
line: 1,
column: 12,
@@ -398,7 +399,7 @@ ruleTester.run("array-callback-return", rule, {
code: "Array.from(foo, function bar(){})",
errors: [{
messageId: "expectedInside",
- data: { name: "function 'bar'" },
+ data: { name: "function 'bar'", arrayMethodName: "Array.from" },
type: "FunctionExpression",
line: 1,
column: 17,
@@ -410,7 +411,7 @@ ruleTester.run("array-callback-return", rule, {
code: "Array.from(foo, bar ? function (){} : baz)",
errors: [{
messageId: "expectedInside",
- data: { name: "function" },
+ data: { name: "function", arrayMethodName: "Array.from" },
type: "FunctionExpression",
line: 1,
column: 23,
@@ -422,7 +423,7 @@ ruleTester.run("array-callback-return", rule, {
code: "foo.filter(function bar() { return \n })",
errors: [{
messageId: "expectedReturnValue",
- data: { name: "Function 'bar'" },
+ data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" },
type: "ReturnStatement",
line: 1,
column: 29,
@@ -435,13 +436,40 @@ ruleTester.run("array-callback-return", rule, {
options: checkForEachOptions,
errors: [{
messageId: "expectedNoReturnValue",
- data: { name: "Function" },
+ data: { name: "function", arrayMethodName: "Array.prototype.forEach" },
type: "ReturnStatement",
line: 2,
column: 10,
endLine: 2,
endColumn: 20
}]
+ },
+
+ // Optional chaining
+ {
+ code: "foo?.filter(() => { console.log('hello') })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" } }]
+ },
+ {
+ code: "(foo?.filter)(() => { console.log('hello') })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" } }]
+ },
+ {
+ code: "Array?.from([], () => { console.log('hello') })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.from" } }]
+ },
+ {
+ code: "(Array?.from)([], () => { console.log('hello') })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.from" } }]
+ },
+ {
+ code: "foo?.filter((function() { return () => { console.log('hello') } })?.())",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" } }]
}
]
});
diff --git a/eslint/tests/lib/rules/arrow-body-style.js b/eslint/tests/lib/rules/arrow-body-style.js
index 7c1bea6..7a8de4f 100644
--- a/eslint/tests/lib/rules/arrow-body-style.js
+++ b/eslint/tests/lib/rules/arrow-body-style.js
@@ -45,6 +45,239 @@ ruleTester.run("arrow-body-style", rule, {
{ code: "var foo = () => { return { bar: 0 }; };", options: ["as-needed", { requireReturnForObjectLiteral: true }] }
],
invalid: [
+ {
+ code: "for (var foo = () => { return a in b ? bar : () => {} } ;;);",
+ output: "for (var foo = () => (a in b ? bar : () => {}) ;;);",
+ options: ["as-needed"],
+ errors: [
+ {
+ line: 1,
+ column: 22,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "a in b; for (var f = () => { return c };;);",
+ output: "a in b; for (var f = () => c;;);",
+ options: ["as-needed"],
+ errors: [
+ {
+ line: 1,
+ column: 28,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (a = b => { return c in d ? e : f } ;;);",
+ output: "for (a = b => (c in d ? e : f) ;;);",
+ options: ["as-needed"],
+ errors: [
+ {
+ line: 1,
+ column: 15,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (var f = () => { return a };;);",
+ output: "for (var f = () => a;;);",
+ options: ["as-needed"],
+ errors: [
+ {
+ line: 1,
+ column: 20,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (var f;f = () => { return a };);",
+ output: "for (var f;f = () => a;);",
+ options: ["as-needed"],
+ errors: [
+ {
+ line: 1,
+ column: 22,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (var f = () => { return a in c };;);",
+ output: "for (var f = () => (a in c);;);",
+ options: ["as-needed"],
+ errors: [
+ {
+ line: 1,
+ column: 20,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (var f;f = () => { return a in c };);",
+ output: "for (var f;f = () => a in c;);",
+ options: ["as-needed"],
+ errors: [
+ {
+ line: 1,
+ column: 22,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (;;){var f = () => { return a in c }}",
+ output: "for (;;){var f = () => a in c}",
+ options: ["as-needed"],
+ errors: [
+ {
+ line: 1,
+ column: 24,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (a = b => { return c = d in e } ;;);",
+ output: "for (a = b => (c = d in e) ;;);",
+ options: ["as-needed"],
+ errors: [
+ {
+ line: 1,
+ column: 15,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (var a;;a = b => { return c = d in e } );",
+ output: "for (var a;;a = b => c = d in e );",
+ options: ["as-needed"],
+ errors: [
+ {
+ line: 1,
+ column: 22,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (let a = (b, c, d) => { return vb && c in d; }; ;);",
+ output: "for (let a = (b, c, d) => (vb && c in d); ;);",
+ errors: [
+ {
+ line: 1,
+ column: 27,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (let a = (b, c, d) => { return v in b && c in d; }; ;);",
+ output: "for (let a = (b, c, d) => (v in b && c in d); ;);",
+ errors: [
+ {
+ line: 1,
+ column: 27,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "function foo(){ for (let a = (b, c, d) => { return v in b && c in d; }; ;); }",
+ output: "function foo(){ for (let a = (b, c, d) => (v in b && c in d); ;); }",
+ errors: [
+ {
+ line: 1,
+ column: 43,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for ( a = (b, c, d) => { return v in b && c in d; }; ;);",
+ output: "for ( a = (b, c, d) => (v in b && c in d); ;);",
+ errors: [
+ {
+ line: 1,
+ column: 24,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for ( a = (b) => { return (c in d) }; ;);",
+ output: "for ( a = (b) => (c in d); ;);",
+ errors: [
+ {
+ line: 1,
+ column: 18,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (let a = (b, c, d) => { return vb in dd ; }; ;);",
+ output: "for (let a = (b, c, d) => (vb in dd ); ;);",
+ errors: [
+ {
+ line: 1,
+ column: 27,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "for (let a = (b, c, d) => { return vb in c in dd ; }; ;);",
+ output: "for (let a = (b, c, d) => (vb in c in dd ); ;);",
+ errors: [
+ {
+ line: 1,
+ column: 27,
+ messageId: "unexpectedSingleBlock"
+ }
+ ]
+ },
+ {
+ code: "do{let a = () => {return f in ff}}while(true){}",
+ output: "do{let a = () => f in ff}while(true){}",
+ errors: [{
+ line: 1,
+ column: 18,
+ messageId: "unexpectedSingleBlock"
+ }]
+ },
+ {
+ code: "do{for (let a = (b, c, d) => { return vb in c in dd ; }; ;);}while(true){}",
+ output: "do{for (let a = (b, c, d) => (vb in c in dd ); ;);}while(true){}",
+ errors: [{
+ line: 1,
+ column: 30,
+ messageId: "unexpectedSingleBlock"
+ }]
+ },
+ {
+ code: "scores.map(score => { return x in +(score / maxScore).toFixed(2)});",
+ output: "scores.map(score => x in +(score / maxScore).toFixed(2));",
+ errors: [{
+ line: 1,
+ column: 21,
+ messageId: "unexpectedSingleBlock"
+ }]
+ },
+ {
+ code: "const fn = (a, b) => { return a + x in Number(b) };",
+ output: "const fn = (a, b) => a + x in Number(b);",
+ errors: [{
+ line: 1,
+ column: 22,
+ messageId: "unexpectedSingleBlock"
+ }]
+ },
{
code: "var foo = () => 0",
output: "var foo = () => {return 0}",
@@ -53,6 +286,8 @@ ruleTester.run("arrow-body-style", rule, {
{
line: 1,
column: 17,
+ endLine: 1,
+ endColumn: 18,
type: "ArrowFunctionExpression",
messageId: "expectedBlock"
}
@@ -368,8 +603,8 @@ ruleTester.run("arrow-body-style", rule, {
// Not fixed; fixing would cause ASI issues.
code:
- "var foo = () => { return bar }\n" +
- "[1, 2, 3].map(foo)",
+ "var foo = () => { return bar }\n" +
+ "[1, 2, 3].map(foo)",
output: null,
options: ["never"],
errors: [
@@ -378,10 +613,11 @@ ruleTester.run("arrow-body-style", rule, {
},
{
+
// Not fixed; fixing would cause ASI issues.
code:
- "var foo = () => { return bar }\n" +
- "(1).toString();",
+ "var foo = () => { return bar }\n" +
+ "(1).toString();",
output: null,
options: ["never"],
errors: [
@@ -440,6 +676,8 @@ ruleTester.run("arrow-body-style", rule, {
{
line: 1,
column: 17,
+ endLine: 3,
+ endColumn: 2,
type: "ArrowFunctionExpression",
messageId: "unexpectedSingleBlock"
}
@@ -452,6 +690,8 @@ ruleTester.run("arrow-body-style", rule, {
{
line: 1,
column: 17,
+ endLine: 2,
+ endColumn: 13,
type: "ArrowFunctionExpression",
messageId: "unexpectedSingleBlock"
}
@@ -464,6 +704,8 @@ ruleTester.run("arrow-body-style", rule, {
{
line: 1,
column: 17,
+ endLine: 2,
+ endColumn: 2,
type: "ArrowFunctionExpression",
messageId: "unexpectedSingleBlock"
}
@@ -508,6 +750,8 @@ ruleTester.run("arrow-body-style", rule, {
{
line: 2,
column: 31,
+ endLine: 7,
+ endColumn: 16,
type: "ArrowFunctionExpression",
messageId: "unexpectedObjectBlock"
}
diff --git a/eslint/tests/lib/rules/arrow-parens.js b/eslint/tests/lib/rules/arrow-parens.js
index edd1ae6..61d7635 100644
--- a/eslint/tests/lib/rules/arrow-parens.js
+++ b/eslint/tests/lib/rules/arrow-parens.js
@@ -45,16 +45,22 @@ const valid = [
{ code: "a.then((foo) => {});", options: ["always"] },
{ code: "a.then((foo) => { if (true) {}; });", options: ["always"] },
{ code: "a.then(async (foo) => { if (true) {}; });", options: ["always"], parserOptions: { ecmaVersion: 8 } },
+ { code: "(a: T) => a", options: ["always"], parser: parser("identifer-type") },
+ { code: "(a): T => a", options: ["always"], parser: parser("return-type") },
// "as-needed"
{ code: "() => {}", options: ["as-needed"] },
{ code: "a => {}", options: ["as-needed"] },
{ code: "a => a", options: ["as-needed"] },
+ { code: "a => (a)", options: ["as-needed"] },
+ { code: "(a => a)", options: ["as-needed"] },
+ { code: "((a => a))", options: ["as-needed"] },
{ code: "([a, b]) => {}", options: ["as-needed"] },
{ code: "({ a, b }) => {}", options: ["as-needed"] },
{ code: "(a = 10) => {}", options: ["as-needed"] },
{ code: "(...a) => a[0]", options: ["as-needed"] },
{ code: "(a, b) => {}", options: ["as-needed"] },
+ { code: "async a => a", options: ["as-needed"], parserOptions: { ecmaVersion: 8 } },
{ code: "async ([a, b]) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 8 } },
{ code: "async (a, b) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 8 } },
{ code: "(a: T) => a", options: ["as-needed"], parser: parser("identifer-type") },
@@ -63,6 +69,9 @@ const valid = [
// "as-needed", { "requireForBlockBody": true }
{ code: "() => {}", options: ["as-needed", { requireForBlockBody: true }] },
{ code: "a => a", options: ["as-needed", { requireForBlockBody: true }] },
+ { code: "a => (a)", options: ["as-needed", { requireForBlockBody: true }] },
+ { code: "(a => a)", options: ["as-needed", { requireForBlockBody: true }] },
+ { code: "((a => a))", options: ["as-needed", { requireForBlockBody: true }] },
{ code: "([a, b]) => {}", options: ["as-needed", { requireForBlockBody: true }] },
{ code: "([a, b]) => a", options: ["as-needed", { requireForBlockBody: true }] },
{ code: "({ a, b }) => {}", options: ["as-needed", { requireForBlockBody: true }] },
@@ -136,6 +145,83 @@ const valid = [
{
code: "var bar = (/*comment here*/{a}) => a",
options: ["as-needed"]
+ },
+
+ // generics
+ {
+ code: "(a) => b",
+ options: ["always"],
+ parser: parser("generics-simple")
+ },
+ {
+ code: "(a) => b",
+ options: ["as-needed"],
+ parser: parser("generics-simple")
+ },
+ {
+ code: "(a) => b",
+ options: ["as-needed", { requireForBlockBody: true }],
+ parser: parser("generics-simple")
+ },
+ {
+ code: "async (a) => b",
+ options: ["always"],
+ parser: parser("generics-simple-async")
+ },
+ {
+ code: "async (a) => b",
+ options: ["as-needed"],
+ parser: parser("generics-simple-async")
+ },
+ {
+ code: "async (a) => b",
+ options: ["as-needed", { requireForBlockBody: true }],
+ parser: parser("generics-simple-async")
+ },
+ {
+ code: "() => b",
+ options: ["always"],
+ parser: parser("generics-simple-no-params")
+ },
+ {
+ code: "() => b",
+ options: ["as-needed"],
+ parser: parser("generics-simple-no-params")
+ },
+ {
+ code: "() => b",
+ options: ["as-needed", { requireForBlockBody: true }],
+ parser: parser("generics-simple-no-params")
+ },
+ {
+ code: "(a) => b",
+ options: ["always"],
+ parser: parser("generics-extends")
+ },
+ {
+ code: "(a) => b",
+ options: ["as-needed"],
+ parser: parser("generics-extends")
+ },
+ {
+ code: "(a) => b",
+ options: ["as-needed", { requireForBlockBody: true }],
+ parser: parser("generics-extends")
+ },
+ {
+ code: "(a) => b",
+ options: ["always"],
+ parser: parser("generics-extends-complex")
+ },
+ {
+ code: "(a) => b",
+ options: ["as-needed"],
+ parser: parser("generics-extends-complex")
+ },
+ {
+ code: "(a) => b",
+ options: ["as-needed", { requireForBlockBody: true }],
+ parser: parser("generics-extends-complex")
}
];
@@ -236,6 +322,30 @@ const invalid = [
type
}]
},
+ {
+ code: "( a ) => b",
+ output: "a => b",
+ options: ["as-needed"],
+ errors: [{
+ line: 1,
+ column: 4,
+ endColumn: 5,
+ messageId: "unexpectedParens",
+ type
+ }]
+ },
+ {
+ code: "(\na\n) => b",
+ output: "a => b",
+ options: ["as-needed"],
+ errors: [{
+ line: 2,
+ column: 1,
+ endColumn: 2,
+ messageId: "unexpectedParens",
+ type
+ }]
+ },
{
code: "(a,) => a",
output: "a => a",
@@ -275,6 +385,30 @@ const invalid = [
type
}]
},
+ {
+ code: "typeof((a) => {})",
+ output: "typeof(a => {})",
+ options: ["as-needed"],
+ errors: [{
+ line: 1,
+ column: 9,
+ endColumn: 10,
+ messageId: "unexpectedParens",
+ type
+ }]
+ },
+ {
+ code: "function *f() { yield(a) => a; }",
+ output: "function *f() { yield a => a; }",
+ options: ["as-needed"],
+ errors: [{
+ line: 1,
+ column: 23,
+ endColumn: 24,
+ messageId: "unexpectedParens",
+ type
+ }]
+ },
// "as-needed", { "requireForBlockBody": true }
{
diff --git a/eslint/tests/lib/rules/camelcase.js b/eslint/tests/lib/rules/camelcase.js
index 7c7a457..4f9cdca 100644
--- a/eslint/tests/lib/rules/camelcase.js
+++ b/eslint/tests/lib/rules/camelcase.js
@@ -169,6 +169,104 @@ ruleTester.run("camelcase", rule, {
options: [{ ignoreImports: false }],
parserOptions: { ecmaVersion: 6, sourceType: "module" }
},
+ {
+ code: "var _camelCased = aGlobalVariable",
+ options: [{ ignoreGlobals: false }],
+ globals: { aGlobalVariable: "readonly" }
+ },
+ {
+ code: "var camelCased = _aGlobalVariable",
+ options: [{ ignoreGlobals: false }],
+ globals: { _aGlobalVariable: "readonly" }
+ },
+ {
+ code: "var camelCased = a_global_variable",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ },
+ {
+ code: "a_global_variable.foo()",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ },
+ {
+ code: "a_global_variable[undefined]",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ },
+ {
+ code: "var foo = a_global_variable.bar",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ },
+ {
+ code: "a_global_variable.foo = bar",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ },
+ {
+ code: "( { foo: a_global_variable.bar } = baz )",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ },
+ {
+ code: "a_global_variable = foo",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ },
+ {
+ code: "a_global_variable = foo",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ },
+ {
+ code: "({ a_global_variable } = foo)",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ },
+ {
+ code: "({ snake_cased: a_global_variable } = foo)",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ },
+ {
+ code: "({ snake_cased: a_global_variable = foo } = bar)",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ },
+ {
+ code: "[a_global_variable] = bar",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ },
+ {
+ code: "[a_global_variable = foo] = bar",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ },
+ {
+ code: "foo[a_global_variable] = bar",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ },
+ {
+ code: "var foo = { [a_global_variable]: bar }",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ },
+ {
+ code: "var { [a_global_variable]: foo } = bar",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ },
{
code: "function foo({ no_camelcased: camelCased }) {};",
parserOptions: { ecmaVersion: 6 }
@@ -652,6 +750,257 @@ ruleTester.run("camelcase", rule, {
}
]
},
+ {
+ code: "var camelCased = snake_cased",
+ options: [{ ignoreGlobals: false }],
+ globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "snake_cased" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "a_global_variable.foo()",
+ options: [{ ignoreGlobals: false }],
+ globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "a_global_variable[undefined]",
+ options: [{ ignoreGlobals: false }],
+ globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "var camelCased = snake_cased",
+ globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "snake_cased" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "var camelCased = snake_cased",
+ options: [{}],
+ globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "snake_cased" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "foo.a_global_variable = bar",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "var foo = { a_global_variable: bar }",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "var foo = { a_global_variable: a_global_variable }",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier",
+ column: 13
+ }
+ ]
+ },
+ {
+ code: "var foo = { a_global_variable() {} }",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "class Foo { a_global_variable() {} }",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "a_global_variable: for (;;);",
+ options: [{ ignoreGlobals: true }],
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "if (foo) { let a_global_variable; a_global_variable = bar; }",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier",
+ column: 16
+ },
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier",
+ column: 35
+ }
+ ]
+ },
+ {
+ code: "function foo(a_global_variable) { foo = a_global_variable; }",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier",
+ column: 14
+ },
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier",
+ column: 41
+ }
+ ]
+ },
+ {
+ code: "var a_global_variable",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "function a_global_variable () {}",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "const a_global_variable = foo; bar = a_global_variable",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier",
+ column: 7
+ },
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier",
+ column: 38
+ }
+ ]
+ },
+ {
+ code: "bar = a_global_variable; var a_global_variable;",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier",
+ column: 7
+ },
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier",
+ column: 30
+ }
+ ]
+ },
+ {
+ code: "var foo = { a_global_variable }",
+ options: [{ ignoreGlobals: true }],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { a_global_variable: "readonly" }, // eslint-disable-line camelcase
+ errors: [
+ {
+ messageId: "notCamelCase",
+ data: { name: "a_global_variable" },
+ type: "Identifier"
+ }
+ ]
+ },
{
code: "export * as snake_cased from 'mod'",
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
@@ -957,6 +1306,20 @@ ruleTester.run("camelcase", rule, {
type: "Identifier"
}
]
+ },
+
+ // Optional chaining.
+ {
+ code: "obj.o_k.non_camelcase = 0",
+ options: [{ properties: "always" }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "notCamelCase", data: { name: "non_camelcase" } }]
+ },
+ {
+ code: "(obj?.o_k).non_camelcase = 0",
+ options: [{ properties: "always" }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "notCamelCase", data: { name: "non_camelcase" } }]
}
]
});
diff --git a/eslint/tests/lib/rules/computed-property-spacing.js b/eslint/tests/lib/rules/computed-property-spacing.js
index b0ecef6..a1e5834 100644
--- a/eslint/tests/lib/rules/computed-property-spacing.js
+++ b/eslint/tests/lib/rules/computed-property-spacing.js
@@ -1906,6 +1906,28 @@ ruleTester.run("computed-property-spacing", rule, {
endColumn: 19
}
]
+ },
+
+ // Optional chaining
+ {
+ code: "obj?.[1];",
+ output: "obj?.[ 1 ];",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "missingSpaceAfter", data: { tokenValue: "[" } },
+ { messageId: "missingSpaceBefore", data: { tokenValue: "]" } }
+ ]
+ },
+ {
+ code: "obj?.[ 1 ];",
+ output: "obj?.[1];",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "unexpectedSpaceAfter", data: { tokenValue: "[" } },
+ { messageId: "unexpectedSpaceBefore", data: { tokenValue: "]" } }
+ ]
}
]
});
diff --git a/eslint/tests/lib/rules/constructor-super.js b/eslint/tests/lib/rules/constructor-super.js
index b6c223d..85e4471 100644
--- a/eslint/tests/lib/rules/constructor-super.js
+++ b/eslint/tests/lib/rules/constructor-super.js
@@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester");
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
ruleTester.run("constructor-super", rule, {
valid: [
@@ -37,7 +37,19 @@ ruleTester.run("constructor-super", rule, {
"class A extends B { constructor() { if (true) { super(); } else { super(); } } }",
"class A extends (class B {}) { constructor() { super(); } }",
"class A extends (B = C) { constructor() { super(); } }",
+ "class A extends (B &&= C) { constructor() { super(); } }",
+ "class A extends (B ||= C) { constructor() { super(); } }",
+ "class A extends (B ??= C) { constructor() { super(); } }",
+ "class A extends (B ||= 5) { constructor() { super(); } }",
+ "class A extends (B ??= 5) { constructor() { super(); } }",
"class A extends (B || C) { constructor() { super(); } }",
+ "class A extends (5 && B) { constructor() { super(); } }",
+
+ // A future improvement could detect the left side as statically falsy, making this invalid.
+ "class A extends (false && B) { constructor() { super(); } }",
+ "class A extends (B || 5) { constructor() { super(); } }",
+ "class A extends (B ?? 5) { constructor() { super(); } }",
+
"class A extends (a ? B : C) { constructor() { super(); } }",
"class A extends (B, C) { constructor() { super(); } }",
@@ -88,7 +100,10 @@ ruleTester.run("constructor-super", rule, {
}
}
}
- `
+ `,
+
+ // Optional chaining
+ "class A extends obj?.prop { constructor() { super(); } }"
],
invalid: [
@@ -109,6 +124,40 @@ ruleTester.run("constructor-super", rule, {
code: "class A extends 'test' { constructor() { super(); } }",
errors: [{ messageId: "badSuper", type: "CallExpression" }]
},
+ {
+ code: "class A extends (B = 5) { constructor() { super(); } }",
+ errors: [{ messageId: "badSuper", type: "CallExpression" }]
+ },
+ {
+ code: "class A extends (B && 5) { constructor() { super(); } }",
+ errors: [{ messageId: "badSuper", type: "CallExpression" }]
+ },
+ {
+
+ // `B &&= 5` evaluates either to a falsy value of `B` (which, then, cannot be a constructor), or to '5'
+ code: "class A extends (B &&= 5) { constructor() { super(); } }",
+ errors: [{ messageId: "badSuper", type: "CallExpression" }]
+ },
+ {
+ code: "class A extends (B += C) { constructor() { super(); } }",
+ errors: [{ messageId: "badSuper", type: "CallExpression" }]
+ },
+ {
+ code: "class A extends (B -= C) { constructor() { super(); } }",
+ errors: [{ messageId: "badSuper", type: "CallExpression" }]
+ },
+ {
+ code: "class A extends (B **= C) { constructor() { super(); } }",
+ errors: [{ messageId: "badSuper", type: "CallExpression" }]
+ },
+ {
+ code: "class A extends (B |= C) { constructor() { super(); } }",
+ errors: [{ messageId: "badSuper", type: "CallExpression" }]
+ },
+ {
+ code: "class A extends (B &= C) { constructor() { super(); } }",
+ errors: [{ messageId: "badSuper", type: "CallExpression" }]
+ },
// derived classes.
{
diff --git a/eslint/tests/lib/rules/curly.js b/eslint/tests/lib/rules/curly.js
index 1d5ee8c..155eec9 100644
--- a/eslint/tests/lib/rules/curly.js
+++ b/eslint/tests/lib/rules/curly.js
@@ -535,6 +535,30 @@ ruleTester.run("curly", rule, {
}
]
},
+ {
+ code: "if (foo) if (bar) { baz() }",
+ output: "if (foo) if (bar) baz() ",
+ options: ["multi"],
+ errors: [
+ {
+ messageId: "unexpectedCurlyAfterCondition",
+ data: { name: "if" },
+ type: "IfStatement"
+ }
+ ]
+ },
+ {
+ code: "if (foo) if (bar) baz(); else if (quux) { quuux(); }",
+ output: "if (foo) if (bar) baz(); else if (quux) quuux(); ",
+ options: ["multi"],
+ errors: [
+ {
+ messageId: "unexpectedCurlyAfterCondition",
+ data: { name: "if" },
+ type: "IfStatement"
+ }
+ ]
+ },
{
code: "while (foo) { bar() }",
output: "while (foo) bar() ",
@@ -559,6 +583,18 @@ ruleTester.run("curly", rule, {
}
]
},
+ {
+ code: "if (foo) if (bar); else { baz() }",
+ output: "if (foo) if (bar); else baz() ",
+ options: ["multi"],
+ errors: [
+ {
+ messageId: "unexpectedCurlyAfter",
+ data: { name: "else" },
+ type: "IfStatement"
+ }
+ ]
+ },
{
code: "if (true) { if (false) console.log(1) }",
output: "if (true) if (false) console.log(1) ",
@@ -981,6 +1017,18 @@ ruleTester.run("curly", rule, {
}
]
},
+ {
+ code: "if (true) if (true) foo(); else { bar(); baz(); }",
+ output: "if (true) if (true) {foo();} else { bar(); baz(); }",
+ options: ["multi", "consistent"],
+ errors: [
+ {
+ messageId: "missingCurlyAfterCondition",
+ data: { name: "if" },
+ type: "IfStatement"
+ }
+ ]
+ },
{
code: "do{foo();} while (bar)",
output: "do foo(); while (bar)",
diff --git a/eslint/tests/lib/rules/dot-location.js b/eslint/tests/lib/rules/dot-location.js
index 1a6ea37..bd56e1f 100644
--- a/eslint/tests/lib/rules/dot-location.js
+++ b/eslint/tests/lib/rules/dot-location.js
@@ -136,6 +136,68 @@ ruleTester.run("dot-location", rule, {
{
code: "(\na &&\nb()\n).toString()",
options: ["object"]
+ },
+
+ // Optional chaining
+ {
+ code: "obj?.prop",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.[key]",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.\nprop",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj\n?.[key]",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.\n[key]",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.[\nkey]",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.prop",
+ options: ["property"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.[key]",
+ options: ["property"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj\n?.prop",
+ options: ["property"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj\n?.[key]",
+ options: ["property"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.\n[key]",
+ options: ["property"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.[\nkey]",
+ options: ["property"],
+ parserOptions: { ecmaVersion: 2020 }
}
],
invalid: [
@@ -169,6 +231,52 @@ ruleTester.run("dot-location", rule, {
options: ["object"],
errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
},
+ {
+ code: "01\n.toExponential()",
+ output: "01.\ntoExponential()",
+ options: ["object"],
+ errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+ },
+ {
+ code: "08\n.toExponential()",
+ output: "08 .\ntoExponential()",
+ options: ["object"],
+ errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+ },
+ {
+ code: "0190\n.toExponential()",
+ output: "0190 .\ntoExponential()",
+ options: ["object"],
+ errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+ },
+ {
+ code: "5_000\n.toExponential()",
+ output: "5_000 .\ntoExponential()",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+ },
+ {
+ code: "5_000_00\n.toExponential()",
+ output: "5_000_00 .\ntoExponential()",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+ },
+ {
+ code: "5.000_000\n.toExponential()",
+ output: "5.000_000.\ntoExponential()",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+ },
+ {
+ code: "0b1010_1010\n.toExponential()",
+ output: "0b1010_1010.\ntoExponential()",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+ },
{
code: "foo /* a */ . /* b */ \n /* c */ bar",
output: "foo /* a */ /* b */ \n /* c */ .bar",
@@ -255,6 +363,29 @@ ruleTester.run("dot-location", rule, {
output: "(5).\ntoExponential()",
options: ["object"],
errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+ },
+
+ // Optional chaining
+ {
+ code: "obj\n?.prop",
+ output: "obj?.\nprop",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expectedDotAfterObject" }]
+ },
+ {
+ code: "10\n?.prop",
+ output: "10?.\nprop",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expectedDotAfterObject" }]
+ },
+ {
+ code: "obj?.\nprop",
+ output: "obj\n?.prop",
+ options: ["property"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expectedDotBeforeProperty" }]
}
]
});
diff --git a/eslint/tests/lib/rules/dot-notation.js b/eslint/tests/lib/rules/dot-notation.js
index 5532ea3..39f3a67 100644
--- a/eslint/tests/lib/rules/dot-notation.js
+++ b/eslint/tests/lib/rules/dot-notation.js
@@ -218,6 +218,93 @@ ruleTester.run("dot-notation", rule, {
output: null, // `let["if"]()` is a syntax error because `let[` indicates a destructuring variable declaration
options: [{ allowKeywords: false }],
errors: [{ messageId: "useBrackets", data: { key: "if" } }]
+ },
+ {
+ code: "5['prop']",
+ output: "5 .prop",
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "-5['prop']",
+ output: "-5 .prop",
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "01['prop']",
+ output: "01.prop",
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "01234567['prop']",
+ output: "01234567.prop",
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "08['prop']",
+ output: "08 .prop",
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "090['prop']",
+ output: "090 .prop",
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "018['prop']",
+ output: "018 .prop",
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "5_000['prop']",
+ output: "5_000 .prop",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "5_000_00['prop']",
+ output: "5_000_00 .prop",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "5.000_000['prop']",
+ output: "5.000_000.prop",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "0b1010_1010['prop']",
+ output: "0b1010_1010.prop",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+
+ // Optional chaining
+ {
+ code: "obj?.['prop']",
+ output: "obj?.prop",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "0?.['prop']",
+ output: "0?.prop",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+ },
+ {
+ code: "obj?.true",
+ output: "obj?.[\"true\"]",
+ options: [{ allowKeywords: false }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "useBrackets", data: { key: "true" } }]
+ },
+ {
+ code: "let?.true",
+ output: "let?.[\"true\"]",
+ options: [{ allowKeywords: false }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "useBrackets", data: { key: "true" } }]
}
]
});
diff --git a/eslint/tests/lib/rules/func-call-spacing.js b/eslint/tests/lib/rules/func-call-spacing.js
index f0199fd..3520882 100644
--- a/eslint/tests/lib/rules/func-call-spacing.js
+++ b/eslint/tests/lib/rules/func-call-spacing.js
@@ -218,6 +218,28 @@ ruleTester.run("func-call-spacing", rule, {
code: "import\n(source)",
options: ["always", { allowNewlines: true }],
parserOptions: { ecmaVersion: 2020 }
+ },
+
+ // Optional chaining
+ {
+ code: "func?.()",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "func ?.()",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "func?. ()",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "func ?. ()",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2020 }
}
],
invalid: [
@@ -560,7 +582,7 @@ ruleTester.run("func-call-spacing", rule, {
},
{
code: "f\n();",
- output: "f ();",
+ output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
},
@@ -572,7 +594,7 @@ ruleTester.run("func-call-spacing", rule, {
},
{
code: "f\n(a, b);",
- output: "f (a, b);",
+ output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
},
@@ -593,7 +615,7 @@ ruleTester.run("func-call-spacing", rule, {
},
{
code: "f.b\n();",
- output: "f.b ();",
+ output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [
{
@@ -614,7 +636,7 @@ ruleTester.run("func-call-spacing", rule, {
},
{
code: "f.b\n().c ();",
- output: "f.b ().c ();",
+ output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [
{
@@ -635,13 +657,13 @@ ruleTester.run("func-call-spacing", rule, {
},
{
code: "f\n() ()",
- output: "f () ()",
+ output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
},
{
code: "f\n()()",
- output: "f () ()",
+ output: "f\n() ()", // Don't fix the first error to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [
{ messageId: "unexpectedNewline", type: "CallExpression" },
@@ -696,25 +718,25 @@ ruleTester.run("func-call-spacing", rule, {
},
{
code: "f\r();",
- output: "f ();",
+ output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
},
{
code: "f\u2028();",
- output: "f ();",
+ output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
},
{
code: "f\u2029();",
- output: "f ();",
+ output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
},
{
code: "f\r\n();",
- output: "f ();",
+ output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
},
@@ -841,7 +863,7 @@ ruleTester.run("func-call-spacing", rule, {
},
{
code: "fnn\n (a, b);",
- output: "fnn (a, b);",
+ output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
options: ["always"],
errors: [
{
@@ -853,6 +875,96 @@ ruleTester.run("func-call-spacing", rule, {
endColumn: 2
}
]
+ },
+ {
+ code: "f /*comment*/ ()",
+ output: null, // Don't remove comments
+ options: ["never"],
+ errors: [{ messageId: "unexpectedWhitespace" }]
+ },
+ {
+ code: "f /*\n*/ ()",
+ output: null, // Don't remove comments
+ options: ["never"],
+ errors: [{ messageId: "unexpectedWhitespace" }]
+ },
+ {
+ code: "f/*comment*/()",
+ output: "f/*comment*/ ()",
+ options: ["always"],
+ errors: [{ messageId: "missing" }]
+ },
+
+ // Optional chaining
+ {
+ code: "func ?.()",
+ output: "func?.()",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace" }]
+ },
+ {
+ code: "func?. ()",
+ output: "func?.()",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace" }]
+ },
+ {
+ code: "func ?. ()",
+ output: "func?.()",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace" }]
+ },
+ {
+ code: "func\n?.()",
+ output: "func?.()",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace" }]
+ },
+ {
+ code: "func\n//comment\n?.()",
+ output: null, // Don't remove comments
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace" }]
+ },
+ {
+ code: "func?.()",
+ output: null, // Not sure inserting a space into either before/after `?.`.
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "missing" }]
+ },
+ {
+ code: "func\n ?.()",
+ output: "func ?.()",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedNewline" }]
+ },
+ {
+ code: "func?.\n ()",
+ output: "func?. ()",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedNewline" }]
+ },
+ {
+ code: "func ?.\n ()",
+ output: "func ?. ()",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedNewline" }]
+ },
+ {
+ code: "func\n /*comment*/ ?.()",
+ output: null, // Don't remove comments
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedNewline" }]
}
]
});
diff --git a/eslint/tests/lib/rules/func-name-matching.js b/eslint/tests/lib/rules/func-name-matching.js
index 72ee66b..908e910 100644
--- a/eslint/tests/lib/rules/func-name-matching.js
+++ b/eslint/tests/lib/rules/func-name-matching.js
@@ -29,6 +29,9 @@ ruleTester.run("func-name-matching", rule, {
"foo = function foo() {};",
{ code: "foo = function foo() {};", options: ["always"] },
{ code: "foo = function bar() {};", options: ["never"] },
+ { code: "foo &&= function foo() {};", parserOptions: { ecmaVersion: 2021 } },
+ { code: "obj.foo ||= function foo() {};", parserOptions: { ecmaVersion: 2021 } },
+ { code: "obj['foo'] ??= function foo() {};", parserOptions: { ecmaVersion: 2021 } },
"obj.foo = function foo() {};",
{ code: "obj.foo = function foo() {};", options: ["always"] },
{ code: "obj.foo = function bar() {};", options: ["never"] },
@@ -284,6 +287,27 @@ ruleTester.run("func-name-matching", rule, {
{ messageId: "matchVariable", data: { funcName: "bar", name: "foo" } }
]
},
+ {
+ code: "foo &&= function bar() {};",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [
+ { messageId: "matchVariable", data: { funcName: "bar", name: "foo" } }
+ ]
+ },
+ {
+ code: "obj.foo ||= function bar() {};",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } }
+ ]
+ },
+ {
+ code: "obj['foo'] ??= function bar() {};",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } }
+ ]
+ },
{
code: "obj.foo = function bar() {};",
parserOptions: { ecmaVersion: 6 },
@@ -457,6 +481,79 @@ ruleTester.run("func-name-matching", rule, {
errors: [
{ messageId: "matchProperty", data: { funcName: "bar", name: "value" } }
]
+ },
+
+ // Optional chaining
+ {
+ code: "(obj?.aaa).foo = function bar() {};",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } }
+ ]
+ },
+ {
+ code: "Object?.defineProperty(foo, 'bar', { value: function baz() {} })",
+ options: ["always", { considerPropertyDescriptor: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } }
+ ]
+ },
+ {
+ code: "(Object?.defineProperty)(foo, 'bar', { value: function baz() {} })",
+ options: ["always", { considerPropertyDescriptor: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } }
+ ]
+ },
+ {
+ code: "Object?.defineProperty(foo, 'bar', { value: function bar() {} })",
+ options: ["never", { considerPropertyDescriptor: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } }
+ ]
+ },
+ {
+ code: "(Object?.defineProperty)(foo, 'bar', { value: function bar() {} })",
+ options: ["never", { considerPropertyDescriptor: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } }
+ ]
+ },
+ {
+ code: "Object?.defineProperties(foo, { bar: { value: function baz() {} } })",
+ options: ["always", { considerPropertyDescriptor: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } }
+ ]
+ },
+ {
+ code: "(Object?.defineProperties)(foo, { bar: { value: function baz() {} } })",
+ options: ["always", { considerPropertyDescriptor: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } }
+ ]
+ },
+ {
+ code: "Object?.defineProperties(foo, { bar: { value: function bar() {} } })",
+ options: ["never", { considerPropertyDescriptor: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } }
+ ]
+ },
+ {
+ code: "(Object?.defineProperties)(foo, { bar: { value: function bar() {} } })",
+ options: ["never", { considerPropertyDescriptor: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } }
+ ]
}
]
});
diff --git a/eslint/tests/lib/rules/function-paren-newline.js b/eslint/tests/lib/rules/function-paren-newline.js
index f918a38..0c9feb7 100644
--- a/eslint/tests/lib/rules/function-paren-newline.js
+++ b/eslint/tests/lib/rules/function-paren-newline.js
@@ -87,6 +87,23 @@ ruleTester.run("function-paren-newline", rule, {
code: "function baz(foo, bar) {}",
options: ["multiline"]
},
+ {
+ code: "async (foo, bar) => {};",
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: `
+ async (
+ foo,
+ bar
+ ) => {};
+ `,
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: "async foo => {};",
+ parserOptions: { ecmaVersion: 2017 }
+ },
{
code: "import(source)",
parserOptions: { ecmaVersion: 2020 }
@@ -249,6 +266,40 @@ ruleTester.run("function-paren-newline", rule, {
code: "new (Foo)",
options: ["multiline-arguments"]
},
+ {
+ code: "async (foo, bar) => {};",
+ options: ["multiline-arguments"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: "async (foo) => {};",
+ options: ["multiline-arguments"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: `
+ async (
+ foo
+ ) => {};
+ `,
+ options: ["multiline-arguments"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: `
+ async (
+ foo,
+ bar
+ ) => {};
+ `,
+ options: ["multiline-arguments"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: "async foo => {};",
+ options: ["multiline-arguments"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
{
code: "import(source)",
options: ["multiline-arguments"],
@@ -329,6 +380,30 @@ ruleTester.run("function-paren-newline", rule, {
`,
options: ["always"]
},
+ {
+ code: `
+ async (
+ foo
+ ) => {};
+ `,
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: `
+ async (
+ foo,
+ bar
+ ) => {};
+ `,
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: "async foo => {};",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
{
code: "import(\n source\n)",
options: ["always"],
@@ -360,6 +435,16 @@ ruleTester.run("function-paren-newline", rule, {
code: "function baz() {}",
options: ["never"]
},
+ {
+ code: "async (foo, bar) => {};",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: "async foo => {};",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
{
code: "import(source)",
options: ["never"],
@@ -391,6 +476,27 @@ ruleTester.run("function-paren-newline", rule, {
code: "baz(foo, bar);",
options: [{ minItems: 3 }]
},
+ {
+ code: "async (foo, bar) => {};",
+ options: [{ minItems: 3 }],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: `
+ async (
+ foo,
+ bar,
+ baz
+ ) => {};
+ `,
+ options: [{ minItems: 3 }],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: "async foo => {};",
+ options: [{ minItems: 3 }],
+ parserOptions: { ecmaVersion: 2017 }
+ },
{
code: "import(source)",
options: [{ minItems: 3 }],
@@ -431,6 +537,43 @@ ruleTester.run("function-paren-newline", rule, {
`,
options: ["consistent"]
},
+ {
+ code: "async (foo, bar) => {};",
+ options: ["consistent"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: "async foo => {};",
+ options: ["consistent"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: `
+ async (foo,
+ bar) => {};
+ `,
+ options: ["consistent"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: `
+ async (
+ foo, bar
+ ) => {};
+ `,
+ options: ["consistent"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
+ {
+ code: `
+ async (
+ foo,
+ bar
+ ) => {};
+ `,
+ options: ["consistent"],
+ parserOptions: { ecmaVersion: 2017 }
+ },
{
code: "import(source)",
options: ["consistent"],
@@ -564,6 +707,55 @@ ruleTester.run("function-paren-newline", rule, {
output: null,
errors: [RIGHT_UNEXPECTED_ERROR]
},
+ {
+ code: `
+ async (
+ foo, bar
+ ) => {};
+ `,
+ output: `
+ async (foo, bar) => {};
+ `,
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+ },
+ {
+ code: `
+ async (foo, bar
+ ) => {};
+ `,
+ output: `
+ async (foo, bar) => {};
+ `,
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [RIGHT_UNEXPECTED_ERROR]
+ },
+ {
+ code: `
+ async (foo,
+ bar) => {};
+ `,
+ output: `
+ async (\nfoo,
+ bar\n) => {};
+ `,
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+ },
+ {
+ code: `
+ async (
+ foo,
+ bar) => {};
+ `,
+ output: `
+ async (
+ foo,
+ bar\n) => {};
+ `,
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [RIGHT_MISSING_ERROR]
+ },
{
code: "import(\n source\n)",
output: "import(source)",
@@ -757,6 +949,46 @@ ruleTester.run("function-paren-newline", rule, {
options: ["multiline-arguments"],
errors: [RIGHT_UNEXPECTED_ERROR]
},
+ {
+ code: `
+ async (foo, bar
+ ) => {};
+ `,
+ output: `
+ async (foo, bar) => {};
+ `,
+ options: ["multiline-arguments"],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [RIGHT_UNEXPECTED_ERROR]
+ },
+ {
+ code: `
+ async (foo,
+ bar) => {};
+ `,
+ output: `
+ async (\nfoo,
+ bar\n) => {};
+ `,
+ options: ["multiline-arguments"],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+ },
+ {
+ code: `
+ async (
+ foo,
+ bar) => {};
+ `,
+ output: `
+ async (
+ foo,
+ bar\n) => {};
+ `,
+ options: ["multiline-arguments"],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [RIGHT_MISSING_ERROR]
+ },
{
code: "import(source\n)",
output: "import(source)",
@@ -849,6 +1081,45 @@ ruleTester.run("function-paren-newline", rule, {
options: ["always"],
errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
},
+ {
+ code: `
+ async (foo, bar) => {};
+ `,
+ output: `
+ async (\nfoo, bar\n) => {};
+ `,
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+ },
+ {
+ code: `
+ async (foo,
+ bar) => {};
+ `,
+ output: `
+ async (\nfoo,
+ bar\n) => {};
+ `,
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+ },
+ {
+ code: `
+ async (
+ foo,
+ bar) => {};
+ `,
+ output: `
+ async (
+ foo,
+ bar\n) => {};
+ `,
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [RIGHT_MISSING_ERROR]
+ },
{
code: "import(source)",
output: "import(\nsource\n)",
@@ -965,6 +1236,35 @@ ruleTester.run("function-paren-newline", rule, {
options: ["never"],
errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
},
+ {
+ code: `
+ async (
+ foo,
+ bar
+ ) => {};
+ `,
+ output: `
+ async (foo,
+ bar) => {};
+ `,
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+ },
+ {
+ code: `
+ async (
+ foo,
+ bar) => {};
+ `,
+ output: `
+ async (foo,
+ bar) => {};
+ `,
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [LEFT_UNEXPECTED_ERROR]
+ },
{
code: "import(\n source\n)",
output: "import(source)",
@@ -1012,6 +1312,46 @@ ruleTester.run("function-paren-newline", rule, {
options: [{ minItems: 3 }],
errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
},
+ {
+ code: `
+ async (
+ foo,
+ bar
+ ) => {};
+ `,
+ output: `
+ async (foo,
+ bar) => {};
+ `,
+ options: [{ minItems: 3 }],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+ },
+ {
+ code: `
+ async (
+ foo,
+ bar) => {};
+ `,
+ output: `
+ async (foo,
+ bar) => {};
+ `,
+ options: [{ minItems: 3 }],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [LEFT_UNEXPECTED_ERROR]
+ },
+ {
+ code: `
+ async (foo, bar, baz) => {};
+ `,
+ output: `
+ async (\nfoo, bar, baz\n) => {};
+ `,
+ options: [{ minItems: 3 }],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+ },
{
code: "import(\n source\n)",
output: "import(source)",
@@ -1055,6 +1395,35 @@ ruleTester.run("function-paren-newline", rule, {
options: ["consistent"],
errors: [RIGHT_UNEXPECTED_ERROR]
},
+ {
+ code: `
+ async (
+ foo,
+ bar) => {};
+ `,
+ output: `
+ async (
+ foo,
+ bar\n) => {};
+ `,
+ options: ["consistent"],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [RIGHT_MISSING_ERROR]
+ },
+ {
+ code: `
+ async (foo,
+ bar
+ ) => {};
+ `,
+ output: `
+ async (foo,
+ bar) => {};
+ `,
+ options: ["consistent"],
+ parserOptions: { ecmaVersion: 2017 },
+ errors: [RIGHT_UNEXPECTED_ERROR]
+ },
{
code: "import(source\n)",
output: "import(source)",
diff --git a/eslint/tests/lib/rules/getter-return.js b/eslint/tests/lib/rules/getter-return.js
index ea18165..c4092d8 100644
--- a/eslint/tests/lib/rules/getter-return.js
+++ b/eslint/tests/lib/rules/getter-return.js
@@ -218,11 +218,35 @@ 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" }] },
+
+ // 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" }] },
- // option: {allowImplicit: true}
- { code: "Object.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ messageId: "expected" }] }
+ // Optional chaining
+ {
+ code: "Object?.defineProperty(foo, 'bar', { get: function (){} });",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expected", data: { name: "method 'get'" } }]
+ },
+ {
+ code: "(Object?.defineProperty)(foo, 'bar', { get: function (){} });",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expected", data: { name: "method 'get'" } }]
+ },
+ {
+ code: "Object?.defineProperty(foo, 'bar', { get: function (){} });",
+ options,
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expected", data: { name: "method 'get'" } }]
+ },
+ {
+ code: "(Object?.defineProperty)(foo, 'bar', { get: function (){} });",
+ options,
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expected", data: { name: "method 'get'" } }]
+ }
]
});
diff --git a/eslint/tests/lib/rules/global-require.js b/eslint/tests/lib/rules/global-require.js
index 3fb6ccc..4993962 100644
--- a/eslint/tests/lib/rules/global-require.js
+++ b/eslint/tests/lib/rules/global-require.js
@@ -30,7 +30,13 @@ const valid = [
{ code: "var logger = require(DEBUG ? 'dev-logger' : 'logger');" },
{ code: "var logger = DEBUG ? require('dev-logger') : require('logger');" },
{ code: "function localScopedRequire(require) { require('y'); }" },
- { code: "var someFunc = require('./someFunc'); someFunc(function(require) { return('bananas'); });" }
+ { code: "var someFunc = require('./someFunc'); someFunc(function(require) { return('bananas'); });" },
+
+ // Optional chaining
+ {
+ code: "var x = require('y')?.foo;",
+ parserOptions: { ecmaVersion: 2020 }
+ }
];
const error = { messageId: "unexpected", type: "CallExpression" };
diff --git a/eslint/tests/lib/rules/id-blacklist.js b/eslint/tests/lib/rules/id-blacklist.js
index 6c3f729..4d13459 100644
--- a/eslint/tests/lib/rules/id-blacklist.js
+++ b/eslint/tests/lib/rules/id-blacklist.js
@@ -17,7 +17,7 @@ const rule = require("../../../lib/rules/id-blacklist"),
//------------------------------------------------------------------------------
const ruleTester = new RuleTester();
-const error = { messageId: "blacklisted", type: "Identifier" };
+const error = { messageId: "restricted", type: "Identifier" };
ruleTester.run("id-blacklist", rule, {
valid: [
@@ -272,7 +272,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["bar"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
@@ -283,7 +283,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo", "bar"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
@@ -294,7 +294,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 17
@@ -305,7 +305,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 10
@@ -316,7 +316,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 22
@@ -327,7 +327,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 8
@@ -338,7 +338,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["bar"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 26
@@ -350,13 +350,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 5
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 19
@@ -369,7 +369,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 5
@@ -377,7 +377,7 @@ ruleTester.run("id-blacklist", rule, {
// reports each occurrence of local identifier, although it's renamed in this export specifier
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 19
@@ -390,19 +390,19 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 5
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 19
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 26
@@ -415,19 +415,19 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 5
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 19
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 26
@@ -447,7 +447,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["bar"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
@@ -458,7 +458,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo", "bar"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
@@ -469,7 +469,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 17
@@ -480,7 +480,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 10
@@ -491,7 +491,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 22
@@ -508,7 +508,7 @@ ruleTester.run("id-blacklist", rule, {
code: "foo[bar] = baz;",
options: ["bar"],
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier"
}]
@@ -517,7 +517,7 @@ ruleTester.run("id-blacklist", rule, {
code: "baz = foo[bar];",
options: ["bar"],
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier"
}]
@@ -633,7 +633,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 8
@@ -646,7 +646,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 13
@@ -659,13 +659,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 9
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 15
@@ -678,7 +678,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "baz" },
type: "Identifier",
column: 19
@@ -691,13 +691,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 15
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "baz" },
type: "Identifier",
column: 21
@@ -710,19 +710,19 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 9
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "baz" },
type: "Identifier",
column: 23
@@ -735,7 +735,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "baz" },
type: "Identifier",
column: 21
@@ -748,7 +748,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "qux" },
type: "Identifier",
column: 27
@@ -761,7 +761,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 12
@@ -774,7 +774,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "baz" },
type: "Identifier",
column: 24
@@ -787,13 +787,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 4
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 14
@@ -806,7 +806,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
@@ -819,7 +819,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 10
@@ -832,7 +832,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "baz" },
type: "Identifier",
column: 18
@@ -845,7 +845,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 10
@@ -858,7 +858,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 11
@@ -871,7 +871,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
@@ -884,7 +884,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 19
@@ -897,7 +897,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 9 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 10
@@ -910,7 +910,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 7
@@ -923,7 +923,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 8
@@ -937,7 +937,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["undefined"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier"
}
@@ -948,7 +948,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["undefined"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier"
}
@@ -959,7 +959,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["undefined"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier",
column: 13
@@ -972,7 +972,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier"
}
@@ -984,7 +984,7 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier"
}
@@ -996,13 +996,13 @@ ruleTester.run("id-blacklist", rule, {
globals: { myGlobal: "readonly" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "myGlobal" },
type: "Identifier",
column: 1
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "myGlobal" },
type: "Identifier",
column: 30
@@ -1017,13 +1017,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 7
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 22
@@ -1036,13 +1036,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 5
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 10
@@ -1054,13 +1054,13 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 7
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 16
@@ -1072,13 +1072,13 @@ ruleTester.run("id-blacklist", rule, {
options: ["foo"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 10
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 29
@@ -1091,13 +1091,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Foo" },
type: "Identifier",
column: 7
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Foo" },
type: "Identifier",
column: 24
@@ -1112,13 +1112,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier",
column: 5
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier",
column: 16
@@ -1130,13 +1130,13 @@ ruleTester.run("id-blacklist", rule, {
options: ["undefined"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier",
column: 7
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier",
column: 22
@@ -1148,13 +1148,13 @@ ruleTester.run("id-blacklist", rule, {
options: ["undefined"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier",
column: 10
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier",
column: 28
@@ -1167,13 +1167,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier",
column: 7
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier",
column: 21
@@ -1182,8 +1182,8 @@ ruleTester.run("id-blacklist", rule, {
},
/*
- * Assignment to a property with a blacklisted name isn't allowed, in general.
- * In this case, that restriction prevents creating a global variable with a blacklisted name.
+ * Assignment to a property with a restricted name isn't allowed, in general.
+ * In this case, that restriction prevents creating a global variable with a restricted name.
*/
{
code: "/* globals myGlobal */ window.myGlobal = 5; foo = myGlobal;",
@@ -1191,7 +1191,7 @@ ruleTester.run("id-blacklist", rule, {
env: { browser: true },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "myGlobal" },
type: "Identifier",
column: 31
@@ -1206,7 +1206,7 @@ ruleTester.run("id-blacklist", rule, {
globals: { undefined: "off" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier"
}
@@ -1217,7 +1217,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["Number"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier"
}
@@ -1228,7 +1228,7 @@ ruleTester.run("id-blacklist", rule, {
options: ["Map"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Map" },
type: "Identifier"
}
@@ -1242,13 +1242,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier",
column: 16
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier",
column: 33
@@ -1260,13 +1260,13 @@ ruleTester.run("id-blacklist", rule, {
options: ["Number"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier",
column: 14
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier",
column: 32
@@ -1279,13 +1279,13 @@ ruleTester.run("id-blacklist", rule, {
globals: { myGlobal: "readonly" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "myGlobal" },
type: "Identifier",
column: 22
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "myGlobal" },
type: "Identifier",
column: 36
@@ -1298,13 +1298,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier",
column: 28
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier",
column: 58
@@ -1317,13 +1317,13 @@ ruleTester.run("id-blacklist", rule, {
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier",
column: 8
},
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier",
column: 44
@@ -1335,21 +1335,21 @@ ruleTester.run("id-blacklist", rule, {
options: ["undefined"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier"
}
]
},
- // this is a reference to a global variable, but at the same time creates a property with a blacklisted name
+ // this is a reference to a global variable, but at the same time creates a property with a restricted name
{
code: "var foo = { undefined }",
options: ["undefined"],
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier"
}
diff --git a/eslint/tests/lib/rules/id-denylist.js b/eslint/tests/lib/rules/id-denylist.js
new file mode 100644
index 0000000..6da179d
--- /dev/null
+++ b/eslint/tests/lib/rules/id-denylist.js
@@ -0,0 +1,1359 @@
+/**
+ * @fileoverview Tests for id-denylist rule.
+ * @author Keith Cirkel
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/id-denylist"),
+ { RuleTester } = require("../../../lib/rule-tester");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester();
+const error = { messageId: "restricted", type: "Identifier" };
+
+ruleTester.run("id-denylist", rule, {
+ valid: [
+ {
+ code: "foo = \"bar\"",
+ options: ["bar"]
+ },
+ {
+ code: "bar = \"bar\"",
+ options: ["foo"]
+ },
+ {
+ code: "foo = \"bar\"",
+ options: ["f", "fo", "fooo", "bar"]
+ },
+ {
+ code: "function foo(){}",
+ options: ["bar"]
+ },
+ {
+ code: "foo()",
+ options: ["f", "fo", "fooo", "bar"]
+ },
+ {
+ code: "import { foo as bar } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" }
+ },
+ {
+ code: "export { foo as bar } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" }
+ },
+ {
+ code: "foo.bar()",
+ options: ["f", "fo", "fooo", "b", "ba", "baz"]
+ },
+ {
+ code: "var foo = bar.baz;",
+ options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz"]
+ },
+ {
+ code: "var foo = bar.baz.bing;",
+ options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+ },
+ {
+ code: "foo.bar.baz = bing.bong.bash;",
+ options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+ },
+ {
+ code: "if (foo.bar) {}",
+ options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+ },
+ {
+ code: "var obj = { key: foo.bar };",
+ options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+ },
+ {
+ code: "const {foo: bar} = baz",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "const {foo: {bar: baz}} = qux",
+ options: ["foo", "bar"],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "function foo({ bar: baz }) {}",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "function foo({ bar: {baz: qux} }) {}",
+ options: ["bar", "baz"],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "function foo({baz} = obj.qux) {}",
+ options: ["qux"],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "function foo({ foo: {baz} = obj.qux }) {}",
+ options: ["qux"],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "({a: bar = obj.baz});",
+ options: ["baz"],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "({foo: {a: bar = obj.baz}} = qux);",
+ options: ["baz"],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "var arr = [foo.bar];",
+ options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+ },
+ {
+ code: "[foo.bar]",
+ options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+ },
+ {
+ code: "[foo.bar.nesting]",
+ options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+ },
+ {
+ code: "if (foo.bar === bar.baz) { [foo.bar] }",
+ options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+ },
+ {
+ code: "var myArray = new Array(); var myDate = new Date();",
+ options: ["array", "date", "mydate", "myarray", "new", "var"]
+ },
+ {
+ code: "foo()",
+ options: ["foo"]
+ },
+ {
+ code: "foo.bar()",
+ options: ["bar"]
+ },
+ {
+ code: "foo.bar",
+ options: ["bar"]
+ },
+ {
+ code: "({foo: obj.bar.bar.bar.baz} = {});",
+ options: ["foo", "bar"],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "({[obj.bar]: a = baz} = qux);",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6 }
+ },
+
+ // references to global variables
+ {
+ code: "Number.parseInt()",
+ options: ["Number"]
+ },
+ {
+ code: "x = Number.NaN;",
+ options: ["Number"]
+ },
+ {
+ code: "var foo = undefined;",
+ options: ["undefined"]
+ },
+ {
+ code: "if (foo === undefined);",
+ options: ["undefined"]
+ },
+ {
+ code: "obj[undefined] = 5;", // creates obj["undefined"]. It should be disallowed, but the rule doesn't know values of globals and can't control computed access.
+ options: ["undefined"]
+ },
+ {
+ code: "foo = { [myGlobal]: 1 };",
+ options: ["myGlobal"],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { myGlobal: "readonly" }
+ },
+ {
+ code: "({ myGlobal } = foo);", // writability doesn't affect the logic, it's always assumed that user doesn't have control over the names of globals.
+ options: ["myGlobal"],
+ parserOptions: { ecmaVersion: 6 },
+ globals: { myGlobal: "writable" }
+ },
+ {
+ code: "/* global myGlobal: readonly */ myGlobal = 5;",
+ options: ["myGlobal"]
+ },
+ {
+ code: "var foo = [Map];",
+ options: ["Map"],
+ env: { es6: true }
+ },
+ {
+ code: "var foo = { bar: window.baz };",
+ options: ["window"],
+ env: { browser: true }
+ }
+ ],
+ invalid: [
+ {
+ code: "foo = \"bar\"",
+ options: ["foo"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "bar = \"bar\"",
+ options: ["bar"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "foo = \"bar\"",
+ options: ["f", "fo", "foo", "bar"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "function foo(){}",
+ options: ["f", "fo", "foo", "bar"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "import foo from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "import * as foo from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "export * as foo from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 2020, sourceType: "module" },
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "import { foo } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "import { foo as bar } from 'mod'",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 17
+ }]
+ },
+ {
+ code: "import { foo as bar } from 'mod'",
+ options: ["foo", "bar"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 17
+ }]
+ },
+ {
+ code: "import { foo as foo } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 17
+ }]
+ },
+ {
+ code: "import { foo, foo as bar } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 10
+ }]
+ },
+ {
+ code: "import { foo as bar, foo } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 22
+ }]
+ },
+ {
+ code: "import foo, { foo as bar } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 8
+ }]
+ },
+ {
+ code: "var foo; export { foo as bar };",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 26
+ }]
+ },
+ {
+ code: "var foo; export { foo };",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 5
+ },
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 19
+ }
+ ]
+ },
+ {
+ code: "var foo; export { foo as bar };",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 5
+ },
+
+ // reports each occurrence of local identifier, although it's renamed in this export specifier
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 19
+ }
+ ]
+ },
+ {
+ code: "var foo; export { foo as foo };",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 5
+ },
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 19
+ },
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 26
+ }
+ ]
+ },
+ {
+ code: "var foo; export { foo as bar };",
+ options: ["foo", "bar"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 5
+ },
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 19
+ },
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 26
+ }
+ ]
+ },
+ {
+ code: "export { foo } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "export { foo as bar } from 'mod'",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 17
+ }]
+ },
+ {
+ code: "export { foo as bar } from 'mod'",
+ options: ["foo", "bar"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 17
+ }]
+ },
+ {
+ code: "export { foo as foo } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 17
+ }]
+ },
+ {
+ code: "export { foo, foo as bar } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 10
+ }]
+ },
+ {
+ code: "export { foo as bar, foo } from 'mod'",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 22
+ }]
+ },
+ {
+ code: "foo.bar()",
+ options: ["f", "fo", "foo", "b", "ba", "baz"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "foo[bar] = baz;",
+ options: ["bar"],
+ errors: [{
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier"
+ }]
+ },
+ {
+ code: "baz = foo[bar];",
+ options: ["bar"],
+ errors: [{
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier"
+ }]
+ },
+ {
+ code: "var foo = bar.baz;",
+ options: ["f", "fo", "foo", "b", "ba", "barr", "bazz"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "var foo = bar.baz;",
+ options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "if (foo.bar) {}",
+ options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "var obj = { key: foo.bar };",
+ options: ["obj"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "var obj = { key: foo.bar };",
+ options: ["key"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "var obj = { key: foo.bar };",
+ options: ["foo"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "var arr = [foo.bar];",
+ options: ["arr"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "var arr = [foo.bar];",
+ options: ["foo"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "[foo.bar]",
+ options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "if (foo.bar === bar.baz) { [bing.baz] }",
+ options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "if (foo.bar === bar.baz) { [foo.bar] }",
+ options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz", "bingg"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "var myArray = new Array(); var myDate = new Date();",
+ options: ["array", "date", "myDate", "myarray", "new", "var"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "var myArray = new Array(); var myDate = new Date();",
+ options: ["array", "date", "mydate", "myArray", "new", "var"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "foo.bar = 1",
+ options: ["bar"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "foo.bar.baz = 1",
+ options: ["bar", "baz"],
+ errors: [
+ error
+ ]
+ },
+ {
+ code: "const {foo} = baz",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 8
+ }
+ ]
+ },
+ {
+ code: "const {foo: bar} = baz",
+ options: ["foo", "bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 13
+ }
+ ]
+ },
+ {
+ code: "const {[foo]: bar} = baz",
+ options: ["foo", "bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 9
+ },
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 15
+ }
+ ]
+ },
+ {
+ code: "const {foo: {bar: baz}} = qux",
+ options: ["foo", "bar", "baz"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "baz" },
+ type: "Identifier",
+ column: 19
+ }
+ ]
+ },
+ {
+ code: "const {foo: {[bar]: baz}} = qux",
+ options: ["foo", "bar", "baz"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 15
+ },
+ {
+ messageId: "restricted",
+ data: { name: "baz" },
+ type: "Identifier",
+ column: 21
+ }
+ ]
+ },
+ {
+ code: "const {[foo]: {[bar]: baz}} = qux",
+ options: ["foo", "bar", "baz"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 9
+ },
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 17
+ },
+ {
+ messageId: "restricted",
+ data: { name: "baz" },
+ type: "Identifier",
+ column: 23
+ }
+ ]
+ },
+ {
+ code: "function foo({ bar: baz }) {}",
+ options: ["bar", "baz"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "baz" },
+ type: "Identifier",
+ column: 21
+ }
+ ]
+ },
+ {
+ code: "function foo({ bar: {baz: qux} }) {}",
+ options: ["bar", "baz", "qux"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "qux" },
+ type: "Identifier",
+ column: 27
+ }
+ ]
+ },
+ {
+ code: "({foo: obj.bar} = baz);",
+ options: ["foo", "bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 12
+ }
+ ]
+ },
+ {
+ code: "({foo: obj.bar.bar.bar.baz} = {});",
+ options: ["foo", "bar", "baz"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "baz" },
+ type: "Identifier",
+ column: 24
+ }
+ ]
+ },
+ {
+ code: "({[foo]: obj.bar} = baz);",
+ options: ["foo", "bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 4
+ },
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 14
+ }
+ ]
+ },
+ {
+ code: "({foo: { a: obj.bar }} = baz);",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 17
+ }
+ ]
+ },
+ {
+ code: "({a: obj.bar = baz} = qux);",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 10
+ }
+ ]
+ },
+ {
+ code: "({a: obj.bar.bar.baz = obj.qux} = obj.qux);",
+ options: ["a", "bar", "baz", "qux"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "baz" },
+ type: "Identifier",
+ column: 18
+ }
+ ]
+ },
+ {
+ code: "({a: obj[bar] = obj.qux} = obj.qux);",
+ options: ["a", "bar", "baz", "qux"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 10
+ }
+ ]
+ },
+ {
+ code: "({a: [obj.bar] = baz} = qux);",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 11
+ }
+ ]
+ },
+ {
+ code: "({foo: { a: obj.bar = baz}} = qux);",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 17
+ }
+ ]
+ },
+ {
+ code: "({foo: { [a]: obj.bar }} = baz);",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 19
+ }
+ ]
+ },
+ {
+ code: "({...obj.bar} = baz);",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 9 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 10
+ }
+ ]
+ },
+ {
+ code: "([obj.bar] = baz);",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 7
+ }
+ ]
+ },
+ {
+ code: "const [bar] = baz;",
+ options: ["bar"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "bar" },
+ type: "Identifier",
+ column: 8
+ }
+ ]
+ },
+
+ // not a reference to a global variable, because it isn't a reference to a variable
+ {
+ code: "foo.undefined = 1;",
+ options: ["undefined"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "var foo = { undefined: 1 };",
+ options: ["undefined"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "var foo = { undefined: undefined };",
+ options: ["undefined"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier",
+ column: 13
+ }
+ ]
+ },
+ {
+ code: "var foo = { Number() {} };",
+ options: ["Number"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "class Foo { Number() {} }",
+ options: ["Number"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "myGlobal: while(foo) { break myGlobal; } ",
+ options: ["myGlobal"],
+ globals: { myGlobal: "readonly" },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "myGlobal" },
+ type: "Identifier",
+ column: 1
+ },
+ {
+ messageId: "restricted",
+ data: { name: "myGlobal" },
+ type: "Identifier",
+ column: 30
+ }
+ ]
+ },
+
+ // globals declared in the given source code are not excluded from consideration
+ {
+ code: "const foo = 1; bar = foo;",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 7
+ },
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 22
+ }
+ ]
+ },
+ {
+ code: "let foo; foo = bar;",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 5
+ },
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 10
+ }
+ ]
+ },
+ {
+ code: "bar = foo; var foo;",
+ options: ["foo"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 7
+ },
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 16
+ }
+ ]
+ },
+ {
+ code: "function foo() {} var bar = foo;",
+ options: ["foo"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 10
+ },
+ {
+ messageId: "restricted",
+ data: { name: "foo" },
+ type: "Identifier",
+ column: 29
+ }
+ ]
+ },
+ {
+ code: "class Foo {} var bar = Foo;",
+ options: ["Foo"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "Foo" },
+ type: "Identifier",
+ column: 7
+ },
+ {
+ messageId: "restricted",
+ data: { name: "Foo" },
+ type: "Identifier",
+ column: 24
+ }
+ ]
+ },
+
+ // redeclared globals are not excluded from consideration
+ {
+ code: "let undefined; undefined = 1;",
+ options: ["undefined"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier",
+ column: 5
+ },
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier",
+ column: 16
+ }
+ ]
+ },
+ {
+ code: "foo = undefined; var undefined;",
+ options: ["undefined"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier",
+ column: 7
+ },
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier",
+ column: 22
+ }
+ ]
+ },
+ {
+ code: "function undefined(){} x = undefined;",
+ options: ["undefined"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier",
+ column: 10
+ },
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier",
+ column: 28
+ }
+ ]
+ },
+ {
+ code: "class Number {} x = Number.NaN;",
+ options: ["Number"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier",
+ column: 7
+ },
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier",
+ column: 21
+ }
+ ]
+ },
+
+ /*
+ * Assignment to a property with a restricted name isn't allowed, in general.
+ * In this case, that restriction prevents creating a global variable with a restricted name.
+ */
+ {
+ code: "/* globals myGlobal */ window.myGlobal = 5; foo = myGlobal;",
+ options: ["myGlobal"],
+ env: { browser: true },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "myGlobal" },
+ type: "Identifier",
+ column: 31
+ }
+ ]
+ },
+
+ // disabled global variables
+ {
+ code: "var foo = undefined;",
+ options: ["undefined"],
+ globals: { undefined: "off" },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "/* globals Number: off */ Number.parseInt()",
+ options: ["Number"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "var foo = [Map];", // this actually isn't a disabled global: it was never enabled because es6 environment isn't enabled
+ options: ["Map"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "Map" },
+ type: "Identifier"
+ }
+ ]
+ },
+
+ // shadowed global variables
+ {
+ code: "if (foo) { let undefined; bar = undefined; }",
+ options: ["undefined"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier",
+ column: 16
+ },
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier",
+ column: 33
+ }
+ ]
+ },
+ {
+ code: "function foo(Number) { var x = Number.NaN; }",
+ options: ["Number"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier",
+ column: 14
+ },
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier",
+ column: 32
+ }
+ ]
+ },
+ {
+ code: "function foo() { var myGlobal; x = myGlobal; }",
+ options: ["myGlobal"],
+ globals: { myGlobal: "readonly" },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "myGlobal" },
+ type: "Identifier",
+ column: 22
+ },
+ {
+ messageId: "restricted",
+ data: { name: "myGlobal" },
+ type: "Identifier",
+ column: 36
+ }
+ ]
+ },
+ {
+ code: "function foo(bar) { return Number.parseInt(bar); } const Number = 1;",
+ options: ["Number"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier",
+ column: 28
+ },
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier",
+ column: 58
+ }
+ ]
+ },
+ {
+ code: "import Number from 'myNumber'; const foo = Number.parseInt(bar);",
+ options: ["Number"],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier",
+ column: 8
+ },
+ {
+ messageId: "restricted",
+ data: { name: "Number" },
+ type: "Identifier",
+ column: 44
+ }
+ ]
+ },
+ {
+ code: "var foo = function undefined() {};",
+ options: ["undefined"],
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier"
+ }
+ ]
+ },
+
+ // this is a reference to a global variable, but at the same time creates a property with a restricted name
+ {
+ code: "var foo = { undefined }",
+ options: ["undefined"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "undefined" },
+ type: "Identifier"
+ }
+ ]
+ }
+ ]
+});
diff --git a/eslint/tests/lib/rules/id-length.js b/eslint/tests/lib/rules/id-length.js
index a63ae1b..99a3957 100644
--- a/eslint/tests/lib/rules/id-length.js
+++ b/eslint/tests/lib/rules/id-length.js
@@ -77,7 +77,12 @@ ruleTester.run("id-length", rule, {
{ code: "var {x} = foo;", options: [{ properties: "never" }], parserOptions: { ecmaVersion: 6 } },
{ code: "var {x, y: {z}} = foo;", options: [{ properties: "never" }], parserOptions: { ecmaVersion: 6 } },
{ code: "let foo = { [a]: 1 };", options: [{ properties: "always" }], parserOptions: { ecmaVersion: 6 } },
- { code: "let foo = { [a + b]: 1 };", options: [{ properties: "always" }], parserOptions: { ecmaVersion: 6 } }
+ { code: "let foo = { [a + b]: 1 };", options: [{ properties: "always" }], parserOptions: { ecmaVersion: 6 } },
+ { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }] },
+ { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "send$"] }] },
+ { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "^A", "^Z"] }] },
+ { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^A", "^BEFORE_", "^Z"] }] },
+ { code: "var x = 1 ;", options: [{ min: 3, max: 5, exceptionPatterns: ["[x-z]"] }] }
],
invalid: [
{ code: "var x = 1;", errors: [tooShortError] },
@@ -107,6 +112,13 @@ ruleTester.run("id-length", rule, {
tooLongError
]
},
+ {
+ code: "var toString;",
+ options: [{ max: 5 }],
+ errors: [
+ tooLongError
+ ]
+ },
{
code: "(a) => { a * a };",
parserOptions: { ecmaVersion: 6 },
@@ -197,6 +209,19 @@ ruleTester.run("id-length", rule, {
}
]
},
+ {
+ code: "var hasOwnProperty;",
+ options: [{ max: 10, exceptions: [] }],
+ errors: [
+ {
+ messageId: "tooLong",
+ data: { name: "hasOwnProperty", max: 10 },
+ line: 1,
+ column: 5,
+ type: "Identifier"
+ }
+ ]
+ },
{
code: "function foo({ a: { b: { c: d, e } } }) { }",
parserOptions: { ecmaVersion: 6 },
@@ -440,6 +465,27 @@ ruleTester.run("id-length", rule, {
errors: [
tooShortError
]
+ },
+ {
+ code: "function BEFORE_send() {};",
+ options: [{ min: 3, max: 5 }],
+ errors: [
+ tooLongError
+ ]
+ },
+ {
+ code: "function NOTMATCHED_send() {};",
+ options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }],
+ errors: [
+ tooLongError
+ ]
+ },
+ {
+ code: "function N() {};",
+ options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }],
+ errors: [
+ tooShortError
+ ]
}
]
});
diff --git a/eslint/tests/lib/rules/indent.js b/eslint/tests/lib/rules/indent.js
index 56589be..ad35ad0 100644
--- a/eslint/tests/lib/rules/indent.js
+++ b/eslint/tests/lib/rules/indent.js
@@ -5724,6 +5724,105 @@ ruleTester.run("indent", rule, {
`,
options: [4, { MemberExpression: 2 }],
parserOptions: { ecmaVersion: 2015 }
+ },
+ {
+ code: unIndent`
+ const foo = async (arg1,
+ arg2) =>
+ {
+ return arg1 + arg2;
+ }
+ `,
+ options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+ },
+ {
+ code: unIndent`
+ const foo = async /* some comments */(arg1,
+ arg2) =>
+ {
+ return arg1 + arg2;
+ }
+ `,
+ options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+ },
+ {
+ code: unIndent`
+ const a = async
+ b => {}
+ `,
+ options: [2]
+ },
+ {
+ code: unIndent`
+ const foo = (arg1,
+ arg2) => async (arr1,
+ arr2) =>
+ {
+ return arg1 + arg2;
+ }
+ `,
+ options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+ },
+ {
+ code: unIndent`
+ const foo = async (arg1,
+ arg2) =>
+ {
+ return arg1 + arg2;
+ }
+ `,
+ options: [2]
+ },
+ {
+ code: unIndent`
+ const foo = async /*comments*/(arg1,
+ arg2) =>
+ {
+ return arg1 + arg2;
+ }
+ `,
+ options: [2]
+ },
+ {
+ code: unIndent`
+ const foo = async (arg1,
+ arg2) =>
+ {
+ return arg1 + arg2;
+ }
+ `,
+ options: [2, { FunctionDeclaration: { parameters: 4 }, FunctionExpression: { parameters: 4 } }]
+ },
+ {
+ code: unIndent`
+ const foo = (arg1,
+ arg2) =>
+ {
+ return arg1 + arg2;
+ }
+ `,
+ options: [2, { FunctionDeclaration: { parameters: 4 }, FunctionExpression: { parameters: 4 } }]
+ },
+ {
+ code: unIndent`
+ async function fn(ar1,
+ ar2){}
+ `,
+ options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+ },
+ {
+ code: unIndent`
+ async function /* some comments */ fn(ar1,
+ ar2){}
+ `,
+ options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+ },
+ {
+ code: unIndent`
+ async /* some comments */ function fn(ar1,
+ ar2){}
+ `,
+ options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
}
],
@@ -11374,6 +11473,140 @@ ruleTester.run("indent", rule, {
[5, 4, 0, "Identifier"],
[6, 0, 4, "Punctuator"]
])
+ },
+
+ // Optional chaining
+ {
+ code: unIndent`
+ obj
+ ?.prop
+ ?.[key]
+ ?.
+ [key]
+ `,
+ output: unIndent`
+ obj
+ ?.prop
+ ?.[key]
+ ?.
+ [key]
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: expectedErrors([
+ [2, 4, 0, "Punctuator"],
+ [3, 4, 0, "Punctuator"],
+ [4, 4, 0, "Punctuator"],
+ [5, 8, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ (
+ longSomething
+ ?.prop
+ ?.[key]
+ )
+ ?.prop
+ ?.[key]
+ `,
+ output: unIndent`
+ (
+ longSomething
+ ?.prop
+ ?.[key]
+ )
+ ?.prop
+ ?.[key]
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: expectedErrors([
+ [6, 4, 0, "Punctuator"],
+ [7, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ obj
+ ?.(arg)
+ ?.
+ (arg)
+ `,
+ output: unIndent`
+ obj
+ ?.(arg)
+ ?.
+ (arg)
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: expectedErrors([
+ [2, 4, 0, "Punctuator"],
+ [3, 4, 0, "Punctuator"],
+ [4, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ (
+ longSomething
+ ?.(arg)
+ ?.(arg)
+ )
+ ?.(arg)
+ ?.(arg)
+ `,
+ output: unIndent`
+ (
+ longSomething
+ ?.(arg)
+ ?.(arg)
+ )
+ ?.(arg)
+ ?.(arg)
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: expectedErrors([
+ [6, 4, 0, "Punctuator"],
+ [7, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ const foo = async (arg1,
+ arg2) =>
+ {
+ return arg1 + arg2;
+ }
+ `,
+ output: unIndent`
+ const foo = async (arg1,
+ arg2) =>
+ {
+ return arg1 + arg2;
+ }
+ `,
+ options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: expectedErrors([
+ [2, 19, 20, "Identifier"]
+ ])
+ },
+ {
+ code: unIndent`
+ const a = async
+ b => {}
+ `,
+ output: unIndent`
+ const a = async
+ b => {}
+ `,
+ options: [2],
+ errors: expectedErrors([
+ [2, 0, 1, "Identifier"]
+ ])
}
]
});
diff --git a/eslint/tests/lib/rules/key-spacing.js b/eslint/tests/lib/rules/key-spacing.js
index 29bbbe8..849ce61 100644
--- a/eslint/tests/lib/rules/key-spacing.js
+++ b/eslint/tests/lib/rules/key-spacing.js
@@ -892,16 +892,110 @@ ruleTester.run("key-spacing", rule, {
on: "value"
}
}]
- }],
-
+ }
+ ],
invalid: [{
+ code: "var a ={'key' : value };",
+ output: "var a ={'key':value };",
+ options: [{
+ beforeColon: false,
+ afterColon: false
+ }],
+ errors: [
+ {
+
+ type: "Literal",
+ messageId: "extraKey",
+ data: { computed: "", key: "key" },
+ line: 1,
+ column: 14,
+ endLine: 1,
+ endColumn: 15
+ },
+ {
+ type: "Identifier",
+ messageId: "extraValue",
+ data: { computed: "", key: "key" },
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 17
+ }
+ ]
+ }, {
+ code: "var a ={'key' :value };",
+ output: "var a ={'key': value };",
+ options: [{
+ beforeColon: false,
+ afterColon: true
+ }],
+ errors: [
+ {
+
+ type: "Literal",
+ messageId: "extraKey",
+ data: { computed: "", key: "key" },
+ line: 1,
+ column: 14,
+ endLine: 1,
+ endColumn: 15
+ },
+ {
+ type: "Identifier",
+ messageId: "missingValue",
+ data: { computed: "", key: "key" },
+ line: 1,
+ column: 16,
+ endLine: 1,
+ endColumn: 21
+ }
+ ]
+ }, {
+ code: "var a ={'key'\n : \nvalue };",
+ output: "var a ={'key':value };",
+ options: [{
+ beforeColon: false,
+ afterColon: false
+ }],
+ errors: [
+ {
+
+ type: "Literal",
+ messageId: "extraKey",
+ data: { computed: "", key: "key" },
+ line: 1,
+ column: 14,
+ endLine: 2,
+ endColumn: 2
+ },
+ {
+ type: "Identifier",
+ messageId: "extraValue",
+ data: { computed: "", key: "key" },
+ line: 2,
+ column: 2,
+ endLine: 3,
+ endColumn: 1
+ }
+ ]
+ }, {
code: "var bat = function() { return { foo:bar, 'key': value }; };",
output: "var bat = function() { return { foo:bar, 'key':value }; };",
options: [{
beforeColon: false,
afterColon: false
}],
- errors: [{ messageId: "extraValue", data: { computed: "", key: "key" }, type: "Identifier", line: 1, column: 49 }]
+ errors: [
+ {
+ messageId: "extraValue",
+ data: { computed: "", key: "key" },
+ type: "Identifier",
+ line: 1,
+ column: 47,
+ endLine: 1,
+ endColumn: 49
+ }
+ ]
}, {
code: "var obj = { [ (a + b) ]:value };",
output: "var obj = { [ (a + b) ]: value };",
@@ -915,7 +1009,7 @@ ruleTester.run("key-spacing", rule, {
beforeColon: false,
afterColon: false
}],
- errors: [{ messageId: "extraKey", data: { computed: "", key: "key" }, type: "Literal", line: 1, column: 15 }]
+ errors: [{ messageId: "extraKey", data: { computed: "", key: "key" }, type: "Literal", line: 1, column: 20, endLine: 1, endColumn: 21 }]
}, {
code: "var obj = {prop :(42)};",
output: "var obj = {prop : (42)};",
@@ -940,10 +1034,10 @@ ruleTester.run("key-spacing", rule, {
afterColon: true
}],
errors: [
- { messageId: "extraKey", data: { computed: "", key: "a" }, type: "Literal", line: 1, column: 3 },
- { messageId: "missingValue", data: { computed: "", key: "a" }, type: "CallExpression", line: 1, column: 9 },
- { messageId: "missingKey", data: { computed: "", key: "b" }, type: "Identifier", line: 1, column: 16 },
- { messageId: "extraValue", data: { computed: "", key: "b" }, type: "CallExpression", line: 1, column: 20 }
+ { messageId: "extraKey", data: { computed: "", key: "a" }, type: "Literal", line: 1, column: 6, endLine: 1, endColumn: 8 },
+ { messageId: "missingValue", data: { computed: "", key: "a" }, type: "CallExpression", line: 1, column: 9, endLine: 1, endColumn: 12 },
+ { messageId: "missingKey", data: { computed: "", key: "b" }, type: "Identifier", line: 1, column: 16, endLine: 1, endColumn: 17 },
+ { messageId: "extraValue", data: { computed: "", key: "b" }, type: "CallExpression", line: 1, column: 17, endLine: 1, endColumn: 20 }
]
}, {
code: "bar = { key:value };",
@@ -973,7 +1067,7 @@ ruleTester.run("key-spacing", rule, {
}],
errors: [
{ messageId: "missingKey", data: { computed: "", key: "key" }, type: "Identifier", line: 2, column: 5 },
- { messageId: "extraValue", data: { computed: "", key: "key" }, type: "Identifier", line: 2, column: 12 },
+ { messageId: "extraValue", data: { computed: "", key: "key" }, type: "Identifier", line: 2, column: 8 },
{ messageId: "missingValue", data: { computed: "", key: "foobar" }, type: "CallExpression", line: 3, column: 12 }
]
}, {
@@ -999,9 +1093,9 @@ ruleTester.run("key-spacing", rule, {
afterColon: false
}],
errors: [
- { messageId: "extraValue", data: { computed: "", key: "a" }, type: "Identifier", line: 2, column: 11 },
+ { messageId: "extraValue", data: { computed: "", key: "a" }, type: "Identifier", line: 2, column: 9 },
{ messageId: "missingKey", data: { computed: "", key: "foo" }, type: "Identifier", line: 3, column: 5 },
- { messageId: "extraKey", data: { computed: "", key: "b" }, type: "Identifier", line: 4, column: 5 }
+ { messageId: "extraKey", data: { computed: "", key: "b" }, type: "Identifier", line: 4, column: 6 }
]
}, {
code: [
@@ -1027,10 +1121,10 @@ ruleTester.run("key-spacing", rule, {
}],
parserOptions: { ecmaVersion: 6 },
errors: [
- { messageId: "extraValue", data: { computed: "", key: "a" }, type: "CallExpression", line: 2, column: 11 },
- { messageId: "extraKey", data: { computed: "", key: "b" }, type: "Literal", line: 3, column: 5 },
+ { messageId: "extraValue", data: { computed: "", key: "a" }, type: "CallExpression", line: 2, column: 6 },
+ { messageId: "extraKey", data: { computed: "", key: "b" }, type: "Literal", line: 3, column: 8 },
{ messageId: "missingValue", data: { computed: "", key: "foo" }, type: "Identifier", line: 4, column: 9 },
- { messageId: "extraKey", data: { computed: "computed ", key: "a" }, type: "Identifier", line: 6, column: 7 }
+ { messageId: "extraKey", data: { computed: "computed ", key: "a" }, type: "Identifier", line: 6, column: 8 }
]
}, {
code: [
@@ -1056,7 +1150,7 @@ ruleTester.run("key-spacing", rule, {
}],
errors: [
{ messageId: "missingKey", data: { computed: "", key: "a" }, type: "Identifier", line: 2, column: 5 },
- { messageId: "extraValue", data: { computed: "", key: "bar" }, type: "CallExpression", line: 5, column: 11 }
+ { messageId: "extraValue", data: { computed: "", key: "bar" }, type: "CallExpression", line: 5, column: 9 }
]
}, {
code: [
@@ -1105,8 +1199,8 @@ ruleTester.run("key-spacing", rule, {
afterColon: false
}],
errors: [
- { messageId: "extraValue", data: { computed: "", key: "key" }, type: "Identifier", line: 3, column: 9 },
- { messageId: "extraKey", data: { computed: "", key: "key2" }, type: "Identifier", line: 4, column: 5 }
+ { messageId: "extraValue", data: { computed: "", key: "key" }, type: "Identifier", line: 2, column: 8 },
+ { messageId: "extraKey", data: { computed: "", key: "key2" }, type: "Identifier", line: 4, column: 9 }
]
}, {
code: [
@@ -1153,7 +1247,7 @@ ruleTester.run("key-spacing", rule, {
output: "var obj = {a: 'foo', bar: 'bam'};",
options: [{ align: "colon" }],
errors: [
- { messageId: "extraKey", data: { computed: "", key: "a" }, line: 1, column: 12, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "a" }, line: 1, column: 13, type: "Identifier" }
]
}, {
code: [
@@ -1170,7 +1264,7 @@ ruleTester.run("key-spacing", rule, {
].join("\n"),
options: [{ align: "colon" }],
errors: [
- { messageId: "extraKey", data: { computed: "", key: "b" }, line: 3, column: 5, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "b" }, line: 3, column: 6, type: "Identifier" }
]
}, {
code: [
@@ -1203,7 +1297,7 @@ ruleTester.run("key-spacing", rule, {
afterColon: true
}],
errors: [
- { messageId: "extraValue", data: { computed: "", key: "key" }, line: 2, column: 8, type: "Identifier" }
+ { messageId: "extraValue", data: { computed: "", key: "key" }, line: 2, column: 2, type: "Identifier" }
]
}, {
code: [
@@ -1243,7 +1337,7 @@ ruleTester.run("key-spacing", rule, {
options: [{ align: "value" }],
parserOptions: { ecmaVersion: 6 },
errors: [
- { messageId: "extraValue", data: { computed: "", key: "foobar" }, line: 2, column: 14, type: "Literal" }
+ { messageId: "extraValue", data: { computed: "", key: "foobar" }, line: 2, column: 11, type: "Literal" }
]
}, {
code: [
@@ -1283,7 +1377,7 @@ ruleTester.run("key-spacing", rule, {
options: [{ align: "value" }],
parserOptions: { ecmaVersion: 6 },
errors: [
- { messageId: "extraValue", data: { computed: "", key: "foobar" }, line: 2, column: 14, type: "Literal" }
+ { messageId: "extraValue", data: { computed: "", key: "foobar" }, line: 2, column: 11, type: "Literal" }
]
}, {
code: [
@@ -1307,7 +1401,7 @@ ruleTester.run("key-spacing", rule, {
options: [{ align: "value" }],
parserOptions: { ecmaVersion: 6 },
errors: [
- { messageId: "extraValue", data: { computed: "", key: "baz" }, line: 6, column: 13, type: "Literal" }
+ { messageId: "extraValue", data: { computed: "", key: "baz" }, line: 6, column: 8, type: "Literal" }
]
}, {
code: [
@@ -1341,7 +1435,7 @@ ruleTester.run("key-spacing", rule, {
].join("\n"),
options: [{ align: "colon" }],
errors: [
- { messageId: "extraValue", data: { computed: "", key: "cats" }, line: 3, column: 12, type: "Identifier" }
+ { messageId: "extraValue", data: { computed: "", key: "cats" }, line: 3, column: 9, type: "Identifier" }
]
}, {
code: [
@@ -1371,7 +1465,7 @@ ruleTester.run("key-spacing", rule, {
].join("\n"),
options: [{ align: "colon" }],
errors: [
- { messageId: "extraKey", data: { computed: "", key: "foo" }, line: 1, column: 13, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "foo" }, line: 1, column: 16, type: "Identifier" }
]
}, {
code: [
@@ -1401,7 +1495,7 @@ ruleTester.run("key-spacing", rule, {
].join("\n"),
options: [{ align: "colon" }],
errors: [
- { messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 20, type: "Identifier" }
+ { messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 17, type: "Identifier" }
]
}, {
code: [
@@ -1416,7 +1510,7 @@ ruleTester.run("key-spacing", rule, {
].join("\n"),
options: [{ align: "colon" }],
errors: [
- { messageId: "extraValue", data: { computed: "", key: "cats" }, line: 2, column: 20, type: "Identifier" }
+ { messageId: "extraValue", data: { computed: "", key: "cats" }, line: 2, column: 17, type: "Identifier" }
]
},
@@ -1456,7 +1550,7 @@ ruleTester.run("key-spacing", rule, {
parserOptions: { ecmaVersion: 2018 },
errors: [
{ messageId: "missingKey", data: { computed: "", key: "a" }, line: 3, column: 5, type: "Identifier" },
- { messageId: "extraKey", data: { computed: "", key: "f" }, line: 12, column: 5, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "f" }, line: 12, column: 6, type: "Identifier" }
]
},
@@ -1478,7 +1572,7 @@ ruleTester.run("key-spacing", rule, {
align: "colon"
}],
errors: [
- { messageId: "extraKey", data: { computed: "", key: "a" }, line: 2, column: 5, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "a" }, line: 2, column: 6, type: "Identifier" }
]
}, {
code: [
@@ -1497,7 +1591,7 @@ ruleTester.run("key-spacing", rule, {
align: "value"
}],
errors: [
- { messageId: "extraKey", data: { computed: "", key: "c" }, line: 3, column: 5, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "c" }, line: 3, column: 6, type: "Identifier" }
]
}, {
code: [
@@ -1554,7 +1648,7 @@ ruleTester.run("key-spacing", rule, {
}
}],
errors: [
- { messageId: "extraValue", data: { computed: "", key: "a1" }, line: 6, column: 17, type: "Literal" }
+ { messageId: "extraValue", data: { computed: "", key: "a1" }, line: 6, column: 15, type: "Literal" }
]
}, {
code: [
@@ -1635,7 +1729,7 @@ ruleTester.run("key-spacing", rule, {
mode: "minimum"
}],
errors: [
- { messageId: "extraKey", data: { computed: "", key: "ex" }, line: 4, column: 4, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "ex" }, line: 4, column: 6, type: "Identifier" }
]
}, {
code: [
@@ -1674,7 +1768,7 @@ ruleTester.run("key-spacing", rule, {
parserOptions: { ecmaVersion: 2018 },
errors: [
{ messageId: "missingValue", data: { computed: "", key: "a" }, line: 1, column: 6, type: "Identifier" },
- { messageId: "extraKey", data: { computed: "", key: "c" }, line: 1, column: 20, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "c" }, line: 1, column: 21, type: "Identifier" }
]
},
@@ -1755,8 +1849,8 @@ ruleTester.run("key-spacing", rule, {
{ messageId: "missingValue", data: { computed: "", key: "func" }, line: 2, column: 10, type: "FunctionExpression" },
{ messageId: "missingKey", data: { computed: "", key: "longName" }, line: 5, column: 5, type: "Identifier" },
{ messageId: "missingKey", data: { computed: "", key: "small" }, line: 6, column: 5, type: "Identifier" },
- { messageId: "extraKey", data: { computed: "", key: "xs" }, line: 7, column: 5, type: "Identifier" },
- { messageId: "extraKey", data: { computed: "", key: "singleLine" }, line: 11, column: 5, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "xs" }, line: 7, column: 7, type: "Identifier" },
+ { messageId: "extraKey", data: { computed: "", key: "singleLine" }, line: 11, column: 15, type: "Identifier" }
]
}, {
code: [
@@ -1803,10 +1897,10 @@ ruleTester.run("key-spacing", rule, {
errors: [
{ messageId: "missingValue", data: { computed: "", key: "func" }, line: 2, column: 10, type: "FunctionExpression" },
{ messageId: "missingKey", data: { computed: "", key: "small" }, line: 6, column: 5, type: "Identifier" },
- { messageId: "extraKey", data: { computed: "", key: "xs" }, line: 7, column: 5, type: "Identifier" },
- { messageId: "extraValue", data: { computed: "", key: "xs" }, line: 7, column: 21, type: "Literal" },
- { messageId: "extraValue", data: { computed: "", key: "func2" }, line: 8, column: 16, type: "FunctionExpression" },
- { messageId: "extraKey", data: { computed: "", key: "singleLine" }, line: 11, column: 5, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "xs" }, line: 7, column: 7, type: "Identifier" },
+ { messageId: "extraValue", data: { computed: "", key: "xs" }, line: 7, column: 19, type: "Literal" },
+ { messageId: "extraValue", data: { computed: "", key: "func2" }, line: 8, column: 14, type: "FunctionExpression" },
+ { messageId: "extraKey", data: { computed: "", key: "singleLine" }, line: 11, column: 15, type: "Identifier" }
]
}, {
code: [
@@ -1843,8 +1937,8 @@ ruleTester.run("key-spacing", rule, {
}],
parserOptions: { ecmaVersion: 6 },
errors: [
- { messageId: "extraValue", data: { computed: "", key: "key2" }, line: 4, column: 14, type: "Literal" },
- { messageId: "extraValue", data: { computed: "", key: "key3" }, line: 5, column: 14, type: "Literal" }
+ { messageId: "extraValue", data: { computed: "", key: "key2" }, line: 4, column: 9, type: "Literal" },
+ { messageId: "extraValue", data: { computed: "", key: "key3" }, line: 5, column: 9, type: "Literal" }
]
}, {
code: [
@@ -1881,20 +1975,20 @@ ruleTester.run("key-spacing", rule, {
}],
parserOptions: { ecmaVersion: 6 },
errors: [
- { messageId: "extraValue", data: { computed: "", key: "key2" }, line: 4, column: 14, type: "Literal" },
- { messageId: "extraValue", data: { computed: "", key: "key3" }, line: 5, column: 14, type: "Literal" }
+ { messageId: "extraValue", data: { computed: "", key: "key2" }, line: 4, column: 9, type: "Literal" },
+ { messageId: "extraValue", data: { computed: "", key: "key3" }, line: 5, column: 9, type: "Literal" }
]
}, {
// https://github.com/eslint/eslint/issues/7603
code: "({ foo/* comment */ : bar })",
output: "({ foo/* comment */: bar })",
- errors: [{ messageId: "extraKey", data: { computed: "", key: "foo" }, line: 1, column: 7, type: "Identifier" }]
+ errors: [{ messageId: "extraKey", data: { computed: "", key: "foo" }, line: 1, column: 20, type: "Identifier" }]
}, {
code: "({ foo: /* comment */bar })",
output: "({ foo:/* comment */bar })",
options: [{ afterColon: false }],
- errors: [{ messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 9, type: "Identifier" }]
+ errors: [{ messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 7, type: "Identifier" }]
},
{
code: "({ foo/*comment*/:/*comment*/bar })",
@@ -1951,10 +2045,10 @@ ruleTester.run("key-spacing", rule, {
}
}],
errors: [
- { messageId: "extraKey", data: { computed: "", key: "foo" }, line: 2, column: 5, type: "Identifier" },
- { messageId: "extraKey", data: { computed: "", key: "bar" }, line: 2, column: 14, type: "Literal" },
- { messageId: "extraKey", data: { computed: "", key: "baz" }, line: 2, column: 25, type: "Identifier" },
- { messageId: "extraKey", data: { computed: "", key: "longlonglong" }, line: 2, column: 34, type: "Identifier" }
+ { messageId: "extraKey", data: { computed: "", key: "foo" }, line: 2, column: 8, type: "Identifier" },
+ { messageId: "extraKey", data: { computed: "", key: "bar" }, line: 2, column: 19, type: "Literal" },
+ { messageId: "extraKey", data: { computed: "", key: "baz" }, line: 2, column: 28, type: "Identifier" },
+ { messageId: "extraKey", data: { computed: "", key: "longlonglong" }, line: 2, column: 46, type: "Identifier" }
]
}, {
code: [
@@ -2015,10 +2109,10 @@ ruleTester.run("key-spacing", rule, {
}
}],
errors: [
- { messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 18, type: "Literal" },
- { messageId: "extraValue", data: { computed: "", key: "bar" }, line: 1, column: 28, type: "Literal" },
- { messageId: "extraKey", data: { computed: "", key: "baz" }, line: 1, column: 31, type: "Identifier" },
- { messageId: "extraKey", data: { computed: "", key: "longlonglong" }, line: 1, column: 39, type: "Identifier" }
+ { messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 16, type: "Literal" },
+ { messageId: "extraValue", data: { computed: "", key: "bar" }, line: 1, column: 26, type: "Literal" },
+ { messageId: "extraKey", data: { computed: "", key: "baz" }, line: 1, column: 34, type: "Identifier" },
+ { messageId: "extraKey", data: { computed: "", key: "longlonglong" }, line: 1, column: 51, type: "Identifier" }
]
}, {
code: [
diff --git a/eslint/tests/lib/rules/keyword-spacing.js b/eslint/tests/lib/rules/keyword-spacing.js
index b67ee52..64ee5a4 100644
--- a/eslint/tests/lib/rules/keyword-spacing.js
+++ b/eslint/tests/lib/rules/keyword-spacing.js
@@ -1364,14 +1364,14 @@ ruleTester.run("keyword-spacing", rule, {
code: "import *as a from \"foo\"",
output: "import * as a from \"foo\"",
parserOptions: { ecmaVersion: 6, sourceType: "module" },
- errors: expectedBefore("as")
- },
- {
- code: "import* as a from\"foo\"",
- output: "import*as a from\"foo\"",
- options: [NEITHER],
- parserOptions: { ecmaVersion: 6, sourceType: "module" },
- errors: unexpectedBefore("as")
+ errors: [{
+ messageId: "expectedBefore",
+ data: { value: "as" },
+ line: 1,
+ column: 9,
+ endLine: 1,
+ endColumn: 11
+ }]
},
{
code: "import* as a from\"foo\"",
@@ -1380,27 +1380,26 @@ ruleTester.run("keyword-spacing", rule, {
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "unexpectedBefore",
+ data: { value: "as" },
line: 1,
column: 8,
endLine: 1,
endColumn: 9
- }
- ]
+ }]
},
{
- code: "import *as a from\"foo\"",
+ code: "import* as a from\"foo\"",
output: "import*as a from\"foo\"",
options: [NEITHER],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
- errors: [
- {
- messageId: "unexpectedAfter",
- line: 1,
- column: 7,
- endLine: 1,
- endColumn: 8
- }
- ]
+ errors: [{
+ messageId: "unexpectedBefore",
+ data: { value: "as" },
+ line: 1,
+ column: 8,
+ endLine: 1,
+ endColumn: 11
+ }]
},
{
code: "import*as a from\"foo\"",
@@ -2510,6 +2509,47 @@ ruleTester.run("keyword-spacing", rule, {
// import
//----------------------------------------------------------------------
+ {
+ code: "import* as a from \"foo\"",
+ output: "import * as a from \"foo\"",
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "expectedAfter",
+ data: { value: "import" },
+ line: 1,
+ column: 1,
+ endLine: 1,
+ endColumn: 7
+ }]
+ },
+ {
+ code: "import *as a from\"foo\"",
+ output: "import*as a from\"foo\"",
+ options: [NEITHER],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "unexpectedAfter",
+ data: { value: "import" },
+ line: 1,
+ column: 7,
+ endLine: 1,
+ endColumn: 8
+ }]
+ },
+ {
+ code: "import *as a from\"foo\"",
+ output: "import*as a from\"foo\"",
+ options: [NEITHER],
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "unexpectedAfter",
+ data: { value: "import" },
+ line: 1,
+ column: 7,
+ endLine: 1,
+ endColumn: 10
+ }]
+ },
{
code: "{}import{a} from \"foo\"",
output: "{} import {a} from \"foo\"",
diff --git a/eslint/tests/lib/rules/max-len.js b/eslint/tests/lib/rules/max-len.js
index f6296b6..557b550 100644
--- a/eslint/tests/lib/rules/max-len.js
+++ b/eslint/tests/lib/rules/max-len.js
@@ -341,7 +341,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 86, maxLength: 80 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 30
}
]
},
@@ -354,7 +356,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 24, maxLength: 10 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 25
}
]
},
@@ -367,7 +371,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 22, maxLength: 15 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 14
}
]
},
@@ -380,14 +386,18 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 22, maxLength: 15 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 14
},
{
messageId: "max",
data: { lineLength: 22, maxLength: 15 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 14
}
]
},
@@ -400,7 +410,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 56, maxLength: 20 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 57
}
]
},
@@ -415,7 +427,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 54, maxLength: 20 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 55
}
]
},
@@ -428,7 +442,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 30, maxLength: 10 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 31
}
]
},
@@ -441,7 +457,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 62, maxLength: 40 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 63
}
]
},
@@ -454,7 +472,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 57, maxLength: 40 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 58
}
]
}, {
@@ -466,7 +486,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 53, maxLength: 40 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 54
}
]
}, {
@@ -478,7 +500,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 49, maxCommentLength: 20 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 50
}
]
}, {
@@ -490,7 +514,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 119, maxCommentLength: 80 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 120
}
]
}, {
@@ -502,7 +528,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 49, maxLength: 20 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 50
}
]
}, {
@@ -514,7 +542,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 73, maxLength: 40 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 74
}
]
},
@@ -531,7 +561,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 29, maxCommentLength: 28 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 30
}
]
}, {
@@ -545,7 +577,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 33, maxCommentLength: 32 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 34
}
]
}, {
@@ -560,14 +594,18 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 29, maxCommentLength: 28 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 30
},
{
messageId: "maxComment",
data: { lineLength: 32, maxCommentLength: 28 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 33
}
]
}, {
@@ -582,14 +620,18 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 33, maxCommentLength: 32 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 34
},
{
messageId: "maxComment",
data: { lineLength: 36, maxCommentLength: 32 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 37
}
]
}, {
@@ -604,14 +646,18 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 40, maxLength: 39 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 41
},
{
messageId: "maxComment",
data: { lineLength: 36, maxCommentLength: 35 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 37
}
]
}, {
@@ -626,14 +672,18 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 33, maxCommentLength: 32 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 34
},
{
messageId: "max",
data: { lineLength: 43, maxLength: 42 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 44
}
]
},
@@ -649,7 +699,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 51, maxLength: 20 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 52
}
]
},
@@ -664,7 +716,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 39, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 40
}
]
},
@@ -677,7 +731,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 45, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 46
}
]
},
@@ -690,7 +746,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 57, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 58
}
]
},
@@ -703,7 +761,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 39, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 40
}
]
},
@@ -717,7 +777,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 39, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 40
}
]
},
@@ -731,14 +793,18 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 37, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 38
},
{
messageId: "max",
data: { lineLength: 44, maxLength: 29 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 45
}
]
},
@@ -752,7 +818,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 58, maxLength: 29 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 59
}
]
},
@@ -767,7 +835,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 12, maxLength: 10 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 21
}
]
},
@@ -781,7 +851,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 1, maxLength: 0 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 2
}
]
},
@@ -799,7 +871,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 38, maxCommentLength: 37 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 39
}
]
},
@@ -815,7 +889,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 44, maxCommentLength: 40 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 39
}
]
},
@@ -831,7 +907,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 38, maxLength: 15 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 39
}
]
},
@@ -847,7 +925,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 38, maxLength: 37 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 39
}
]
},
@@ -863,7 +943,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 38, maxLength: 37 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 39
}
]
},
@@ -879,7 +961,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 50, maxLength: 49 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 51
}
]
},
@@ -896,7 +980,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 44, maxLength: 37 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 45
}
]
},
@@ -912,7 +998,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 57, maxLength: 56 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 58
}
]
},
@@ -928,7 +1016,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 57, maxLength: 56 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 58
}
]
},
@@ -944,7 +1034,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 56, maxLength: 55 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 57
}
]
},
@@ -961,7 +1053,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 51, maxLength: 30 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 52
}
]
},
@@ -978,7 +1072,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 80, maxLength: 79 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 81
}
]
},
@@ -994,7 +1090,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 87, maxLength: 85 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 88
}
]
},
@@ -1011,7 +1109,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 87, maxLength: 37 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 88
}
]
},
@@ -1028,7 +1128,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 119, maxLength: 37 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 120
}
]
},
@@ -1045,7 +1147,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 55, maxLength: 37 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 56
}
]
},
@@ -1062,7 +1166,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 55, maxLength: 37 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 56
}
]
},
@@ -1079,7 +1185,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 15, maxLength: 14 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 16
}
]
},
@@ -1096,7 +1204,9 @@ ruleTester.run("max-len", rule, {
data: { lineLength: 31, maxLength: 30 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 32
}
]
}
diff --git a/eslint/tests/lib/rules/max-lines.js b/eslint/tests/lib/rules/max-lines.js
index 2929551..24c8119 100644
--- a/eslint/tests/lib/rules/max-lines.js
+++ b/eslint/tests/lib/rules/max-lines.js
@@ -9,10 +9,8 @@
//------------------------------------------------------------------------------
const rule = require("../../../lib/rules/max-lines"),
-
{ RuleTester } = require("../../../lib/rule-tester");
-
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
@@ -23,8 +21,15 @@ ruleTester.run("max-lines", rule, {
valid: [
"var x;",
"var xy;\nvar xy;",
+ { code: "A", options: [1] },
+ { code: "A\n", options: [1] },
+ { code: "A\r", options: [1] },
+ { code: "A\r\n", options: [1] },
{ code: "var xy;\nvar xy;", options: [2] },
+ { code: "var xy;\nvar xy;\n", options: [2] },
{ code: "var xy;\nvar xy;", options: [{ max: 2 }] },
+ { code: "// comment\n", options: [{ max: 0, skipComments: true }] },
+ { code: "foo;\n /* comment */\n", options: [{ max: 1, skipComments: true }] },
{
code: [
"//a single line comment",
@@ -79,17 +84,44 @@ ruleTester.run("max-lines", rule, {
{
code: "var xyz;\nvar xyz;\nvar xyz;",
options: [2],
- errors: [{ messageId: "exceed", data: { max: 2, actual: 3 } }]
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 3 },
+ line: 3,
+ column: 1,
+ endLine: 3,
+ endColumn: 9
+ }
+ ]
},
{
code: "/* a multiline comment\n that goes to many lines*/\nvar xy;\nvar xy;",
options: [2],
- errors: [{ messageId: "exceed", data: { max: 2, actual: 4 } }]
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 4 },
+ line: 3,
+ column: 1,
+ endLine: 4,
+ endColumn: 8
+ }
+ ]
},
{
code: "//a single line comment\nvar xy;\nvar xy;",
options: [2],
- errors: [{ messageId: "exceed", data: { max: 2, actual: 3 } }]
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 3 },
+ line: 3,
+ column: 1,
+ endLine: 3,
+ endColumn: 8
+ }
+ ]
},
{
code: [
@@ -100,7 +132,16 @@ ruleTester.run("max-lines", rule, {
"var y;"
].join("\n"),
options: [{ max: 2 }],
- errors: [{ messageId: "exceed", data: { max: 2, actual: 5 } }]
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 5 },
+ line: 3,
+ column: 1,
+ endLine: 5,
+ endColumn: 7
+ }
+ ]
},
{
code: [
@@ -114,7 +155,16 @@ ruleTester.run("max-lines", rule, {
" long comment*/"
].join("\n"),
options: [{ max: 2, skipComments: true }],
- errors: [{ messageId: "exceed", data: { max: 2, actual: 4 } }]
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 4 },
+ line: 4,
+ column: 1,
+ endLine: 8,
+ endColumn: 16
+ }
+ ]
},
{
code: [
@@ -123,7 +173,16 @@ ruleTester.run("max-lines", rule, {
"var z;"
].join("\n"),
options: [{ max: 2, skipComments: true }],
- errors: [{ messageId: "exceed", data: { max: 2, actual: 3 } }]
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 3 },
+ line: 3,
+ column: 1,
+ endLine: 3,
+ endColumn: 7
+ }
+ ]
},
{
code: [
@@ -133,7 +192,16 @@ ruleTester.run("max-lines", rule, {
"var z;"
].join("\n"),
options: [{ max: 2, skipComments: true }],
- errors: [{ messageId: "exceed", data: { max: 2, actual: 3 } }]
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 3 },
+ line: 4,
+ column: 1,
+ endLine: 4,
+ endColumn: 7
+ }
+ ]
},
{
code: [
@@ -147,17 +215,391 @@ ruleTester.run("max-lines", rule, {
" long comment*/"
].join("\n"),
options: [{ max: 2, skipBlankLines: true }],
- errors: [{ messageId: "exceed", data: { max: 2, actual: 6 } }]
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 6 },
+ line: 4,
+ column: 1,
+ endLine: 8,
+ endColumn: 16
+ }
+ ]
},
{
code: "AAAAAAAA\n".repeat(301).trim(),
options: [{}],
- errors: [{ messageId: "exceed", data: { max: 300, actual: 301 } }]
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 300, actual: 301 },
+ line: 301,
+ column: 1,
+ endLine: 301,
+ endColumn: 9
+ }
+ ]
+ },
+ {
+
+ // Questionable. Makes sense to report this, and makes sense to not report this.
+ code: "",
+ options: [{ max: 0 }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 0, actual: 1 },
+ line: 1,
+ column: 1,
+ endLine: 1,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: " ",
+ options: [{ max: 0 }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 0, actual: 1 },
+ line: 1,
+ column: 1,
+ endLine: 1,
+ endColumn: 2
+ }
+ ]
+ },
+ {
+ code: "\n",
+ options: [{ max: 0 }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 0, actual: 1 },
+ line: 1,
+ column: 1,
+ endLine: 2,
+ endColumn: 1
+ }
+ ]
},
{
code: "A",
options: [{ max: 0 }],
- errors: [{ messageId: "exceed", data: { max: 0, actual: 1 } }]
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 0, actual: 1 },
+ line: 1,
+ column: 1,
+ endLine: 1,
+ endColumn: 2
+ }
+ ]
+ },
+ {
+ code: "A\n",
+ options: [{ max: 0 }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 0, actual: 1 },
+ line: 1,
+ column: 1,
+ endLine: 2,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: "A\n ",
+ options: [{ max: 0 }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 0, actual: 2 },
+ line: 1,
+ column: 1,
+ endLine: 2,
+ endColumn: 2
+ }
+ ]
+ },
+ {
+ code: "A\n ",
+ options: [{ max: 1 }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 1, actual: 2 },
+ line: 2,
+ column: 1,
+ endLine: 2,
+ endColumn: 2
+ }
+ ]
+ },
+ {
+ code: "A\n\n",
+ options: [{ max: 1 }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 1, actual: 2 },
+ line: 2,
+ column: 1,
+ endLine: 3,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: ["var a = 'a'; ", "var x", "var c;", "console.log"].join(
+ "\n"
+ ),
+ options: [{ max: 2 }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 4 },
+ line: 3,
+ column: 1,
+ endLine: 4,
+ endColumn: 12
+ }
+ ]
+ },
+ {
+ code: "var a = 'a',\nc,\nx;\r",
+ options: [{ max: 2 }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 3 },
+ line: 3,
+ column: 1,
+ endLine: 4,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: "var a = 'a',\nc,\nx;\n",
+ options: [{ max: 2 }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 3 },
+ line: 3,
+ column: 1,
+ endLine: 4,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: "\n\nvar a = 'a',\nc,\nx;\n",
+ options: [{ max: 2, skipBlankLines: true }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 3 },
+ line: 5,
+ column: 1,
+ endLine: 6,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: [
+ "var a = 'a'; ",
+ "var x",
+ "var c;",
+ "console.log",
+ "// some block ",
+ "// comments"
+ ].join("\n"),
+ options: [{ max: 2, skipComments: true }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 4 },
+ line: 3,
+ column: 1,
+ endLine: 6,
+ endColumn: 12
+ }
+ ]
+ },
+ {
+ code: [
+ "var a = 'a'; ",
+ "var x",
+ "var c;",
+ "console.log",
+ "/* block comments */"
+ ].join("\n"),
+ options: [{ max: 2, skipComments: true }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 4 },
+ line: 3,
+ column: 1,
+ endLine: 5,
+ endColumn: 21
+ }
+ ]
+ },
+ {
+ code: [
+ "var a = 'a'; ",
+ "var x",
+ "var c;",
+ "console.log",
+ "/* block comments */\n"
+ ].join("\n"),
+ options: [{ max: 2, skipComments: true }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 4 },
+ line: 3,
+ column: 1,
+ endLine: 6,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: [
+ "var a = 'a'; ",
+ "var x",
+ "var c;",
+ "console.log",
+ "/** block \n\n comments */"
+ ].join("\n"),
+ options: [{ max: 2, skipComments: true }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 4 },
+ line: 3,
+ column: 1,
+ endLine: 7,
+ endColumn: 13
+ }
+ ]
+ },
+ {
+ code: [
+ "var a = 'a'; ",
+ "",
+ "",
+ "// comment"
+ ].join("\n"),
+ options: [{ max: 2, skipComments: true }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 3 },
+ line: 3,
+ column: 1,
+ endLine: 4,
+ endColumn: 11
+ }
+ ]
+ },
+ {
+ code: [
+ "var a = 'a'; ",
+ "var x",
+ "\n",
+ "var c;",
+ "console.log",
+ "\n"
+ ].join("\n"),
+ options: [{ max: 2, skipBlankLines: true }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 4 },
+ line: 5,
+ column: 1,
+ endLine: 8,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: [
+ "var a = 'a'; ",
+ "\n",
+ "var x",
+ "var c;",
+ "console.log",
+ "\n"
+ ].join("\n"),
+ options: [{ max: 2, skipBlankLines: true }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 4 },
+ line: 5,
+ column: 1,
+ endLine: 8,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: [
+ "var a = 'a'; ",
+ "//",
+ "var x",
+ "var c;",
+ "console.log",
+ "//"
+ ].join("\n"),
+ options: [{ max: 2, skipComments: true }],
+ errors: [
+ {
+ messageId: "exceed",
+ data: { max: 2, actual: 4 },
+ line: 4,
+ column: 1,
+ endLine: 6,
+ endColumn: 3
+ }
+ ]
+ },
+ {
+ code: ["// hello world", "/*hello", " world 2 */", "var a,", "b", "// hh", "c,", "e,", "f;"].join("\n"),
+ options: [{ max: 2, skipComments: true }],
+ errors: [{
+ data: { max: 2, actual: 5 },
+ messageId: "exceed",
+ line: 7,
+ column: 1,
+ endLine: 9,
+ endColumn: 3
+
+ }]
+ },
+ {
+ code: ["", "var x = '';", "", "// comment", "", "var b = '',", "c,", "d,", "e", "", "// comment"].join("\n"),
+ options: [{ max: 2, skipComments: true, skipBlankLines: true }],
+ errors: [{
+ data: { max: 2, actual: 5 },
+ messageId: "exceed",
+ line: 7,
+ column: 1,
+ endLine: 11,
+ endColumn: 11
+ }]
}
+
]
});
diff --git a/eslint/tests/lib/rules/new-cap.js b/eslint/tests/lib/rules/new-cap.js
index ec85c4b..5953c87 100644
--- a/eslint/tests/lib/rules/new-cap.js
+++ b/eslint/tests/lib/rules/new-cap.js
@@ -71,7 +71,39 @@ ruleTester.run("new-cap", rule, {
{ code: "var x = new foo.bar(42);", options: [{ newIsCapExceptionPattern: "^foo\\.." }] },
{ code: "var x = new foo.bar(42);", options: [{ properties: false }] },
{ code: "var x = Foo.bar(42);", options: [{ properties: false }] },
- { code: "var x = foo.Bar(42);", options: [{ capIsNew: false, properties: false }] }
+ { code: "var x = foo.Bar(42);", options: [{ capIsNew: false, properties: false }] },
+
+ // Optional chaining
+ {
+ code: "foo?.bar();",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "(foo?.bar)();",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "new (foo?.Bar)();",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "(foo?.Bar)();",
+ options: [{ properties: false }],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "new (foo?.bar)();",
+ options: [{ properties: false }],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "Date?.UTC();",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "(Date?.UTC)();",
+ parserOptions: { ecmaVersion: 2020 }
+ }
],
invalid: [
{
@@ -302,6 +334,23 @@ ruleTester.run("new-cap", rule, {
options: [{ newIsCapExceptionPattern: "^foo\\.." }],
errors: [{ type: "NewExpression", messageId: "lower" }]
+ },
+
+ // Optional chaining
+ {
+ code: "new (foo?.bar)();",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "lower", column: 11, endColumn: 14 }]
+ },
+ {
+ code: "foo?.Bar();",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "upper", column: 6, endColumn: 9 }]
+ },
+ {
+ code: "(foo?.Bar)();",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "upper", column: 7, endColumn: 10 }]
}
]
});
diff --git a/eslint/tests/lib/rules/newline-per-chained-call.js b/eslint/tests/lib/rules/newline-per-chained-call.js
index 28d45c6..2a26937 100644
--- a/eslint/tests/lib/rules/newline-per-chained-call.js
+++ b/eslint/tests/lib/rules/newline-per-chained-call.js
@@ -340,5 +340,69 @@ ruleTester.run("newline-per-chained-call", rule, {
endLine: 1,
endColumn: 35
}]
- }]
+ },
+
+ // Optional chaining
+ {
+ code: "obj?.foo1()?.foo2()?.foo3()",
+ output: "obj?.foo1()\n?.foo2()\n?.foo3()",
+ options: [{ ignoreChainWithDepth: 1 }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "expected", data: { callee: "?.foo2" } },
+ { messageId: "expected", data: { callee: "?.foo3" } }
+ ]
+ },
+ {
+ code: "(obj?.foo1()?.foo2)()?.foo3()",
+ output: "(obj?.foo1()\n?.foo2)()\n?.foo3()",
+ options: [{ ignoreChainWithDepth: 1 }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "expected", data: { callee: "?.foo2" } },
+ { messageId: "expected", data: { callee: "?.foo3" } }
+ ]
+ },
+ {
+ code: "(obj?.foo1())?.foo2()?.foo3()",
+ output: "(obj?.foo1())\n?.foo2()\n?.foo3()",
+ options: [{ ignoreChainWithDepth: 1 }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "expected", data: { callee: "?.foo2" } },
+ { messageId: "expected", data: { callee: "?.foo3" } }
+ ]
+ },
+ {
+ code: "obj?.[foo1]()?.[foo2]()?.[foo3]()",
+ output: "obj?.[foo1]()\n?.[foo2]()\n?.[foo3]()",
+ options: [{ ignoreChainWithDepth: 1 }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "expected", data: { callee: "?.[foo2]" } },
+ { messageId: "expected", data: { callee: "?.[foo3]" } }
+ ]
+ },
+ {
+ code: "(obj?.[foo1]()?.[foo2])()?.[foo3]()",
+ output: "(obj?.[foo1]()\n?.[foo2])()\n?.[foo3]()",
+ options: [{ ignoreChainWithDepth: 1 }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "expected", data: { callee: "?.[foo2]" } },
+ { messageId: "expected", data: { callee: "?.[foo3]" } }
+ ]
+ },
+ {
+ code: "(obj?.[foo1]())?.[foo2]()?.[foo3]()",
+ output: "(obj?.[foo1]())\n?.[foo2]()\n?.[foo3]()",
+ options: [{ ignoreChainWithDepth: 1 }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "expected", data: { callee: "?.[foo2]" } },
+ { messageId: "expected", data: { callee: "?.[foo3]" } }
+ ]
+ }
+
+ ]
});
diff --git a/eslint/tests/lib/rules/no-alert.js b/eslint/tests/lib/rules/no-alert.js
index 4851d5b..03fcddb 100644
--- a/eslint/tests/lib/rules/no-alert.js
+++ b/eslint/tests/lib/rules/no-alert.js
@@ -124,6 +124,18 @@ ruleTester.run("no-alert", rule, {
code: "function foo() { var globalThis = bar; globalThis.alert(); }\nglobalThis.alert();",
env: { es2020: true },
errors: [{ messageId: "unexpected", data: { name: "alert" }, type: "CallExpression", line: 2, column: 1 }]
+ },
+
+ // Optional chaining
+ {
+ code: "window?.alert(foo)",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected", data: { name: "alert" } }]
+ },
+ {
+ code: "(window?.alert)(foo)",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected", data: { name: "alert" } }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-bitwise.js b/eslint/tests/lib/rules/no-bitwise.js
index ae3e8a8..25cc286 100644
--- a/eslint/tests/lib/rules/no-bitwise.js
+++ b/eslint/tests/lib/rules/no-bitwise.js
@@ -22,7 +22,12 @@ ruleTester.run("no-bitwise", rule, {
valid: [
"a + b",
"!a",
+ "a && b",
+ "a || b",
"a += b",
+ { code: "a &&= b", parserOptions: { ecmaVersion: 2021 } },
+ { code: "a ||= b", parserOptions: { ecmaVersion: 2021 } },
+ { code: "a ??= b", parserOptions: { ecmaVersion: 2021 } },
{ code: "~[1, 2, 3].indexOf(1)", options: [{ allow: ["~"] }] },
{ code: "~1<<2 === -8", options: [{ allow: ["~", "<<"] }] },
{ code: "a|0", options: [{ int32Hint: true }] },
diff --git a/eslint/tests/lib/rules/no-constant-condition.js b/eslint/tests/lib/rules/no-constant-condition.js
index 2aa4fa7..b83b337 100644
--- a/eslint/tests/lib/rules/no-constant-condition.js
+++ b/eslint/tests/lib/rules/no-constant-condition.js
@@ -74,12 +74,11 @@ ruleTester.run("no-constant-condition", rule, {
"if(true && typeof abc==='string'){}",
// #11181, string literals
- "if('str' || a){}",
"if('str1' && a){}",
"if(a && 'str'){}",
- "if('str' || abc==='str'){}",
// #11306
+ "if ((foo || true) === 'baz') {}",
"if ((foo || 'bar') === 'baz') {}",
"if ((foo || 'bar') !== 'baz') {}",
"if ((foo || 'bar') == 'baz') {}",
@@ -90,6 +89,19 @@ ruleTester.run("no-constant-condition", rule, {
"if ((foo || 233) <= 666) {}",
"if ((key || 'k') in obj) {}",
"if ((foo || {}) instanceof obj) {}",
+ "if ((foo || 'bar' || 'bar') === 'bar');",
+ {
+ code: "if ((foo || 1n) === 'baz') {}",
+ parserOptions: { ecmaVersion: 11 }
+ },
+ {
+ code: "if (a && 0n || b);",
+ parserOptions: { ecmaVersion: 11 }
+ },
+ {
+ code: "if(1n && a){};",
+ parserOptions: { ecmaVersion: 11 }
+ },
// #12225
"if ('' + [y] === '' + [ty]) {}",
@@ -121,11 +133,14 @@ ruleTester.run("no-constant-condition", rule, {
{ code: "for(;`foo`;);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "for(;`foo${bar}`;);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "do{}while(true)", errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "do{}while('1')", errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "do{}while(0)", errors: [{ messageId: "unexpected", type: "Literal" }] },
{ code: "do{}while(t = -2)", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] },
{ code: "do{}while(``)", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "do{}while(`foo`)", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "do{}while(`foo${bar}`)", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "true ? 1 : 2;", errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "1 ? 1 : 2;", errors: [{ messageId: "unexpected", type: "Literal" }] },
{ code: "q = 0 ? 1 : 2;", errors: [{ messageId: "unexpected", type: "Literal" }] },
{ code: "(q = 0) ? 1 : 2;", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] },
{ code: "`` ? 1 : 2;", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
@@ -133,6 +148,7 @@ ruleTester.run("no-constant-condition", rule, {
{ code: "`foo${bar}` ? 1 : 2;", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "if(-2);", errors: [{ messageId: "unexpected", type: "UnaryExpression" }] },
{ code: "if(true);", errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "if(1);", errors: [{ messageId: "unexpected", type: "Literal" }] },
{ code: "if({});", errors: [{ messageId: "unexpected", type: "ObjectExpression" }] },
{ code: "if(0 < 1);", errors: [{ messageId: "unexpected", type: "BinaryExpression" }] },
{ code: "if(0 || 1);", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
@@ -143,6 +159,7 @@ ruleTester.run("no-constant-condition", rule, {
{ code: "if(`${'bar'}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "if(`${'bar' + `foo`}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "if(`foo${false || true}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
+ { code: "if(`foo${0 || 1}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "if(`foo${bar}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "if(`${bar}foo`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
@@ -152,6 +169,7 @@ ruleTester.run("no-constant-condition", rule, {
{ code: "while(x = 1);", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] },
{ code: "while(function(){});", errors: [{ messageId: "unexpected", type: "FunctionExpression" }] },
{ code: "while(true);", errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "while(1);", errors: [{ messageId: "unexpected", type: "Literal" }] },
{ code: "while(() => {});", errors: [{ messageId: "unexpected", type: "ArrowFunctionExpression" }] },
{ code: "while(`foo`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
{ code: "while(``);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
@@ -180,12 +198,15 @@ ruleTester.run("no-constant-condition", rule, {
// #5693
{ code: "if(false && abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
{ code: "if(true || abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
+ { code: "if(1 || abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
{ code: "if(abc==='str' || true){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
{ code: "if(abc==='str' || true || def ==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
{ code: "if(false || true){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
{ code: "if(typeof abc==='str' || true){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
// #11181, string literals
+ { code: "if('str' || a){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
+ { code: "if('str' || abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
{ code: "if('str1' || 'str2'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
{ code: "if('str1' && 'str2'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
{ code: "if(abc==='str' || 'str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
@@ -272,6 +293,17 @@ ruleTester.run("no-constant-condition", rule, {
{
code: "if ([,] + ''){}",
errors: [{ messageId: "unexpected", type: "BinaryExpression" }]
- }
+ },
+
+ // #13238
+ { code: "if(/foo/ui);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "if(0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "if(0b0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "if(0o0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "if(0x0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "if(0b1n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "if(0o1n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "if(0x1n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "if(0x1n || foo);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }
]
});
diff --git a/eslint/tests/lib/rules/no-dupe-keys.js b/eslint/tests/lib/rules/no-dupe-keys.js
index a6062a2..6c650db 100644
--- a/eslint/tests/lib/rules/no-dupe-keys.js
+++ b/eslint/tests/lib/rules/no-dupe-keys.js
@@ -33,7 +33,9 @@ ruleTester.run("no-dupe-keys", rule, {
{ code: "var x = { get a() {}, set a (value) {} };", parserOptions: { ecmaVersion: 6 } },
{ code: "var x = { a: 1, b: { a: 2 } };", parserOptions: { ecmaVersion: 6 } },
{ code: "var x = ({ null: 1, [/(?0)/]: 2 })", parserOptions: { ecmaVersion: 2018 } },
- { code: "var {a, a} = obj", parserOptions: { ecmaVersion: 6 } }
+ { code: "var {a, a} = obj", parserOptions: { ecmaVersion: 6 } },
+ "var x = { 012: 1, 12: 2 };",
+ { code: "var x = { 1_0: 1, 1: 2 };", parserOptions: { ecmaVersion: 2021 } }
],
invalid: [
{ code: "var x = { a: b, ['a']: b };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
@@ -41,6 +43,11 @@ ruleTester.run("no-dupe-keys", rule, {
{ code: "var x = { '': 1, '': 2 };", errors: [{ messageId: "unexpected", data: { name: "" }, type: "ObjectExpression" }] },
{ code: "var x = { '': 1, [``]: 2 };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "" }, type: "ObjectExpression" }] },
{ code: "var foo = { 0x1: 1, 1: 2};", errors: [{ messageId: "unexpected", data: { name: "1" }, type: "ObjectExpression" }] },
+ { code: "var x = { 012: 1, 10: 2 };", errors: [{ messageId: "unexpected", data: { name: "10" }, type: "ObjectExpression" }] },
+ { code: "var x = { 0b1: 1, 1: 2 };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "1" }, type: "ObjectExpression" }] },
+ { code: "var x = { 0o1: 1, 1: 2 };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "1" }, type: "ObjectExpression" }] },
+ { code: "var x = { 1n: 1, 1: 2 };", parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpected", data: { name: "1" }, type: "ObjectExpression" }] },
+ { code: "var x = { 1_0: 1, 10: 2 };", parserOptions: { ecmaVersion: 2021 }, errors: [{ messageId: "unexpected", data: { name: "10" }, type: "ObjectExpression" }] },
{ code: "var x = { \"z\": 1, z: 2 };", errors: [{ messageId: "unexpected", data: { name: "z" }, type: "ObjectExpression" }] },
{ code: "var foo = {\n bar: 1,\n bar: 1,\n}", errors: [{ messageId: "unexpected", data: { name: "bar" }, line: 3, column: 3 }] },
{ code: "var x = { a: 1, get a() {} };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
diff --git a/eslint/tests/lib/rules/no-duplicate-case.js b/eslint/tests/lib/rules/no-duplicate-case.js
index a6e8e0f..42dbda3 100644
--- a/eslint/tests/lib/rules/no-duplicate-case.js
+++ b/eslint/tests/lib/rules/no-duplicate-case.js
@@ -128,6 +128,66 @@ ruleTester.run("no-duplicate-case", rule, {
column: 74
}
]
+ },
+ {
+ code: "var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p. p // comment\n .p1: break; default: break;}",
+ errors: [{
+ messageId: "unexpected",
+ type: "SwitchCase",
+ column: 69
+ }]
+ },
+ {
+ code: "var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p .p\n/* comment */\n.p1: break; case p.p.p1: break; default: break;}",
+ errors: [{
+ messageId: "unexpected",
+ type: "SwitchCase",
+ line: 3,
+ column: 13
+ }]
+ },
+ {
+ code: "var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p .p\n/* comment */\n.p1: break; case p. p // comment\n .p1: break; default: break;}",
+ errors: [{
+ messageId: "unexpected",
+ type: "SwitchCase",
+ line: 3,
+ column: 13
+ }]
+ },
+ {
+ code: "var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p. p // comment\n .p1: break; case p .p\n/* comment */\n.p1: break; default: break;}",
+ errors: [
+ {
+ messageId: "unexpected",
+ type: "SwitchCase",
+ line: 1,
+ column: 69
+ },
+ {
+ messageId: "unexpected",
+ type: "SwitchCase",
+ line: 2,
+ column: 14
+ }
+ ]
+ },
+ {
+ code: "var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(a + 1).p1: break; case f(a+1).p1: break; default: break;}",
+ errors: [{
+ messageId: "unexpected",
+ type: "SwitchCase",
+ column: 87
+ }]
+ },
+ {
+ code: "var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(\na + 1 // comment\n).p1: break; case f(a+1)\n.p1: break; default: break;}",
+ errors: [{
+ messageId: "unexpected",
+ type: "SwitchCase",
+ line: 3,
+ column: 14
+ }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-eval.js b/eslint/tests/lib/rules/no-eval.js
index ad184bd..bc8d384 100644
--- a/eslint/tests/lib/rules/no-eval.js
+++ b/eslint/tests/lib/rules/no-eval.js
@@ -67,7 +67,10 @@ ruleTester.run("no-eval", rule, {
{ code: "(0, globalThis['eval'])('foo')", options: [{ allowIndirect: true }], env: { es2020: true } },
{ code: "var EVAL = globalThis.eval; EVAL('foo')", options: [{ allowIndirect: true }] },
{ code: "function foo() { globalThis.eval('foo') }", options: [{ allowIndirect: true }], env: { es2020: true } },
- { code: "globalThis.globalThis.eval('foo');", options: [{ allowIndirect: true }], env: { es2020: true } }
+ { code: "globalThis.globalThis.eval('foo');", options: [{ allowIndirect: true }], env: { es2020: true } },
+ { code: "eval?.('foo')", options: [{ allowIndirect: true }], parserOptions: { ecmaVersion: 2020 } },
+ { code: "window?.eval('foo')", options: [{ allowIndirect: true }], parserOptions: { ecmaVersion: 2020 }, env: { browser: true } },
+ { code: "(window?.eval)('foo')", options: [{ allowIndirect: true }], parserOptions: { ecmaVersion: 2020 }, env: { browser: true } }
],
invalid: [
@@ -100,6 +103,26 @@ ruleTester.run("no-eval", rule, {
{ code: "globalThis.globalThis.eval('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "CallExpression", column: 23, endColumn: 27 }] },
{ code: "globalThis.globalThis['eval']('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "CallExpression", column: 23, endColumn: 29 }] },
{ code: "(0, globalThis.eval)('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "MemberExpression", column: 16, endColumn: 20 }] },
- { code: "(0, globalThis['eval'])('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "MemberExpression", column: 16, endColumn: 22 }] }
+ { code: "(0, globalThis['eval'])('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "MemberExpression", column: 16, endColumn: 22 }] },
+
+ // Optional chaining
+ {
+ code: "window?.eval('foo')",
+ parserOptions: { ecmaVersion: 2020 },
+ globals: { window: "readonly" },
+ errors: [{ messageId: "unexpected" }]
+ },
+ {
+ code: "(window?.eval)('foo')",
+ parserOptions: { ecmaVersion: 2020 },
+ globals: { window: "readonly" },
+ errors: [{ messageId: "unexpected" }]
+ },
+ {
+ code: "(window?.window).eval('foo')",
+ parserOptions: { ecmaVersion: 2020 },
+ globals: { window: "readonly" },
+ errors: [{ messageId: "unexpected" }]
+ }
]
});
diff --git a/eslint/tests/lib/rules/no-extend-native.js b/eslint/tests/lib/rules/no-extend-native.js
index a381774..ca5b430 100644
--- a/eslint/tests/lib/rules/no-extend-native.js
+++ b/eslint/tests/lib/rules/no-extend-native.js
@@ -37,6 +37,7 @@ ruleTester.run("no-extend-native", rule, {
code: "Object.prototype.g = 0",
options: [{ exceptions: ["Object"] }]
},
+ "obj[Object.prototype] = 0",
// https://github.com/eslint/eslint/issues/4438
"Object.defineProperty()",
@@ -47,6 +48,12 @@ ruleTester.run("no-extend-native", rule, {
{
code: "{ let Object = function() {}; Object.prototype.p = 0 }",
parserOptions: { ecmaVersion: 6 }
+ },
+
+ // TODO(mdjermanovic): This test should become `invalid` in the next major version, when we upgrade the `globals` package.
+ {
+ code: "WeakRef.prototype.p = 0",
+ env: { es2021: true }
}
],
invalid: [{
@@ -137,5 +144,46 @@ ruleTester.run("no-extend-native", rule, {
data: { builtin: "Object" },
type: "AssignmentExpression"
}]
- }]
+ },
+
+ // Optional chaining
+ {
+ code: "(Object?.prototype).p = 0",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected", data: { builtin: "Object" } }]
+ },
+ {
+ code: "Object.defineProperty(Object?.prototype, 'p', { value: 0 })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected", data: { builtin: "Object" } }]
+ },
+ {
+ code: "Object?.defineProperty(Object.prototype, 'p', { value: 0 })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected", data: { builtin: "Object" } }]
+ },
+ {
+ code: "(Object?.defineProperty)(Object.prototype, 'p', { value: 0 })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected", data: { builtin: "Object" } }]
+ },
+
+ // Logical assignments
+ {
+ code: "Array.prototype.p &&= 0",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "unexpected", data: { builtin: "Array" } }]
+ },
+ {
+ code: "Array.prototype.p ||= 0",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "unexpected", data: { builtin: "Array" } }]
+ },
+ {
+ code: "Array.prototype.p ??= 0",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "unexpected", data: { builtin: "Array" } }]
+ }
+
+ ]
});
diff --git a/eslint/tests/lib/rules/no-extra-bind.js b/eslint/tests/lib/rules/no-extra-bind.js
index 1219796..8422f3d 100644
--- a/eslint/tests/lib/rules/no-extra-bind.js
+++ b/eslint/tests/lib/rules/no-extra-bind.js
@@ -102,6 +102,16 @@ ruleTester.run("no-extra-bind", rule, {
output: "var a = function() { (function(){ (function(){ this.d }.bind(c)) }) }",
errors: [{ messageId: "unexpected", type: "CallExpression", column: 71 }]
},
+ {
+ code: "var a = (function() { return 1; }).bind(this)",
+ output: "var a = (function() { return 1; })",
+ errors
+ },
+ {
+ code: "var a = (function() { return 1; }.bind)(this)",
+ output: "var a = (function() { return 1; })",
+ errors
+ },
// Should not autofix if bind expression args have side effects
{
@@ -180,6 +190,44 @@ ruleTester.run("no-extra-bind", rule, {
code: "var a = function() {}.bind(b)/**/",
output: "var a = function() {}/**/",
errors
+ },
+
+ // Optional chaining
+ {
+ code: "var a = function() { return 1; }.bind?.(b)",
+ output: "var a = function() { return 1; }",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected" }]
+ },
+ {
+ code: "var a = function() { return 1; }?.bind(b)",
+ output: "var a = function() { return 1; }",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected" }]
+ },
+ {
+ code: "var a = (function() { return 1; }?.bind)(b)",
+ output: "var a = (function() { return 1; })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected" }]
+ },
+ {
+ code: "var a = function() { return 1; }['bind']?.(b)",
+ output: "var a = function() { return 1; }",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected" }]
+ },
+ {
+ code: "var a = function() { return 1; }?.['bind'](b)",
+ output: "var a = function() { return 1; }",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected" }]
+ },
+ {
+ code: "var a = (function() { return 1; }?.['bind'])(b)",
+ output: "var a = (function() { return 1; })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected" }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-extra-boolean-cast.js b/eslint/tests/lib/rules/no-extra-boolean-cast.js
index 70ec8bd..8dda699 100644
--- a/eslint/tests/lib/rules/no-extra-boolean-cast.js
+++ b/eslint/tests/lib/rules/no-extra-boolean-cast.js
@@ -2408,6 +2408,21 @@ ruleTester.run("no-extra-boolean-cast", rule, {
options: [{ enforceForLogicalOperands: true }],
parserOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "unexpectedCall", type: "CallExpression" }]
+ },
+
+ // Optional chaining
+ {
+ code: "if (Boolean?.(foo)) ;",
+ output: "if (foo) ;",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedCall" }]
+ },
+ {
+ code: "if (Boolean?.(a ?? b) || c) {}",
+ output: "if ((a ?? b) || c) {}",
+ options: [{ enforceForLogicalOperands: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedCall" }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-extra-parens.js b/eslint/tests/lib/rules/no-extra-parens.js
index 91099f8..c0d0387 100644
--- a/eslint/tests/lib/rules/no-extra-parens.js
+++ b/eslint/tests/lib/rules/no-extra-parens.js
@@ -44,7 +44,7 @@ function invalid(code, output, type, line, config) {
const ruleTester = new RuleTester({
parserOptions: {
- ecmaVersion: 2020,
+ ecmaVersion: 2021,
ecmaFeatures: {
jsx: true
}
@@ -190,6 +190,13 @@ ruleTester.run("no-extra-parens", rule, {
// special cases
"(0).a",
+ "(123).a",
+ "(08).a",
+ "(09).a",
+ "(018).a",
+ "(012934).a",
+ "(5_000).a",
+ "(5_000_00).a",
"(function(){ }())",
"({a: function(){}}.a());",
"({a:0}.a ? b : c)",
@@ -356,6 +363,15 @@ ruleTester.run("no-extra-parens", rule, {
"}"
].join("\n"),
+ // linebreaks before postfix update operators are not allowed
+ "(a\n)++",
+ "(a\n)--",
+ "(a\n\n)++",
+ "(a.b\n)--",
+ "(a\n.b\n)++",
+ "(a[\nb\n]\n)--",
+ "(a[b]\n\n)++",
+
// async/await
"async function a() { await (a + b) }",
"async function a() { await (a + await b) }",
@@ -612,6 +628,14 @@ ruleTester.run("no-extra-parens", rule, {
"for (; a; a); a; a;",
"for (let a = (b && c) === d; ;);",
+ "new (a()).b.c;",
+ "new (a().b).c;",
+ "new (a().b.c);",
+ "new (a().b().d);",
+ "new a().b().d;",
+ "new (a(b()).c)",
+ "new (a.b()).c",
+
// Nullish coalescing
{ code: "var v = (a ?? b) || c", parserOptions: { ecmaVersion: 2020 } },
{ code: "var v = a ?? (b || c)", parserOptions: { ecmaVersion: 2020 } },
@@ -620,7 +644,32 @@ ruleTester.run("no-extra-parens", rule, {
{ code: "var v = (a || b) ?? c", parserOptions: { ecmaVersion: 2020 } },
{ code: "var v = a || (b ?? c)", parserOptions: { ecmaVersion: 2020 } },
{ code: "var v = (a && b) ?? c", parserOptions: { ecmaVersion: 2020 } },
- { code: "var v = a && (b ?? c)", parserOptions: { ecmaVersion: 2020 } }
+ { code: "var v = a && (b ?? c)", parserOptions: { ecmaVersion: 2020 } },
+
+ // Optional chaining
+ { code: "var v = (obj?.aaa).bbb", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var v = (obj?.aaa)()", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var v = new (obj?.aaa)()", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var v = new (obj?.aaa)", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var v = (obj?.aaa)`template`", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var v = (obj?.()).bbb", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var v = (obj?.())()", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var v = new (obj?.())()", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var v = new (obj?.())", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var v = (obj?.())`template`", parserOptions: { ecmaVersion: 2020 } },
+ { code: "(obj?.aaa).bbb = 0", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var foo = (function(){})?.()", parserOptions: { ecmaVersion: 2020 } },
+ { code: "var foo = (function(){}?.())", parserOptions: { ecmaVersion: 2020 } },
+ {
+ code: "var foo = (function(){})?.call()",
+ options: ["all", { enforceForFunctionPrototypeMethods: false }],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "var foo = (function(){}?.call())",
+ options: ["all", { enforceForFunctionPrototypeMethods: false }],
+ parserOptions: { ecmaVersion: 2020 }
+ }
],
invalid: [
@@ -715,6 +764,18 @@ ruleTester.run("no-extra-parens", rule, {
invalid("+((bar-foo))", "+(bar-foo)", "BinaryExpression"),
invalid("++(foo)", "++foo", "Identifier"),
invalid("--(foo)", "--foo", "Identifier"),
+ invalid("++\n(foo)", "++\nfoo", "Identifier"),
+ invalid("--\n(foo)", "--\nfoo", "Identifier"),
+ invalid("++(\nfoo)", "++\nfoo", "Identifier"),
+ invalid("--(\nfoo)", "--\nfoo", "Identifier"),
+ invalid("(foo)++", "foo++", "Identifier"),
+ invalid("(foo)--", "foo--", "Identifier"),
+ invalid("((foo)\n)++", "(foo\n)++", "Identifier"),
+ invalid("((foo\n))--", "(foo\n)--", "Identifier"),
+ invalid("((foo\n)\n)++", "(foo\n\n)++", "Identifier"),
+ invalid("(a\n.b)--", "a\n.b--", "MemberExpression"),
+ invalid("(a.\nb)++", "a.\nb++", "MemberExpression"),
+ invalid("(a\n[\nb\n])--", "a\n[\nb\n]--", "MemberExpression"),
invalid("(a || b) ? c : d", "a || b ? c : d", "LogicalExpression"),
invalid("a ? (b = c) : d", "a ? b = c : d", "AssignmentExpression"),
invalid("a ? b : (c = d)", "a ? b : c = d", "AssignmentExpression"),
@@ -742,9 +803,14 @@ ruleTester.run("no-extra-parens", rule, {
invalid("(a).b", "a.b", "Identifier"),
invalid("(0)[a]", "0[a]", "Literal"),
invalid("(0.0).a", "0.0.a", "Literal"),
+ invalid("(123.4).a", "123.4.a", "Literal"),
+ invalid("(0.0_0).a", "0.0_0.a", "Literal"),
invalid("(0xBEEF).a", "0xBEEF.a", "Literal"),
+ invalid("(0xBE_EF).a", "0xBE_EF.a", "Literal"),
invalid("(1e6).a", "1e6.a", "Literal"),
invalid("(0123).a", "0123.a", "Literal"),
+ invalid("(08.1).a", "08.1.a", "Literal"),
+ invalid("(09.).a", "09..a", "Literal"),
invalid("a[(function() {})]", "a[function() {}]", "FunctionExpression"),
invalid("new (function(){})", "new function(){}", "FunctionExpression"),
invalid("new (\nfunction(){}\n)", "new \nfunction(){}\n", "FunctionExpression", 1),
@@ -759,6 +825,7 @@ ruleTester.run("no-extra-parens", rule, {
invalid("(new foo(bar)).baz", "new foo(bar).baz", "NewExpression"),
invalid("(new foo.bar()).baz", "new foo.bar().baz", "NewExpression"),
invalid("(new foo.bar()).baz()", "new foo.bar().baz()", "NewExpression"),
+ invalid("new a[(b()).c]", "new a[b().c]", "CallExpression"),
invalid("(a)()", "a()", "Identifier"),
invalid("(a.b)()", "a.b()", "MemberExpression"),
@@ -772,6 +839,14 @@ ruleTester.run("no-extra-parens", rule, {
invalid("((new A))()", "(new A)()", "NewExpression"),
invalid("new (foo\n.baz\n.bar\n.foo.baz)", "new foo\n.baz\n.bar\n.foo.baz", "MemberExpression"),
invalid("new (foo.baz.bar.baz)", "new foo.baz.bar.baz", "MemberExpression"),
+ invalid("new ((a.b())).c", "new (a.b()).c", "CallExpression"),
+ invalid("new ((a().b)).c", "new (a().b).c", "MemberExpression"),
+ invalid("new ((a().b().d))", "new (a().b().d)", "MemberExpression"),
+ invalid("new ((a())).b.d", "new (a()).b.d", "CallExpression"),
+ invalid("new (a.b).d;", "new a.b.d;", "MemberExpression"),
+ invalid("(a().b).d;", "a().b.d;", "MemberExpression"),
+ invalid("(a.b()).d;", "a.b().d;", "CallExpression"),
+ invalid("(a.b).d;", "a.b.d;", "MemberExpression"),
invalid("0, (_ => 0)", "0, _ => 0", "ArrowFunctionExpression", 1),
invalid("(_ => 0), 0", "_ => 0, 0", "ArrowFunctionExpression", 1),
@@ -2698,6 +2773,34 @@ ruleTester.run("no-extra-parens", rule, {
output: "var v = a | b ?? c | d",
parserOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "unexpected" }]
+ },
+
+ // Optional chaining
+ {
+ code: "var v = (obj?.aaa)?.aaa",
+ output: "var v = obj?.aaa?.aaa",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected" }]
+ },
+ {
+ code: "var v = (obj.aaa)?.aaa",
+ output: "var v = obj.aaa?.aaa",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected" }]
+ },
+ {
+ code: "var foo = (function(){})?.call()",
+ output: "var foo = function(){}?.call()",
+ options: ["all", { enforceForFunctionPrototypeMethods: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected" }]
+ },
+ {
+ code: "var foo = (function(){}?.call())",
+ output: "var foo = function(){}?.call()",
+ options: ["all", { enforceForFunctionPrototypeMethods: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpected" }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-func-assign.js b/eslint/tests/lib/rules/no-func-assign.js
index 6623421..85a6bdc 100644
--- a/eslint/tests/lib/rules/no-func-assign.js
+++ b/eslint/tests/lib/rules/no-func-assign.js
@@ -88,6 +88,14 @@ ruleTester.run("no-func-assign", rule, {
data: { name: "foo" },
type: "Identifier"
}]
+ },
+ {
+ code: "var a = function foo() { foo = 123; };",
+ errors: [{
+ messageId: "isAFunction",
+ data: { name: "foo" },
+ type: "Identifier"
+ }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-implicit-coercion.js b/eslint/tests/lib/rules/no-implicit-coercion.js
index 73bfe7d..fa2b68b 100644
--- a/eslint/tests/lib/rules/no-implicit-coercion.js
+++ b/eslint/tests/lib/rules/no-implicit-coercion.js
@@ -355,6 +355,28 @@ ruleTester.run("no-implicit-coercion", rule, {
data: { recommendation: "String(1n)" },
type: "BinaryExpression"
}]
+ },
+
+ // Optional chaining
+ {
+ code: "~foo?.indexOf(1)",
+ output: null,
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{
+ messageId: "useRecommendation",
+ data: { recommendation: "foo?.indexOf(1) >= 0" },
+ type: "UnaryExpression"
+ }]
+ },
+ {
+ code: "~(foo?.indexOf)(1)",
+ output: null,
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{
+ messageId: "useRecommendation",
+ data: { recommendation: "(foo?.indexOf)(1) !== -1" },
+ type: "UnaryExpression"
+ }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-implied-eval.js b/eslint/tests/lib/rules/no-implied-eval.js
index 1c712f4..ce06f66 100644
--- a/eslint/tests/lib/rules/no-implied-eval.js
+++ b/eslint/tests/lib/rules/no-implied-eval.js
@@ -235,6 +235,20 @@ ruleTester.run("no-implied-eval", rule, {
line: 3
}
]
+ },
+
+ // Optional chaining
+ {
+ code: "window?.setTimeout('code', 0)",
+ parserOptions: { ecmaVersion: 2020 },
+ globals: { window: "readonly" },
+ errors: [{ messageId: "impliedEval" }]
+ },
+ {
+ code: "(window?.setTimeout)('code', 0)",
+ parserOptions: { ecmaVersion: 2020 },
+ globals: { window: "readonly" },
+ errors: [{ messageId: "impliedEval" }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-import-assign.js b/eslint/tests/lib/rules/no-import-assign.js
index ab65451..babfdfc 100644
--- a/eslint/tests/lib/rules/no-import-assign.js
+++ b/eslint/tests/lib/rules/no-import-assign.js
@@ -310,6 +310,23 @@ ruleTester.run("no-import-assign", rule, {
{
code: "import mod, * as mod_ns from 'mod'; mod.prop = 0; mod_ns.prop = 0",
errors: [{ messageId: "readonlyMember", data: { name: "mod_ns" }, column: 51 }]
+ },
+
+ // Optional chaining
+ {
+ code: "import * as mod from 'mod'; Object?.defineProperty(mod, key, d)",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "readonlyMember", data: { name: "mod" }, column: 29 }]
+ },
+ {
+ code: "import * as mod from 'mod'; (Object?.defineProperty)(mod, key, d)",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "readonlyMember", data: { name: "mod" }, column: 29 }]
+ },
+ {
+ code: "import * as mod from 'mod'; delete mod?.prop",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "readonlyMember", data: { name: "mod" }, column: 29 }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-inline-comments.js b/eslint/tests/lib/rules/no-inline-comments.js
index ecb4759..463c86e 100644
--- a/eslint/tests/lib/rules/no-inline-comments.js
+++ b/eslint/tests/lib/rules/no-inline-comments.js
@@ -88,7 +88,24 @@ ruleTester.run("no-inline-comments", rule, {
comment
*/}
- )`
+ )`,
+ {
+ code: "import(/* webpackChunkName: \"my-chunk-name\" */ './locale/en');",
+ options: [
+ {
+ ignorePattern: "(?:webpackChunkName):\\s.+"
+ }
+ ],
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "var foo = 2; // Note: This comment is legal.",
+ options: [
+ {
+ ignorePattern: "Note: "
+ }
+ ]
+ }
],
invalid: [
@@ -100,6 +117,15 @@ ruleTester.run("no-inline-comments", rule, {
code: "/*A block comment inline before code*/ var a = 2;",
errors: [blockError]
},
+ {
+ code: "/* something */ var a = 2;",
+ options: [
+ {
+ ignorePattern: "otherthing"
+ }
+ ],
+ errors: [blockError]
+ },
{
code: "var a = 3; //A comment inline with code",
errors: [lineError]
@@ -108,6 +134,15 @@ ruleTester.run("no-inline-comments", rule, {
code: "var a = 3; // someday use eslint-disable-line here",
errors: [lineError]
},
+ {
+ code: "var a = 3; // other line comment",
+ options: [
+ {
+ ignorePattern: "something"
+ }
+ ],
+ errors: [lineError]
+ },
{
code: "var a = 4;\n/**A\n * block\n * comment\n * inline\n * between\n * code*/ var foo = a;",
errors: [blockError]
diff --git a/eslint/tests/lib/rules/no-invalid-this.js b/eslint/tests/lib/rules/no-invalid-this.js
index 3662a83..6e1b757 100644
--- a/eslint/tests/lib/rules/no-invalid-this.js
+++ b/eslint/tests/lib/rules/no-invalid-this.js
@@ -366,6 +366,12 @@ const patterns = [
invalid: [USE_STRICT, IMPLIED_STRICT, MODULES],
errors
},
+ {
+ code: "obj.foo = (function() { return function() { console.log(this); z(x => console.log(x, this)); }; })?.();",
+ parserOptions: { ecmaVersion: 2020 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
// Class Instance Methods.
{
@@ -421,6 +427,24 @@ const patterns = [
valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
invalid: []
},
+ {
+ code: "var foo = function() { console.log(this); z(x => console.log(x, this)); }?.bind(obj);",
+ parserOptions: { ecmaVersion: 2020 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "var foo = (function() { console.log(this); z(x => console.log(x, this)); }?.bind)(obj);",
+ parserOptions: { ecmaVersion: 2020 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "var foo = function() { console.log(this); z(x => console.log(x, this)); }.bind?.(obj);",
+ parserOptions: { ecmaVersion: 2020 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
// Array methods.
{
@@ -534,6 +558,30 @@ const patterns = [
valid: [NORMAL],
invalid: [USE_STRICT, IMPLIED_STRICT, MODULES]
},
+ {
+ code: "Array?.from([], function() { console.log(this); z(x => console.log(x, this)); }, obj);",
+ parserOptions: { ecmaVersion: 2020 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "foo?.every(function() { console.log(this); z(x => console.log(x, this)); }, obj);",
+ parserOptions: { ecmaVersion: 2020 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "(Array?.from)([], function() { console.log(this); z(x => console.log(x, this)); }, obj);",
+ parserOptions: { ecmaVersion: 2020 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "(foo?.every)(function() { console.log(this); z(x => console.log(x, this)); }, obj);",
+ parserOptions: { ecmaVersion: 2020 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
// @this tag.
{
@@ -671,6 +719,24 @@ const patterns = [
errors,
valid: [NORMAL],
invalid: [USE_STRICT, IMPLIED_STRICT, MODULES]
+ },
+ {
+ code: "obj.method &&= function () { console.log(this); z(x => console.log(x, this)); }",
+ parserOptions: { ecmaVersion: 2021 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "obj.method ||= function () { console.log(this); z(x => console.log(x, this)); }",
+ parserOptions: { ecmaVersion: 2021 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "obj.method ??= function () { console.log(this); z(x => console.log(x, this)); }",
+ parserOptions: { ecmaVersion: 2021 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
}
];
diff --git a/eslint/tests/lib/rules/no-irregular-whitespace.js b/eslint/tests/lib/rules/no-irregular-whitespace.js
index 7852d5e..4055d36 100644
--- a/eslint/tests/lib/rules/no-irregular-whitespace.js
+++ b/eslint/tests/lib/rules/no-irregular-whitespace.js
@@ -540,6 +540,335 @@ ruleTester.run("no-irregular-whitespace", rule, {
column: 14
}
]
+ },
+
+ // full location tests
+ {
+ code: "var foo = \u000B bar;",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 11,
+ endLine: 1,
+ endColumn: 12
+ }
+ ]
+ },
+ {
+ code: "var foo =\u000Bbar;",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 10,
+ endLine: 1,
+ endColumn: 11
+ }
+ ]
+ },
+ {
+ code: "var foo = \u000B\u000B bar;",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 11,
+ endLine: 1,
+ endColumn: 13
+ }
+ ]
+ },
+ {
+ code: "var foo = \u000B\u000C bar;",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 11,
+ endLine: 1,
+ endColumn: 13
+ }
+ ]
+ },
+ {
+ code: "var foo = \u000B \u000B bar;",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 11,
+ endLine: 1,
+ endColumn: 12
+ },
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 13,
+ endLine: 1,
+ endColumn: 14
+ }
+ ]
+ },
+ {
+ code: "var foo = \u000Bbar\u000B;",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 11,
+ endLine: 1,
+ endColumn: 12
+ },
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
+ }
+ ]
+ },
+ {
+ code: "\u000B",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 1,
+ endLine: 1,
+ endColumn: 2
+ }
+ ]
+ },
+ {
+ code: "\u00A0\u2002\u2003",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 1,
+ endLine: 1,
+ endColumn: 4
+ }
+ ]
+ },
+ {
+ code: "var foo = \u000B\nbar;",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 11,
+ endLine: 1,
+ endColumn: 12
+ }
+ ]
+ },
+ {
+ code: "var foo =\u000B\n\u000Bbar;",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 10,
+ endLine: 1,
+ endColumn: 11
+ },
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 2,
+ column: 1,
+ endLine: 2,
+ endColumn: 2
+ }
+ ]
+ },
+ {
+ code: "var foo = \u000C\u000B\n\u000C\u000B\u000Cbar\n;\u000B\u000C\n",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 11,
+ endLine: 1,
+ endColumn: 13
+ },
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 2,
+ column: 1,
+ endLine: 2,
+ endColumn: 4
+ },
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 3,
+ column: 2,
+ endLine: 3,
+ endColumn: 4
+ }
+ ]
+ },
+ {
+ code: "var foo = \u2028bar;",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 11,
+ endLine: 2,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: "var foo =\u2029 bar;",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 10,
+ endLine: 2,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: "var foo = bar;\u2028",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 15,
+ endLine: 2,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: "\u2029",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 1,
+ endLine: 2,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: "foo\u2028\u2028",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 4,
+ endLine: 2,
+ endColumn: 1
+ },
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 2,
+ column: 1,
+ endLine: 3,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: "foo\u2029\u2028",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 4,
+ endLine: 2,
+ endColumn: 1
+ },
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 2,
+ column: 1,
+ endLine: 3,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: "foo\u2028\n\u2028",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 4,
+ endLine: 2,
+ endColumn: 1
+ },
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 3,
+ column: 1,
+ endLine: 4,
+ endColumn: 1
+ }
+ ]
+ },
+ {
+ code: "foo\u000B\u2028\u000B",
+ errors: [
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 4,
+ endLine: 1,
+ endColumn: 5
+ },
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 1,
+ column: 5,
+ endLine: 2,
+ endColumn: 1
+ },
+ {
+ messageId: "noIrregularWhitespace",
+ type: "Program",
+ line: 2,
+ column: 1,
+ endLine: 2,
+ endColumn: 2
+ }
+ ]
}
]
});
diff --git a/eslint/tests/lib/rules/no-loss-of-precision.js b/eslint/tests/lib/rules/no-loss-of-precision.js
index fd2fb20..c690dd7 100644
--- a/eslint/tests/lib/rules/no-loss-of-precision.js
+++ b/eslint/tests/lib/rules/no-loss-of-precision.js
@@ -47,14 +47,33 @@ ruleTester.run("no-loss-of-precision", rule, {
"var x = 0195",
"var x = 0e5",
+ { code: "var x = 12_34_56", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = 12_3.4_56", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = -12_3.4_56", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = -12_34_56", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = 12_3e3_4", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = 123.0e3_4", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = 12_3e-3_4", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = 12_3.0e-3_4", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = -1_23e-3_4", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = -1_23.8e-3_4", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = 1_230000000_00000000_00000_000", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = -1_230000000_00000000_00000_000", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = 0.0_00_000000000_000000000_00123", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = -0.0_00_000000000_000000000_00123", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = 0e5_3", parserOptions: { ecmaVersion: 2021 } },
{ code: "var x = 0b11111111111111111111111111111111111111111111111111111", parserOptions: { ecmaVersion: 6 } },
+ { code: "var x = 0b111_111_111_111_1111_11111_111_11111_1111111111_11111111_111_111", parserOptions: { ecmaVersion: 2021 } },
+
{ code: "var x = 0B11111111111111111111111111111111111111111111111111111", parserOptions: { ecmaVersion: 6 } },
+ { code: "var x = 0B111_111_111_111_1111_11111_111_11111_1111111111_11111111_111_111", parserOptions: { ecmaVersion: 2021 } },
{ code: "var x = 0o377777777777777777", parserOptions: { ecmaVersion: 6 } },
+ { code: "var x = 0o3_77_777_777_777_777_777", parserOptions: { ecmaVersion: 2021 } },
{ code: "var x = 0O377777777777777777", parserOptions: { ecmaVersion: 6 } },
- "var x = 0377777777777777777",
+ "var x = 0377777777777777777",
"var x = 0x1FFFFFFFFFFFFF",
"var x = 0X1FFFFFFFFFFFFF",
"var x = true",
@@ -65,8 +84,10 @@ ruleTester.run("no-loss-of-precision", rule, {
"var x = {}",
"var x = ['a', 'b']",
"var x = new Date()",
- "var x = '9007199254740993'"
+ "var x = '9007199254740993'",
+ { code: "var x = 0x1FFF_FFFF_FFF_FFF", parserOptions: { ecmaVersion: 2021 } },
+ { code: "var x = 0X1_FFF_FFFF_FFF_FFF", parserOptions: { ecmaVersion: 2021 } }
],
invalid: [
{
@@ -93,7 +114,36 @@ ruleTester.run("no-loss-of-precision", rule, {
code: "var x = -900719.9254740994",
errors: [{ messageId: "noLossOfPrecision" }]
},
-
+ {
+ code: "var x = 900719925474099_3",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 90_0719925_4740.9_93e3",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 9.0_0719925_474099_3e15",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = -9_00719_9254_740993",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 900_719.92_54740_994",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = -900_719.92_5474_0994",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
{
code: "var x = 5123000000000000000000000000001",
errors: [{ messageId: "noLossOfPrecision" }]
@@ -153,7 +203,71 @@ ruleTester.run("no-loss-of-precision", rule, {
{
code: "var x = 0X20000000000001",
errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 5123_00000000000000000000000000_1",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = -5_12300000000000000000000_0000001",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 123_00000000000000000000_00.0_0",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 1.0_00000000000000000_0000123",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 174_980057982_640953949800178169_409709228253554471456994_914061648512796239935950073857881054_1618443059_2",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 2e9_99",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = .1_23000000000000_00000_0000_0",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 0b1_0000000000000000000000000000000000000000000000000000_1",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 0B10000000000_0000000000000000000000000000_000000000000001",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 0o4_00000000000000_001",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 0O4_0000000000000000_1",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 0x2_0000000000001",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
+ },
+ {
+ code: "var x = 0X200000_0000000_1",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noLossOfPrecision" }]
}
-
]
});
diff --git a/eslint/tests/lib/rules/no-magic-numbers.js b/eslint/tests/lib/rules/no-magic-numbers.js
index da75571..bbdf8ca 100644
--- a/eslint/tests/lib/rules/no-magic-numbers.js
+++ b/eslint/tests/lib/rules/no-magic-numbers.js
@@ -213,6 +213,50 @@ ruleTester.run("no-magic-numbers", rule, {
code: "f(-100n)",
options: [{ ignore: ["-100n"] }],
parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "const { param = 123 } = sourceObject;",
+ options: [{ ignoreDefaultValues: true }],
+ env: { es6: true }
+ },
+ {
+ code: "const func = (param = 123) => {}",
+ options: [{ ignoreDefaultValues: true }],
+ env: { es6: true }
+ },
+ {
+ code: "const func = ({ param = 123 }) => {}",
+ options: [{ ignoreDefaultValues: true }],
+ env: { es6: true }
+ },
+ {
+ code: "const [one = 1, two = 2] = []",
+ options: [{ ignoreDefaultValues: true }],
+ env: { es6: true }
+ },
+ {
+ code: "var one, two; [one = 1, two = 2] = []",
+ options: [{ ignoreDefaultValues: true }],
+ env: { es6: true }
+ },
+
+ // Optional chaining
+ {
+ code: "var x = parseInt?.(y, 10);",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "var x = Number?.parseInt(y, 10);",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "var x = (Number?.parseInt)(y, 10);",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "foo?.[777]",
+ options: [{ ignoreArrayIndexes: true }],
+ parserOptions: { ecmaVersion: 2020 }
}
],
invalid: [
@@ -719,6 +763,55 @@ ruleTester.run("no-magic-numbers", rule, {
errors: [
{ messageId: "noMagic", data: { raw: "100" }, line: 1 }
]
+ },
+ {
+ code: "const func = (param = 123) => {}",
+ options: [{ ignoreDefaultValues: false }],
+ env: { es6: true },
+ errors: [
+ { messageId: "noMagic", data: { raw: "123" }, line: 1 }
+ ]
+ },
+ {
+ code: "const { param = 123 } = sourceObject;",
+ options: [{}],
+ env: { es6: true },
+ errors: [
+ { messageId: "noMagic", data: { raw: "123" }, line: 1 }
+ ]
+ },
+ {
+ code: "const { param = 123 } = sourceObject;",
+ env: { es6: true },
+ errors: [
+ { messageId: "noMagic", data: { raw: "123" }, line: 1 }
+ ]
+ },
+ {
+ code: "const { param = 123 } = sourceObject;",
+ options: [{ ignoreDefaultValues: false }],
+ env: { es6: true },
+ errors: [
+ { messageId: "noMagic", data: { raw: "123" }, line: 1 }
+ ]
+ },
+ {
+ code: "const [one = 1, two = 2] = []",
+ options: [{ ignoreDefaultValues: false }],
+ env: { es6: true },
+ errors: [
+ { messageId: "noMagic", data: { raw: "1" }, line: 1 },
+ { messageId: "noMagic", data: { raw: "2" }, line: 1 }
+ ]
+ },
+ {
+ code: "var one, two; [one = 1, two = 2] = []",
+ options: [{ ignoreDefaultValues: false }],
+ env: { es6: true },
+ errors: [
+ { messageId: "noMagic", data: { raw: "1" }, line: 1 },
+ { messageId: "noMagic", data: { raw: "2" }, line: 1 }
+ ]
}
]
});
diff --git a/eslint/tests/lib/rules/no-obj-calls.js b/eslint/tests/lib/rules/no-obj-calls.js
index 4c21d9b..6270e7a 100644
--- a/eslint/tests/lib/rules/no-obj-calls.js
+++ b/eslint/tests/lib/rules/no-obj-calls.js
@@ -315,6 +315,18 @@ ruleTester.run("no-obj-calls", rule, {
code: "var foo = window.Atomics; new foo;",
env: { es2020: true, browser: true },
errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Atomics" }, type: "NewExpression" }]
+ },
+
+ // Optional chaining
+ {
+ code: "var x = globalThis?.Reflect();",
+ env: { es2020: true },
+ errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }]
+ },
+ {
+ code: "var x = (globalThis?.Reflect)();",
+ env: { es2020: true },
+ errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-param-reassign.js b/eslint/tests/lib/rules/no-param-reassign.js
index a79249d..5f521cb 100644
--- a/eslint/tests/lib/rules/no-param-reassign.js
+++ b/eslint/tests/lib/rules/no-param-reassign.js
@@ -368,6 +368,57 @@ ruleTester.run("no-param-reassign", rule, {
messageId: "assignmentToFunctionParamProp",
data: { name: "a" }
}]
+ },
+ {
+ code: "function foo(a) { a &&= b; }",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "assignmentToFunctionParam",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "function foo(a) { a ||= b; }",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "assignmentToFunctionParam",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "function foo(a) { a ??= b; }",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "assignmentToFunctionParam",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "function foo(a) { a.b &&= c; }",
+ options: [{ props: true }],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "assignmentToFunctionParamProp",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "function foo(a) { a.b.c ||= d; }",
+ options: [{ props: true }],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "assignmentToFunctionParamProp",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "function foo(a) { a[b] ??= c; }",
+ options: [{ props: true }],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "assignmentToFunctionParamProp",
+ data: { name: "a" }
+ }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-promise-executor-return.js b/eslint/tests/lib/rules/no-promise-executor-return.js
new file mode 100644
index 0000000..a24629b
--- /dev/null
+++ b/eslint/tests/lib/rules/no-promise-executor-return.js
@@ -0,0 +1,303 @@
+/**
+ * @fileoverview Tests for the no-promise-executor-return rule
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/no-promise-executor-return");
+const { RuleTester } = require("../../../lib/rule-tester");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Creates an error object.
+ * @param {number} [column] Reported column.
+ * @param {string} [type="ReturnStatement"] Reported node type.
+ * @returns {Object} The error object.
+ */
+function error(column, type = "ReturnStatement") {
+ const errorObject = {
+ messageId: "returnsValue",
+ type
+ };
+
+ if (column) {
+ errorObject.column = column;
+ }
+
+ return errorObject;
+}
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 }, env: { es6: true } });
+
+ruleTester.run("no-promise-executor-return", rule, {
+ valid: [
+
+ //------------------------------------------------------------------------------
+ // General
+ //------------------------------------------------------------------------------
+
+ // not a promise executor
+ "function foo(resolve, reject) { return 1; }",
+ "function Promise(resolve, reject) { return 1; }",
+ "(function (resolve, reject) { return 1; })",
+ "(function foo(resolve, reject) { return 1; })",
+ "(function Promise(resolve, reject) { return 1; })",
+ "var foo = function (resolve, reject) { return 1; }",
+ "var foo = function Promise(resolve, reject) { return 1; }",
+ "var Promise = function (resolve, reject) { return 1; }",
+ "(resolve, reject) => { return 1; }",
+ "(resolve, reject) => 1",
+ "var foo = (resolve, reject) => { return 1; }",
+ "var Promise = (resolve, reject) => { return 1; }",
+ "var foo = (resolve, reject) => 1",
+ "var Promise = (resolve, reject) => 1",
+ "var foo = { bar(resolve, reject) { return 1; } }",
+ "var foo = { Promise(resolve, reject) { return 1; } }",
+ "new foo(function (resolve, reject) { return 1; });",
+ "new foo(function bar(resolve, reject) { return 1; });",
+ "new foo(function Promise(resolve, reject) { return 1; });",
+ "new foo((resolve, reject) => { return 1; });",
+ "new foo((resolve, reject) => 1);",
+ "new promise(function foo(resolve, reject) { return 1; });",
+ "new Promise.foo(function foo(resolve, reject) { return 1; });",
+ "new foo.Promise(function foo(resolve, reject) { return 1; });",
+ "new Promise.Promise(function foo(resolve, reject) { return 1; });",
+ "new Promise()(function foo(resolve, reject) { return 1; });",
+
+ // not a promise executor - Promise() without new
+ "Promise(function (resolve, reject) { return 1; });",
+ "Promise((resolve, reject) => { return 1; });",
+ "Promise((resolve, reject) => 1);",
+
+ // not a promise executor - not the first argument
+ "new Promise(foo, function (resolve, reject) { return 1; });",
+ "new Promise(foo, (resolve, reject) => { return 1; });",
+ "new Promise(foo, (resolve, reject) => 1);",
+
+ // global Promise doesn't exist
+ "/* globals Promise:off */ new Promise(function (resolve, reject) { return 1; });",
+ {
+ code: "new Promise((resolve, reject) => { return 1; });",
+ globals: { Promise: "off" }
+ },
+ {
+ code: "new Promise((resolve, reject) => 1);",
+ env: { es6: false }
+ },
+
+ // global Promise is shadowed
+ "let Promise; new Promise(function (resolve, reject) { return 1; });",
+ "function f() { new Promise((resolve, reject) => { return 1; }); var Promise; }",
+ "function f(Promise) { new Promise((resolve, reject) => 1); }",
+ "if (x) { const Promise = foo(); new Promise(function (resolve, reject) { return 1; }); }",
+ "x = function Promise() { new Promise((resolve, reject) => { return 1; }); }",
+
+ // return without a value is allowed
+ "new Promise(function (resolve, reject) { return; });",
+ "new Promise(function (resolve, reject) { reject(new Error()); return; });",
+ "new Promise(function (resolve, reject) { if (foo) { return; } });",
+ "new Promise((resolve, reject) => { return; });",
+ "new Promise((resolve, reject) => { if (foo) { resolve(1); return; } reject(new Error()); });",
+
+ // throw is allowed
+ "new Promise(function (resolve, reject) { throw new Error(); });",
+ "new Promise((resolve, reject) => { throw new Error(); });",
+
+ // not returning from the promise executor
+ "new Promise(function (resolve, reject) { function foo() { return 1; } });",
+ "new Promise((resolve, reject) => { (function foo() { return 1; })(); });",
+ "new Promise(function (resolve, reject) { () => { return 1; } });",
+ "new Promise((resolve, reject) => { () => 1 });",
+ "function foo() { return new Promise(function (resolve, reject) { resolve(bar); }) };",
+ "foo => new Promise((resolve, reject) => { bar(foo, (err, data) => { if (err) { reject(err); return; } resolve(data); })});",
+
+ // promise executors do not have effect on other functions (tests function info tracking)
+ "new Promise(function (resolve, reject) {}); function foo() { return 1; }",
+ "new Promise((resolve, reject) => {}); (function () { return 1; });",
+ "new Promise(function (resolve, reject) {}); () => { return 1; };",
+ "new Promise((resolve, reject) => {}); () => 1;",
+
+ // does not report global return
+ {
+ code: "return 1;",
+ env: { node: true }
+ },
+ {
+ code: "return 1;",
+ parserOptions: { ecmaFeatures: { globalReturn: true } }
+ },
+ {
+ code: "return 1; function foo(){ return 1; } return 1;",
+ env: { node: true }
+ },
+ {
+ code: "function foo(){} return 1; var bar = function*(){ return 1; }; return 1; var baz = () => {}; return 1;",
+ env: { node: true }
+ },
+ {
+ code: "new Promise(function (resolve, reject) {}); return 1;",
+ env: { node: true }
+ }
+ ],
+
+ invalid: [
+
+ // full error tests
+ {
+ code: "new Promise(function (resolve, reject) { return 1; })",
+ errors: [{ message: "Return values from promise executor functions cannot be read.", type: "ReturnStatement", column: 42, endColumn: 51 }]
+ },
+ {
+ code: "new Promise((resolve, reject) => resolve(1))",
+ errors: [{ message: "Return values from promise executor functions cannot be read.", type: "CallExpression", column: 34, endColumn: 44 }]
+ },
+
+ // other basic tests
+ {
+ code: "new Promise(function foo(resolve, reject) { return 1; })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise((resolve, reject) => { return 1; })",
+ errors: [error()]
+ },
+
+ // any returned value
+ {
+ code: "new Promise(function (resolve, reject) { return undefined; })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise((resolve, reject) => { return null; })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise(function (resolve, reject) { return false; })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise((resolve, reject) => resolve)",
+ errors: [error(34, "Identifier")]
+ },
+ {
+ code: "new Promise((resolve, reject) => null)",
+ errors: [error(34, "Literal")]
+ },
+ {
+ code: "new Promise(function (resolve, reject) { return resolve(foo); })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise((resolve, reject) => { return reject(foo); })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise((resolve, reject) => x + y)",
+ errors: [error(34, "BinaryExpression")]
+ },
+ {
+ code: "new Promise((resolve, reject) => { return Promise.resolve(42); })",
+ errors: [error()]
+ },
+
+ // any return statement location
+ {
+ code: "new Promise(function (resolve, reject) { if (foo) { return 1; } })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise((resolve, reject) => { try { return 1; } catch(e) {} })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise(function (resolve, reject) { while (foo){ if (bar) break; else return 1; } })",
+ errors: [error()]
+ },
+
+ // absence of arguments has no effect
+ {
+ code: "new Promise(function () { return 1; })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise(() => { return 1; })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise(() => 1)",
+ errors: [error(19, "Literal")]
+ },
+
+ // various scope tracking tests
+ {
+ code: "function foo() {} new Promise(function () { return 1; });",
+ errors: [error(45)]
+ },
+ {
+ code: "function foo() { return; } new Promise(() => { return 1; });",
+ errors: [error(48)]
+ },
+ {
+ code: "function foo() { return 1; } new Promise(() => { return 2; });",
+ errors: [error(50)]
+ },
+ {
+ code: "function foo () { return new Promise(function () { return 1; }); }",
+ errors: [error(52)]
+ },
+ {
+ code: "function foo() { return new Promise(() => { bar(() => { return 1; }); return false; }); }",
+ errors: [error(71)]
+ },
+ {
+ code: "() => new Promise(() => { if (foo) { return 0; } else bar(() => { return 1; }); })",
+ errors: [error(38)]
+ },
+ {
+ code: "function foo () { return 1; return new Promise(function () { return 2; }); return 3;}",
+ errors: [error(62)]
+ },
+ {
+ code: "() => 1; new Promise(() => { return 1; })",
+ errors: [error(30)]
+ },
+ {
+ code: "new Promise(function () { return 1; }); function foo() { return 1; } ",
+ errors: [error(27)]
+ },
+ {
+ code: "() => new Promise(() => { return 1; });",
+ errors: [error(27)]
+ },
+ {
+ code: "() => new Promise(() => 1);",
+ errors: [error(25, "Literal")]
+ },
+ {
+ code: "() => new Promise(() => () => 1);",
+ errors: [error(25, "ArrowFunctionExpression")]
+ },
+
+ // edge cases for global Promise reference
+ {
+ code: "new Promise((Promise) => { return 1; })",
+ errors: [error()]
+ },
+ {
+ code: "new Promise(function Promise(resolve, reject) { return 1; })",
+ errors: [error()]
+ }
+ ]
+});
diff --git a/eslint/tests/lib/rules/no-prototype-builtins.js b/eslint/tests/lib/rules/no-prototype-builtins.js
index e4f0fa3..a65b54d 100644
--- a/eslint/tests/lib/rules/no-prototype-builtins.js
+++ b/eslint/tests/lib/rules/no-prototype-builtins.js
@@ -17,87 +17,156 @@ const rule = require("../../../lib/rules/no-prototype-builtins"),
//------------------------------------------------------------------------------
const ruleTester = new RuleTester();
-const valid = [
- { code: "Object.prototype.hasOwnProperty.call(foo, 'bar')" },
- { code: "Object.prototype.isPrototypeOf.call(foo, 'bar')" },
- { code: "Object.prototype.propertyIsEnumerable.call(foo, 'bar')" },
- { code: "Object.prototype.hasOwnProperty.apply(foo, ['bar'])" },
- { code: "Object.prototype.isPrototypeOf.apply(foo, ['bar'])" },
- { code: "Object.prototype.propertyIsEnumerable.apply(foo, ['bar'])" },
- { code: "hasOwnProperty(foo, 'bar')" },
- { code: "isPrototypeOf(foo, 'bar')" },
- { code: "propertyIsEnumerable(foo, 'bar')" },
- { code: "({}.hasOwnProperty.call(foo, 'bar'))" },
- { code: "({}.isPrototypeOf.call(foo, 'bar'))" },
- { code: "({}.propertyIsEnumerable.call(foo, 'bar'))" },
- { code: "({}.hasOwnProperty.apply(foo, ['bar']))" },
- { code: "({}.isPrototypeOf.apply(foo, ['bar']))" },
- { code: "({}.propertyIsEnumerable.apply(foo, ['bar']))" }
-];
-
-const invalid = [
- {
- code: "foo.hasOwnProperty('bar')",
- errors: [{
- line: 1,
- column: 5,
- endLine: 1,
- endColumn: 19,
- messageId: "prototypeBuildIn",
- data: { prop: "hasOwnProperty" },
- type: "CallExpression"
- }]
- },
- {
- code: "foo.isPrototypeOf('bar')",
- errors: [{
- line: 1,
- column: 5,
- endLine: 1,
- endColumn: 18,
- messageId: "prototypeBuildIn",
- data: { prop: "isPrototypeOf" },
- type: "CallExpression"
- }]
- },
- {
- code: "foo.propertyIsEnumerable('bar')",
- errors: [{
- line: 1,
- column: 5,
- endLine: 1,
- endColumn: 25,
- messageId: "prototypeBuildIn",
- data: { prop: "propertyIsEnumerable" }
- }]
- },
- {
- code: "foo.bar.hasOwnProperty('bar')",
- errors: [{
- line: 1,
- column: 9,
- endLine: 1,
- endColumn: 23,
- messageId: "prototypeBuildIn",
- data: { prop: "hasOwnProperty" },
- type: "CallExpression"
- }]
- },
- {
- code: "foo.bar.baz.isPrototypeOf('bar')",
- errors: [{
- line: 1,
- column: 13,
- endLine: 1,
- endColumn: 26,
- messageId: "prototypeBuildIn",
- data: { prop: "isPrototypeOf" },
- type: "CallExpression"
- }]
- }
-];
-
ruleTester.run("no-prototype-builtins", rule, {
- valid,
- invalid
+ valid: [
+ "Object.prototype.hasOwnProperty.call(foo, 'bar')",
+ "Object.prototype.isPrototypeOf.call(foo, 'bar')",
+ "Object.prototype.propertyIsEnumerable.call(foo, 'bar')",
+ "Object.prototype.hasOwnProperty.apply(foo, ['bar'])",
+ "Object.prototype.isPrototypeOf.apply(foo, ['bar'])",
+ "Object.prototype.propertyIsEnumerable.apply(foo, ['bar'])",
+ "foo.hasOwnProperty",
+ "foo.hasOwnProperty.bar()",
+ "foo(hasOwnProperty)",
+ "hasOwnProperty(foo, 'bar')",
+ "isPrototypeOf(foo, 'bar')",
+ "propertyIsEnumerable(foo, 'bar')",
+ "({}.hasOwnProperty.call(foo, 'bar'))",
+ "({}.isPrototypeOf.call(foo, 'bar'))",
+ "({}.propertyIsEnumerable.call(foo, 'bar'))",
+ "({}.hasOwnProperty.apply(foo, ['bar']))",
+ "({}.isPrototypeOf.apply(foo, ['bar']))",
+ "({}.propertyIsEnumerable.apply(foo, ['bar']))",
+ "foo[hasOwnProperty]('bar')",
+ "foo['HasOwnProperty']('bar')",
+ { code: "foo[`isPrototypeOff`]('bar')", parserOptions: { ecmaVersion: 2015 } },
+ { code: "foo?.['propertyIsEnumerabl']('bar')", parserOptions: { ecmaVersion: 2020 } },
+ "foo[1]('bar')",
+ "foo[null]('bar')",
+
+ // out of scope for this rule
+ "foo['hasOwn' + 'Property']('bar')",
+ { code: "foo[`hasOwnProperty${''}`]('bar')", parserOptions: { ecmaVersion: 2015 } }
+ ],
+
+ invalid: [
+ {
+ code: "foo.hasOwnProperty('bar')",
+ errors: [{
+ line: 1,
+ column: 5,
+ endLine: 1,
+ endColumn: 19,
+ messageId: "prototypeBuildIn",
+ data: { prop: "hasOwnProperty" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "foo.isPrototypeOf('bar')",
+ errors: [{
+ line: 1,
+ column: 5,
+ endLine: 1,
+ endColumn: 18,
+ messageId: "prototypeBuildIn",
+ data: { prop: "isPrototypeOf" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "foo.propertyIsEnumerable('bar')",
+ errors: [{
+ line: 1,
+ column: 5,
+ endLine: 1,
+ endColumn: 25,
+ messageId: "prototypeBuildIn",
+ data: { prop: "propertyIsEnumerable" }
+ }]
+ },
+ {
+ code: "foo.bar.hasOwnProperty('bar')",
+ errors: [{
+ line: 1,
+ column: 9,
+ endLine: 1,
+ endColumn: 23,
+ messageId: "prototypeBuildIn",
+ data: { prop: "hasOwnProperty" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "foo.bar.baz.isPrototypeOf('bar')",
+ errors: [{
+ line: 1,
+ column: 13,
+ endLine: 1,
+ endColumn: 26,
+ messageId: "prototypeBuildIn",
+ data: { prop: "isPrototypeOf" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "foo['hasOwnProperty']('bar')",
+ errors: [{
+ line: 1,
+ column: 5,
+ endLine: 1,
+ endColumn: 21,
+ messageId: "prototypeBuildIn",
+ data: { prop: "hasOwnProperty" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "foo[`isPrototypeOf`]('bar').baz",
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [{
+ line: 1,
+ column: 5,
+ endLine: 1,
+ endColumn: 20,
+ messageId: "prototypeBuildIn",
+ data: { prop: "isPrototypeOf" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: String.raw`foo.bar["propertyIsEnumerable"]('baz')`,
+ errors: [{
+ line: 1,
+ column: 9,
+ endLine: 1,
+ endColumn: 31,
+ messageId: "prototypeBuildIn",
+ data: { prop: "propertyIsEnumerable" },
+ type: "CallExpression"
+ }]
+ },
+
+ // Optional chaining
+ {
+ code: "foo?.hasOwnProperty('bar')",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }]
+ },
+ {
+ code: "(foo?.hasOwnProperty)('bar')",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }]
+ },
+ {
+ code: "foo?.['hasOwnProperty']('bar')",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }]
+ },
+ {
+ code: "(foo?.[`hasOwnProperty`])('bar')",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }]
+ }
+ ]
});
diff --git a/eslint/tests/lib/rules/no-restricted-syntax.js b/eslint/tests/lib/rules/no-restricted-syntax.js
index c8ba48f..cf8bc41 100644
--- a/eslint/tests/lib/rules/no-restricted-syntax.js
+++ b/eslint/tests/lib/rules/no-restricted-syntax.js
@@ -128,6 +128,35 @@ ruleTester.run("no-restricted-syntax", rule, {
code: "console.log(/a/i);",
options: ["Literal[regex.flags=/./]"],
errors: [{ messageId: "restrictedSyntax", data: { message: "Using 'Literal[regex.flags=/./]' is not allowed." }, type: "Literal" }]
+ },
+
+ // Optional chaining
+ {
+ code: "var foo = foo?.bar?.();",
+ options: ["ChainExpression"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "restrictedSyntax", data: { message: "Using 'ChainExpression' is not allowed." }, type: "ChainExpression" }]
+ },
+ {
+ code: "var foo = foo?.bar?.();",
+ options: ["[optional=true]"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [
+ { messageId: "restrictedSyntax", data: { message: "Using '[optional=true]' is not allowed." }, type: "CallExpression" },
+ { messageId: "restrictedSyntax", data: { message: "Using '[optional=true]' is not allowed." }, type: "MemberExpression" }
+ ]
}
+
+ /*
+ * TODO(mysticatea): fix https://github.com/estools/esquery/issues/110
+ * {
+ * code: "a?.b",
+ * options: [":nth-child(1)"],
+ * parserOptions: { ecmaVersion: 2020 },
+ * errors: [
+ * { messageId: "restrictedSyntax", data: { message: "Using ':nth-child(1)' is not allowed." }, type: "ExpressionStatement" }
+ * ]
+ * }
+ */
]
});
diff --git a/eslint/tests/lib/rules/no-script-url.js b/eslint/tests/lib/rules/no-script-url.js
index 6eca268..8dd2961 100644
--- a/eslint/tests/lib/rules/no-script-url.js
+++ b/eslint/tests/lib/rules/no-script-url.js
@@ -22,7 +22,19 @@ ruleTester.run("no-script-url", rule, {
valid: [
"var a = 'Hello World!';",
"var a = 10;",
- "var url = 'xjavascript:'"
+ "var url = 'xjavascript:'",
+ {
+ code: "var url = `xjavascript:`",
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "var url = `${foo}javascript:`",
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "var a = foo`javaScript:`;",
+ parserOptions: { ecmaVersion: 6 }
+ }
],
invalid: [
{
@@ -36,6 +48,20 @@ ruleTester.run("no-script-url", rule, {
errors: [
{ messageId: "unexpectedScriptURL", type: "Literal" }
]
+ },
+ {
+ code: "var a = `javascript:`;",
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ { messageId: "unexpectedScriptURL", type: "TemplateLiteral" }
+ ]
+ },
+ {
+ code: "var a = `JavaScript:`;",
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ { messageId: "unexpectedScriptURL", type: "TemplateLiteral" }
+ ]
}
]
});
diff --git a/eslint/tests/lib/rules/no-self-assign.js b/eslint/tests/lib/rules/no-self-assign.js
index 5149dac..5a9bb6f 100644
--- a/eslint/tests/lib/rules/no-self-assign.js
+++ b/eslint/tests/lib/rules/no-self-assign.js
@@ -135,6 +135,18 @@ ruleTester.run("no-self-assign", rule, {
options: [{ props: true }],
errors: [{ messageId: "selfAssignment", data: { name: "this.x" } }]
},
- { code: "a['/(?0)/'] = a[/(?0)/]", options: [{ props: true }], parserOptions: { ecmaVersion: 2018 }, errors: [{ messageId: "selfAssignment", data: { name: "a[/(?0)/]" } }] }
+ { code: "a['/(?0)/'] = a[/(?0)/]", options: [{ props: true }], parserOptions: { ecmaVersion: 2018 }, errors: [{ messageId: "selfAssignment", data: { name: "a[/(?0)/]" } }] },
+
+ // Optional chaining
+ {
+ code: "(a?.b).c = (a?.b).c",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "selfAssignment", data: { name: "(a?.b).c" } }]
+ },
+ {
+ code: "a.b = a?.b",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "selfAssignment", data: { name: "a?.b" } }]
+ }
]
});
diff --git a/eslint/tests/lib/rules/no-setter-return.js b/eslint/tests/lib/rules/no-setter-return.js
index b70b4e3..0c64e8b 100644
--- a/eslint/tests/lib/rules/no-setter-return.js
+++ b/eslint/tests/lib/rules/no-setter-return.js
@@ -39,7 +39,7 @@ function error(column, type = "ReturnStatement") {
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
ruleTester.run("no-setter-return", rule, {
valid: [
@@ -505,6 +505,18 @@ ruleTester.run("no-setter-return", rule, {
{
code: "Object.defineProperty(foo, 'bar', { set: function(Object) { return 1; } })",
errors: [error()]
+ },
+
+ // Optional chaining
+ {
+ code: "Object?.defineProperty(foo, 'bar', { set(val) { return 1; } })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [error()]
+ },
+ {
+ code: "(Object?.defineProperty)(foo, 'bar', { set(val) { return 1; } })",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [error()]
}
]
});
diff --git a/eslint/tests/lib/rules/no-this-before-super.js b/eslint/tests/lib/rules/no-this-before-super.js
index c41b8c9..7a947c0 100644
--- a/eslint/tests/lib/rules/no-this-before-super.js
+++ b/eslint/tests/lib/rules/no-this-before-super.js
@@ -39,6 +39,10 @@ ruleTester.run("no-this-before-super", rule, {
"class A extends B { constructor() { super(); this.c(); } }",
"class A extends B { constructor() { super(); super.c(); } }",
"class A extends B { constructor() { if (true) { super(); } else { super(); } this.c(); } }",
+ "class A extends B { constructor() { foo = super(); this.c(); } }",
+ "class A extends B { constructor() { foo += super().a; this.c(); } }",
+ "class A extends B { constructor() { foo |= super().a; this.c(); } }",
+ "class A extends B { constructor() { foo &= super().a; this.c(); } }",
// allows `this`/`super` in nested executable scopes, even if before `super()`.
"class A extends B { constructor() { class B extends C { constructor() { super(); this.d = 0; } } super(); } }",
@@ -161,6 +165,21 @@ ruleTester.run("no-this-before-super", rule, {
{
code: "class A extends B { constructor() { try { super(); } catch (err) { } this.a; } }",
errors: [{ messageId: "noBeforeSuper", data: { kind: "this" }, type: "ThisExpression" }]
+ },
+ {
+ code: "class A extends B { constructor() { foo &&= super().a; this.c(); } }",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noBeforeSuper", data: { kind: "this" }, type: "ThisExpression" }]
+ },
+ {
+ code: "class A extends B { constructor() { foo ||= super().a; this.c(); } }",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noBeforeSuper", data: { kind: "this" }, type: "ThisExpression" }]
+ },
+ {
+ code: "class A extends B { constructor() { foo ??= super().a; this.c(); } }",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{ messageId: "noBeforeSuper", data: { kind: "this" }, type: "ThisExpression" }]
}
]
});
diff --git a/eslint/tests/lib/rules/no-throw-literal.js b/eslint/tests/lib/rules/no-throw-literal.js
index 0500211..3855b58 100644
--- a/eslint/tests/lib/rules/no-throw-literal.js
+++ b/eslint/tests/lib/rules/no-throw-literal.js
@@ -30,7 +30,9 @@ ruleTester.run("no-throw-literal", rule, {
"throw new foo();", // NewExpression
"throw foo.bar;", // MemberExpression
"throw foo[bar];", // MemberExpression
- "throw foo = new Error();", // AssignmentExpression
+ "throw foo = new Error();", // AssignmentExpression with the `=` operator
+ { code: "throw foo.bar ||= 'literal'", parserOptions: { ecmaVersion: 2021 } }, // AssignmentExpression with a logical operator
+ { code: "throw foo[bar] ??= 'literal'", parserOptions: { ecmaVersion: 2021 } }, // AssignmentExpression with a logical operator
"throw 1, 2, new Error();", // SequenceExpression
"throw 'literal' && new Error();", // LogicalExpression (right)
"throw new Error() || 'literal';", // LogicalExpression (left)
@@ -38,7 +40,9 @@ ruleTester.run("no-throw-literal", rule, {
"throw foo ? 'literal' : new Error();", // ConditionalExpression (alternate)
{ code: "throw tag `${foo}`;", parserOptions: { ecmaVersion: 6 } }, // TaggedTemplateExpression
{ code: "function* foo() { var index = 0; throw yield index++; }", parserOptions: { ecmaVersion: 6 } }, // YieldExpression
- { code: "async function foo() { throw await bar; }", parserOptions: { ecmaVersion: 8 } } // AwaitExpression
+ { code: "async function foo() { throw await bar; }", parserOptions: { ecmaVersion: 8 } }, // AwaitExpression
+ { code: "throw obj?.foo", parserOptions: { ecmaVersion: 2020 } }, // ChainExpression
+ { code: "throw obj?.foo()", parserOptions: { ecmaVersion: 2020 } } // ChainExpression
],
invalid: [
{
@@ -102,7 +106,29 @@ ruleTester.run("no-throw-literal", rule, {
// AssignmentExpression
{
- code: "throw foo = 'error';",
+ code: "throw foo = 'error';", // RHS is a literal
+ errors: [{
+ messageId: "object",
+ type: "ThrowStatement"
+ }]
+ },
+ {
+ code: "throw foo += new Error();", // evaluates to a primitive value, or throws while evaluating
+ errors: [{
+ messageId: "object",
+ type: "ThrowStatement"
+ }]
+ },
+ {
+ code: "throw foo &= new Error();", // evaluates to a primitive value, or throws while evaluating
+ errors: [{
+ messageId: "object",
+ type: "ThrowStatement"
+ }]
+ },
+ {
+ code: "throw foo &&= 'literal'", // evaluates either to a falsy value of `foo` (which, then, cannot be an Error object), or to 'literal'
+ parserOptions: { ecmaVersion: 2021 },
errors: [{
messageId: "object",
type: "ThrowStatement"
@@ -126,6 +152,13 @@ ruleTester.run("no-throw-literal", rule, {
type: "ThrowStatement"
}]
},
+ {
+ code: "throw foo && 'literal'", // evaluates either to a falsy value of `foo` (which, then, cannot be an Error object), or to 'literal'
+ errors: [{
+ messageId: "object",
+ type: "ThrowStatement"
+ }]
+ },
// ConditionalExpression
{
diff --git a/eslint/tests/lib/rules/no-undef.js b/eslint/tests/lib/rules/no-undef.js
index 8c9c053..43697ec 100644
--- a/eslint/tests/lib/rules/no-undef.js
+++ b/eslint/tests/lib/rules/no-undef.js
@@ -59,6 +59,7 @@ ruleTester.run("no-undef", rule, {
{ code: "requestIdleCallback;", env: { browser: true } },
{ code: "customElements;", env: { browser: true } },
{ code: "PromiseRejectionEvent;", env: { browser: true } },
+ { code: "(foo, bar) => { foo ||= WeakRef; bar ??= FinalizationRegistry; }", env: { es2021: true } },
// Notifications of readonly are removed: https://github.com/eslint/eslint/issues/4504
"/*global b:false*/ function f() { b = 1; }",
diff --git a/eslint/tests/lib/rules/no-underscore-dangle.js b/eslint/tests/lib/rules/no-underscore-dangle.js
index 4baef3e..89f2083 100644
--- a/eslint/tests/lib/rules/no-underscore-dangle.js
+++ b/eslint/tests/lib/rules/no-underscore-dangle.js
@@ -26,6 +26,18 @@ ruleTester.run("no-underscore-dangle", rule, {
"console.log(__filename); console.log(__dirname);",
"var _ = require('underscore');",
"var a = b._;",
+ "function foo(_bar) {}",
+ "function foo(bar_) {}",
+ "(function _foo() {})",
+ { code: "function foo(_bar) {}", options: [{}] },
+ { code: "function foo( _bar = 0) {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = { onClick(_bar) { } }", parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = { onClick(_bar = 0) { } }", parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = (_bar) => {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = (_bar = 0) => {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "function foo( ..._bar) {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = (..._bar) => {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = { onClick(..._bar) { } }", parserOptions: { ecmaVersion: 6 } },
{ code: "export default function() {}", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
{ code: "var _foo = 1", options: [{ allow: ["_foo"] }] },
{ code: "var __proto__ = 1;", options: [{ allow: ["__proto__"] }] },
@@ -40,7 +52,24 @@ ruleTester.run("no-underscore-dangle", rule, {
{ code: "const o = { _onClick() { } }", options: [{ allow: ["_onClick"], enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 } },
{ code: "const o = { _foo: 'bar' }", parserOptions: { ecmaVersion: 6 } },
{ code: "const o = { foo_: 'bar' }", parserOptions: { ecmaVersion: 6 } },
- { code: "this.constructor._bar", options: [{ allowAfterThisConstructor: true }] }
+ { code: "this.constructor._bar", options: [{ allowAfterThisConstructor: true }] },
+ { code: "const foo = { onClick(bar) { } }", parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = (bar) => {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "function foo(_bar) {}", options: [{ allowFunctionParams: true }] },
+ { code: "function foo( _bar = 0) {}", options: [{ allowFunctionParams: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = { onClick(_bar) { } }", options: [{ allowFunctionParams: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = (_bar) => {}", options: [{ allowFunctionParams: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "function foo(bar) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = { onClick(bar) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = (bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+ { code: "function foo(_bar) {}", options: [{ allowFunctionParams: false, allow: ["_bar"] }] },
+ { code: "const foo = { onClick(_bar) { } }", options: [{ allowFunctionParams: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 6 } },
+ { code: "const foo = (_bar) => {}", options: [{ allowFunctionParams: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 6 } },
+ { code: "function foo([_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+ { code: "function foo([_bar] = []) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+ { 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 } }
],
invalid: [
{ code: "var _foo = 1", errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" }, type: "VariableDeclarator" }] },
@@ -57,6 +86,18 @@ ruleTester.run("no-underscore-dangle", rule, {
{ code: "const o = { _onClick() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_onClick" }, type: "Property" }] },
{ code: "const o = { onClick_() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "onClick_" }, type: "Property" }] },
{ code: "this.constructor._bar", errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "MemberExpression" }] },
- { code: "foo.constructor._bar", options: [{ allowAfterThisConstructor: true }], errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "MemberExpression" }] }
+ { code: "function foo(_bar) {}", options: [{ allowFunctionParams: false }], errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "Identifier" }] },
+ { code: "(function foo(_bar) {})", options: [{ allowFunctionParams: false }], errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "Identifier" }] },
+ { code: "function foo(bar, _foo) {}", options: [{ allowFunctionParams: false }], errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" }, type: "Identifier" }] },
+ { code: "const foo = { onClick(_bar) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "Identifier" }] },
+ { code: "const foo = (_bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "Identifier" }] },
+ { code: "function foo(_bar = 0) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "AssignmentPattern" }] },
+ { code: "const foo = { onClick(_bar = 0) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "AssignmentPattern" }] },
+ { code: "const foo = (_bar = 0) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "AssignmentPattern" }] },
+ { code: "function foo(..._bar) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] },
+ { 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" }] }
+
+
]
});
diff --git a/eslint/tests/lib/rules/no-unexpected-multiline.js b/eslint/tests/lib/rules/no-unexpected-multiline.js
index 29d05a9..83c7bf6 100644
--- a/eslint/tests/lib/rules/no-unexpected-multiline.js
+++ b/eslint/tests/lib/rules/no-unexpected-multiline.js
@@ -122,6 +122,24 @@ ruleTester.run("no-unexpected-multiline", rule, {
>\`multiline\`;
`,
parser: require.resolve("../../fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-3")
+ },
+
+ // Optional chaining
+ {
+ code: "var a = b\n ?.(x || y).doSomething()",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "var a = b\n ?.[a, b, c].forEach(doSomething)",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "var a = b?.\n (x || y).doSomething()",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "var a = b?.\n [a, b, c].forEach(doSomething)",
+ parserOptions: { ecmaVersion: 2020 }
}
],
invalid: [
diff --git a/eslint/tests/lib/rules/no-unneeded-ternary.js b/eslint/tests/lib/rules/no-unneeded-ternary.js
index 82479a9..7ad11d2 100644
--- a/eslint/tests/lib/rules/no-unneeded-ternary.js
+++ b/eslint/tests/lib/rules/no-unneeded-ternary.js
@@ -54,7 +54,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 19
+ column: 9,
+ endLine: 1,
+ endColumn: 31
}]
},
{
@@ -64,7 +66,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 18
+ column: 9,
+ endLine: 1,
+ endColumn: 30
}]
},
{
@@ -74,7 +78,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 25
}]
},
{
@@ -84,7 +90,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 19
+ column: 9,
+ endLine: 1,
+ endColumn: 31
}]
},
{
@@ -94,7 +102,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 18
+ column: 9,
+ endLine: 1,
+ endColumn: 30
}]
},
{
@@ -104,7 +114,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 17
+ column: 9,
+ endLine: 1,
+ endColumn: 29
}]
},
{
@@ -114,7 +126,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 18
+ column: 9,
+ endLine: 1,
+ endColumn: 30
}]
},
{
@@ -124,7 +138,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 21
+ column: 9,
+ endLine: 1,
+ endColumn: 33
}]
},
{
@@ -134,7 +150,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 28
+ column: 9,
+ endLine: 1,
+ endColumn: 40
}]
},
{
@@ -144,7 +162,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 15
+ column: 9,
+ endLine: 1,
+ endColumn: 28
}]
},
{
@@ -154,7 +174,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 17
+ column: 9,
+ endLine: 1,
+ endColumn: 30
}]
},
{
@@ -164,7 +186,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 28
+ column: 9,
+ endLine: 1,
+ endColumn: 40
}]
},
{
@@ -174,7 +198,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 16
+ column: 9,
+ endLine: 1,
+ endColumn: 28
}]
},
{
@@ -193,7 +219,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 4,
- column: 38
+ column: 30,
+ endLine: 4,
+ endColumn: 78
}]
},
{
@@ -204,7 +232,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 7
+ column: 1,
+ endLine: 1,
+ endColumn: 30
}]
},
{
@@ -216,7 +246,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 24
+ column: 18,
+ endLine: 1,
+ endColumn: 39
}]
},
{
@@ -227,7 +259,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 15
+ column: 9,
+ endLine: 1,
+ endColumn: 25
}]
},
{
@@ -238,7 +272,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 24
+ column: 9,
+ endLine: 1,
+ endColumn: 66
}]
},
{
@@ -250,7 +286,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 23
}]
},
{
@@ -262,7 +300,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 22
}]
},
{
@@ -274,7 +314,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 25
}]
},
{
@@ -286,7 +328,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 24
}]
},
{
@@ -298,7 +342,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 27
}]
},
{
@@ -310,7 +356,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 18
}]
},
{
@@ -322,7 +370,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 23
}]
},
{
@@ -333,7 +383,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 7
+ column: 3,
+ endLine: 1,
+ endColumn: 12
}]
},
{
@@ -344,7 +396,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 5
+ column: 1,
+ endLine: 1,
+ endColumn: 10
}]
},
{
@@ -355,7 +409,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 15
+ column: 9,
+ endLine: 1,
+ endColumn: 24
}]
},
{
@@ -367,7 +423,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 15
+ column: 9,
+ endLine: 1,
+ endColumn: 27
}]
}
]
diff --git a/eslint/tests/lib/rules/no-unreachable-loop.js b/eslint/tests/lib/rules/no-unreachable-loop.js
new file mode 100644
index 0000000..10eaed4
--- /dev/null
+++ b/eslint/tests/lib/rules/no-unreachable-loop.js
@@ -0,0 +1,431 @@
+/**
+ * @fileoverview Tests for the no-unreachable-loop rule
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/no-unreachable-loop");
+const { RuleTester } = require("../../../lib/rule-tester");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+
+const loopTemplates = {
+ WhileStatement: [
+ "while (a) ",
+ "while (a && b) "
+ ],
+ DoWhileStatement: [
+ "do while (a)",
+ "do while (a && b)"
+ ],
+ ForStatement: [
+ "for (a; b; c) ",
+ "for (var i = 0; i < a.length; i++) ",
+ "for (; b; c) ",
+ "for (; b < foo; c++) ",
+ "for (a; ; c) ",
+ "for (a = 0; ; c++) ",
+ "for (a; b;) ",
+ "for (a = 0; b < foo; ) ",
+ "for (; ; c) ",
+ "for (; ; c++) ",
+ "for (; b;) ",
+ "for (; b < foo; ) ",
+ "for (a; ;) ",
+ "for (a = 0; ;) ",
+ "for (;;) "
+ ],
+ ForInStatement: [
+ "for (a in b) ",
+ "for (a in f(b)) ",
+ "for (var a in b) ",
+ "for (let a in f(b)) "
+ ],
+ ForOfStatement: [
+ "for (a of b) ",
+ "for (a of f(b)) ",
+ "for ({ a, b } of c) ",
+ "for (var a of f(b)) ",
+ "async function foo() { for await (const a of b) }"
+ ]
+};
+
+const validLoopBodies = [
+ ";",
+ "{}",
+ "{ bar(); }",
+ "continue;",
+ "{ continue; }",
+ "{ if (foo) break; }",
+ "{ if (foo) { return; } bar(); }",
+ "{ if (foo) { bar(); } else { break; } }",
+ "{ if (foo) { continue; } return; }",
+ "{ switch (foo) { case 1: return; } }",
+ "{ switch (foo) { case 1: break; default: return; } }",
+ "{ switch (foo) { case 1: continue; default: return; } throw err; }",
+ "{ try { return bar(); } catch (e) {} }",
+
+ // unreachable break
+ "{ continue; break; }",
+
+ // functions in loops
+ "() => a;",
+ "{ () => a }",
+ "(() => a)();",
+ "{ (() => a)() }",
+
+ // loops in loops
+ "while (a);",
+ "do ; while (a)",
+ "for (a; b; c);",
+ "for (; b;);",
+ "for (; ; c) if (foo) break;",
+ "for (;;) if (foo) break;",
+ "while (true) if (foo) break;",
+ "while (foo) if (bar) return;",
+ "for (a in b);",
+ "for (a of b);"
+];
+
+const invalidLoopBodies = [
+ "break;",
+ "{ break; }",
+ "return;",
+ "{ return; }",
+ "throw err;",
+ "{ throw err; }",
+ "{ foo(); break; }",
+ "{ break; foo(); }",
+ "if (foo) break; else return;",
+ "{ if (foo) { return; } else { break; } bar(); }",
+ "{ if (foo) { return; } throw err; }",
+ "{ switch (foo) { default: throw err; } }",
+ "{ switch (foo) { case 1: throw err; default: return; } }",
+ "{ switch (foo) { case 1: something(); default: return; } }",
+ "{ try { return bar(); } catch (e) { break; } }",
+
+ // unreachable continue
+ "{ break; continue; }",
+
+ // functions in loops
+ "{ () => a; break; }",
+ "{ (() => a)(); break; }",
+
+ // loops in loops
+ "{ while (a); break; }",
+ "{ do ; while (a) break; }",
+ "{ for (a; b; c); break; }",
+ "{ for (; b;); break; }",
+ "{ for (; ; c) if (foo) break; break; }",
+ "{ for(;;) if (foo) break; break; }",
+ "{ for (a in b); break; }",
+ "{ for (a of b); break; }",
+
+ /**
+ * Additional cases where code path analysis detects unreachable code: after loops that don't have a test condition or have a
+ * constant truthy test condition, and at the same time don't have any exit statements in the body. These are special cases
+ * where this rule reports error not because the outer loop's body exits in all paths, but because it has an infinite loop
+ * inside, thus it (the outer loop) cannot have more than one iteration.
+ */
+ "for (;;);",
+ "{ for (var i = 0; ; i< 10) { foo(); } }",
+ "while (true);"
+];
+
+/**
+ * Creates source code from the given loop template and loop body.
+ * @param {string} template Loop template.
+ * @param {string} body Loop body.
+ * @returns {string} Source code.
+ */
+function getSourceCode(template, body) {
+ const loop = template.replace("", body);
+
+ return body.includes("return") && !template.includes("function") ? `function someFunc() { ${loop} }` : loop;
+}
+
+/**
+ * Generates basic valid tests from `loopTemplates` and `validLoopBodies`
+ * @returns {IterableIterator} The list of source code strings.
+ */
+function *getBasicValidTests() {
+ for (const templates of Object.values(loopTemplates)) {
+ for (const template of templates) {
+ yield* validLoopBodies.map(body => getSourceCode(template, body));
+ }
+ }
+}
+
+/**
+ * Generates basic invalid tests from `loopTemplates` and `invalidLoopBodies`
+ * @returns {IterableIterator |