SRCDIR=src
UPSTREAM=eslint
-UPSTREAMTAG=v7.2.0
+UPSTREAMTAG=v7.12.1
BUILDSRC=${UPSTREAM}-${UPSTREAMTAG}
all: ${DEB}
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"
}
},
{
--- /dev/null
+# 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
* **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:**
* **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:**
+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)
* [`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)
* [`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)
* [`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)
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++;
}
-[![NPM version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint)
+[![npm version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint)
[![Downloads](https://img.shields.io/npm/dm/eslint.svg)](https://www.npmjs.com/package/eslint)
[![Build Status](https://github.com/eslint/eslint/workflows/CI/badge.svg)](https://github.com/eslint/eslint/actions)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_shield)
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)
## <a name="installation-and-usage"></a>Installation and Usage
### 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?
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.
+## <a name="security-policy"></a>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).
+
## <a name="semantic-versioning-policy"></a>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.
## <a name="license"></a>License
<img src="https://github.com/kaicataldo.png?s=75" width="75" height="75"><br />
Kai Cataldo
</a>
+</td><td align="center" valign="top" width="11%">
+<a href="https://github.com/mdjermanovic">
+<img src="https://github.com/mdjermanovic.png?s=75" width="75" height="75"><br />
+Milos Djermanovic
+</a>
</td></tr></tbody></table>
<img src="https://github.com/aladdin-add.png?s=75" width="75" height="75"><br />
薛定谔的猫
</a>
-</td><td align="center" valign="top" width="11%">
-<a href="https://github.com/mdjermanovic">
-<img src="https://github.com/mdjermanovic.png?s=75" width="75" height="75"><br />
-Milos Djermanovic
-</a>
</td></tr></tbody></table>
Pig Fang
</a>
</td><td align="center" valign="top" width="11%">
+<a href="https://github.com/anikethsaha">
+<img src="https://github.com/anikethsaha.png?s=75" width="75" height="75"><br />
+Anix
+</a>
+</td><td align="center" valign="top" width="11%">
<a href="https://github.com/yeonjuan">
<img src="https://github.com/yeonjuan.png?s=75" width="75" height="75"><br />
YeonJuan
<!-- NOTE: This section is autogenerated. Do not manually edit.-->
<!--sponsorsstart-->
-<h3>Gold Sponsors</h3>
-<p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/e780cd4/logo.png" alt="Shopify" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
+<h3>Platinum Sponsors</h3>
+<p><a href="https://automattic.com"><img src="https://images.opencollective.com/photomatt/ff91f0b/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
+<p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/e780cd4/logo.png" alt="Shopify" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://aka.ms/microsoftfossfund"><img src="https://avatars1.githubusercontent.com/u/67931232?u=7fddc652a464d7151b97e8f108392af7d54fa3e8&v=4" alt="Microsoft FOSS Fund Sponsorships" height="96"></a></p><h3>Silver Sponsors</h3>
<p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/c8a3b25/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://bruce.agency"><img src="https://images.opencollective.com/brucemade/0c70c59/logo.png" alt="Bruce" height="32"></a> <a href="https://edubirdie.com/"><img src="https://images.opencollective.com/edubirdie2/b1d51ab/logo.png" alt="EduBirdie" height="32"></a> <a href="https://www.casinotop.com/"><img src="https://images.opencollective.com/casinotop-com/10fd95b/logo.png" alt="CasinoTop.com" height="32"></a> <a href="https://www.casinotopp.net/"><img src="https://images.opencollective.com/casino-topp/1dd399a/logo.png" alt="Casino Topp" height="32"></a> <a href="https://writersperhour.com/write-my-essay"><img src="https://images.opencollective.com/writersperhour/5787d4b/logo.png" alt="Writers Per Hour" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://cooltechzone.com/netflix-vpn"><img src="https://images.opencollective.com/vpn-netflix/4850160/logo.png" alt="vpn netflix" height="32"></a> <a href="https://www.kasinot.fi"><img src="https://images.opencollective.com/kasinot-fi/e09aa2e/logo.png" alt="Kasinot.fi" height="32"></a> <a href="https://www.pelisivut.com"><img src="https://images.opencollective.com/pelisivut/04f08f2/logo.png" alt="Pelisivut" height="32"></a> <a href="https://www.nettikasinot.org"><img src="https://images.opencollective.com/nettikasinot-org/bbd887f/logo.png" alt="Nettikasinot.org" height="32"></a> <a href="https://www.bonus.com.de/freispiele"><img src="https://images.opencollective.com/bonusfinder-deutschland/646169e/logo.png" alt="BonusFinder Deutschland" height="32"></a> <a href="https://www.bugsnag.com/platforms?utm_source=Open Collective&utm_medium=Website&utm_content=open-source&utm_campaign=2019-community&utm_term="><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a></p>
+<p><a href="https://writersperhour.com"><img src="https://images.opencollective.com/writersperhour/5787d4b/logo.png" alt="Writers Per Hour" height="32"></a> <a href="https://www.betacalendars.com/printable-calendar"><img src="https://images.opencollective.com/betacalendars/9334b33/logo.png" alt="2021 calendar" height="32"></a> <a href="https://buy.fineproxy.org/eng/"><img src="https://images.opencollective.com/buy-fineproxy-org/2002c40/logo.png" alt="Buy.Fineproxy.Org" height="32"></a> <a href="https://www.veikkaajat.com"><img src="https://images.opencollective.com/veikkaajat/b92b427/logo.png" alt="Veikkaajat.com" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="null"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a></p>
<!--sponsorsend-->
## <a name="technology-sponsors"></a>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)
+/*
+ * 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
+++ /dev/null
-/**
- * @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<string,boolean>} current The newer object.
- * @param {Record<string,boolean>} prev The older object.
- * @returns {Record<string,boolean>} 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<string, import("../lib/shared/types").Environment>} */
-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
- }
-}));
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.
Go to <https://nodejs.org/> 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
## 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:
* [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)
* `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"]`).
[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
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
};
```
-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/
## 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/
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"
}
}
* `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)
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:
{% 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:
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.
+++ /dev/null
-# 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.
* 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.
* `"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"
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:
## 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)
# 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.
--- /dev/null
+# 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.
* `"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
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)
### 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:
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(){
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
* `"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
foo > bar ? value1 : value2;
foo > bar ? (baz > qux ? value1 : value2) : value3;
+
+foo > bar ? (
+ baz > qux ? value1 : value2
+) : value3;
```
## When Not To Use It
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) {
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) {
function foo() {
foo = bar;
}
+
+var a = function hello() {
+ hello = 123;
+};
```
Examples of **incorrect** code for this rule, unlike the corresponding rule in JSHint:
</div>
)
```
+
+## 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
+```
const x = 1230000000000000000000000.0
const x = .1230000000000000000000000
const x = 0X20000000000001
+const x = 0X2_000000000_0001;
```
Examples of **correct** code for this rule:
const x = 12300000000000000000000000
const x = 0x1FFFFFFFFFFFFF
const x = 9007199254740991
+const x = 9007_1992547409_91
```
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.
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
### 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;
```
-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
--- /dev/null
+# 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)
Examples of **incorrect** code for this rule:
```js
+/*eslint no-return-await: "error"*/
+
async function foo() {
return await bar();
}
Examples of **correct** code for this rule:
```js
+/*eslint no-return-await: "error"*/
+
async function foo() {
return bar();
}
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();
}
```
-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:
/*eslint no-script-url: "error"*/
location.href = "javascript:void(0)";
+
+location.href = `javascript:void(0)`;
```
## Compatibility
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
};
```
+### 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.
Examples of **incorrect** code for this rule:
```js
+/*eslint no-unmodified-loop-condition: "error"*/
+
+var node = something;
+
while (node) {
doSomething(node);
}
Examples of **correct** code for this rule:
```js
+/*eslint no-unmodified-loop-condition: "error"*/
+
while (node) {
doSomething(node);
node = node.parent;
--- /dev/null
+# 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)
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 {
+ m, n} = obj;
let {
- j, k} = obj;
-let {l = function() {
+ 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:
/*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
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
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:
## 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:
- `"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.
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]
- 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
Examples of **incorrect** code for this rule:
```js
+/*eslint prefer-object-spread: "error"*/
Object.assign({}, foo)
Examples of **correct** code for this rule:
```js
+/*eslint prefer-object-spread: "error"*/
Object.assign(...foo);
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)
* `all` = import all members provided by exported bindings.
* `multiple` = import multiple members.
* `single` = import single member.
+* `allowSeparatedGroups` (default: `false`)
Default option settings are:
"ignoreCase": false,
"ignoreDeclarationSort": false,
"ignoreMemberSort": false,
- "memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
+ "memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
+ "allowSeparatedGroups": false
}]
}
```
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.
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
```js
/*eslint space-unary-ops: "error"*/
-// Word unary operator "delete" is followed by a whitespace.
-delete foo.bar;
+// Word unary operator "typeof" is followed by a whitespace.
+typeof !foo;
+
+// Word unary operator "void" is followed by a whitespace.
+void {foo:0};
// Word unary operator "new" is followed by a whitespace.
-new Foo;
+new [foo][0];
-// Word unary operator "void" is followed by a whitespace.
-void 0;
+// Word unary operator "delete" is followed by a whitespace.
+delete (foo.bar);
// Unary operator "++" is not followed by whitespace.
++foo;
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"
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"
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.
{ "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
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.
}
```
-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
{
* `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.
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
```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 '!<relative/path/to/filename>'") 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).
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.
---
-## <a name="eslint-recommended-changes"/> `eslint:recommended` changes
+## <a name="eslint-recommended-changes"></a> `eslint:recommended` changes
Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config:
}
```
-## <a name="indent-rewrite"/> The `indent` rule is more strict
+## <a name="indent-rewrite"></a> 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.
}
```
-## <a name="config-validation"/> Unrecognized properties in config files now cause a fatal error
+## <a name="config-validation"></a> 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.
-## <a name="eslintignore-patterns"/> .eslintignore patterns are now resolved from the location of the file
+## <a name="eslintignore-patterns"></a> .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.
-## <a name="padded-blocks-defaults"/> The `padded-blocks` rule is more strict by default
+## <a name="padded-blocks-defaults"></a> 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.
-## <a name="space-before-function-paren-defaults"/> The `space-before-function-paren` rule is more strict by default
+## <a name="space-before-function-paren-defaults"></a> 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.
}
```
-## <a name="no-multi-spaces-eol-comments"/> The `no-multi-spaces` rule is more strict by default
+## <a name="no-multi-spaces-eol-comments"></a> 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.
}
```
-## <a name="scoped-plugin-resolution"/> References to scoped plugins in config files are now required to include the scope
+## <a name="scoped-plugin-resolution"></a> 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:
---
-## <a name="rule-tester-validation"/> `RuleTester` now validates properties of test cases
+## <a name="rule-tester-validation"></a> `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.
-## <a name="comment-attachment"/> AST Nodes no longer have comment properties
+## <a name="comment-attachment"></a> 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.
* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option
* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option
-## <a name="event-comments"/> `LineComment` and `BlockComment` events will no longer be emitted during AST traversal
+## <a name="event-comments"></a> `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:
sourceCode.getAllComments().filter(comment => comment.type === "Block");
```
-## <a name="shebangs"/> Shebangs are now returned from comment APIs
+## <a name="shebangs"></a> 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.
---
-## <a name="global-property"/> The `global` property in the `linter.verify()` API is no longer supported
+## <a name="global-property"></a> 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.
-## <a name="report-locations"/> More report messages now have full location ranges
+## <a name="report-locations"></a> 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.
**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.
-## <a name="exposed-es2015-classes"/> Some exposed APIs are now ES2015 classes
+## <a name="exposed-es2015-classes"></a> 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).
- [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.
"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({
+++ /dev/null
-/**
- * @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 <https://github.com/mysticatea>
- */
-"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<ConfigArrayFactory["create"]>} ConfigArray */
-
-/**
- * @typedef {Object} CascadingConfigArrayFactoryOptions
- * @property {Map<string,Plugin>} [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<string, ConfigArray>} configCache The cache from directory paths to config arrays.
- * @property {string} cwd The base directory to start lookup.
- * @property {WeakMap<ConfigArray, ConfigArray>} 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<CascadingConfigArrayFactory, CascadingConfigArrayFactoryInternalSlots>} */
-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 };
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");
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,
+++ /dev/null
-/**
- * @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 <https://github.com/mysticatea>
- */
-"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<string,Plugin>} [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<string,Plugin>} 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<ConfigArrayFactory, ConfigArrayFactoryInternalSlots>} */
-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<ConfigArrayElement>} 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<ConfigArrayElement>} 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<ConfigArrayElement>} 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<ConfigArrayElement>} 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<ConfigArrayElement>} 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<ConfigArrayElement>} 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<ConfigArrayElement>} 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<ConfigArrayElement>} 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<ConfigArrayElement>} 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<string,DependentPlugin>} 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<string,DependentPlugin>} plugins The plugin definitions.
- * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
- * @returns {IterableIterator<ConfigArrayElement>} 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 };
+++ /dev/null
-/**
- * @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 <https://github.com/mysticatea>
- */
-"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<OverrideTester>|null} criteria The tester for the `files` and `excludedFiles` of this config element.
- * @property {Record<string, boolean>|undefined} env The environment settings.
- * @property {Record<string, GlobalConf>|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<string, DependentPlugin>|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<string, RuleConf>|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<string, ExtractedConfig>} cache The cache to extract configs.
- * @property {ReadonlyMap<string, Environment>|null} envMap The map from environment ID to environment definition.
- * @property {ReadonlyMap<string, Processor>|null} processorMap The map from processor ID to environment definition.
- * @property {ReadonlyMap<string, Rule>|null} ruleMap The map from rule ID to rule definition.
- */
-
-/** @type {WeakMap<ConfigArray, ConfigArrayInternalSlots>} */
-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<string, DependentPlugin>} target The destination to merge
- * @param {Record<string, DependentPlugin>|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<string, Array>} target The destination to merge
- * @param {Record<string, RuleConf>|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<string,T>} defs The definitions to collect.
- * @param {Map<string, U>} 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<any, any>} 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<ConfigArrayElement>}
- */
-class ConfigArray extends Array {
-
- /**
- * Get the plugin environments.
- * The returned map cannot be mutated.
- * @type {ReadonlyMap<string, Environment>} The plugin environments.
- */
- get pluginEnvironments() {
- return ensurePluginMemberMaps(this).envMap;
- }
-
- /**
- * Get the plugin processors.
- * The returned map cannot be mutated.
- * @type {ReadonlyMap<string, Processor>} The plugin processors.
- */
- get pluginProcessors() {
- return ensurePluginMemberMaps(this).processorMap;
- }
-
- /**
- * Get the plugin rules.
- * The returned map cannot be mutated.
- * @returns {ReadonlyMap<string, Rule>} 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;
+++ /dev/null
-/**
- * @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 <https://github.com/mysticatea>
- */
-"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<import("../../shared/types").Parser>} DependentParser */
-/** @typedef {ConfigDependency<import("../../shared/types").Plugin>} DependentPlugin */
-
-module.exports = { ConfigDependency };
+++ /dev/null
-/**
- * @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 <https://github.com/mysticatea>
- */
-"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<string, boolean>}
- */
- this.env = {};
-
- /**
- * Global variables.
- * @type {Record<string, GlobalConf>}
- */
- 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<string, DependentPlugin>}
- */
- 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<string, [SeverityConf, ...any[]]>}
- */
- 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 };
+++ /dev/null
-/**
- * @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 <https://github.com/mysticatea>
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("assert");
-const path = require("path");
-const ignore = require("ignore");
-const debug = require("debug")("eslint:ignore-pattern");
-
-/** @typedef {ReturnType<import("ignore").default>} 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 };
+++ /dev/null
-/**
- * @fileoverview `ConfigArray` class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"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
-};
+++ /dev/null
-/**
- * @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 <https://github.com/mysticatea>
- */
-"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<Minimatch>[] | null} includes The positive matchers.
- * @property {InstanceType<Minimatch>[] | 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<Minimatch>[] | 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 };
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");
//------------------------------------------------------------------------------
*/
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,
messages.forEach(message => {
output += [
- `<error line="${xmlEscape(message.line)}"`,
- `column="${xmlEscape(message.column)}"`,
+ `<error line="${xmlEscape(message.line || 0)}"`,
+ `column="${xmlEscape(message.column || 0)}"`,
`severity="${xmlEscape(getMessageType(message))}"`,
`message="${xmlEscape(message.message)}${message.ruleId ? ` (${message.ruleId})` : ""}"`,
`source="${message.ruleId ? xmlEscape(`eslint.rules.${message.ruleId}`) : ""}" />`
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");
//------------------------------------------------------------------------------
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");
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"),
*
* 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
*/
/**
* 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) {
};
config.parserOptions.ecmaVersion = espree.latestEcmaVersion;
- config.env.es2020 = true;
+ config.env.es2021 = true;
// set the module type
if (answers.moduleType === "esm") {
}
}
if (answers.typescript && config.extends.includes("eslint:recommended")) {
- config.extends.push("plugin:@typescript-eslint/eslint-recommended");
config.extends.push("plugin:@typescript-eslint/recommended");
}
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.
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 }) => {
});
}
-/* 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 <space> to select, <a> to toggle all, <i> 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)
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 => {
}
// 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);
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.
case "LogicalExpression":
return isHandledLogicalOperator(parent.operator);
+ case "AssignmentExpression":
+ return isLogicalAssignmentOperator(parent.operator);
+
default:
return false;
}
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 &&
}
break;
+ case "AssignmentExpression":
+ if (
+ parent.right === node &&
+ isLogicalAssignmentOperator(parent.operator)
+ ) {
+ state.makeLogicalRight();
+ }
+ break;
+
case "ConditionalExpression":
case "IfStatement":
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(
}
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);
let dontForward = false;
switch (node.type) {
+ case "ChainExpression":
+ state.popChainContext();
+ break;
+
case "IfStatement":
case "ConditionalExpression":
state.popChoiceContext();
}
break;
+ case "AssignmentExpression":
+ if (isLogicalAssignmentOperator(node.operator)) {
+ state.popChoiceContext();
+ }
+ break;
+
case "SwitchStatement":
state.popSwitchContext();
break;
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;
}
/* istanbul ignore if */
if (debug.enabled) {
this.internal.nodes = [];
- this.internal.exitNodes = [];
}
}
this.tryContext = null;
this.loopContext = null;
this.breakContext = null;
+ this.chainContext = null;
this.currentSegments = [];
this.initialSegment = this.forkContext.head[0];
//--------------------------------------------------------------------------
/**
- * 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
* 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
);
}
+ //--------------------------------------------------------------------------
+ // 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
//--------------------------------------------------------------------------
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
//------------------------------------------------------------------------------
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"));
}
}
text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\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 += "????";
}
//------------------------------------------------------------------------------
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");
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"),
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);
return [];
}
- return descriptor.suggest.map(suggestInfo => {
- const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId];
-
- return {
- ...suggestInfo,
- desc: interpolate(computedDesc, suggestInfo.data),
- fix: normalizeFixes(suggestInfo, sourceCode)
- };
- });
+ return descriptor.suggest
+ .map(suggestInfo => {
+ const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId];
+
+ return {
+ ...suggestInfo,
+ desc: interpolate(computedDesc, suggestInfo.data),
+ fix: normalizeFixes(suggestInfo, sourceCode)
+ };
+ })
+
+ // Remove suggestions that didn't provide a fix
+ .filter(({ fix }) => fix);
}
/**
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;
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 {
);
}
+ // 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);
}
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.
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
);
}
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
-
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
}
/**
- * 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);
}
/**
*/
case "LogicalExpression":
case "ConditionalExpression":
+ case "ChainExpression":
currentNode = parent;
break;
],
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}}."
}
},
}
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) }
});
}
}
node,
messageId,
data: {
- name: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
+ name: astUtils.getFunctionNameWithKind(funcInfo.node),
+ arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
}
});
}
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.
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.
context.report({
node,
- loc: arrowBody.loc.start,
+ loc: arrowBody.loc,
messageId,
fix(fixer) {
const fixes = [];
* 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, ")")
+ );
+ }
}
/*
if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
context.report({
node,
- loc: arrowBody.loc.start,
+ loc: arrowBody.loc,
messageId: "expectedBlock",
fix(fixer) {
const fixes = [];
}
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;
+ }
};
}
};
//------------------------------------------------------------------------------
/**
- * 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";
}
//------------------------------------------------------------------------------
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}`);
+ function findOpeningParenOfParams(node) {
+ const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]);
+
+ if (
+ tokenBeforeParams &&
+ astUtils.isOpeningParenToken(tokenBeforeParams) &&
+ node.range[0] <= tokenBeforeParams.range[0]
+ ) {
+ return tokenBeforeParams;
}
- /**
- * 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 null;
+ }
- return closingParenToken && sourceCode.commentsExistBetween(firstTokenOfParam, closingParenToken);
- }
- return false;
+ /**
+ * 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);
+ }
- }
+ /**
+ * 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));
+ }
- if (hasCommentsInParens()) {
- return;
- }
+ /**
+ * 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: <T>(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;
- // "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;
- }
+ return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen;
+ }
- if (
- requireForBlockBody &&
- node.body.type === "BlockStatement"
- ) {
- if (!astUtils.isOpeningParenToken(firstTokenOfParam)) {
- context.report({
- node,
- messageId: "expectedParensBlock",
- loc: getLocation(node),
- fix(fixer) {
- return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
- }
- });
- }
- return;
- }
+ return {
+ "ArrowFunctionExpression[params.length=1]"(node) {
+ const shouldHaveParens = !asNeeded || requireForBlockBody && hasBlockBody(node);
+ const openingParen = findOpeningParenOfParams(node);
+ const hasParens = openingParen !== null;
+ const [param] = node.params;
- // "as-needed": x => x
- if (asNeeded &&
- node.params[0].type === "Identifier" &&
- !node.params[0].typeAnnotation &&
- !node.returnType
- ) {
- if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
+ if (shouldHaveParens && !hasParens) {
context.report({
node,
- messageId: "unexpectedParens",
- loc: getLocation(node),
- fix: fixParamsWithParenthesis
+ messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens",
+ loc: param.loc,
+ *fix(fixer) {
+ yield fixer.insertTextBefore(param, "(");
+ yield fixer.insertTextAfter(param, ")");
+ }
});
}
- return;
- }
- if (firstTokenOfParam.type === "Identifier") {
- const after = sourceCode.getTokenAfter(firstTokenOfParam);
-
- // (x) => x
- if (after.value !== ")") {
+ if (
+ !shouldHaveParens &&
+ hasParens &&
+ param.type === "Identifier" &&
+ !param.typeAnnotation &&
+ !node.returnType &&
+ !hasCommentsInParensOfParams(node, openingParen) &&
+ !hasUnexpectedTokensBeforeOpeningParen(node, openingParen)
+ ) {
context.report({
node,
- messageId: "expectedParens",
- loc: getLocation(node),
- fix(fixer) {
- return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
+ 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]]);
}
});
}
}
- }
-
- return {
- "ArrowFunctionExpression[params.length=1]": parens
};
}
};
type: "boolean",
default: false
},
+ ignoreGlobals: {
+ type: "boolean",
+ default: false
+ },
properties: {
enum: ["always", "never"]
},
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";
}
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.
return {
+ Program() {
+ globalScope = context.getScope();
+ },
+
Identifier(node) {
/*
return;
}
+ // Check if it's a global variable
+ if (ignoreGlobals && isReferenceToGlobalVariable(node) && !isPropertyNameInObjectLiteral(node)) {
+ return;
+ }
+
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
//------------------------------------------------------------------------------
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.
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) {
case "MemberExpression":
case "CallExpression":
case "NewExpression":
+ case "ChainExpression":
case "YieldExpression":
case "TaggedTemplateExpression":
case "MetaProperty":
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)
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) {
*/
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);
+ }
});
}
}
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, " ");
+ }
}
});
}
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]);
-
- if (textAfterDot.trim()) {
+ *fix(fixer) {
+ const dotToken = sourceCode.getTokenBefore(node.property);
- // 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}"]`);
}
});
}
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) {
},
messageId: "missing",
fix(fixer) {
+ if (node.optional) {
+ return null; // Not sure if inserting a space to either before/after `?.` token.
+ }
return fixer.insertTextBefore(rightToken, " ");
}
});
},
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, " ?. ");
}
});
}
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])) {
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);
}
/**
}
case "ArrowFunctionExpression": {
- const firstToken = sourceCode.getFirstToken(node);
+ const firstToken = sourceCode.getFirstToken(node, { skip: (node.async ? 1 : 0) });
if (!astUtils.isOpeningParenToken(firstToken)) {
"CallExpression",
"ConditionalExpression",
"Program",
- "VariableDeclaration"
+ "VariableDeclaration",
+ "ChainExpression"
];
/**
/**
* @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)
*/
module.exports = {
meta: {
+ deprecated: true,
+ replacedBy: ["id-denylist"],
+
type: "suggestion",
docs: {
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);
}
/**
/*
* 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" &&
if (!reportedNodes.has(node)) {
context.report({
node,
- messageId: "blacklisted",
+ messageId: "restricted",
data: {
name: node.name
}
},
Identifier(node) {
- if (isBlacklisted(node.name) && shouldCheck(node)) {
+ if (isRestricted(node.name) && shouldCheck(node)) {
report(node);
}
}
--- /dev/null
+/**
+ * @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);
+ }
+ }
+ };
+ }
+};
type: "string"
}
},
+ exceptionPatterns: {
+ type: "array",
+ uniqueItems: true,
+ items: {
+ type: "string"
+ }
+ },
properties: {
enum: ["always", "never"]
}
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 && (
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
}
type: "boolean",
default: false
}
- }
+ },
+ additionalProperties: false
}
],
messages: {
"BreakStatement",
"CallExpression",
"CatchClause",
+ "ChainExpression",
"ClassBody",
"ClassDeclaration",
"ClassExpression",
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);
},
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);
},
"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"),
"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"),
"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"),
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" ||
context.report({
node: property[side],
- loc: locStart,
+ loc,
messageId,
data: {
computed: property.computed ? "computed " : "",
!sourceCode.isSpaceBetweenTokens(prevToken, token)
) {
context.report({
- loc: token.loc.start,
+ loc: token.loc,
messageId: "expectedBefore",
data: token,
fix(fixer) {
!sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
context.report({
- loc: token.loc.start,
+ loc: token.loc,
messageId: "expectedAfter",
data: token,
fix(fixer) {
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,
} else if (lineLength > maxLength) {
context.report({
node,
- loc: { line: lineNumber, column: 0 },
+ loc,
messageId: "max",
data: {
lineLength,
}
],
messages: {
- exceed: "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
+ exceed:
+ "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
}
},
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;
token = comment;
do {
- token = sourceCode.getTokenBefore(token, { includeComments: true });
+ token = sourceCode.getTokenBefore(token, {
+ includeComments: true
+ });
} while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(token, comment)) {
token = comment;
do {
- token = sourceCode.getTokenAfter(token, { includeComments: true });
+ token = sourceCode.getTokenAfter(token, {
+ includeComments: true
+ });
} while (isCommentNodeType(token));
if (token && astUtils.isTokenOnSameLine(comment, token)) {
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() !== "");
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,
* @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) || "";
}
/**
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";
}
/**
* @returns {void}
*/
function report(node, messageId) {
- let callee = node.callee;
+ let callee = astUtils.skipChainExpression(node.callee);
if (callee.type === "MemberExpression") {
callee = callee.property;
* @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 ".";
}
/**
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)) {
const {
getStaticPropertyName: getPropertyName,
- getVariableByName
+ getVariableByName,
+ skipChainExpression
} = require("./utils/ast-utils");
//------------------------------------------------------------------------------
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);
}
create(context) {
return {
CallExpression(node) {
- const callee = node.callee,
+ const callee = skipChainExpression(node.callee),
currentScope = context.getScope();
// without window.
// Helpers
//------------------------------------------------------------------------------
-const EQUALITY_OPERATORS = ["===", "!==", "==", "!="];
-const RELATIONAL_OPERATORS = [">", "<", ">=", "<=", "in", "instanceof"];
-
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
// 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
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
}
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;
}
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
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);
}
}
}
"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.
* 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);
}
//------------------------------------------------------------------------------
"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);
}
}
"CallExpression:exit"(node) {
const callee = node.callee;
- if (isIdentifier(callee, "eval")) {
+ if (astUtils.isSpecificId(callee, "eval")) {
report(callee);
}
},
const astUtils = require("./utils/ast-utils");
const globals = require("globals");
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-const propertyDefinitionMethods = new Set(["defineProperty", "defineProperties"]);
-
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
}
/**
- * 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)
);
}
* @returns {void}
*/
function checkAndReportPrototypeExtension(identifierNode) {
- if (isInPrototypePropertyAssignment(identifierNode)) {
+ if (!isPrototypePropertyAccessed(identifierNode)) {
+ return; // This is not `*.prototype` access.
+ }
+
+ /*
+ * `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;
+
+ if (isAssigningToPropertyOf(prototypeNode)) {
- // Identifier --> MemberExpression --> MemberExpression --> AssignmentExpression
- reportNode(identifierNode.parent.parent.parent, identifierNode.name);
- } else if (isInDefinePropertyCall(identifierNode)) {
+ // `*.prototype` -> MemberExpression -> AssignmentExpression
+ reportNode(prototypeNode.parent.parent, identifierNode.name);
+ } else if (isInDefinePropertyCall(prototypeNode)) {
- // Identifier --> MemberExpression --> CallExpression
- reportNode(identifierNode.parent.parent, identifierNode.name);
+ // `*.prototype` -> CallExpression
+ reportNode(prototypeNode.parent, identifierNode.name);
}
}
* @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]]));
}
});
}
* @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"
);
}
* @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) &&
* @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
* @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))
+ );
}
/**
* @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";
}
/**
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);
}
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
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 &&
}
if (nodeObjHasExcessParens &&
- node.object.type === "CallExpression" &&
- node.parent.type !== "NewExpression") {
+ node.object.type === "CallExpression"
+ ) {
report(node.object);
}
report(node.object);
}
+ if (nodeObjHasExcessParens &&
+ node.optional &&
+ node.object.type === "ChainExpression"
+ ) {
+ report(node.object);
+ }
+
if (node.computed && hasExcessParens(node.property)) {
report(node.property);
}
},
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) {
* @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)
);
}
// ~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);
}
},
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.
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.
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);
}
}
return {
CallExpression(node) {
- if (isSpecifiedIdentifier(node.callee, EVAL_LIKE_FUNCS)) {
+ if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) {
reportImpliedEvalCallExpression(node);
}
},
// Helpers
//------------------------------------------------------------------------------
-const { findVariable, getPropertyName } = require("eslint-utils");
-
-const MutationMethods = {
- Object: new Set([
- "assign", "defineProperties", "defineProperty", "freeze",
- "setPrototypeOf"
- ]),
- Reflect: new Set([
- "defineProperty", "deleteProperty", "set", "setPrototypeOf"
- ])
+const { findVariable } = require("eslint-utils");
+const astUtils = require("./utils/ast-utils");
+
+const WellKnownMutationFunctions = {
+ Object: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u,
+ Reflect: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u
};
/**
* @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
)
);
}
}
/**
- * 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) {
+ return false;
+ }
+ const callee = astUtils.skipChainExpression(parent.callee);
+
if (
- parent.type === "CallExpression" &&
- parent.arguments[0] === node &&
- parent.callee.type === "MemberExpression" &&
- parent.callee.object.type === "Identifier"
+ !astUtils.isSpecificMemberAccess(callee, "Object", WellKnownMutationFunctions.Object) &&
+ !astUtils.isSpecificMemberAccess(callee, "Reflect", WellKnownMutationFunctions.Reflect)
) {
- 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))
- );
- }
+ return false;
}
+ const variable = findVariable(scope, callee.object);
- return false;
+ return variable !== null && variable.scope.type === "global";
}
/**
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."
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
return;
}
+ // Matches the ignore pattern
+ if (customIgnoreRegExp && customIgnoreRegExp.test(node.value)) {
+ return;
+ }
+
// JSX Exception
if (
(isPreambleEmpty || preamble === "{") &&
return {
Program() {
- const comments = sourceCode.getAllComments();
-
- comments.filter(token => token.type !== "Shebang").forEach(testCodeAroundComment);
+ sourceCode.getAllComments()
+ .filter(token => token.type !== "Shebang")
+ .forEach(testCodeAroundComment);
}
};
}
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;
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
+ }
+ }
});
}
});
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;
}
}
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
* @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")) {
* @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) {
"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;
ignoreArrayIndexes: {
type: "boolean",
default: false
+ },
+ ignoreDefaultValues: {
+ type: "boolean",
+ default: false
}
},
additionalProperties: false
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"];
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
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")
);
}
return {
Literal(node) {
- if (!isNumericLiteral(node)) {
+ if (!astUtils.isNumericLiteral(node)) {
return;
}
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))
return;
}
- const parent = fullNumberNode.parent;
-
if (parent.type === "VariableDeclarator") {
if (enforceConst && parent.parent.kind !== "const") {
context.report({
* @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;
}
//------------------------------------------------------------------------------
}
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";
--- /dev/null
+/**
+ * @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);
+ }
+ }
+ };
+ }
+};
*/
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
* @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
});
"use strict";
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
create(context) {
+ /**
+ * 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);
}
}
};
-
}
};
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
}
} 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);
}
* @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);
}
/**
/**
- * @fileoverview Rule to flag trailing underscores in variable declarations.
+ * @fileoverview Rule to flag dangling underscores in variable declarations.
* @author Matt DuVall <http://www.mattduvall.com>
*/
enforceInMethodNames: {
type: "boolean",
default: false
+ },
+ allowFunctionParams: {
+ type: "boolean",
+ default: true
}
},
additionalProperties: false
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
}
/**
- * 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] === "_");
}
/**
- * 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",
});
}
}
+ 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,
}
/**
- * 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) &&
}
/**
- * 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",
//--------------------------------------------------------------------------
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
};
}
return {
MemberExpression(node) {
- if (!node.computed) {
+ if (!node.computed || node.optional) {
return;
}
checkForBreakAfter(node.object, "property");
},
CallExpression(node) {
- if (node.arguments.length === 0) {
+ if (node.arguments.length === 0 || node.optional) {
return;
}
checkForBreakAfter(node.callee, "function");
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) {
} else if (!defaultAssignment && matchesDefaultAssignment(node)) {
context.report({
node,
- loc: node.consequent.loc.start,
messageId: "unnecessaryConditionalAssignment",
fix: fixer => {
const shouldParenthesizeAlternate =
--- /dev/null
+/**
+ * @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" })
+ );
+ }
+ };
+ }
+};
// 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",
}
/**
- * 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) {
-
- // Recursive check for ternary and logical expressions
- if (node.type === "ConditionalExpression") {
- return isValidExpression(node.consequent) && isValidExpression(node.alternate);
+ const Checker = Object.assign(Object.create(null), {
+ isDisallowed(node) {
+ return (Checker[node.type] || alwaysFalse)(node);
+ },
+
+ 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);
+ return true;
+ },
+ FunctionExpression: alwaysTrue,
+ Identifier: alwaysTrue,
+ Literal: alwaysTrue,
+ LogicalExpression(node) {
+ if (allowShortCircuit) {
+ return Checker.isDisallowed(node.right);
}
- }
-
- if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
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" });
}
}
};
-
}
};
caughtErrorsIgnorePattern: {
type: "string"
}
- }
+ },
+ additionalProperties: false
}
]
}
* @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")
)
);
}
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 } });
}
}
};
const { escapeRegExp } = require("lodash");
const astUtils = require("./utils/ast-utils");
+const CHAR_LIMIT = 40;
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
],
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"],
* \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);
* @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 ? "..." : ""
+ }`
}
});
});
Program() {
const comments = sourceCode.getAllComments();
- comments.filter(token => token.type !== "Shebang").forEach(checkComment);
+ comments
+ .filter(token => token.type !== "Shebang")
+ .forEach(checkComment);
}
};
}
* @private
*/
function reportError(node, leftToken, rightToken) {
- const replacementText = node.computed ? "" : ".";
-
context.report({
node,
messageId: "unexpectedWhitespace",
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.
*/
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);
}
});
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);
context.report({
messageId: "expectedLinebreakAfterOpeningBrace",
node,
- loc: openBrace.loc.start,
+ loc: openBrace.loc,
fix(fixer) {
if (hasCommentsFirstToken) {
return null;
context.report({
messageId: "expectedLinebreakBeforeClosingBrace",
node,
- loc: closeBrace.loc.start,
+ loc: closeBrace.loc,
fix(fixer) {
if (hasCommentsLastToken) {
return null;
context.report({
messageId: "unexpectedLinebreakAfterOpeningBrace",
node,
- loc: openBrace.loc.start,
+ loc: openBrace.loc,
fix(fixer) {
if (hasCommentsFirstToken) {
return null;
context.report({
messageId: "unexpectedLinebreakBeforeClosingBrace",
node,
- loc: closeBrace.loc.start,
+ loc: closeBrace.loc,
fix(fixer) {
if (hasCommentsLastToken) {
return null;
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);
// 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)
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]);
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.
* @returns {void}
*/
function prohibit(node) {
- if (node.operator !== "=") {
+ if (node.operator !== "=" && !astUtils.isLogicalAssignmentOperator(node.operator)) {
context.report({
node,
messageId: "unexpected",
properties: {
overrides: {
type: "object",
- properties: {
- anyOf: {
- type: "string",
- enum: ["after", "before", "none", "ignore"]
- }
+ additionalProperties: {
+ enum: ["after", "before", "none", "ignore"]
}
}
},
allowSingleLineBlocks: {
type: "boolean"
}
- }
+ },
+ additionalProperties: false
}
],
*/
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);
}
"use strict";
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const retv = { isCallback: false, isLexicalThis: false };
let currentNode = node;
let parent = node.parent;
+ let bound = false;
while (currentNode) {
switch (parent.type) {
// 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;
}
context.report({
node,
messageId: "preferArrowCallback",
- fix(fixer) {
+ *fix(fixer) {
if ((!callbackInfo.isLexicalThis && scopeInfo.this) || hasDuplicateParams(node.params)) {
/*
* 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, ")");
+ }
}
});
}
*/
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const PRECEDENCE_OF_ASSIGNMENT_EXPR = astUtils.getPrecedence({ type: "AssignmentExpression" });
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
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;
}
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}`
);
}
* @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" ||
* 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")
+ );
}
//------------------------------------------------------------------------------
/*
* 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;
}
* @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");
}
//----------------------------------------------------------------------
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.
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.
*/
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);
}
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();
};
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" });
}
}
*/
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.
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];
}
/**
- * 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;
+function hasOctalOrNonOctalDecimalEscapeSequence(node) {
+ if (isConcatenation(node)) {
+ return (
+ hasOctalOrNonOctalDecimalEscapeSequence(node.left) ||
+ hasOctalOrNonOctalDecimalEscapeSequence(node.right)
+ );
}
- 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) {
- if (isConcatenation(node)) {
- return hasOctalEscapeSequence(node.left) || hasOctalEscapeSequence(node.right);
+ // No need to check TemplateLiterals – would throw parsing error
+ if (node.type === "Literal" && typeof node.value === "string") {
+ return astUtils.hasOctalOrNonOctalDecimalEscapeSequence(node.raw);
}
- return isOctalEscapeSequence(node);
+ return false;
}
/**
function fixNonStringBinaryExpression(fixer, node) {
const topBinaryExpr = getTopConcatBinaryExpression(node.parent);
- if (hasOctalEscapeSequence(topBinaryExpr)) {
+ if (hasOctalOrNonOctalDecimalEscapeSequence(topBinaryExpr)) {
return null;
}
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;
}
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);
}
});
}
BreakStatement: checkNode,
ContinueStatement: checkNode,
DebuggerStatement: checkNode,
+ DoWhileStatement: checkNode,
ReturnStatement: checkNode,
ThrowStatement: checkNode,
ImportDeclaration: checkNode,
ignoreMemberSort: {
type: "boolean",
default: false
+ },
+ allowSeparatedGroups: {
+ type: "boolean",
+ default: false
}
},
additionalProperties: false
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;
}
+ /**
+ * 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);
"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
//------------------------------------------------------------------------------
}
/**
- * 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);
}
/**
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") {
* @returns {void}
*/
function checkCallExpression(node) {
- const callee = node.callee;
+ const callee = astUtils.skipChainExpression(node.callee);
if (callee.type === "MemberExpression") {
const methodName = astUtils.getStaticPropertyName(callee);
// 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.
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.
*/
function isNullOrUndefined(node) {
return (
- module.exports.isNullLiteral(node) ||
+ isNullLiteral(node) ||
(node.type === "Identifier" && node.name === "undefined") ||
(node.type === "UnaryExpression" && node.operator === "void")
);
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");
}
/**
* @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");
}
/**
* @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);
}
/**
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.
);
}
+/**
+ * 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
//------------------------------------------------------------------------------
isCommaToken,
isCommentToken,
isDotToken,
+ isQuestionDotToken,
isKeywordToken,
isNotClosingBraceToken: negate(isClosingBraceToken),
isNotClosingBracketToken: negate(isClosingBracketToken),
isNotColonToken: negate(isColonToken),
isNotCommaToken: negate(isCommaToken),
isNotDotToken: negate(isDotToken),
+ isNotQuestionDotToken: negate(isQuestionDotToken),
isNotOpeningBraceToken: negate(isOpeningBraceToken),
isNotOpeningBracketToken: negate(isOpeningBracketToken),
isNotOpeningParenToken: negate(isOpeningParenToken),
*/
case "LogicalExpression":
case "ConditionalExpression":
+ case "ChainExpression":
currentNode = parent;
break;
* (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.
return 17;
case "CallExpression":
+ case "ChainExpression":
case "ImportExpression":
return 18;
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.
* @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" &&
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}'`);
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;
}
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":
}
},
- /**
- * 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.
},
/**
- * 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
};
* @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
+ );
}
//------------------------------------------------------------------------------
* @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;
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
//------------------------------------------------------------------------------
* @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);
* @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);
* @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 &&
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
);
}
+++ /dev/null
-/**
- * @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')`);
- }
- }
-};
+/*
+ * 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
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")();
+++ /dev/null
-/**
- * @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
-};
+/*
+ * 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
/**
* @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.
*/
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.
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
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.
--- /dev/null
+"<%- 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.
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.
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.
{
"name": "eslint",
- "version": "7.2.0",
+ "version": "7.12.1",
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
"description": "An AST-based pattern checker for JavaScript.",
"bin": {
"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",
"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",
"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",
"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",
"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",
* **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:**
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 =
+/**
+ * @fileoverview Utilities used in tests
+ */
+
"use strict";
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
const {
defineInMemoryFs,
defineConfigArrayFactoryWithInMemoryFileSystem,
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
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,
defineCascadingConfigArrayFactoryWithInMemoryFileSystem,
defineFileEnumeratorWithInMemoryFileSystem,
defineCLIEngineWithInMemoryFileSystem,
- defineESLintWithInMemoryFileSystem
+ defineESLintWithInMemoryFileSystem,
+ createCustomTeardown
};
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));
});
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+/*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;
+}
+*/
--- /dev/null
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * <T extends (A | B) & C>(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: [],
+ });
--- /dev/null
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * <T extends A>(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: [],
+ });
--- /dev/null
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * async <T>(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: [],
+ });
--- /dev/null
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * <T>() => 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: [],
+ });
--- /dev/null
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * <T>(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: [],
+ });
--- /dev/null
+"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: [],
+ });
--- /dev/null
+"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: [],
+ });
// Rule Definition
//------------------------------------------------------------------------------
-module.exports = function(context) {
-
- "use strict";
-
-
- var sourceCode = context.getSourceCode();
-
- 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));
- }
- })
+"use strict";
+
+module.exports = {
+
+ meta: {
+ fixable: "code"
+ },
+
+ create(context) {
+
+ var sourceCode = context.getSourceCode();
+
+ 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));
+ }
+ })
+ }
}
- }
- };
-
+ };
+ }
};
+++ /dev/null
-/**
- * @fileoverview Tests for CascadingConfigArrayFactory class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"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<ReturnType<defineCascadingConfigArrayFactoryWithInMemoryFileSystem>["CascadingConfigArrayFactory"]>} CascadingConfigArrayFactory */
-/** @typedef {ReturnType<CascadingConfigArrayFactory["getConfigArrayForFile"]>} 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<void>} 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<void>} -
- */
- 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<string, Object>} */
- 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: {}
- }
- );
- });
- });
- });
-});
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");
}
// 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);
});
engine = new CLIEngine({
parser: "espree",
parserOptions: {
- ecmaVersion: 2020
+ ecmaVersion: 2021
},
useEslintrc: false
});
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.");
});
describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => {
+
+ 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(() => {
- ({ 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();
+ engine = new CLIEngine({
+ cwd: getPath()
+ });
+
+ return prepare();
});
+ afterEach(cleanup);
+
it("should not report 'no-console' error.", () => {
const { results } = engine.executeOnFiles("a.js");
});
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");
});
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" },
}
};
`,
- ".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");
`
};
- 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"]);
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"]);
`);
});
- 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"]);
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"]);
`);
});
- 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: [
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"]);
`);
});
- 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: [
}
}
]
- })
+ }
}
- }).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"]);
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: [
}
}
]
- })
+ }
}
- }).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"]);
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: [
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"]);
});
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')"
+ }
+ });
+
+ beforeEach(prepare);
+ afterEach(cleanup);
+
+
+ it("should use the configured rules which are defined by '--rulesdir' option.", () => {
- engine = new StubbedCLIEngine({
+ engine = new CLIEngine({
+ cwd: getPath(),
rulePaths: ["internal-rules"]
});
const report = engine.executeOnFiles(["test.js"]);
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": "",
"[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));
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));
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;
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;
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;
});
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;
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;
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;
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");
});
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,
]
};
- fakeFS.writeFileSync = function() {};
const spy = sinon.spy(fakeFS, "writeFileSync");
localCLIEngine.outputFixes(report);
});
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,
]
};
- fakeFS.writeFileSync = function() {};
const spy = sinon.spy(fakeFS, "writeFileSync");
localCLIEngine.outputFixes(report);
describe("resolveFileGlobPatterns", () => {
- leche.withData([
+ [
[".", ["**/*.{js}"]],
["./", ["**/*.{js}"]],
["../", ["../**/*.{js}"]],
["", []]
- ], (input, expected) => {
+ ].forEach(([input, expected]) => {
it(`should correctly resolve ${input} to ${expected}`, () => {
const engine = new 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)
});
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)
});
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)
});
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)
});
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)
});
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)
});
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);
});
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)
});
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)
});
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)
});
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)
});
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)
});
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)
});
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)
});
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)
});
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)
});
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\"");
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)
});
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)
});
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)
});
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)
});
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)
});
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)
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
});
});
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
});
});
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
});
});
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
});
});
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")
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.
}
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"),
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"
}
]
});
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"
});
});
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"
});
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"
}
]
});
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"]
});
});
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"]
});
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`
}
]
});
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");
});
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);
+++ /dev/null
-/**
- * @fileoverview Tests for ConfigArrayFactory class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"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<ReturnType<defineConfigArrayFactoryWithInMemoryFileSystem>["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<string,Object>} 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<string,Object>} 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" } }
- );
- });
- });
-});
+++ /dev/null
-/**
- * @fileoverview Tests for ConfigArray class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"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);
- });
- });
-});
+++ /dev/null
-/**
- * @fileoverview Tests for ConfigDependency class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"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);
- });
- });
-});
+++ /dev/null
-/**
- * @fileoverview Tests for ExtractedConfig class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"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 });
- });
- });
-});
+++ /dev/null
-/**
- * @fileoverview Tests for IgnorePattern class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"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();
- }
- });
- });
-});
+++ /dev/null
-/**
- * @fileoverview Tests for OverrideTester class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"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);
- });
- });
-});
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: {
"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.
});
// 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;
/**
);
}
- 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);
});
});
});
+
+ // 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(["."]));
+ });
+ });
});
assert.strictEqual(result, "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle version=\"4.3\"><file name=\"foo.js\"><error line=\"5\" column=\"10\" severity=\"error\" message=\"Unexpected foo.\" source=\"\" /></file></checkstyle>");
});
});
+
+ 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, "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle version=\"4.3\"><file name=\"foo.js\"><error line=\"0\" column=\"0\" severity=\"error\" message=\"Unexpected foo. (foo)\" source=\"eslint.rules.foo\" /></file></checkstyle>");
+ });
+ });
});
}
// 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);
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
}
// 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);
});
overrideConfig: {
parser: "espree",
parserOptions: {
- ecmaVersion: 2020
+ ecmaVersion: 2021
}
},
useEslintrc: false
});
describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => {
+
+ 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 } = 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();
+ eslint = new ESLint({ cwd: getPath() });
+ return prepare();
});
+ afterEach(cleanup);
+
it("should not report 'no-console' error.", async () => {
const results = await eslint.lintFiles("a.js");
});
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"] }
};
}
};
`,
- ".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");
});
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" },
}
};
`,
- ".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");
`
};
+ 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);
});
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);
});
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);
});
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);
`);
});
- 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: [
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);
});
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: [
}
}
]
- })
+ }
}
- }).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);
});
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: [
}
}
]
- })
+ }
}
- }).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);
});
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"]);
});
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: [
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);
});
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"]);
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": "",
"[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));
});
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));
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;
});
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;
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;
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;
});
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;
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;
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");
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", () => {
});
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"),
});
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"),
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();
});
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();
});
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();
});
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();
});
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();
});
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();
});
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);
});
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();
});
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();
});
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();
});
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();
});
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();
});
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();
});
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();
});
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();
});
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();
});
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);
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();
});
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();
});
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();
});
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();
});
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();
});
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();
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
});
assert.deepStrictEqual(results, [
{
errorCount: 1,
- filePath: path.join(root, "foo/test.js"),
+ filePath: path.join(getPath(), "foo/test.js"),
fixableErrorCount: 0,
fixableWarningCount: 0,
messages: [
});
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,
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: [],
});
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,
assert.deepStrictEqual(results, [
{
errorCount: 0,
- filePath: path.join(root, "foo/test.js"),
+ filePath: path.join(getPath(), "foo/test.js"),
fixableErrorCount: 0,
fixableWarningCount: 0,
messages: [],
});
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,
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: [
});
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"))
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.
}
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"),
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"
}
]
});
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"
});
});
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"
});
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"
}
]
});
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"] }
});
});
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"] }
});
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`
}
]
});
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");
});
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);
//------------------------------------------------------------------------------
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"),
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,
});
it("should include a newline character at EOF", () => {
- const fakeFS = leche.fake(fs);
+ const fakeFS = {
+ writeFileSync: () => {}
+ };
sinon.mock(fakeFS).expects("writeFileSync").withExactArgs(
filename,
});
it("should make sure js config files match linting rules", () => {
- const fakeFS = leche.fake(fs);
+ const fakeFS = {
+ writeFileSync: () => {}
+ };
const singleQuoteConfig = {
rules: {
});
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,
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);
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", () => {
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"]);
});
}
}));
const messages = linter.verify(source, {
- parserOptions: { ecmaVersion: 2020 },
+ parserOptions: { ecmaVersion: 2021 },
rules: { test: 2 }
});
}
);
});
+
+ 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", () => {
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") });
}, 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"), {
{
code: "eval(foo)",
parser: require.resolve("esprima"),
- errors: [{}]
+ errors: [{ line: 1 }]
}
]
});
}, "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, {
});
});
+
});
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
],
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
{
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedInside",
- data: { name: "arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
type: "ArrowFunctionExpression",
line: 1,
column: 16,
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedInside",
- data: { name: "arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
type: "ArrowFunctionExpression",
line: 2,
column: 4,
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedInside",
- data: { name: "arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
type: "ArrowFunctionExpression",
line: 1,
column: 26,
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedReturnValue",
- data: { name: "Arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
type: "ReturnStatement",
line: 1,
column: 21,
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedInside",
- data: { name: "arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.from" },
type: "ArrowFunctionExpression",
line: 1,
column: 21,
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedNoReturnValue",
- data: { name: "Arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
type: "ArrowFunctionExpression",
line: 1,
column: 17,
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedNoReturnValue",
- data: { name: "Arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
type: "ArrowFunctionExpression",
line: 1,
column: 41,
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedNoReturnValue",
- data: { name: "Arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
type: "ArrowFunctionExpression",
line: 2,
column: 13,
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "expectedNoReturnValue",
- data: { name: "Arrow function" },
+ data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
type: "ReturnStatement",
line: 1,
column: 52,
code: "foo.filter(function(){})",
errors: [{
messageId: "expectedInside",
- data: { name: "function" },
+ data: { name: "function", arrayMethodName: "Array.prototype.filter" },
type: "FunctionExpression",
line: 1,
column: 12,
code: "foo.filter(function (){})",
errors: [{
messageId: "expectedInside",
- data: { name: "function" },
+ data: { name: "function", arrayMethodName: "Array.prototype.filter" },
type: "FunctionExpression",
line: 1,
column: 12,
code: "foo.filter(function\n(){})",
errors: [{
messageId: "expectedInside",
- data: { name: "function" },
+ data: { name: "function", arrayMethodName: "Array.prototype.filter" },
type: "FunctionExpression",
line: 1,
column: 12,
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,
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,
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,
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,
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,
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,
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" } }]
}
]
});
{ 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}",
{
line: 1,
column: 17,
+ endLine: 1,
+ endColumn: 18,
type: "ArrowFunctionExpression",
messageId: "expectedBlock"
}
// 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: [
},
{
+
// 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: [
{
line: 1,
column: 17,
+ endLine: 3,
+ endColumn: 2,
type: "ArrowFunctionExpression",
messageId: "unexpectedSingleBlock"
}
{
line: 1,
column: 17,
+ endLine: 2,
+ endColumn: 13,
type: "ArrowFunctionExpression",
messageId: "unexpectedSingleBlock"
}
{
line: 1,
column: 17,
+ endLine: 2,
+ endColumn: 2,
type: "ArrowFunctionExpression",
messageId: "unexpectedSingleBlock"
}
{
line: 2,
column: 31,
+ endLine: 7,
+ endColumn: 16,
type: "ArrowFunctionExpression",
messageId: "unexpectedObjectBlock"
}
{ 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") },
// "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 }] },
{
code: "var bar = (/*comment here*/{a}) => a",
options: ["as-needed"]
+ },
+
+ // generics
+ {
+ code: "<T>(a) => b",
+ options: ["always"],
+ parser: parser("generics-simple")
+ },
+ {
+ code: "<T>(a) => b",
+ options: ["as-needed"],
+ parser: parser("generics-simple")
+ },
+ {
+ code: "<T>(a) => b",
+ options: ["as-needed", { requireForBlockBody: true }],
+ parser: parser("generics-simple")
+ },
+ {
+ code: "async <T>(a) => b",
+ options: ["always"],
+ parser: parser("generics-simple-async")
+ },
+ {
+ code: "async <T>(a) => b",
+ options: ["as-needed"],
+ parser: parser("generics-simple-async")
+ },
+ {
+ code: "async <T>(a) => b",
+ options: ["as-needed", { requireForBlockBody: true }],
+ parser: parser("generics-simple-async")
+ },
+ {
+ code: "<T>() => b",
+ options: ["always"],
+ parser: parser("generics-simple-no-params")
+ },
+ {
+ code: "<T>() => b",
+ options: ["as-needed"],
+ parser: parser("generics-simple-no-params")
+ },
+ {
+ code: "<T>() => b",
+ options: ["as-needed", { requireForBlockBody: true }],
+ parser: parser("generics-simple-no-params")
+ },
+ {
+ code: "<T extends A>(a) => b",
+ options: ["always"],
+ parser: parser("generics-extends")
+ },
+ {
+ code: "<T extends A>(a) => b",
+ options: ["as-needed"],
+ parser: parser("generics-extends")
+ },
+ {
+ code: "<T extends A>(a) => b",
+ options: ["as-needed", { requireForBlockBody: true }],
+ parser: parser("generics-extends")
+ },
+ {
+ code: "<T extends (A | B) & C>(a) => b",
+ options: ["always"],
+ parser: parser("generics-extends-complex")
+ },
+ {
+ code: "<T extends (A | B) & C>(a) => b",
+ options: ["as-needed"],
+ parser: parser("generics-extends-complex")
+ },
+ {
+ code: "<T extends (A | B) & C>(a) => b",
+ options: ["as-needed", { requireForBlockBody: true }],
+ parser: parser("generics-extends-complex")
}
];
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",
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 }
{
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 }
}
]
},
+ {
+ 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" },
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" } }]
}
]
});
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: "]" } }
+ ]
}
]
});
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
ruleTester.run("constructor-super", rule, {
valid: [
"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(); } }",
}
}
}
- `
+ `,
+
+ // Optional chaining
+ "class A extends obj?.prop { constructor() { super(); } }"
],
invalid: [
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.
{
}
]
},
+ {
+ 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() ",
}
]
},
+ {
+ 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) ",
}
]
},
+ {
+ 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)",
{
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: [
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",
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" }]
}
]
});
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" } }]
}
]
});
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: [
},
{
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(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" }]
},
},
{
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: [
{
},
{
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: [
{
},
{
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" },
},
{
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" }]
},
},
{
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: [
{
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" }]
}
]
});
"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"] },
{ 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 },
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" } }
+ ]
}
]
});
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 }
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"],
`,
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"],
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"],
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 }],
`,
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"],
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)",
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)",
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)",
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)",
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)",
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)",
},
{ 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'" } }]
+ }
]
});
{ 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" };
//------------------------------------------------------------------------------
const ruleTester = new RuleTester();
-const error = { messageId: "blacklisted", type: "Identifier" };
+const error = { messageId: "restricted", type: "Identifier" };
ruleTester.run("id-blacklist", rule, {
valid: [
options: ["bar"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
options: ["foo", "bar"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 17
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 10
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 22
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 8
options: ["bar"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 26
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
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 5
// 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
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
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
options: ["bar"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
options: ["foo", "bar"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 17
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 10
options: ["foo"],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 22
code: "foo[bar] = baz;",
options: ["bar"],
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier"
}]
code: "baz = foo[bar];",
options: ["bar"],
errors: [{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier"
}]
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "foo" },
type: "Identifier",
column: 8
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 13
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
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "baz" },
type: "Identifier",
column: 19
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
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
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "baz" },
type: "Identifier",
column: 21
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "qux" },
type: "Identifier",
column: 27
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 12
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "baz" },
type: "Identifier",
column: 24
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
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 10
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "baz" },
type: "Identifier",
column: 18
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 10
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 11
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 17
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 19
parserOptions: { ecmaVersion: 9 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 10
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 7
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "bar" },
type: "Identifier",
column: 8
options: ["undefined"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier"
}
options: ["undefined"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier"
}
options: ["undefined"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier",
column: 13
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier"
}
parserOptions: { ecmaVersion: 6 },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier"
}
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
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
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
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
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
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
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
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
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
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
},
/*
- * 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;",
env: { browser: true },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "myGlobal" },
type: "Identifier",
column: 31
globals: { undefined: "off" },
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "undefined" },
type: "Identifier"
}
options: ["Number"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Number" },
type: "Identifier"
}
options: ["Map"],
errors: [
{
- messageId: "blacklisted",
+ messageId: "restricted",
data: { name: "Map" },
type: "Identifier"
}
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
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
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
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
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
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"
}
--- /dev/null
+/**
+ * @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"
+ }
+ ]
+ }
+ ]
+});
{ 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] },
tooLongError
]
},
+ {
+ code: "var toString;",
+ options: [{ max: 5 }],
+ errors: [
+ tooLongError
+ ]
+ },
{
code: "(a) => { a * a };",
parserOptions: { ecmaVersion: 6 },
}
]
},
+ {
+ 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 },
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
+ ]
}
]
});
`,
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" } }]
}
],
[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"]
+ ])
}
]
});
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 };",
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)};",
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 };",
}],
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 }
]
}, {
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: [
}],
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: [
}],
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: [
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: [
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: [
].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: [
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: [
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: [
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: [
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: [
].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: [
].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: [
].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: [
].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" }
]
},
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" }
]
},
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: [
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: [
}
}],
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: [
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: [
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" }
]
},
{ 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: [
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: [
}],
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: [
}],
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 })",
}
}],
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: [
}
}],
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: [
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\"",
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\"",
// 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\"",
data: { lineLength: 86, maxLength: 80 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 30
}
]
},
data: { lineLength: 24, maxLength: 10 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 25
}
]
},
data: { lineLength: 22, maxLength: 15 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 14
}
]
},
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
}
]
},
data: { lineLength: 56, maxLength: 20 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 57
}
]
},
data: { lineLength: 54, maxLength: 20 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 55
}
]
},
data: { lineLength: 30, maxLength: 10 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 31
}
]
},
data: { lineLength: 62, maxLength: 40 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 63
}
]
},
data: { lineLength: 57, maxLength: 40 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 58
}
]
}, {
data: { lineLength: 53, maxLength: 40 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 54
}
]
}, {
data: { lineLength: 49, maxCommentLength: 20 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 50
}
]
}, {
data: { lineLength: 119, maxCommentLength: 80 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 120
}
]
}, {
data: { lineLength: 49, maxLength: 20 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 50
}
]
}, {
data: { lineLength: 73, maxLength: 40 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 74
}
]
},
data: { lineLength: 29, maxCommentLength: 28 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 30
}
]
}, {
data: { lineLength: 33, maxCommentLength: 32 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 34
}
]
}, {
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
}
]
}, {
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
}
]
}, {
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
}
]
}, {
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
}
]
},
data: { lineLength: 51, maxLength: 20 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 52
}
]
},
data: { lineLength: 39, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 40
}
]
},
data: { lineLength: 45, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 46
}
]
},
data: { lineLength: 57, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 58
}
]
},
data: { lineLength: 39, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 40
}
]
},
data: { lineLength: 39, maxLength: 29 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 40
}
]
},
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
}
]
},
data: { lineLength: 58, maxLength: 29 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 59
}
]
},
data: { lineLength: 12, maxLength: 10 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 21
}
]
},
data: { lineLength: 1, maxLength: 0 },
type: "Program",
line: 1,
- column: 1
+ column: 1,
+ endLine: 1,
+ endColumn: 2
}
]
},
data: { lineLength: 38, maxCommentLength: 37 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 39
}
]
},
data: { lineLength: 44, maxCommentLength: 40 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 39
}
]
},
data: { lineLength: 38, maxLength: 15 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 39
}
]
},
data: { lineLength: 38, maxLength: 37 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 39
}
]
},
data: { lineLength: 38, maxLength: 37 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 39
}
]
},
data: { lineLength: 50, maxLength: 49 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 51
}
]
},
data: { lineLength: 44, maxLength: 37 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 45
}
]
},
data: { lineLength: 57, maxLength: 56 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 58
}
]
},
data: { lineLength: 57, maxLength: 56 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 58
}
]
},
data: { lineLength: 56, maxLength: 55 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 57
}
]
},
data: { lineLength: 51, maxLength: 30 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 52
}
]
},
data: { lineLength: 80, maxLength: 79 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 81
}
]
},
data: { lineLength: 87, maxLength: 85 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 88
}
]
},
data: { lineLength: 87, maxLength: 37 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 88
}
]
},
data: { lineLength: 119, maxLength: 37 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 120
}
]
},
data: { lineLength: 55, maxLength: 37 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 56
}
]
},
data: { lineLength: 55, maxLength: 37 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 56
}
]
},
data: { lineLength: 15, maxLength: 14 },
type: "Program",
line: 2,
- column: 1
+ column: 1,
+ endLine: 2,
+ endColumn: 16
}
]
},
data: { lineLength: 31, maxLength: 30 },
type: "Program",
line: 3,
- column: 1
+ column: 1,
+ endLine: 3,
+ endColumn: 32
}
]
}
//------------------------------------------------------------------------------
const rule = require("../../../lib/rules/max-lines"),
-
{ RuleTester } = require("../../../lib/rule-tester");
-
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
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",
{
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: [
"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: [
" 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: [
"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: [
"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: [
" 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
+ }]
}
+
]
});
{ 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: [
{
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 }]
}
]
});
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]" } }
+ ]
+ }
+
+ ]
});
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" } }]
}
]
});
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 }] },
"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') {}",
"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]) {}",
{ 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" }] },
{ 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" }] },
{ 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" }] },
{ 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" }] },
// #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" }] },
{
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" }] }
]
});
{ 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, [/(?<zero>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" }] },
{ 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" }] },
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
+ }]
}
]
});
{ 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: [
{ 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" }]
+ }
]
});
code: "Object.prototype.g = 0",
options: [{ exceptions: ["Object"] }]
},
+ "obj[Object.prototype] = 0",
// https://github.com/eslint/eslint/issues/4438
"Object.defineProperty()",
{
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: [{
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" } }]
+ }
+
+ ]
});
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
{
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" }]
}
]
});
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" }]
}
]
});
const ruleTester = new RuleTester({
parserOptions: {
- ecmaVersion: 2020,
+ ecmaVersion: 2021,
ecmaFeatures: {
jsx: true
}
// 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)",
"}"
].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) }",
"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 } },
{ 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: [
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"),
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),
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"),
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),
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" }]
}
]
});
data: { name: "foo" },
type: "Identifier"
}]
+ },
+ {
+ code: "var a = function foo() { foo = 123; };",
+ errors: [{
+ messageId: "isAFunction",
+ data: { name: "foo" },
+ type: "Identifier"
+ }]
}
]
});
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"
+ }]
}
]
});
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" }]
}
]
});
{
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 }]
}
]
});
comment
*/}
</div>
- )`
+ )`,
+ {
+ 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: [
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]
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]
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.
{
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.
{
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.
{
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: []
}
];
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
+ }
+ ]
}
]
});
"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",
"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: [
{
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" }]
{
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" }]
}
-
]
});
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: [
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 }
+ ]
}
]
});
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" }]
}
]
});
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" }
+ }]
}
]
});
--- /dev/null
+/**
+ * @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()]
+ }
+ ]
+});
//------------------------------------------------------------------------------
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']))" }
-];
+ruleTester.run("no-prototype-builtins", rule, {
+ 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')",
-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"
- }]
- }
-];
+ // out of scope for this rule
+ "foo['hasOwn' + 'Property']('bar')",
+ { code: "foo[`hasOwnProperty${''}`]('bar')", parserOptions: { ecmaVersion: 2015 } }
+ ],
-ruleTester.run("no-prototype-builtins", rule, {
- valid,
- invalid
+ 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" } }]
+ }
+ ]
});
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" }
+ * ]
+ * }
+ */
]
});
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: [
{
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" }
+ ]
}
]
});
options: [{ props: true }],
errors: [{ messageId: "selfAssignment", data: { name: "this.x" } }]
},
- { code: "a['/(?<zero>0)/'] = a[/(?<zero>0)/]", options: [{ props: true }], parserOptions: { ecmaVersion: 2018 }, errors: [{ messageId: "selfAssignment", data: { name: "a[/(?<zero>0)/]" } }] }
+ { code: "a['/(?<zero>0)/'] = a[/(?<zero>0)/]", options: [{ props: true }], parserOptions: { ecmaVersion: 2018 }, errors: [{ messageId: "selfAssignment", data: { name: "a[/(?<zero>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" } }]
+ }
]
});
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
ruleTester.run("no-setter-return", rule, {
valid: [
{
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()]
}
]
});
"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(); } }",
{
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" }]
}
]
});
"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)
"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: [
{
// 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"
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
{
{ 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; }",
"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__"] }] },
{ 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" }] },
{ 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" }] }
+
+
]
});
>\`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: [
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 19
+ column: 9,
+ endLine: 1,
+ endColumn: 31
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 18
+ column: 9,
+ endLine: 1,
+ endColumn: 30
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 25
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 19
+ column: 9,
+ endLine: 1,
+ endColumn: 31
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 18
+ column: 9,
+ endLine: 1,
+ endColumn: 30
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 17
+ column: 9,
+ endLine: 1,
+ endColumn: 29
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 18
+ column: 9,
+ endLine: 1,
+ endColumn: 30
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 21
+ column: 9,
+ endLine: 1,
+ endColumn: 33
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 28
+ column: 9,
+ endLine: 1,
+ endColumn: 40
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 15
+ column: 9,
+ endLine: 1,
+ endColumn: 28
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 17
+ column: 9,
+ endLine: 1,
+ endColumn: 30
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 28
+ column: 9,
+ endLine: 1,
+ endColumn: 40
}]
},
{
messageId: "unnecessaryConditionalExpression",
type: "ConditionalExpression",
line: 1,
- column: 16
+ column: 9,
+ endLine: 1,
+ endColumn: 28
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 4,
- column: 38
+ column: 30,
+ endLine: 4,
+ endColumn: 78
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 7
+ column: 1,
+ endLine: 1,
+ endColumn: 30
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 24
+ column: 18,
+ endLine: 1,
+ endColumn: 39
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 15
+ column: 9,
+ endLine: 1,
+ endColumn: 25
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 24
+ column: 9,
+ endLine: 1,
+ endColumn: 66
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 23
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 22
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 25
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 24
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 27
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 18
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 13
+ column: 9,
+ endLine: 1,
+ endColumn: 23
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 7
+ column: 3,
+ endLine: 1,
+ endColumn: 12
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 5
+ column: 1,
+ endLine: 1,
+ endColumn: 10
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 15
+ column: 9,
+ endLine: 1,
+ endColumn: 24
}]
},
{
messageId: "unnecessaryConditionalAssignment",
type: "ConditionalExpression",
line: 1,
- column: 15
+ column: 9,
+ endLine: 1,
+ endColumn: 27
}]
}
]
--- /dev/null
+/**
+ * @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) <body>",
+ "while (a && b) <body>"
+ ],
+ DoWhileStatement: [
+ "do <body> while (a)",
+ "do <body> while (a && b)"
+ ],
+ ForStatement: [
+ "for (a; b; c) <body>",
+ "for (var i = 0; i < a.length; i++) <body>",
+ "for (; b; c) <body>",
+ "for (; b < foo; c++) <body>",
+ "for (a; ; c) <body>",
+ "for (a = 0; ; c++) <body>",
+ "for (a; b;) <body>",
+ "for (a = 0; b < foo; ) <body>",
+ "for (; ; c) <body>",
+ "for (; ; c++) <body>",
+ "for (; b;) <body>",
+ "for (; b < foo; ) <body>",
+ "for (a; ;) <body>",
+ "for (a = 0; ;) <body>",
+ "for (;;) <body>"
+ ],
+ ForInStatement: [
+ "for (a in b) <body>",
+ "for (a in f(b)) <body>",
+ "for (var a in b) <body>",
+ "for (let a in f(b)) <body>"
+ ],
+ ForOfStatement: [
+ "for (a of b) <body>",
+ "for (a of f(b)) <body>",
+ "for ({ a, b } of c) <body>",
+ "for (var a of f(b)) <body>",
+ "async function foo() { for await (const a of b) <body> }"
+ ]
+};
+
+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>", body);
+
+ return body.includes("return") && !template.includes("function") ? `function someFunc() { ${loop} }` : loop;
+}
+
+/**
+ * Generates basic valid tests from `loopTemplates` and `validLoopBodies`
+ * @returns {IterableIterator<string>} 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<Object>} The list of objects for the invalid[] array.
+ */
+function *getBasicInvalidTests() {
+ for (const [type, templates] of Object.entries(loopTemplates)) {
+ for (const template of templates) {
+ yield* invalidLoopBodies.map(
+ body => ({
+ code: getSourceCode(template, body),
+ errors: [{ type, messageId: "invalid" }]
+ })
+ );
+ }
+ }
+}
+
+ruleTester.run("no-unreachable-loop", rule, {
+ valid: [
+
+ ...getBasicValidTests(),
+
+ // out of scope for the code path analysis and consequently out of scope for this rule
+ "while (false) { foo(); }",
+ "while (bar) { foo(); if (true) { break; } }",
+ "do foo(); while (false)",
+ "for (x = 1; x < 10; i++) { if (x > 0) { foo(); throw err; } }",
+ "for (x of []);",
+ "for (x of [1]);",
+
+ // doesn't report unreachable loop statements, regardless of whether they would be valid or not in a reachable position
+ "function foo() { return; while (a); }",
+ "function foo() { return; while (a) break; }",
+ "while(true); while(true);",
+ "while(true); while(true) break;",
+
+ // "ignore"
+ {
+ code: "while (a) break;",
+ options: [{ ignore: ["WhileStatement"] }]
+ },
+ {
+ code: "do break; while (a)",
+ options: [{ ignore: ["DoWhileStatement"] }]
+ },
+ {
+ code: "for (a; b; c) break;",
+ options: [{ ignore: ["ForStatement"] }]
+ },
+ {
+ code: "for (a in b) break;",
+ options: [{ ignore: ["ForInStatement"] }]
+ },
+ {
+ code: "for (a of b) break;",
+ options: [{ ignore: ["ForOfStatement"] }]
+ },
+ {
+ code: "for (var key in obj) { hasEnumerableProperties = true; break; } for (const a of b) break;",
+ options: [{ ignore: ["ForInStatement", "ForOfStatement"] }]
+ }
+ ],
+
+ invalid: [
+
+ ...getBasicInvalidTests(),
+
+ // invalid loop nested in a valid loop (valid in valid, and valid in invalid are covered by basic tests)
+ {
+ code: "while (foo) { for (a of b) { if (baz) { break; } else { throw err; } } }",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "ForOfStatement"
+ }
+ ]
+ },
+ {
+ code: "lbl: for (var i = 0; i < 10; i++) { while (foo) break lbl; } /* outer is valid because inner can have 0 iterations */",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "WhileStatement"
+ }
+ ]
+ },
+
+ // invalid loop nested in another invalid loop
+ {
+ code: "for (a in b) { while (foo) { if(baz) { break; } else { break; } } break; }",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "ForInStatement"
+ },
+ {
+ messageId: "invalid",
+ type: "WhileStatement"
+ }
+ ]
+ },
+
+ // loop and nested loop both invalid because of the same exit statement
+ {
+ code: "function foo() { for (var i = 0; i < 10; i++) { do { return; } while(i) } }",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "ForStatement"
+ },
+ {
+ messageId: "invalid",
+ type: "DoWhileStatement"
+ }
+ ]
+ },
+ {
+ code: "lbl: while(foo) { do { break lbl; } while(baz) }",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "WhileStatement"
+ },
+ {
+ messageId: "invalid",
+ type: "DoWhileStatement"
+ }
+ ]
+ },
+
+ // inner loop has continue, but to an outer loop
+ {
+ code: "lbl: for (a in b) { while(foo) { continue lbl; } }",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "WhileStatement"
+ }
+ ]
+ },
+
+ // edge cases - inner loop has only one exit path, but at the same time it exits the outer loop in the first iteration
+ {
+ code: "for (a of b) { for(;;) { if (foo) { throw err; } } }",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "ForOfStatement"
+ }
+ ]
+ },
+ {
+ code: "function foo () { for (a in b) { while (true) { if (bar) { return; } } } }",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "ForInStatement"
+ }
+ ]
+ },
+
+ // edge cases where parts of the loops belong to the same code path segment, tests for false negatives
+ {
+ code: "do for (var i = 1; i < 10; i++) break; while(foo)",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "ForStatement"
+ }
+ ]
+ },
+ {
+ code: "do { for (var i = 1; i < 10; i++) continue; break; } while(foo)",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "DoWhileStatement"
+ }
+ ]
+ },
+ {
+ code: "for (;;) { for (var i = 1; i < 10; i ++) break; if (foo) break; continue; }",
+ errors: [
+ {
+ messageId: "invalid",
+ type: "ForStatement",
+ column: 12
+ }
+ ]
+ },
+
+ // "ignore"
+ {
+ code: "while (a) break; do break; while (b); for (;;) break; for (c in d) break; for (e of f) break;",
+ options: [{ ignore: [] }],
+ errors: [
+ {
+ messageId: "invalid",
+ type: "WhileStatement"
+ },
+ {
+ messageId: "invalid",
+ type: "DoWhileStatement"
+ },
+ {
+ messageId: "invalid",
+ type: "ForStatement"
+ },
+ {
+ messageId: "invalid",
+ type: "ForInStatement"
+ },
+ {
+ messageId: "invalid",
+ type: "ForOfStatement"
+ }
+ ]
+ },
+ {
+ code: "while (a) break;",
+ options: [{ ignore: ["DoWhileStatement"] }],
+ errors: [
+ {
+ messageId: "invalid",
+ type: "WhileStatement"
+ }
+ ]
+ },
+ {
+ code: "do break; while (a)",
+ options: [{ ignore: ["WhileStatement"] }],
+ errors: [
+ {
+ messageId: "invalid",
+ type: "DoWhileStatement"
+ }
+ ]
+ },
+ {
+ code: "for (a in b) break; for (c of d) break;",
+ options: [{ ignore: ["ForStatement"] }],
+ errors: [
+ {
+ messageId: "invalid",
+ type: "ForInStatement"
+ },
+ {
+ messageId: "invalid",
+ type: "ForOfStatement"
+ }
+ ]
+ },
+ {
+ code: "for (a in b) break; for (;;) break; for (c of d) break;",
+ options: [{ ignore: ["ForInStatement", "ForOfStatement"] }],
+ errors: [
+ {
+ messageId: "invalid",
+ type: "ForStatement"
+ }
+ ]
+ }
+ ]
+});
{
code: "import(\"foo\")",
parserOptions: { ecmaVersion: 11 }
+ },
+ {
+ code: "func?.(\"foo\")",
+ parserOptions: { ecmaVersion: 11 }
+ },
+ {
+ code: "obj?.foo(\"bar\")",
+ parserOptions: { ecmaVersion: 11 }
}
],
invalid: [
options: [{ allowTaggedTemplates: false }],
parserOptions: { ecmaVersion: 6 },
errors: [{ messageId: "unusedExpression" }]
+ },
+
+ // Optional chaining
+ {
+ code: "obj?.foo",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
+ },
+ {
+ code: "obj?.foo.bar",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
+ },
+ {
+ code: "obj?.foo().bar",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
}
]
});
"foo.call();",
"obj.foo.call();",
"foo.apply();",
- "obj.foo.apply();"
+ "obj.foo.apply();",
+
+ // Optional chaining
+ {
+ code: "obj?.foo.bar.call(obj.foo, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 }
+ }
],
invalid: [
data: { name: "apply" },
type: "CallExpression"
}]
+ },
+
+ // Optional chaining
+ {
+ code: "foo.call?.(undefined, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unnecessaryCall", data: { name: "call" } }]
+ },
+ {
+ code: "foo?.call(undefined, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unnecessaryCall", data: { name: "call" } }]
+ },
+ {
+ code: "(foo?.call)(undefined, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unnecessaryCall", data: { name: "call" } }]
+ },
+ {
+ code: "obj.foo.call?.(obj, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{
+ messageId: "unnecessaryCall",
+ data: { name: "call" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "obj?.foo.call(obj, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{
+ messageId: "unnecessaryCall",
+ data: { name: "call" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "(obj?.foo).call(obj, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{
+ messageId: "unnecessaryCall",
+ data: { name: "call" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "(obj?.foo.call)(obj, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{
+ messageId: "unnecessaryCall",
+ data: { name: "call" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "obj?.foo.bar.call(obj?.foo, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{
+ messageId: "unnecessaryCall",
+ data: { name: "call" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "(obj?.foo).bar.call(obj?.foo, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{
+ messageId: "unnecessaryCall",
+ data: { name: "call" },
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "obj.foo?.bar.call(obj.foo, 1, 2);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{
+ messageId: "unnecessaryCall",
+ data: { name: "call" },
+ type: "CallExpression"
+ }]
}
]
});
{
code: "// fixme",
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "fixme" } }
]
},
{
code: "// any fixme",
options: [{ location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme" } }
]
},
{
code: "// any fixme",
options: [{ terms: ["fixme"], location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme" } }
]
},
{
code: "// any FIXME",
options: [{ terms: ["fixme"], location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any FIXME" } }
]
},
{
code: "// any fIxMe",
options: [{ terms: ["fixme"], location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fIxMe" } }
]
},
{
code: "/* any fixme */",
options: [{ terms: ["FIXME"], location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "FIXME" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "FIXME", comment: "any fixme" } }
]
},
{
code: "/* any FIXME */",
options: [{ terms: ["FIXME"], location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "FIXME" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "FIXME", comment: "any FIXME" } }
]
},
{
code: "/* any fIxMe */",
options: [{ terms: ["FIXME"], location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "FIXME" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "FIXME", comment: "any fIxMe" } }
]
},
{
code: "// any fixme or todo",
options: [{ terms: ["fixme", "todo"], location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "todo" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme or todo" } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any fixme or todo" } }
]
},
{
code: "/* any fixme or todo */",
options: [{ terms: ["fixme", "todo"], location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "todo" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme or todo" } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any fixme or todo" } }
]
},
{
code: "/* any fixme or todo */",
options: [{ location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any fixme or todo" } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme or todo" } }
]
},
{
code: "/* fixme and todo */",
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "fixme and todo" } }
]
},
{
code: "/* fixme and todo */",
options: [{ location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "fixme and todo" } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "fixme and todo" } }
]
},
{
code: "/* any fixme */",
options: [{ location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme" } }
]
},
{
code: "/* fixme! */",
options: [{ terms: ["fixme"] }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "fixme!" } }
]
},
{
code: "// regex [litera|$]",
options: [{ terms: ["[litera|$]"], location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "[litera|$]" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "[litera|$]", comment: "regex [litera|$]" } }
]
},
{
code: "/* eslint one-var: 2 */",
options: [{ terms: ["eslint"] }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "eslint" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "eslint", comment: "eslint one-var: 2" } }
]
},
{
code: "/* eslint one-var: 2 */",
options: [{ terms: ["one"], location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "one" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "one", comment: "eslint one-var: 2" } }
]
},
{
code: "/* any block comment with TODO, FIXME or XXX */",
options: [{ location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "xxx" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any block comment with TODO, FIXME or..." } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any block comment with TODO, FIXME or..." } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "xxx", comment: "any block comment with TODO, FIXME or..." } }
]
},
{
code: "/* any block comment with (TODO, FIXME's or XXX!) */",
options: [{ location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "xxx" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any block comment with (TODO, FIXME's or..." } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any block comment with (TODO, FIXME's or..." } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "xxx", comment: "any block comment with (TODO, FIXME's or..." } }
]
},
{
code: "/** \n *any block comment \n*with (TODO, FIXME's or XXX!) **/",
options: [{ location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "xxx" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "* *any block comment *with (TODO,..." } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "* *any block comment *with (TODO,..." } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "xxx", comment: "* *any block comment *with (TODO,..." } }
]
},
{
code: "// any comment with TODO, FIXME or XXX",
options: [{ location: "anywhere" }],
errors: [
- { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
- { messageId: "unexpectedComment", data: { matchedTerm: "xxx" } }
+ { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any comment with TODO, FIXME or XXX" } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any comment with TODO, FIXME or XXX" } },
+ { messageId: "unexpectedComment", data: { matchedTerm: "xxx", comment: "any comment with TODO, FIXME or XXX" } }
+ ]
+ },
+ {
+ code: "// TODO: something small",
+ options: [{ location: "anywhere" }],
+ errors: [
+ { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "TODO: something small" } }
+ ]
+ },
+ {
+ code: "// TODO: something really longer than 40 characters",
+ options: [{ location: "anywhere" }],
+ errors: [
+ { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "TODO: something really longer than 40..." } }
+ ]
+ },
+ {
+ code:
+ "/* TODO: something \n really longer than 40 characters \n and also a new line */",
+ options: [{ location: "anywhere" }],
+ errors: [
+ {
+ messageId: "unexpectedComment",
+ data: {
+ matchedTerm: "todo",
+ comment: "TODO: something really longer than 40..."
+ }
+ }
+ ]
+ },
+ {
+ code: "// TODO: small",
+ options: [{ location: "anywhere" }],
+ errors: [
+ {
+ messageId: "unexpectedComment",
+ data: {
+ matchedTerm: "todo",
+ comment: "TODO: small"
+ }
+ }
+ ]
+ },
+ {
+ code: "// https://github.com/eslint/eslint/pull/13522#discussion_r470293411 TODO",
+ options: [{ location: "anywhere" }],
+ errors: [
+ {
+ messageId: "unexpectedComment",
+ data: {
+ matchedTerm: "todo",
+ comment: "..."
+ }
+ }
]
}
]
"foo[bar.baz('qux')]",
"foo[(bar.baz() + 0) + qux]",
"foo['bar ' + 1 + ' baz']",
- "5['toExponential']()"
+ "5['toExponential']()",
+
+ // Optional chaining
+ {
+ code: "obj?.prop",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "( obj )?.prop",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj\n ?.prop",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.\n prop",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.[key]",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "( obj )?.[ key ]",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj\n ?.[key]",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj?.\n [key]",
+ parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "obj\n ?.\n [key]",
+ parserOptions: { ecmaVersion: 2020 }
+ }
],
invalid: [
data: { propName: "toExponential" }
}]
},
+ {
+ code: "08 .toExponential()",
+ output: null, // Not fixed
+ errors: [{
+ messageId: "unexpectedWhitespace",
+ data: { propName: "toExponential" }
+ }]
+ },
+ {
+ code: "0192 .toExponential()",
+ output: null, // Not fixed
+ errors: [{
+ messageId: "unexpectedWhitespace",
+ data: { propName: "toExponential" }
+ }]
+ },
+ {
+ code: "5_000 .toExponential()",
+ output: null, // Not fixed,
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "unexpectedWhitespace",
+ data: { propName: "toExponential" }
+ }]
+ },
+ {
+ code: "5_000_00 .toExponential()",
+ output: null, // Not fixed,
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "unexpectedWhitespace",
+ data: { propName: "toExponential" }
+ }]
+ },
{
code: "5. .toExponential()",
output: "5..toExponential()",
data: { propName: "toExponential" }
}]
},
+ {
+ code: "5.0_0 .toExponential()",
+ output: "5.0_0.toExponential()",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "unexpectedWhitespace",
+ data: { propName: "toExponential" }
+ }]
+ },
{
code: "0x5 .toExponential()",
output: "0x5.toExponential()",
data: { propName: "toExponential" }
}]
},
+ {
+ code: "0x56_78 .toExponential()",
+ output: "0x56_78.toExponential()",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "unexpectedWhitespace",
+ data: { propName: "toExponential" }
+ }]
+ },
{
code: "5e0 .toExponential()",
output: "5e0.toExponential()",
messageId: "unexpectedWhitespace",
data: { propName: "toExponential" }
}]
+ },
+
+ // Optional chaining
+ {
+ code: "obj?. prop",
+ output: "obj?.prop",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace", data: { propName: "prop" } }]
+ },
+ {
+ code: "obj ?.prop",
+ output: "obj?.prop",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace", data: { propName: "prop" } }]
+ },
+ {
+ code: "obj?. [key]",
+ output: "obj?.[key]",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace", data: { propName: "key" } }]
+ },
+ {
+ code: "obj ?.[key]",
+ output: "obj?.[key]",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace", data: { propName: "key" } }]
+ },
+ {
+ code: "5 ?. prop",
+ output: "5?.prop",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace", data: { propName: "prop" } }]
+ },
+ {
+ code: "5 ?. [key]",
+ output: "5?.[key]",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace", data: { propName: "key" } }]
+ },
+ {
+ code: "obj/* comment */?. prop",
+ output: null,
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace", data: { propName: "prop" } }]
+ },
+ {
+ code: "obj ?./* comment */prop",
+ output: null,
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "unexpectedWhitespace", data: { propName: "prop" } }]
}
]
});
].join("\n"),
options: ["always"],
errors: [
- { line: 1, column: 9, messageId: "expectedLinebreakAfterOpeningBrace" },
- { line: 1, column: 14, messageId: "expectedLinebreakBeforeClosingBrace" }
+ {
+ line: 1,
+ column: 9,
+ endLine: 1,
+ endColumn: 10,
+ messageId: "expectedLinebreakAfterOpeningBrace"
+ },
+ {
+ line: 1,
+ column: 14,
+ endLine: 1,
+ endColumn: 15,
+ messageId: "expectedLinebreakBeforeClosingBrace"
+ }
]
},
{
].join("\n"),
options: ["never"],
errors: [
- { line: 1, column: 9, messageId: "unexpectedLinebreakAfterOpeningBrace" },
- { line: 3, column: 1, messageId: "unexpectedLinebreakBeforeClosingBrace" }
+ {
+ line: 1,
+ column: 9,
+ endLine: 1,
+ endColumn: 10,
+ messageId: "unexpectedLinebreakAfterOpeningBrace"
+ },
+ {
+ line: 3,
+ column: 1,
+ endLine: 3,
+ endColumn: 2,
+ messageId: "unexpectedLinebreakBeforeClosingBrace"
+ }
]
},
{
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 25
+ column: 25,
+ endLine: 1,
+ endColumn: 27
},
{
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 37
+ column: 37,
+ endLine: 1,
+ endColumn: 39
+ }
+ ]
+ },
+ {
+ code: "var obj = { k1: 'val1', k2: \n'val2', \nk3: 'val3' };",
+ output: "var obj = { k1: 'val1',\nk2: \n'val2', \nk3: 'val3' };",
+ errors: [
+ {
+ messageId: "propertiesOnNewline",
+ type: "ObjectExpression",
+ line: 1,
+ column: 25,
+ endLine: 1,
+ endColumn: 27
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 13
+ column: 13,
+ endLine: 2,
+ endColumn: 15
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 13
+ column: 13,
+ endLine: 2,
+ endColumn: 15
},
{
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 3,
- column: 13
+ column: 13,
+ endLine: 3,
+ endColumn: 15
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 32
+ column: 32,
+ endLine: 1,
+ endColumn: 34
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 3,
- column: 4
+ column: 4,
+ endLine: 3,
+ endColumn: 6
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 13
+ column: 13,
+ endLine: 2,
+ endColumn: 15
},
{
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 29
+ column: 29,
+ endLine: 2,
+ endColumn: 31
},
{
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 41
+ column: 41,
+ endLine: 2,
+ endColumn: 43
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 3,
- column: 17
+ column: 17,
+ endLine: 3,
+ endColumn: 19
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 4,
- column: 4
+ column: 4,
+ endLine: 4,
+ endColumn: 6
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 25
+ column: 25,
+ endLine: 1,
+ endColumn: 26
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 25
+ column: 25,
+ endLine: 1,
+ endColumn: 28
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 13
+ column: 13,
+ endLine: 2,
+ endColumn: 16
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 19
+ column: 19,
+ endLine: 1,
+ endColumn: 21
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 13
+ column: 13,
+ endLine: 2,
+ endColumn: 15
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 10
+ column: 10,
+ endLine: 1,
+ endColumn: 11
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 4
+ column: 4,
+ endLine: 2,
+ endColumn: 5
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 11
+ column: 11,
+ endLine: 2,
+ endColumn: 14
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 15
+ column: 15,
+ endLine: 2,
+ endColumn: 18
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 19
+ column: 19,
+ endLine: 1,
+ endColumn: 20
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 19
+ column: 19,
+ endLine: 1,
+ endColumn: 22
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 2,
- column: 13
+ column: 13,
+ endLine: 2,
+ endColumn: 16
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 5,
- column: 4
+ column: 4,
+ endLine: 5,
+ endColumn: 5
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 26
+ column: 26,
+ endLine: 1,
+ endColumn: 29
}
]
},
messageId: "propertiesOnNewline",
type: "ObjectExpression",
line: 1,
- column: 26
+ column: 26,
+ endLine: 1,
+ endColumn: 29
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 3,
- column: 13
+ column: 13,
+ endLine: 3,
+ endColumn: 15
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 3,
- column: 9
+ column: 9,
+ endLine: 3,
+ endColumn: 11
},
{
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 3,
- column: 21
+ column: 21,
+ endLine: 3,
+ endColumn: 23
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 4,
- column: 4
+ column: 4,
+ endLine: 4,
+ endColumn: 6
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 3,
- column: 4
+ column: 4,
+ endLine: 3,
+ endColumn: 6
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 2,
- column: 13
+ column: 13,
+ endLine: 2,
+ endColumn: 15
},
{
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 4,
- column: 4
+ column: 4,
+ endLine: 4,
+ endColumn: 6
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 4,
- column: 4
+ column: 4,
+ endLine: 4,
+ endColumn: 6
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 2,
- column: 14
+ column: 14,
+ endLine: 2,
+ endColumn: 16
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 3,
- column: 13
+ column: 13,
+ endLine: 3,
+ endColumn: 16
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 3,
- column: 13
+ column: 13,
+ endLine: 3,
+ endColumn: 15
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 2,
- column: 14
+ column: 14,
+ endLine: 2,
+ endColumn: 16
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 3,
- column: 13
+ column: 13,
+ endLine: 3,
+ endColumn: 16
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 3,
- column: 13
+ column: 13,
+ endLine: 3,
+ endColumn: 15
}
]
},
messageId: "propertiesOnNewlineAll",
type: "ObjectExpression",
line: 3,
- column: 13
+ column: 13,
+ endLine: 3,
+ endColumn: 15
}
]
}
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 7 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
const EXPECTED_OPERATOR_ASSIGNMENT = [{ messageId: "replaced", type: "AssignmentExpression" }];
const UNEXPECTED_OPERATOR_ASSIGNMENT = [{ messageId: "unexpected", type: "AssignmentExpression" }];
{
code: "this.x = foo.this.x + y",
options: ["always"]
+ },
+
+ // does not check logical operators
+ {
+ code: "x = x && y",
+ options: ["always"]
+ },
+ {
+ code: "x = x || y",
+ options: ["always"]
+ },
+ {
+ code: "x = x ?? y",
+ options: ["always"]
+ },
+ {
+ code: "x &&= y",
+ options: ["never"]
+ },
+ {
+ code: "x ||= y",
+ options: ["never"]
+ },
+ {
+ code: "x ??= y",
+ options: ["never"]
}
],
output: "foo= foo+(+bar===baz)", // tokens cannot be adjacent, but the right side will be parenthesised
options: ["never"],
errors: UNEXPECTED_OPERATOR_ASSIGNMENT
- }]
+ },
+
+ // Optional chaining
+ {
+ code: "(obj?.a).b = (obj?.a).b + y",
+ output: null,
+ errors: EXPECTED_OPERATOR_ASSIGNMENT
+ },
+ {
+ code: "obj.a = obj?.a + b",
+ output: null,
+ errors: EXPECTED_OPERATOR_ASSIGNMENT
+ }
+
+ ]
});
{ code: "1 + 1\n", options: ["none"] },
{ code: "answer = everything ? 42 : foo;", options: ["none"] },
{ code: "answer = everything \n?\n 42 : foo;", options: [null, { overrides: { "?": "ignore" } }] },
- { code: "answer = everything ? 42 \n:\n foo;", options: [null, { overrides: { ":": "ignore" } }] }
+ { code: "answer = everything ? 42 \n:\n foo;", options: [null, { overrides: { ":": "ignore" } }] },
+
+ {
+ code: "a \n &&= b",
+ options: ["after", { overrides: { "&&=": "ignore" } }],
+ parserOptions: { ecmaVersion: 2021 }
+ },
+ {
+ code: "a ??= \n b",
+ options: ["before", { overrides: { "??=": "ignore" } }],
+ parserOptions: { ecmaVersion: 2021 }
+ },
+ {
+ code: "a ||= \n b",
+ options: ["after", { overrides: { "=": "before" } }],
+ parserOptions: { ecmaVersion: 2021 }
+ },
+ {
+ code: "a \n &&= b",
+ options: ["before", { overrides: { "&=": "after" } }],
+ parserOptions: { ecmaVersion: 2021 }
+ },
+ {
+ code: "a \n ||= b",
+ options: ["before", { overrides: { "|=": "after" } }],
+ parserOptions: { ecmaVersion: 2021 }
+ },
+ {
+ code: "a &&= \n b",
+ options: ["after", { overrides: { "&&": "before" } }],
+ parserOptions: { ecmaVersion: 2021 }
+ },
+ {
+ code: "a ||= \n b",
+ options: ["after", { overrides: { "||": "before" } }],
+ parserOptions: { ecmaVersion: 2021 }
+ },
+ {
+ code: "a ??= \n b",
+ options: ["after", { overrides: { "??": "before" } }],
+ parserOptions: { ecmaVersion: 2021 }
+ }
],
invalid: [
messageId: "operatorAtBeginning",
data: { operator: "??" }
}]
+ },
+
+ {
+ code: "a \n &&= b",
+ output: "a &&= \n b",
+ options: ["after"],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "operatorAtEnd",
+ data: { operator: "&&=" },
+ type: "AssignmentExpression",
+ line: 2,
+ column: 3,
+ endLine: 2,
+ endColumn: 6
+ }]
+ },
+ {
+ code: "a ||=\n b",
+ output: "a\n ||= b",
+ options: ["before"],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "operatorAtBeginning",
+ data: { operator: "||=" },
+ type: "AssignmentExpression",
+ line: 1,
+ column: 3,
+ endLine: 1,
+ endColumn: 6
+ }]
+ },
+ {
+ code: "a ??=\n b",
+ output: "a ??= b",
+ options: ["none"],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "noLinebreak",
+ data: { operator: "??=" },
+ type: "AssignmentExpression",
+ line: 1,
+ column: 4,
+ endLine: 1,
+ endColumn: 7
+ }]
+ },
+ {
+ code: "a \n &&= b",
+ output: "a &&= b",
+ options: ["before", { overrides: { "&&=": "none" } }],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "noLinebreak",
+ data: { operator: "&&=" },
+ type: "AssignmentExpression",
+ line: 2,
+ column: 3,
+ endLine: 2,
+ endColumn: 6
+ }]
+ },
+ {
+ code: "a ||=\nb",
+ output: "a\n||= b",
+ options: ["after", { overrides: { "||=": "before" } }],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "operatorAtBeginning",
+ data: { operator: "||=" },
+ type: "AssignmentExpression",
+ line: 1,
+ column: 3,
+ endLine: 1,
+ endColumn: 6
+ }]
+ },
+ {
+ code: "a\n??=b",
+ output: "a??=\nb",
+ options: ["none", { overrides: { "??=": "after" } }],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "operatorAtEnd",
+ data: { operator: "??=" },
+ type: "AssignmentExpression",
+ line: 2,
+ column: 1,
+ endLine: 2,
+ endColumn: 4
+ }]
}
]
});
errors: [{ messageId: "expectedBlankLine" }]
},
+ // Optional chaining
+ {
+ code: "(function(){\n})?.()\nvar a = 2;",
+ output: "(function(){\n})?.()\n\nvar a = 2;",
+ options: [{ blankLine: "always", prev: "iife", next: "*" }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expectedBlankLine" }]
+ },
+ {
+ code: "void (function(){\n})?.()\nvar a = 2;",
+ output: "void (function(){\n})?.()\n\nvar a = 2;",
+ options: [{ blankLine: "always", prev: "iife", next: "*" }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "expectedBlankLine" }]
+ },
+
//----------------------------------------------------------------------
// import
//----------------------------------------------------------------------
type: "FunctionExpression"
}];
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
ruleTester.run("prefer-arrow-callback", rule, {
valid: [
"foo(function bar() { arguments; }.bind(this));",
"foo(function bar() { new.target; });",
"foo(function bar() { new.target; }.bind(this));",
- "foo(function bar() { this; }.bind(this, somethingElse));"
+ "foo(function bar() { this; }.bind(this, somethingElse));",
+ "foo((function() {}).bind.bar)",
+ "foo((function() { this.bar(); }).bind(obj).bind(this))"
],
invalid: [
{
{
code: "qux(async function (foo = 1, bar = 2, baz = 3) { return baz; })",
output: "qux(async (foo = 1, bar = 2, baz = 3) => { return baz; })",
- parserOptions: { ecmaVersion: 8 },
errors
},
{
code: "qux(async function (foo = 1, bar = 2, baz = 3) { return this; }.bind(this))",
output: "qux(async (foo = 1, bar = 2, baz = 3) => { return this; })",
- parserOptions: { ecmaVersion: 8 },
+ errors
+ },
+ {
+ code: "foo((bar || function() {}).bind(this))",
+ output: null,
+ errors
+ },
+ {
+ code: "foo(function() {}.bind(this).bind(obj))",
+ output: "foo((() => {}).bind(obj))",
+ errors
+ },
+
+ // Optional chaining
+ {
+ code: "foo?.(function() {});",
+ output: "foo?.(() => {});",
+ errors
+ },
+ {
+ code: "foo?.(function() { return this; }.bind(this));",
+ output: "foo?.(() => { return this; });",
+ errors
+ },
+ {
+ code: "foo(function() { return this; }?.bind(this));",
+ output: "foo(() => { return this; });",
+ errors
+ },
+ {
+ code: "foo((function() { return this; }?.bind)(this));",
+ output: null,
errors
}
]
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
ruleTester.run("prefer-destructuring", rule, {
valid: [
},
"[foo] = array;",
"foo += array[0]",
+ {
+ code: "foo &&= array[0]",
+ parserOptions: { ecmaVersion: 2021 }
+ },
"foo += bar.foo",
+ {
+ code: "foo ||= bar.foo",
+ parserOptions: { ecmaVersion: 2021 }
+ },
+ {
+ code: "foo ??= bar['foo']",
+ parserOptions: { ecmaVersion: 2021 }
+ },
{
code: "foo = object.foo;",
options: [{ AssignmentExpression: { object: false } }, { enforceForRenamedProperties: true }]
{
code: "var {bar} = object.foo;",
options: [{ object: true }]
- }
+ },
+
+ // Optional chaining
+ "var foo = array?.[0];", // because the fixed code can throw TypeError.
+ "var foo = object?.foo;"
],
invalid: [
type: "VariableDeclarator"
}]
},
+ {
+ code: "var foo = (a, b).foo;",
+ output: "var {foo} = (a, b);",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var length = (() => {}).length;",
+ output: "var {length} = () => {};",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = (a = b).foo;",
+ output: "var {foo} = a = b;",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = (a || b).foo;",
+ output: "var {foo} = a || b;",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = (f()).foo;",
+ output: "var {foo} = f();",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
{
code: "var foo = object.bar.foo;",
output: "var {foo} = object.bar;",
type: "VariableDeclarator"
}]
},
+ {
+ code: "var foo = object[foo];",
+ output: null,
+ options: [{ object: true }, { enforceForRenamedProperties: true }],
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
{
code: "var foo = object['foo'];",
output: null,
data: { type: "object" },
type: "VariableDeclarator"
}]
+ },
+
+ // comments
+ {
+ code: "var /* comment */ foo = object.foo;",
+ output: "var /* comment */ {foo} = object;",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var a, /* comment */foo = object.foo;",
+ output: "var a, /* comment */{foo} = object;",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo /* comment */ = object.foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var a, foo /* comment */ = object.foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo /* comment */ = object.foo, a;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo // comment\n = object.foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = /* comment */ object.foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = // comment\n object.foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = (/* comment */ object).foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = (object /* comment */).foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = bar(/* comment */).foo;",
+ output: "var {foo} = bar(/* comment */);",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = bar/* comment */.baz.foo;",
+ output: "var {foo} = bar/* comment */.baz;",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = bar[// comment\nbaz].foo;",
+ output: "var {foo} = bar[// comment\nbaz];",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo // comment\n = bar(/* comment */).foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = bar/* comment */.baz/* comment */.foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = object// comment\n.foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = object./* comment */foo;",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = (/* comment */ object.foo);",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = (object.foo /* comment */);",
+ output: null,
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = object.foo/* comment */;",
+ output: "var {foo} = object/* comment */;",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = object.foo// comment",
+ output: "var {foo} = object// comment",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = object.foo/* comment */, a;",
+ output: "var {foo} = object/* comment */, a;",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = object.foo// comment\n, a;",
+ output: "var {foo} = object// comment\n, a;",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
+ },
+ {
+ code: "var foo = object.foo, /* comment */ a;",
+ output: "var {foo} = object, /* comment */ a;",
+ errors: [{
+ messageId: "preferDestructuring",
+ data: { type: "object" },
+ type: "VariableDeclarator"
+ }]
}
]
});
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
ruleTester.run("prefer-exponentiation-operator", rule, {
valid: [
invalid("Math.pow(a, b/**/)", null),
invalid("Math.pow(a, b//\n)", null),
invalid("Math.pow(a, b)/* comment */;", "a**b/* comment */;"),
- invalid("Math.pow(a, b)// comment\n;", "a**b// comment\n;")
+ invalid("Math.pow(a, b)// comment\n;", "a**b// comment\n;"),
+
+ // Optional chaining
+ invalid("Math.pow?.(a, b)", "a**b"),
+ invalid("Math?.pow(a, b)", "a**b"),
+ invalid("Math?.pow?.(a, b)", "a**b"),
+ invalid("(Math?.pow)(a, b)", "a**b"),
+ invalid("(Math?.pow)?.(a, b)", "a**b")
]
});
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
ruleTester.run("prefer-numeric-literals", rule, {
valid: [
code: "parseInt('11', 2)//comment\n;",
output: "0b11//comment\n;",
errors: 1
+ },
+
+ // Optional chaining
+ {
+ code: "parseInt?.(\"1F7\", 16) === 255;",
+ output: "0x1F7 === 255;",
+ errors: [{ message: "Use hexadecimal literals instead of parseInt()." }]
+ },
+ {
+ code: "Number?.parseInt(\"1F7\", 16) === 255;",
+ output: "0x1F7 === 255;",
+ errors: [{ message: "Use hexadecimal literals instead of Number?.parseInt()." }]
+ },
+ {
+ code: "Number?.parseInt?.(\"1F7\", 16) === 255;",
+ output: "0x1F7 === 255;",
+ errors: [{ message: "Use hexadecimal literals instead of Number?.parseInt()." }]
+ },
+ {
+ code: "(Number?.parseInt)(\"1F7\", 16) === 255;",
+ output: "0x1F7 === 255;",
+ errors: [{ message: "Use hexadecimal literals instead of Number?.parseInt()." }]
+ },
+ {
+ code: "(Number?.parseInt)?.(\"1F7\", 16) === 255;",
+ output: "0x1F7 === 255;",
+ errors: [{ message: "Use hexadecimal literals instead of Number?.parseInt()." }]
+ },
+
+ // `parseInt` doesn't support numeric separators. The rule shouldn't autofix in those cases.
+ {
+ code: "parseInt('1_0', 2);",
+ output: null,
+ errors: [{ message: "Use binary literals instead of parseInt()." }]
+ },
+ {
+ code: "Number.parseInt('5_000', 8);",
+ output: null,
+ errors: [{ message: "Use octal literals instead of Number.parseInt()." }]
+ },
+ {
+ code: "parseInt('0_1', 16);",
+ output: null,
+ errors: [{ message: "Use hexadecimal literals instead of parseInt()." }]
+ },
+ {
+
+ // this would be indeed the same as `0x0_0`, but there's no need to autofix this edge case that looks more like a mistake.
+ code: "Number.parseInt('0_0', 16);",
+ output: null,
+ errors: [{ message: "Use hexadecimal literals instead of Number.parseInt()." }]
}
]
});
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
ruleTester.run("prefer-promise-reject-errors", rule, {
"Promise.reject(new Error())",
"Promise.reject(new TypeError)",
"Promise.reject(new Error('foo'))",
+ "Promise.reject(foo || 5)",
+ "Promise.reject(5 && foo)",
"new Foo((resolve, reject) => reject(5))",
"new Promise(function(resolve, reject) { return function(reject) { reject(5) } })",
"new Promise(function(resolve, reject) { if (foo) { const reject = somethingElse; reject(5) } })",
{
code: "new Promise(function(resolve, reject) { reject() })",
options: [{ allowEmptyReject: true }]
- }
+ },
+
+ // Optional chaining
+ "Promise.reject(obj?.foo)",
+ "Promise.reject(obj?.foo())",
+
+ // Assignments
+ "Promise.reject(foo = new Error())",
+ "Promise.reject(foo ||= 5)",
+ "Promise.reject(foo.bar ??= 5)",
+ "Promise.reject(foo[bar] ??= 5)"
],
invalid: [
"new Promise((foo, arguments) => arguments(5))",
"new Promise(function({}, reject) { reject(5) })",
"new Promise(({}, reject) => reject(5))",
- "new Promise((resolve, reject, somethingElse = reject(5)) => {})"
+ "new Promise((resolve, reject, somethingElse = reject(5)) => {})",
+
+ // Optional chaining
+ "Promise.reject?.(5)",
+ "Promise?.reject(5)",
+ "Promise?.reject?.(5)",
+ "(Promise?.reject)(5)",
+ "(Promise?.reject)?.(5)",
+
+ // Assignments with mathematical operators will either evaluate to a primitive value or throw a TypeError
+ "Promise.reject(foo += new Error())",
+ "Promise.reject(foo -= new Error())",
+ "Promise.reject(foo **= new Error())",
+ "Promise.reject(foo <<= new Error())",
+ "Promise.reject(foo |= new Error())",
+ "Promise.reject(foo &= new Error())",
+
+ // evaluates either to a falsy value of `foo` (which, then, cannot be an Error object), or to `5`
+ "Promise.reject(foo && 5)",
+ "Promise.reject(foo &&= 5)"
+
].map(invalidCase => {
const errors = { errors: [{ messageId: "rejectAnError", type: "CallExpression" }] };
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
ruleTester.run("prefer-regex-literals", rule, {
valid: [
"/abc/",
"/abc/g",
+
// considered as dynamic
"new RegExp(pattern)",
"RegExp(pattern, 'g')",
"new RegExp(String.raw`a${''}c`);",
"new RegExp('a' + 'b')",
"RegExp(1)",
+ "new RegExp(/a/, 'u');",
+ "new RegExp(/a/);",
+ {
+ code: "new RegExp(/a/, flags);",
+ options: [{ disallowRedundantWrapping: true }]
+ },
+ {
+ code: "new RegExp(/a/, `u${flags}`);",
+ options: [{ disallowRedundantWrapping: true }]
+ },
+
+ // redundant wrapping is allowed
+ {
+ code: "new RegExp(/a/);",
+ options: [{}]
+ },
+ {
+ code: "new RegExp(/a/);",
+ options: [{ disallowRedundantWrapping: false }]
+ },
// invalid number of arguments
"new RegExp;",
"RegExp(`a`, `g`, `b`);",
"new RegExp(String.raw`a`, String.raw`g`, String.raw`b`);",
"RegExp(String.raw`a`, String.raw`g`, String.raw`b`);",
+ {
+ code: "new RegExp(/a/, 'u', 'foo');",
+ options: [{ disallowRedundantWrapping: true }]
+ },
// not String.raw``
"new RegExp(String`a`);",
code: "globalThis.RegExp('a');",
env: { es2020: true },
errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+ },
+
+ {
+ code: "new RegExp(/a/);",
+ options: [{ disallowRedundantWrapping: true }],
+ errors: [{ messageId: "unexpectedRedundantRegExp", type: "NewExpression", line: 1, column: 1 }]
+ },
+ {
+ code: "new RegExp(/a/, 'u');",
+ options: [{ disallowRedundantWrapping: true }],
+ errors: [{ messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, column: 1 }]
+ },
+ {
+ code: "new RegExp(/a/, `u`);",
+ options: [{ disallowRedundantWrapping: true }],
+ errors: [{ messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, column: 1 }]
+ },
+ {
+ code: "new RegExp('a');",
+ options: [{ disallowRedundantWrapping: true }],
+ errors: [{ messageId: "unexpectedRegExp", type: "NewExpression", line: 1, column: 1 }]
+ },
+
+ // Optional chaining
+ {
+ code: "new RegExp((String?.raw)`a`);",
+ errors: [{ messageId: "unexpectedRegExp" }]
}
]
});
const errors = [{ messageId: "preferSpread", type: "CallExpression" }];
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
ruleTester.run("prefer-spread", rule, {
valid: [
// ignores incomplete things.
"foo.apply();",
"obj.foo.apply();",
- "obj.foo.apply(obj, ...args)"
+ "obj.foo.apply(obj, ...args)",
+
+ // Optional chaining
+ "(a?.b).c.foo.apply(a?.b.c, args);",
+ "a?.b.c.foo.apply((a?.b).c, args);"
],
invalid: [
{
{
code: "[].concat.apply([\n/*empty*/\n], args);",
errors
+ },
+
+ // Optional chaining
+ {
+ code: "foo.apply?.(undefined, args);",
+ errors
+ },
+ {
+ code: "foo?.apply(undefined, args);",
+ errors
+ },
+ {
+ code: "foo?.apply?.(undefined, args);",
+ errors
+ },
+ {
+ code: "(foo?.apply)(undefined, args);",
+ errors
+ },
+ {
+ code: "(foo?.apply)?.(undefined, args);",
+ errors
+ },
+ {
+ code: "(obj?.foo).apply(obj, args);",
+ errors
+ },
+ {
+ code: "a?.b.c.foo.apply(a?.b.c, args);",
+ errors
+ },
+ {
+ code: "(a?.b.c).foo.apply(a?.b.c, args);",
+ errors
+ },
+ {
+ code: "(a?.b).c.foo.apply((a?.b).c, args);",
+ errors
}
]
});
output: null,
errors
},
+ {
+ code: "foo + 'does not autofix non-octal decimal escape sequence' + '\\8'",
+ output: null,
+ errors
+ },
{
code: "foo + '\\n other text \\033'",
output: null,
{ code: "({ 1n: 1 })", options: ["consistent"], parserOptions: { ecmaVersion: 2020 } },
{ code: "({ 1n: 1 })", options: ["consistent-as-needed"], parserOptions: { ecmaVersion: 2020 } },
{ code: "({ '99999999999999999': 1 })", options: ["as-needed"], parserOptions: { ecmaVersion: 2020 } },
- { code: "({ '1n': 1 })", options: ["as-needed"], parserOptions: { ecmaVersion: 2020 } }
+ { code: "({ '1n': 1 })", options: ["as-needed"], parserOptions: { ecmaVersion: 2020 } },
+ { code: "({ 1_0: 1 })", options: ["as-needed"], parserOptions: { ecmaVersion: 2021 } },
+ { code: "({ 1_0: 1 })", options: ["as-needed", { numbers: false }], parserOptions: { ecmaVersion: 2021 } },
+ { code: "({ '1_0': 1 })", options: ["as-needed"], parserOptions: { ecmaVersion: 2021 } },
+ { code: "({ '1_0': 1 })", options: ["as-needed", { numbers: false }], parserOptions: { ecmaVersion: 2021 } },
+ { code: "({ '1_0': 1 })", options: ["as-needed", { numbers: true }], parserOptions: { ecmaVersion: 2021 } },
+ { code: "({ 1_0: 1, 1: 1 })", options: ["consistent-as-needed"], parserOptions: { ecmaVersion: 2021 } }
],
invalid: [{
code: "({ a: 0 })",
messageId: "unquotedNumericProperty",
data: { property: "1" }
}]
+ }, {
+ code: "({ 1_0: 1 })",
+ output: "({ \"10\": 1 })",
+ options: ["as-needed", { numbers: true }],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "unquotedNumericProperty",
+ data: { property: "10" }
+ }]
+ }, {
+ code: "({ 1_2.3_4e0_2: 1 })",
+ output: "({ \"1234\": 1 })",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "unquotedPropertyFound",
+ data: { property: "1234" }
+ }]
+ }, {
+ code: "({ 0b1_000: 1 })",
+ output: "({ \"8\": 1 })",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "unquotedPropertyFound",
+ data: { property: "8" }
+ }]
+ }, {
+ code: "({ 1_000: a, '1_000': b })",
+ output: "({ \"1000\": a, '1_000': b })",
+ options: ["consistent-as-needed"],
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "inconsistentlyQuotedProperty",
+ data: { key: "1000" }
+ }]
}]
});
type: "Literal"
}
]
+ },
+ {
+ code: "var nonOctalDecimalEscape = '\\8'",
+ output: null,
+ options: ["backtick"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "wrongQuotes",
+ data: { description: "backtick" },
+ type: "Literal"
+ }
+ ]
}
]
});
messageId: "redundantRadix",
type: "CallExpression"
}]
+ },
+
+ // Optional chaining
+ {
+ code: "parseInt?.(\"10\");",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "missingRadix" }]
+ },
+ {
+ code: "Number.parseInt?.(\"10\");",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "missingRadix" }]
+ },
+ {
+ code: "Number?.parseInt(\"10\");",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "missingRadix" }]
+ },
+ {
+ code: "(Number?.parseInt)(\"10\");",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "missingRadix" }]
}
]
});
"function foo(){return 2;}",
"for(var i = 0; i < results.length;) {}",
{ code: "function foo() { return 2; }", options: [{ after: false }] },
- { code: "for ( var i = 0;i < results.length; ) {}", options: [{ after: false }] }
+ { code: "for ( var i = 0;i < results.length; ) {}", options: [{ after: false }] },
+
+ "do {} while (true); foo"
],
invalid: [
{
endColumn: 7
}
]
+ },
+ {
+ code: "do {} while (true) ;",
+ output: "do {} while (true);",
+ errors: [{
+ messageId: "unexpectedWhitespaceBefore",
+ type: "DoWhileStatement",
+ line: 1,
+ column: 19,
+ endLine: 1,
+ endColumn: 20
+ }]
+ },
+ {
+ code: "do {} while (true);foo",
+ output: "do {} while (true); foo",
+ errors: [{
+ messageId: "missingWhitespaceAfter",
+ type: "DoWhileStatement",
+ line: 1,
+ column: 19,
+ endLine: 1,
+ endColumn: 20
+ }]
+ },
+ {
+ code: "do {} while (true); foo",
+ output: "do {} while (true) ;foo",
+ options: [{ before: true, after: false }],
+ errors: [{
+ messageId: "missingWhitespaceBefore",
+ type: "DoWhileStatement",
+ line: 1,
+ column: 19,
+ endLine: 1,
+ endColumn: 20
+ },
+ {
+ messageId: "unexpectedWhitespaceAfter",
+ type: "DoWhileStatement",
+ line: 1,
+ column: 20,
+ endLine: 1,
+ endColumn: 22
+ }]
}
]
});
},
// https://github.com/eslint/eslint/issues/5305
- "import React, {Component} from 'react';"
+ "import React, {Component} from 'react';",
+
+ // allowSeparatedGroups
+ {
+ code: "import b from 'b';\n\nimport a from 'a';",
+ options: [{ allowSeparatedGroups: true }]
+ },
+ {
+ code: "import a from 'a';\n\nimport 'b';",
+ options: [{ allowSeparatedGroups: true }]
+ },
+ {
+ code: "import { b } from 'b';\n\n\nimport { a } from 'a';",
+ options: [{ allowSeparatedGroups: true }]
+ },
+ {
+ code: "import b from 'b';\n// comment\nimport a from 'a';",
+ options: [{ allowSeparatedGroups: true }]
+ },
+ {
+ code: "import b from 'b';\nfoo();\nimport a from 'a';",
+ options: [{ allowSeparatedGroups: true }]
+ },
+ {
+ code: "import { b } from 'b';/*\n comment \n*/import { a } from 'a';",
+ options: [{ allowSeparatedGroups: true }]
+ },
+ {
+ code: "import b from\n'b';\n\nimport\n a from 'a';",
+ options: [{ allowSeparatedGroups: true }]
+ },
+ {
+ code: "import c from 'c';\n\nimport a from 'a';\nimport b from 'b';",
+ options: [{ allowSeparatedGroups: true }]
+ },
+ {
+ code: "import c from 'c';\n\nimport b from 'b';\n\nimport a from 'a';",
+ options: [{ allowSeparatedGroups: true }]
+ }
],
invalid: [
{
data: { memberName: "qux" },
type: "ImportSpecifier"
}]
+ },
+
+ // allowSeparatedGroups
+ {
+ code: "import b from 'b';\nimport a from 'a';",
+ output: null,
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import b from 'b';\nimport a from 'a';",
+ output: null,
+ options: [{}],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import b from 'b';\nimport a from 'a';",
+ output: null,
+ options: [{ allowSeparatedGroups: false }],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import b from 'b';import a from 'a';",
+ output: null,
+ options: [{ allowSeparatedGroups: false }],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import b from 'b'; /* comment */ import a from 'a';",
+ output: null,
+ options: [{ allowSeparatedGroups: false }],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import b from 'b'; // comment\nimport a from 'a';",
+ output: null,
+ options: [{ allowSeparatedGroups: false }],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import b from 'b'; // comment 1\n/* comment 2 */import a from 'a';",
+ output: null,
+ options: [{ allowSeparatedGroups: false }],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import { b } from 'b'; /* comment line 1 \n comment line 2 */ import { a } from 'a';",
+ output: null,
+ options: [{ allowSeparatedGroups: false }],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import b\nfrom 'b'; import a\nfrom 'a';",
+ output: null,
+ options: [{ allowSeparatedGroups: false }],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import { b } from \n'b'; /* comment */ import\n { a } from 'a';",
+ output: null,
+ options: [{ allowSeparatedGroups: false }],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import { b } from \n'b';\nimport\n { a } from 'a';",
+ output: null,
+ options: [{ allowSeparatedGroups: false }],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration"
+ }]
+ },
+ {
+ code: "import c from 'c';\n\nimport b from 'b';\nimport a from 'a';",
+ output: null,
+ options: [{ allowSeparatedGroups: true }],
+ errors: [{
+ messageId: "sortImportsAlphabetically",
+ type: "ImportDeclaration",
+ line: 4
+ }]
+ },
+ {
+ code: "import b from 'b';\n\nimport { c, a } from 'c';",
+ output: "import b from 'b';\n\nimport { a, c } from 'c';",
+ options: [{ allowSeparatedGroups: true }],
+ errors: [{
+ messageId: "sortMembersAlphabetically",
+ type: "ImportSpecifier"
+ }]
}
]
});
//------------------------------------------------------------------------------
const rule = require("../../../lib/rules/space-before-blocks"),
- { RuleTester } = require("../../../lib/rule-tester");
+ { RuleTester } = require("../../../lib/rule-tester"),
+ fixtureParser = require("../../fixtures/fixture-parser");
//------------------------------------------------------------------------------
// Tests
options: classesNeverOthersOffArgs,
parserOptions: { ecmaVersion: 6 },
errors: [expectedNoSpacingError]
+ },
+
+ // https://github.com/eslint/eslint/issues/13553
+ {
+ code: "class A { foo(bar: string): void{} }",
+ output: "class A { foo(bar: string): void {} }",
+ parser: fixtureParser("space-before-blocks", "return-type-keyword-1"),
+ errors: [expectedSpacingError]
+ },
+ {
+ code: "function foo(): null {}",
+ output: "function foo(): null{}",
+ options: neverArgs,
+ parser: fixtureParser("space-before-blocks", "return-type-keyword-2"),
+ errors: [expectedNoSpacingError]
}
]
});
{ code: "const foo = function(a: number = 0): Bar { };", parser: parser("type-annotations/function-expression-type-annotation"), parserOptions: { ecmaVersion: 6 } },
// TypeScript Type Aliases
- { code: "type Foo<T> = T;", parser: parser("typescript-parsers/type-alias"), parserOptions: { ecmaVersion: 6 } }
+ { code: "type Foo<T> = T;", parser: parser("typescript-parsers/type-alias"), parserOptions: { ecmaVersion: 6 } },
+
+ { code: "a &&= b", parserOptions: { ecmaVersion: 2021 } },
+ { code: "a ||= b", parserOptions: { ecmaVersion: 2021 } },
+ { code: "a ??= b", parserOptions: { ecmaVersion: 2021 } }
],
invalid: [
{
},
// Type Annotations
-
{
code: "var a: Foo= b;",
output: "var a: Foo = b;",
column: 23,
type: "AssignmentPattern"
}]
+ },
+
+ {
+ code: "a&&=b",
+ output: "a &&= b",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "missingSpace",
+ data: { operator: "&&=" },
+ line: 1,
+ column: 2,
+ endLine: 1,
+ endColumn: 5,
+ type: "AssignmentExpression"
+ }]
+ },
+ {
+ code: "a ||=b",
+ output: "a ||= b",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "missingSpace",
+ data: { operator: "||=" },
+ line: 1,
+ column: 3,
+ endLine: 1,
+ endColumn: 6,
+ type: "AssignmentExpression"
+ }]
+ },
+ {
+ code: "a??= b",
+ output: "a ??= b",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "missingSpace",
+ data: { operator: "??=" },
+ line: 1,
+ column: 2,
+ endLine: 1,
+ endColumn: 5,
+ type: "AssignmentExpression"
+ }]
}
]
});
code: "foo.bar.lastIndexOf(NaN)",
options: [{ enforceForIndexOf: true }],
errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
+ },
+ {
+ code: "foo.indexOf?.(NaN)",
+ options: [{ enforceForIndexOf: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
+ },
+ {
+ code: "foo?.indexOf(NaN)",
+ options: [{ enforceForIndexOf: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
+ },
+ {
+ code: "(foo?.indexOf)(NaN)",
+ options: [{ enforceForIndexOf: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
}
]
});
//------------------------------------------------------------------------------
const assert = require("chai").assert,
+ util = require("util"),
espree = require("espree"),
astUtils = require("../../../../lib/rules/utils/ast-utils"),
{ Linter } = require("../../../../lib/linter"),
assert.strictEqual(astUtils.getStaticStringValue(ast.body[0].expression), expectedResults[key]);
});
});
+
+ it("should return text of regex literal even if it's not supported natively.", () => {
+ const node = {
+ type: "Literal",
+ value: null,
+ regex: { pattern: "(?:)", flags: "u" }
+ };
+ const expectedText = "/(?:)/u";
+
+ assert.strictEqual(astUtils.getStaticStringValue(node), expectedText);
+ });
+
+ it("should return text of bigint literal even if it's not supported natively.", () => {
+ const node = {
+ type: "Literal",
+ value: null,
+ bigint: "100n"
+ };
+ const expectedText = "100n";
+
+ assert.strictEqual(astUtils.getStaticStringValue(node), expectedText);
+ });
});
describe("getStaticPropertyName", () => {
{
const expectedResults = {
- 5: true,
0: true,
+ 5: true,
+ 8: true,
+ 9: true,
+ 50: true,
+ 123: true,
+ "1_0": true,
+ "1_0_1": true,
+ "12_3": true,
+ "5_000": true,
+ "500_0": true,
+ "500_00": true,
+ "5_000_00": true,
+ "1_234_56": true,
+ "1_2_3_4": true,
+ "11_22_33_44": true,
+ "1_23_4_56_7_89": true,
+ "08": true,
+ "09": true,
+ "008": true,
+ "0192": true,
+ "0180": true,
+ "090": true,
+ "088": true,
+ "099": true,
+ "089": true,
+ "0098": true,
+ "01892": true,
+ "08192": true,
+ "01829": true,
+ "018290": true,
+ "0.": false,
"5.": false,
+ ".0": false,
+ ".5": false,
"5.0": false,
+ "5.00_00": false,
+ "5.0_1": false,
+ "0.1_0": false,
+ "5.1_2": false,
+ "1.23_45": false,
+ ".0_1": false,
+ ".12_34": false,
"05": false,
+ "0123": false,
+ "076543210": false,
+ "08.": false,
"0x5": false,
+ "0b11_01": false,
+ "0o0_1": false,
+ "0x56_78": false,
"5e0": false,
+ "0.e1": false,
+ ".0e1": false,
+ "5e0_1": false,
+ "5e1_000": false,
+ "5e12_34": false,
"5e-0": false,
+ "5e-0_1": false,
+ "5e-1_2": false,
+ "1_2.3_4e5_6": false,
+ "1n": false,
+ "1_2n": false,
+ "1_000n": false,
"'5'": false
};
+ const ecmaVersion = espree.latestEcmaVersion;
+
describe("isDecimalInteger", () => {
Object.keys(expectedResults).forEach(key => {
it(`should return ${expectedResults[key]} for ${key}`, () => {
- assert.strictEqual(astUtils.isDecimalInteger(espree.parse(key).body[0].expression), expectedResults[key]);
+ assert.strictEqual(
+ astUtils.isDecimalInteger(
+ espree.parse(key, { ecmaVersion }).body[0].expression
+ ),
+ expectedResults[key]
+ );
});
});
});
describe("isDecimalIntegerNumericToken", () => {
Object.keys(expectedResults).forEach(key => {
it(`should return ${expectedResults[key]} for ${key}`, () => {
- assert.strictEqual(astUtils.isDecimalIntegerNumericToken(espree.tokenize(key)[0]), expectedResults[key]);
+ assert.strictEqual(
+ astUtils.isDecimalIntegerNumericToken(
+ espree.tokenize(key, { ecmaVersion })[0]
+ ),
+ expectedResults[key]
+ );
});
});
});
"foo.bar": true,
"(foo = bar)": true,
"(foo = 1)": false,
+ "(foo += bar)": false,
+ "(foo -= bar)": false,
+ "(foo *= bar)": false,
+ "(foo /= bar)": false,
+ "(foo %= bar)": false,
+ "(foo **= bar)": false,
+ "(foo <<= bar)": false,
+ "(foo >>= bar)": false,
+ "(foo >>>= bar)": false,
+ "(foo &= bar)": false,
+ "(foo |= bar)": false,
+ "(foo ^= bar)": false,
"(1, 2, 3)": false,
"(foo, 2, 3)": false,
"(1, 2, foo)": true,
"1 && 2": false,
"1 && foo": true,
- "foo && 2": true,
+ "foo && 2": false,
+
+ // A future improvement could detect the left side as statically falsy, making this false.
+ "false && foo": true,
+ "foo &&= 2": false,
+ "foo.bar ??= 2": true,
+ "foo[bar] ||= 2": true,
"foo ? 1 : 2": false,
"foo ? bar : 2": true,
"foo ? 1 : bar": true,
Object.keys(EXPECTED_RESULTS).forEach(key => {
it(`returns ${EXPECTED_RESULTS[key]} for ${key}`, () => {
- const ast = espree.parse(key, { ecmaVersion: 6 });
+ const ast = espree.parse(key, { ecmaVersion: 2021 });
assert.strictEqual(astUtils.couldBeError(ast.body[0].expression), EXPECTED_RESULTS[key]);
});
});
});
- describe("hasOctalEscapeSequence", () => {
+ describe("equalLiteralValue", () => {
+ describe("should return true if two regex values are same, even if it's not supported natively.", () => {
+ const patterns = [
+ {
+ nodeA: {
+ type: "Literal",
+ value: /(?:)/u,
+ regex: { pattern: "(?:)", flags: "u" }
+ },
+ nodeB: {
+ type: "Literal",
+ value: /(?:)/u,
+ regex: { pattern: "(?:)", flags: "u" }
+ },
+ expected: true
+ },
+ {
+ nodeA: {
+ type: "Literal",
+ value: null,
+ regex: { pattern: "(?:)", flags: "u" }
+ },
+ nodeB: {
+ type: "Literal",
+ value: null,
+ regex: { pattern: "(?:)", flags: "u" }
+ },
+ expected: true
+ },
+ {
+ nodeA: {
+ type: "Literal",
+ value: null,
+ regex: { pattern: "(?:)", flags: "u" }
+ },
+ nodeB: {
+ type: "Literal",
+ value: /(?:)/, // eslint-disable-line require-unicode-regexp
+ regex: { pattern: "(?:)", flags: "" }
+ },
+ expected: false
+ },
+ {
+ nodeA: {
+ type: "Literal",
+ value: null,
+ regex: { pattern: "(?:a)", flags: "u" }
+ },
+ nodeB: {
+ type: "Literal",
+ value: null,
+ regex: { pattern: "(?:b)", flags: "u" }
+ },
+ expected: false
+ }
+ ];
+
+ for (const { nodeA, nodeB, expected } of patterns) {
+ it(`should return ${expected} if it compared ${util.format("%o", nodeA)} and ${util.format("%o", nodeB)}`, () => {
+ assert.strictEqual(astUtils.equalLiteralValue(nodeA, nodeB), expected);
+ });
+ }
+ });
+
+ describe("should return true if two bigint values are same, even if it's not supported natively.", () => {
+ const patterns = [
+ {
+ nodeA: {
+ type: "Literal",
+ value: null,
+ bigint: "1"
+ },
+ nodeB: {
+ type: "Literal",
+ value: null,
+ bigint: "1"
+ },
+ expected: true
+ },
+ {
+ nodeA: {
+ type: "Literal",
+ value: null,
+ bigint: "1"
+ },
+ nodeB: {
+ type: "Literal",
+ value: null,
+ bigint: "2"
+ },
+ expected: false
+ },
+ {
+ nodeA: {
+ type: "Literal",
+ value: 1n,
+ bigint: "1"
+ },
+ nodeB: {
+ type: "Literal",
+ value: 1n,
+ bigint: "1"
+ },
+ expected: true
+ },
+ {
+ nodeA: {
+ type: "Literal",
+ value: 1n,
+ bigint: "1"
+ },
+ nodeB: {
+ type: "Literal",
+ value: 2n,
+ bigint: "2"
+ },
+ expected: false
+ }
+ ];
+
+ for (const { nodeA, nodeB, expected } of patterns) {
+ it(`should return ${expected} if it compared ${util.format("%o", nodeA)} and ${util.format("%o", nodeB)}`, () => {
+ assert.strictEqual(astUtils.equalLiteralValue(nodeA, nodeB), expected);
+ });
+ }
+ });
+ });
+
+ describe("hasOctalOrNonOctalDecimalEscapeSequence", () => {
/* eslint-disable quote-props */
const expectedResults = {
"\\\\\\1": true,
"\\\\\\01": true,
"\\\\\\08": true,
+ "\\8": true,
+ "\\9": true,
+ "a\\8a": true,
+ "\\0\\8": true,
+ "\\8\\0": true,
+ "\\80": true,
+ "\\81": true,
+ "\\\\\\8": true,
+ "\\\n\\1": true,
+ "foo\\\nbar\\2baz": true,
+ "\\\n\\8": true,
+ "foo\\\nbar\\9baz": true,
"\\0": false,
- "\\8": false,
- "\\9": false,
" \\0": false,
"\\0 ": false,
"a\\0": false,
"\\0a": false,
- "a\\8a": false,
- "\\0\\8": false,
- "\\8\\0": false,
- "\\80": false,
- "\\81": false,
"\\\\": false,
"\\\\0": false,
"\\\\01": false,
"\\\\1": false,
"\\\\12": false,
"\\\\\\0": false,
- "\\\\\\8": false,
"\\0\\\\": false,
"0": false,
"1": false,
"80": false,
"12": false,
"\\a": false,
- "\\n": false
+ "\\n": false,
+ "\\\n": false,
+ "foo\\\nbar": false,
+ "128\\\n349": false
};
/* eslint-enable quote-props */
it(`should return ${expectedResults[key]} for ${key}`, () => {
const ast = espree.parse(`"${key}"`);
- assert.strictEqual(astUtils.hasOctalEscapeSequence(ast.body[0].expression.raw), expectedResults[key]);
+ assert.strictEqual(astUtils.hasOctalOrNonOctalDecimalEscapeSequence(ast.body[0].expression.raw), expectedResults[key]);
+ });
+ });
+ });
+
+ describe("isLogicalAssignmentOperator", () => {
+ const expectedResults = {
+ "&&=": true,
+ "||=": true,
+ "??=": true,
+ "&&": false,
+ "||": false,
+ "??": false,
+ "=": false,
+ "&=": false,
+ "|=": false,
+ "+=": false,
+ "**=": false,
+ "==": false,
+ "===": false
+ };
+
+ Object.entries(expectedResults).forEach(([key, value]) => {
+ it(`should return ${value} for ${key}`, () => {
+ assert.strictEqual(astUtils.isLogicalAssignmentOperator(key), value);
});
});
});
options: ["inside", { functionPrototypeMethods: true }],
parserOptions: { ecmaVersion: 2020 },
errors: [wrapExpressionError]
+ },
+
+ // Optional chaining
+ {
+ code: "window.bar = function() { return 3; }.call?.(this, arg1);",
+ output: "window.bar = (function() { return 3; }).call?.(this, arg1);",
+ options: ["inside", { functionPrototypeMethods: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [wrapInvocationError]
+ },
+ {
+ code: "window.bar = function() { return 3; }?.call(this, arg1);",
+ output: "window.bar = (function() { return 3; })?.call(this, arg1);",
+ options: ["inside", { functionPrototypeMethods: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [wrapInvocationError]
+ },
+ {
+ code: "window.bar = (function() { return 3; }?.call)(this, arg1);",
+ output: "window.bar = ((function() { return 3; })?.call)(this, arg1);",
+ options: ["inside", { functionPrototypeMethods: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [wrapInvocationError]
+ },
+ {
+ code: "new (function () {} ?.());",
+ output: "new ((function () {}) ?.());",
+ options: ["inside"],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [wrapExpressionError]
}
]
});
code: "if('a' <= x && x < MAX) {}",
options: ["never", { exceptRange: true }]
},
+ {
+ code: "if (0 <= obj?.a && obj?.a < 1) {}",
+ options: ["never", { exceptRange: true }],
+ parserOptions: { ecmaVersion: 2020 }
+ },
// onlyEquality
{
}
]
},
+ {
+ code: "0 < f()in obj",
+ output: "f() > 0 in obj",
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "right", operator: "<" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "1 > x++instanceof foo",
+ output: "x++ < 1 instanceof foo",
+ options: ["never"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "right", operator: ">" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "x < ('foo')in bar",
+ output: "('foo') > x in bar",
+ options: ["always"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "left", operator: "<" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "false <= ((x))in foo",
+ output: "((x)) >= false in foo",
+ options: ["never"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "right", operator: "<=" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "x >= (1)instanceof foo",
+ output: "(1) <= x instanceof foo",
+ options: ["always"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "left", operator: ">=" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "false <= ((x)) in foo",
+ output: "((x)) >= false in foo",
+ options: ["never"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "right", operator: "<=" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "x >= 1 instanceof foo",
+ output: "1 <= x instanceof foo",
+ options: ["always"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "left", operator: ">=" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "x >= 1/**/instanceof foo",
+ output: "1 <= x/**/instanceof foo",
+ options: ["always"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "left", operator: ">=" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "(x >= 1)instanceof foo",
+ output: "(1 <= x)instanceof foo",
+ options: ["always"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "left", operator: ">=" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "(x) >= (1)instanceof foo",
+ output: "(1) <= (x)instanceof foo",
+ options: ["always"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "left", operator: ">=" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "1 > x===foo",
+ output: "x < 1===foo",
+ options: ["never"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "right", operator: ">" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+ {
+ code: "1 > x",
+ output: "x < 1",
+ options: ["never"],
+ errors: [
+ {
+ messageId: "expected",
+ data: { expectedSide: "right", operator: ">" },
+ type: "BinaryExpression"
+ }
+ ]
+ },
+
{
code: "if (`green` < x.y && x.y < `blue`) {}",
output: "if (`green` < x.y && `blue` > x.y) {}",
+++ /dev/null
-/**
- * @fileoverview Tests for ConfigOps
- * @author Nicholas C. Zakas
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("chai").assert,
- leche = require("leche"),
- util = require("util"),
- ConfigOps = require("../../../lib/shared/config-ops");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-describe("ConfigOps", () => {
-
- describe("getRuleSeverity()", () => {
- const EXPECTED_RESULTS = new Map([
- [0, 0],
- [1, 1],
- [2, 2],
- [[0], 0],
- [[1], 1],
- [[2], 2],
- ["off", 0],
- ["warn", 1],
- ["error", 2],
- [["off"], 0],
- [["warn"], 1],
- [["error"], 2],
- ["OFF", 0],
- ["wArN", 1],
- [["ErRoR"], 2],
- ["invalid config", 0],
- [["invalid config"], 0],
- [3, 0],
- [[3], 0],
- [1.5, 0],
- [[1.5], 0]
- ]);
-
- for (const key of EXPECTED_RESULTS.keys()) {
- it(`returns ${util.inspect(EXPECTED_RESULTS.get(key))} for ${util.inspect(key)}`, () => {
- assert.strictEqual(ConfigOps.getRuleSeverity(key), EXPECTED_RESULTS.get(key));
- });
- }
- });
-
- describe("normalizeToStrings()", () => {
- it("should convert 2 rule setting to error when rule has just a severity", () => {
- const config = {
- rules: {
- foo: 2,
- bar: 2
- }
- };
-
- ConfigOps.normalizeToStrings(config);
-
- assert.deepStrictEqual(config, {
- rules: {
- foo: "error",
- bar: "error"
- }
- });
- });
-
- it("should convert 2 rule setting to error when rule has array with severity", () => {
- const config = {
- rules: {
- foo: [2, "something"],
- bar: 2
- }
- };
-
- ConfigOps.normalizeToStrings(config);
-
- assert.deepStrictEqual(config, {
- rules: {
- foo: ["error", "something"],
- bar: "error"
- }
- });
- });
-
- it("should convert 1 rule setting to warn when rule has just a severity", () => {
- const config = {
- rules: {
- foo: 1,
- bar: 1
- }
- };
-
- ConfigOps.normalizeToStrings(config);
-
- assert.deepStrictEqual(config, {
- rules: {
- foo: "warn",
- bar: "warn"
- }
- });
- });
-
- it("should convert 1 rule setting to warn when rule has array with severity", () => {
- const config = {
- rules: {
- foo: [1, "something"],
- bar: 1
- }
- };
-
- ConfigOps.normalizeToStrings(config);
-
- assert.deepStrictEqual(config, {
- rules: {
- foo: ["warn", "something"],
- bar: "warn"
- }
- });
- });
-
- it("should convert 0 rule setting to off when rule has just a severity", () => {
- const config = {
- rules: {
- foo: 0,
- bar: 0
- }
- };
-
- ConfigOps.normalizeToStrings(config);
-
- assert.deepStrictEqual(config, {
- rules: {
- foo: "off",
- bar: "off"
- }
- });
- });
-
- it("should convert 0 rule setting to off when rule has array with severity", () => {
- const config = {
- rules: {
- foo: [0, "something"],
- bar: 0
- }
- };
-
- ConfigOps.normalizeToStrings(config);
-
- assert.deepStrictEqual(config, {
- rules: {
- foo: ["off", "something"],
- bar: "off"
- }
- });
- });
-
- it("should convert 256 rule setting to off when rule has just a severity", () => {
- const config = {
- rules: {
- foo: 256,
- bar: 256
- }
- };
-
- ConfigOps.normalizeToStrings(config);
-
- assert.deepStrictEqual(config, {
- rules: {
- foo: "off",
- bar: "off"
- }
- });
- });
-
- it("should convert 256 rule setting to off when rule has array with severity", () => {
- const config = {
- rules: {
- foo: [256, "something"],
- bar: 256
- }
- };
-
- ConfigOps.normalizeToStrings(config);
-
- assert.deepStrictEqual(config, {
- rules: {
- foo: ["off", "something"],
- bar: "off"
- }
- });
- });
- });
-
- describe("isError()", () => {
-
- leche.withData([
- ["error", true],
- ["Error", true],
- [2, true],
- [["error"], true],
- [["erRor"], true],
- [[2], true],
- [["error", "foo"], true],
- [["eRror", "bar"], true],
- [[2, "baz"], true]
- ], (input, expected) => {
-
- it(`should return ${expected}when passed ${input}`, () => {
- const result = ConfigOps.isErrorSeverity(input);
-
- assert.strictEqual(result, expected);
- });
-
- });
-
- });
-
- describe("normalizeConfigGlobal", () => {
- [
- ["off", "off"],
- [true, "writable"],
- ["true", "writable"],
- [false, "readonly"],
- ["false", "readonly"],
- [null, "readonly"],
- ["writeable", "writable"],
- ["writable", "writable"],
- ["readable", "readonly"],
- ["readonly", "readonly"],
- ["writable", "writable"]
- ].forEach(([input, output]) => {
- it(util.inspect(input), () => {
- assert.strictEqual(ConfigOps.normalizeConfigGlobal(input), output);
- });
- });
-
- it("throws on other inputs", () => {
- assert.throws(
- () => ConfigOps.normalizeConfigGlobal("something else"),
- /^'something else' is not a valid configuration for a global \(use 'readonly', 'writable', or 'off'\)$/u
- );
- });
- });
-});
+/*
+ * 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 Tests for config validator.
* @author Brandon Mills
+++ /dev/null
-/**
- * @fileoverview Tests for naming util
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("chai").assert,
- leche = require("leche"),
- naming = require("../../../lib/shared/naming");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-describe("naming", () => {
- describe("normalizePackageName()", () => {
-
- leche.withData([
- ["foo", "eslint-config-foo"],
- ["eslint-config-foo", "eslint-config-foo"],
- ["@z/foo", "@z/eslint-config-foo"],
- ["@z\\foo", "@z/eslint-config-foo"],
- ["@z\\foo\\bar.js", "@z/eslint-config-foo/bar.js"],
- ["@z/eslint-config", "@z/eslint-config"],
- ["@z/eslint-config-foo", "@z/eslint-config-foo"]
- ], (input, expected) => {
- it(`should return ${expected} when passed ${input}`, () => {
- const result = naming.normalizePackageName(input, "eslint-config");
-
- assert.strictEqual(result, expected);
- });
- });
-
- });
-
- describe("getShorthandName()", () => {
-
- leche.withData([
- ["foo", "foo"],
- ["eslint-config-foo", "foo"],
- ["@z", "@z"],
- ["@z/eslint-config", "@z"],
- ["@z/foo", "@z/foo"],
- ["@z/eslint-config-foo", "@z/foo"]
- ], (input, expected) => {
- it(`should return ${expected} when passed ${input}`, () => {
- const result = naming.getShorthandName(input, "eslint-config");
-
- assert.strictEqual(result, expected);
- });
- });
-
- });
-
- describe("getNamespaceFromTerm()", () => {
- it("should remove namespace when passed with namespace", () => {
- const namespace = naming.getNamespaceFromTerm("@namespace/eslint-plugin-test");
-
- assert.strictEqual(namespace, "@namespace/");
- });
- });
-});
assert = require("chai").assert,
espree = require("espree"),
sinon = require("sinon"),
- leche = require("leche"),
{ Linter } = require("../../../lib/linter"),
SourceCode = require("../../../lib/source-code/source-code"),
astUtils = require("../../../lib/shared/ast-utils");
describe("isSpaceBetween()", () => {
describe("should return true when there is at least one whitespace character between two tokens", () => {
- leche.withData([
+ [
["let foo", true],
["let foo", true],
["let /**/ foo", true],
["let/**/foo", false],
["let/*\n*/foo", false]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
describe("when the first given is located before the second", () => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
});
});
- leche.withData([
+ [
["a+b", false],
["a +b", true],
["a/**/+b", false],
["a/* */+ ` /*\n*/ `/* */+c", true],
["a/* */+` /*\n*/ ` /* */+c", true],
["a/* */+ ` /*\n*/ ` /* */+c", true]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
describe("when the first given is located before the second", () => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
});
describe("should return true when there is at least one whitespace character between a token and a node", () => {
- leche.withData([
+ [
[";let foo = bar", false],
[";/**/let foo = bar", false],
[";/* */let foo = bar", false],
[";/* */\nlet foo = bar", true],
[";\n/**/\nlet foo = bar", true],
[";\n/* */\nlet foo = bar", true]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
describe("when the first given is located before the second", () => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
});
describe("should return true when there is at least one whitespace character between a node and a token", () => {
- leche.withData([
+ [
["let foo = bar;;", false],
["let foo = bar;;;", false],
["let foo = 1; let bar = 2;;", true],
["let foo = bar;/* */\n;", true],
["let foo = bar;\n/**/\n;", true],
["let foo = bar;\n/* */\n;", true]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
describe("when the first given is located before the second", () => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
});
describe("should return true when there is at least one whitespace character between two nodes", () => {
- leche.withData([
+ [
["let foo = bar;let baz = qux;", false],
["let foo = bar;/**/let baz = qux;", false],
["let foo = bar;/* */let baz = qux;", false],
["let foo = bar;\n/**/\nlet baz = qux;", true],
["let foo = bar;\n/* */\nlet baz = qux;", true],
["let foo = 1;let foo2 = 2; let foo3 = 3;", true]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
describe("when the first given is located before the second", () => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
});
describe("should return false either of the arguments' location is inside the other one", () => {
- leche.withData([
+ [
["let foo = bar;", false]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
sourceCode = new SourceCode(code, ast);
describe("isSpaceBetweenTokens()", () => {
describe("should return true when there is at least one whitespace character between two tokens", () => {
- leche.withData([
+ [
["let foo", true],
["let foo", true],
["let /**/ foo", true],
["let/**/foo", false],
["let/*\n*/foo", false]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
describe("when the first given is located before the second", () => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
});
});
- leche.withData([
+ [
["a+b", false],
["a +b", true],
["a/**/+b", false],
["a/* */+ ` /*\n*/ `/* */+c", true],
["a/* */+` /*\n*/ ` /* */+c", true],
["a/* */+ ` /*\n*/ ` /* */+c", true]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
describe("when the first given is located before the second", () => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
});
describe("should return true when there is at least one whitespace character between a token and a node", () => {
- leche.withData([
+ [
[";let foo = bar", false],
[";/**/let foo = bar", false],
[";/* */let foo = bar", false],
[";/* */\nlet foo = bar", true],
[";\n/**/\nlet foo = bar", true],
[";\n/* */\nlet foo = bar", true]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
describe("when the first given is located before the second", () => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
});
describe("should return true when there is at least one whitespace character between a node and a token", () => {
- leche.withData([
+ [
["let foo = bar;;", false],
["let foo = bar;;;", false],
["let foo = 1; let bar = 2;;", true],
["let foo = bar;/* */\n;", true],
["let foo = bar;\n/**/\n;", true],
["let foo = bar;\n/* */\n;", true]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
describe("when the first given is located before the second", () => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
});
describe("should return true when there is at least one whitespace character between two nodes", () => {
- leche.withData([
+ [
["let foo = bar;let baz = qux;", false],
["let foo = bar;/**/let baz = qux;", false],
["let foo = bar;/* */let baz = qux;", false],
["let foo = bar;\n/**/\nlet baz = qux;", true],
["let foo = bar;\n/* */\nlet baz = qux;", true],
["let foo = 1;let foo2 = 2; let foo3 = 3;", true]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
describe("when the first given is located before the second", () => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
});
describe("should return false either of the arguments' location is inside the other one", () => {
- leche.withData([
+ [
["let foo = bar;", false]
- ], (code, expected) => {
+ ].forEach(([code, expected]) => {
it(code, () => {
const ast = espree.parse(code, DEFAULT_CONFIG),
sourceCode = new SourceCode(code, ast);
"};"
].join("\n"),
errors: [{
- message: "Rule is missing a meta.docs property",
+ messageId: "missingMetaDocs",
line: 2,
column: 5
}]
"};"
].join("\n"),
errors: [{
- message: "Rule is missing a meta.docs.url property",
+ messageId: "missingMetaDocsUrl",
line: 3,
column: 9
}]
"};"
].join("\n"),
errors: [{
- message: "Incorrect url. Expected \"https://eslint.org/docs/rules/<input>\" but got \"http://example.com/wrong-url\"",
+ messageId: "incorrectUrl",
+ data: {
+ expected: "https://eslint.org/docs/rules/<input>",
+ url: "http://example.com/wrong-url"
+ },
line: 4,
column: 18
}]
comment: true,
eslintVisitorKeys: true,
eslintScopeManager: true,
- ecmaVersion: 2018,
+ ecmaVersion: espree.latestEcmaVersion,
sourceType: "script"
})
},
if (!metaDocs) {
context.report({
node: metaProperty,
- message: "Rule is missing a meta.docs property"
+ messageId: "missingMetaDocs"
});
return;
}
if (!metaDocsUrl) {
context.report({
node: metaDocs,
- message: "Rule is missing a meta.docs.url property"
+ messageId: "missingMetaDocsUrl"
});
return;
}
if (url !== expected) {
context.report({
node: metaDocsUrl.value,
- message: `Incorrect url. Expected "${expected}" but got "${url}"`
+ messageId: "incorrectUrl",
+ data: { expected, url }
});
}
recommended: false
},
type: "suggestion",
- schema: []
+ schema: [],
+ messages: {
+ missingMetaDocs: "Rule is missing a meta.docs property.",
+ missingMetaDocsUrl: "Rule is missing a meta.docs.url property.",
+ incorrectUrl: 'Incorrect url. Expected "{{ expected }}" but got "{{ url }}".'
+ }
},
create(context) {
"guard-for-in": "suggestion",
"handle-callback-err": "suggestion",
"id-blacklist": "suggestion",
+ "id-denylist": "suggestion",
"id-length": "suggestion",
"id-match": "suggestion",
"implicit-arrow-linebreak": "layout",
"no-plusplus": "suggestion",
"no-process-env": "suggestion",
"no-process-exit": "suggestion",
+ "no-promise-executor-return": "problem",
"no-proto": "suggestion",
"no-prototype-builtins": "problem",
"no-redeclare": "suggestion",
"no-unmodified-loop-condition": "problem",
"no-unneeded-ternary": "suggestion",
"no-unreachable": "problem",
+ "no-unreachable-loop": "problem",
"no-unsafe-finally": "problem",
"no-unsafe-negation": "problem",
"no-unused-expressions": "suggestion",
"wrap-regex": "layout",
"yield-star-spacing": "layout",
"yoda": "suggestion"
-}
\ No newline at end of file
+}
]
},
resolve: {
- mainFields: ["main", "module"]
+ mainFields: ["browser", "main", "module"]
},
stats: "errors-only"
};