From d8f8161d850630ec67c34daf819d4ed67e1fe752 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Fri, 10 May 2024 21:50:12 +0800 Subject: [PATCH 01/14] Fix edge cases when add `new` to calls (#2352) --- ...witch-call-expression-to-new-expression.js | 2 + test/new-for-builtins.mjs | 41 ++-- test/snapshots/new-for-builtins.mjs.md | 191 ++++++++++-------- test/snapshots/new-for-builtins.mjs.snap | Bin 3034 -> 3075 bytes test/snapshots/throw-new-error.mjs.md | 27 +++ test/snapshots/throw-new-error.mjs.snap | Bin 1238 -> 1330 bytes test/throw-new-error.mjs | 5 + 7 files changed, 162 insertions(+), 104 deletions(-) diff --git a/rules/fix/switch-call-expression-to-new-expression.js b/rules/fix/switch-call-expression-to-new-expression.js index fc3073527c..813e3dfdf0 100644 --- a/rules/fix/switch-call-expression-to-new-expression.js +++ b/rules/fix/switch-call-expression-to-new-expression.js @@ -1,8 +1,10 @@ 'use strict'; const {isParenthesized} = require('../utils/parentheses.js'); const shouldAddParenthesesToNewExpressionCallee = require('../utils/should-add-parentheses-to-new-expression-callee.js'); +const fixSpaceAroundKeyword = require('./fix-space-around-keywords.js'); function * switchCallExpressionToNewExpression(node, sourceCode, fixer) { + yield * fixSpaceAroundKeyword(fixer, node, sourceCode); yield fixer.insertTextBefore(node, 'new '); const {callee} = node; diff --git a/test/new-for-builtins.mjs b/test/new-for-builtins.mjs index fa042643da..f737fc28e1 100644 --- a/test/new-for-builtins.mjs +++ b/test/new-for-builtins.mjs @@ -231,26 +231,31 @@ test.snapshot({ 'const foo = new Number(\'123\')', 'const foo = new String()', 'const foo = new Symbol()', - ` - function varCheck() { - { - var WeakMap = function() {}; - } - // This should not reported - return WeakMap() + outdent` + function varCheck() { + { + var WeakMap = function() {}; } - function constCheck() { - { - const Array = function() {}; - } - return Array() + // This should not reported + return WeakMap() + } + function constCheck() { + { + const Array = function() {}; } - function letCheck() { - { - let Map = function() {}; - } - return Map() + return Array() + } + function letCheck() { + { + let Map = function() {}; } - `, + return Map() + } + `, + outdent` + function foo() { + return(globalThis).Map() + } + `, ], }); diff --git a/test/snapshots/new-for-builtins.mjs.md b/test/snapshots/new-for-builtins.mjs.md index df6ce0dce8..196f2cb928 100644 --- a/test/snapshots/new-for-builtins.mjs.md +++ b/test/snapshots/new-for-builtins.mjs.md @@ -1267,105 +1267,124 @@ Generated by [AVA](https://avajs.dev). > Input `␊ - 1 |␊ - 2 | function varCheck() {␊ - 3 | {␊ - 4 | var WeakMap = function() {};␊ - 5 | }␊ - 6 | // This should not reported␊ - 7 | return WeakMap()␊ - 8 | }␊ - 9 | function constCheck() {␊ - 10 | {␊ - 11 | const Array = function() {};␊ - 12 | }␊ - 13 | return Array()␊ - 14 | }␊ - 15 | function letCheck() {␊ - 16 | {␊ - 17 | let Map = function() {};␊ - 18 | }␊ - 19 | return Map()␊ - 20 | }␊ - 21 | ␊ + 1 | function varCheck() {␊ + 2 | {␊ + 3 | var WeakMap = function() {};␊ + 4 | }␊ + 5 | // This should not reported␊ + 6 | return WeakMap()␊ + 7 | }␊ + 8 | function constCheck() {␊ + 9 | {␊ + 10 | const Array = function() {};␊ + 11 | }␊ + 12 | return Array()␊ + 13 | }␊ + 14 | function letCheck() {␊ + 15 | {␊ + 16 | let Map = function() {};␊ + 17 | }␊ + 18 | return Map()␊ + 19 | }␊ ` > Output `␊ - 1 |␊ - 2 | function varCheck() {␊ - 3 | {␊ - 4 | var WeakMap = function() {};␊ - 5 | }␊ - 6 | // This should not reported␊ - 7 | return WeakMap()␊ - 8 | }␊ - 9 | function constCheck() {␊ - 10 | {␊ - 11 | const Array = function() {};␊ - 12 | }␊ - 13 | return new Array()␊ - 14 | }␊ - 15 | function letCheck() {␊ - 16 | {␊ - 17 | let Map = function() {};␊ - 18 | }␊ - 19 | return new Map()␊ - 20 | }␊ - 21 | ␊ + 1 | function varCheck() {␊ + 2 | {␊ + 3 | var WeakMap = function() {};␊ + 4 | }␊ + 5 | // This should not reported␊ + 6 | return WeakMap()␊ + 7 | }␊ + 8 | function constCheck() {␊ + 9 | {␊ + 10 | const Array = function() {};␊ + 11 | }␊ + 12 | return new Array()␊ + 13 | }␊ + 14 | function letCheck() {␊ + 15 | {␊ + 16 | let Map = function() {};␊ + 17 | }␊ + 18 | return new Map()␊ + 19 | }␊ ` > Error 1/2 `␊ - 1 |␊ - 2 | function varCheck() {␊ - 3 | {␊ - 4 | var WeakMap = function() {};␊ - 5 | }␊ - 6 | // This should not reported␊ - 7 | return WeakMap()␊ - 8 | }␊ - 9 | function constCheck() {␊ - 10 | {␊ - 11 | const Array = function() {};␊ - 12 | }␊ - > 13 | return Array()␊ - | ^^^^^^^ Use \`new Array()\` instead of \`Array()\`.␊ - 14 | }␊ - 15 | function letCheck() {␊ - 16 | {␊ - 17 | let Map = function() {};␊ - 18 | }␊ - 19 | return Map()␊ - 20 | }␊ - 21 | ␊ + 1 | function varCheck() {␊ + 2 | {␊ + 3 | var WeakMap = function() {};␊ + 4 | }␊ + 5 | // This should not reported␊ + 6 | return WeakMap()␊ + 7 | }␊ + 8 | function constCheck() {␊ + 9 | {␊ + 10 | const Array = function() {};␊ + 11 | }␊ + > 12 | return Array()␊ + | ^^^^^^^ Use \`new Array()\` instead of \`Array()\`.␊ + 13 | }␊ + 14 | function letCheck() {␊ + 15 | {␊ + 16 | let Map = function() {};␊ + 17 | }␊ + 18 | return Map()␊ + 19 | }␊ ` > Error 2/2 `␊ - 1 |␊ - 2 | function varCheck() {␊ - 3 | {␊ - 4 | var WeakMap = function() {};␊ - 5 | }␊ - 6 | // This should not reported␊ - 7 | return WeakMap()␊ - 8 | }␊ - 9 | function constCheck() {␊ - 10 | {␊ - 11 | const Array = function() {};␊ - 12 | }␊ - 13 | return Array()␊ - 14 | }␊ - 15 | function letCheck() {␊ - 16 | {␊ - 17 | let Map = function() {};␊ - 18 | }␊ - > 19 | return Map()␊ - | ^^^^^ Use \`new Map()\` instead of \`Map()\`.␊ - 20 | }␊ - 21 | ␊ + 1 | function varCheck() {␊ + 2 | {␊ + 3 | var WeakMap = function() {};␊ + 4 | }␊ + 5 | // This should not reported␊ + 6 | return WeakMap()␊ + 7 | }␊ + 8 | function constCheck() {␊ + 9 | {␊ + 10 | const Array = function() {};␊ + 11 | }␊ + 12 | return Array()␊ + 13 | }␊ + 14 | function letCheck() {␊ + 15 | {␊ + 16 | let Map = function() {};␊ + 17 | }␊ + > 18 | return Map()␊ + | ^^^^^ Use \`new Map()\` instead of \`Map()\`.␊ + 19 | }␊ + ` + +## invalid(60): function foo() { return(globalThis).Map() } + +> Input + + `␊ + 1 | function foo() {␊ + 2 | return(globalThis).Map()␊ + 3 | }␊ + ` + +> Output + + `␊ + 1 | function foo() {␊ + 2 | return new (globalThis).Map()␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + > 2 | return(globalThis).Map()␊ + | ^^^^^^^^^^^^^^^^^^ Use \`new Map()\` instead of \`Map()\`.␊ + 3 | }␊ ` diff --git a/test/snapshots/new-for-builtins.mjs.snap b/test/snapshots/new-for-builtins.mjs.snap index 51412c8f5e439f7b20fa69baaa7e29935aae547d..c47a5dd2d2232d58e352205ae246e8040d0de4bf 100644 GIT binary patch literal 3075 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 RApKpG&j5clQs-V-000&y29p2) literal 3034 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 diff --git a/test/snapshots/throw-new-error.mjs.md b/test/snapshots/throw-new-error.mjs.md index 8e36df5d8e..903b034a02 100644 --- a/test/snapshots/throw-new-error.mjs.md +++ b/test/snapshots/throw-new-error.mjs.md @@ -534,3 +534,30 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ 3 | });␊ ` + +## invalid(26): function foo() { return[globalThis][0].Error('message'); } + +> Input + + `␊ + 1 | function foo() {␊ + 2 | return[globalThis][0].Error('message');␊ + 3 | }␊ + ` + +> Output + + `␊ + 1 | function foo() {␊ + 2 | return new [globalThis][0].Error('message');␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + > 2 | return[globalThis][0].Error('message');␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + 3 | }␊ + ` diff --git a/test/snapshots/throw-new-error.mjs.snap b/test/snapshots/throw-new-error.mjs.snap index aa2b8ade04c70672ba986fdc3a834abce7a90778..84e6045ed9ea5795e0597b5f57238aaf01cbb28e 100644 GIT binary patch literal 1330 zcmV-21ST?XkUNy%X)OlZGZwh_8TnLsa6G&xrhIclPYu8L!tiev6cI?w#Mv-aB{q?{>oz z&7<(ot8aMZMjn54#r01d&uv*X`%4ye198e82Z0DI+YNo^IP8ij{OwJvHRWeHFl1oGWVPY_xMx)xF3yW6_8y4na{J*_D(;N+UKDCro2UjWQ?(M zHv0WM`mz0xvzIzJCW;mnBy}(kX6Ug+@T4pt<+*5*J2>`1c){KC!0cY z7xMc6)|SX9C^iVMDNwRv#BvDg9l#=$1elr7T#wPLC1^aiQPrLo)5fQS6rTGay{_O< zubt;pdrFSZNDUA?R6<_En-Ej+7KQ z)4`QZ5%kEc2?qN_XB>-lxMm=ACnohpLh4aJj6~1Os|qPIShK39sMW#YjzUfAg6ss? zIKYmuFdn#BI96hg>j}rNh1hn2ZRb}rO4D;kT2M3}fJsH6X);2feQ@dkP};@JbWWY5 zwwiE#xc$gX)YOAB;vzkmw#eUuv--vK;G~PxSr5fCrnIwGr<936Ifn+M!)>T#KtoK+X8S{jf1RZB5g zP}O1yJ~=x)JM$ywm6o09?FC3FaHgSS0FE+1Y8)JX0BWe#BAdr+keVD6_IICZVSNnQ zehBN8I*QaiP=2J4Qq+j)1<1b#OerbNXfDsAoDVE36_VLgK95=Tw9lhuWq3&sMAik9 zpKub*CXog=e|GB>J7$xwA#<`Ds*BVzjYs}krWh=!mfcI<-pZ?gheuC5(Qv%It{Ym0 zw;^k=fV)6JV?x1P2b=pUOwvn7^$gsOfT}L3iNn=Qj8_xJa!BY$t`}BwlQtnTT2Z{e z0<)UR+u*39{yTV{0(EnOIoQu}2kPk}L8hds7^WRCT2VW&31%x)`Iu;!pTF7d|KOjz zV5ko!n5OWZWAJzY)jDamtL<**Uwc4OeDt zjhzpIQrp*ou+NKzO}%h8g~pBkCms8(KT7$7Z%tnF;3Qlwem%-&t{#Xd$$=OQeeJ&A zj9lTfwh)#*-2SK^_y?V&$Q~Z7#^11HsvGP=Jqp=x(<3a|2yzE<1&UWh9fAe~+{+bk oxJ}bW#r^*m_W!SvF7DQ}3r+34v4Kr>6qJMTALw`hi3=hC0B>)Pb^rhX literal 1238 zcmV;{1S$JLRzVR|A(1ezEyg;Mu*KOU1y>)4wGN4Q?TBm^~gbLj% z7*pAt
=zRGsmm9?)B{{rGIllVW8o7m^C@41WXB=}vVe82bH=g!Y_&-tHj%NOna z=vY`M{dwvAg%1{z z=fcH>4=;RFW6WVED5`PaZE>GJ4GxC!SXKwwBFF+>lyhplLL+-|2&|_}Ecp__zNAu21n6u)fK(3@fni&x5>`yj@TSRxl?0fn&|FK=tYv6?uhr1*7t_M$gcP28AibvGQRN88 z1^qJsl-H7orh;@ch4dLf8iaCwF4OmQ#~oCe|b6yXu^m$>UK)%%Z$|HVHAsjnO6fsW-w<}Pf=@v!)=9{)&urwX;7VOG>hjcyj3al_=$eRVb$t$ZIM+klS;|Pf7a2%0YOSTR>yvsu#wDq`5Q!c8jWG#(H`K+ZF%qeTJ z44>>Bo*f6V`$o&obbA3(3Y@9wNWf7BNR5NTPe2XTT4b}h261Ggu)FhA3+q#e_G6f@ z)KR4Fg7T_DN>L-GS0MipFy*8)qnW&qvOln_+>p#Z=lfVxpZ9&VtO76Tfynw`@(XsN z#US$FX3uV&V#jRq8nPt2Gj)|*rtv7B%M^n-<+8ik?X5ie_jvrRFIukuyzfPp;WlIq zCU6HRXiO-W>tJ(Fg-Lqps2+pcK2X&KHLz>VJag5l}Y=Sc3fwSD=|s5@bl4jA2>*x;*o@H=)a!Ptq3VDf5CwANng|G3kEubLUEhX*EP9wWmmJ^`i-~ah8sm*FR;er zFSEB@Ax_V^bB$K#-MMI%V_Psp#;4#YVCR~{qlnLn`}Z05q(w^R@ Date: Sat, 11 May 2024 02:04:34 +0700 Subject: [PATCH 02/14] Add issue template for a rule change (#2279) Co-authored-by: Sindre Sorhus Co-authored-by: fisker Cheung --- .github/ISSUE_TEMPLATE/rule_change.yml | 55 ++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/rule_change.yml diff --git a/.github/ISSUE_TEMPLATE/rule_change.yml b/.github/ISSUE_TEMPLATE/rule_change.yml new file mode 100644 index 0000000000..fc9fab8f2d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/rule_change.yml @@ -0,0 +1,55 @@ +name: Rule change +description: A rule could be improved or extended? +title: "`rule-name`: " +labels: + - change request + - evaluating +body: + - type: markdown + attributes: + value: | + Before opening a new issue: + - Look for existing [open or closed rule proposals](https://github.com/sindresorhus/eslint-plugin-unicorn/issues?q=is:issue+label%25enhancement) + - [Report a bug](https://github.com/sindresorhus/eslint-plugin-unicorn/issues/new?assignees=&labels=&projects=&template=bug_report.md) instead if it's a false positive + - type: textarea + validations: + required: true + attributes: + label: Description + description: Explain the improvement you would like to see. + - type: textarea + validations: + required: true + attributes: + label: Fail + description: Specify examples of code that should be detected. + value: | + ```js + var replace = 'me'; + ``` + + ```js + function foo() { + var replace = 'me'; + return replace; + } + ``` + - type: textarea + validations: + required: true + attributes: + label: Pass + description: Specify examples of code that would be accepted in its place. + value: | + ```js + const replace = 'me'; + ``` + + ```js + function foo() { + return 'me'; + } + ``` + - type: textarea + attributes: + label: Additional Info From 175ea0491ddf52378ac05f86af4d2a6578652a51 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Sat, 11 May 2024 12:03:26 +0800 Subject: [PATCH 03/14] `prefer-string-raw`: Ignore strings in Enums (#2354) --- rules/prefer-string-raw.js | 1 + test/prefer-string-raw.mjs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/rules/prefer-string-raw.js b/rules/prefer-string-raw.js index 4241e11163..134e0e5d02 100644 --- a/rules/prefer-string-raw.js +++ b/rules/prefer-string-raw.js @@ -47,6 +47,7 @@ const create = context => { ) || (node.parent.type === 'Property' && !node.parent.computed && node.parent.key === node) || (node.parent.type === 'JSXAttribute' && node.parent.value === node) + || (node.parent.type === 'TSEnumMember' && (node.parent.initializer === node || node.parent.id === node)) ) { return; } diff --git a/test/prefer-string-raw.mjs b/test/prefer-string-raw.mjs index 2f818eac66..af5e034c7a 100644 --- a/test/prefer-string-raw.mjs +++ b/test/prefer-string-raw.mjs @@ -38,3 +38,19 @@ test.snapshot({ String.raw`const foo = "foo \\x46";`, ], }); + +test.typescript({ + valid: [ + outdent` + enum Files { + Foo = "C:\\\\path\\\\to\\\\foo.js", + } + `, + outdent` + enum Foo { + "\\\\a\\\\b" = "baz", + } + `, + ], + invalid: [], +}); From d2df181d57a091a8c30a5acf1fe0869d2ccbc2ec Mon Sep 17 00:00:00 2001 From: TomatoCake <60300461+DEVTomatoCake@users.noreply.github.com> Date: Sat, 11 May 2024 07:50:27 +0200 Subject: [PATCH 04/14] Remove useless "recommended" properties (#2355) --- rules/prefer-array-flat.js | 4 ---- rules/prefer-modern-math-apis.js | 1 - rules/prefer-structured-clone.js | 1 - 3 files changed, 6 deletions(-) diff --git a/rules/prefer-array-flat.js b/rules/prefer-array-flat.js index 2bf40baca9..854ff24a80 100644 --- a/rules/prefer-array-flat.js +++ b/rules/prefer-array-flat.js @@ -46,7 +46,6 @@ const arrayFlatMap = { }, getArrayNode: node => node.callee.object, description: 'Array#flatMap()', - recommended: true, }; // `array.reduce((a, b) => a.concat(b), [])` @@ -100,7 +99,6 @@ const arrayReduce = { }, getArrayNode: node => node.callee.object, description: 'Array#reduce()', - recommended: true, }; // `[].concat(maybeArray)` @@ -121,7 +119,6 @@ const emptyArrayConcat = { return argumentNode.type === 'SpreadElement' ? argumentNode.argument : argumentNode; }, description: '[].concat()', - recommended: true, shouldSwitchToArray: node => node.arguments[0].type !== 'SpreadElement', }; @@ -157,7 +154,6 @@ const arrayPrototypeConcat = { return argumentNode.type === 'SpreadElement' ? argumentNode.argument : argumentNode; }, description: 'Array.prototype.concat()', - recommended: true, shouldSwitchToArray: node => node.arguments[1].type !== 'SpreadElement' && node.callee.property.name === 'call', }; diff --git a/rules/prefer-modern-math-apis.js b/rules/prefer-modern-math-apis.js index 232803e1f0..1cb6fb605b 100644 --- a/rules/prefer-modern-math-apis.js +++ b/rules/prefer-modern-math-apis.js @@ -154,7 +154,6 @@ const create = context => { data: { replacement: `Math.${replacementMethod}(…)`, description: 'Math.sqrt(…)', - recommended: true, }, * fix(fixer) { const {sourceCode} = context; diff --git a/rules/prefer-structured-clone.js b/rules/prefer-structured-clone.js index e66f54e930..70b7768c42 100644 --- a/rules/prefer-structured-clone.js +++ b/rules/prefer-structured-clone.js @@ -61,7 +61,6 @@ const create = context => { messageId: MESSAGE_ID_ERROR, data: { description: 'JSON.parse(JSON.stringify(…))', - recommended: true, }, suggest: [ { From 810575620e3d9d90c60c2d6c5f2098c0a7b0348d Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 15 May 2024 20:42:11 +0800 Subject: [PATCH 05/14] Update `@typescript-eslint/parse` to v8 (#2358) --- .github/workflows/main.yml | 8 ++++---- .github/workflows/smoke-test.yml | 8 ++++---- package.json | 2 +- rules/ast/is-reference-identifier.js | 7 +++++++ 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1d33c82ba1..bd0cf3e0a5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - run: npm install --legacy-peer-deps + - run: npm install - run: npx ava lint-test: runs-on: ${{ matrix.os }} @@ -44,7 +44,7 @@ jobs: with: # Locked due to the difference of `zlib.gzipSync()` between Node.js versions node-version: 20 - - run: npm install --legacy-peer-deps + - run: npm install - run: npm run lint - run: npx del-cli test/snapshots --verbose # Force update snapshots, https://github.com/avajs/ava/discussions/2754 @@ -62,7 +62,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - - run: npm install --legacy-peer-deps + - run: npm install - run: npm run run-rules-on-codebase integration: name: Integration test (${{ matrix.group }}) @@ -85,5 +85,5 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - - run: npm install --legacy-peer-deps + - run: npm install - run: npm run integration -- --group ${{ matrix.group }} diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 04104b0abc..68e4352394 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -9,12 +9,12 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 - run: | - npm install --legacy-peer-deps + npm install npm link - npm link eslint-plugin-unicorn --legacy-peer-deps + npm link eslint-plugin-unicorn - uses: AriPerkkio/eslint-remote-tester-run-action@v4 with: issue-title: "Results of weekly scheduled smoke test" diff --git a/package.json b/package.json index c0205f9052..054f432b51 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@babel/core": "^7.24.5", "@babel/eslint-parser": "^7.24.5", "@lubien/fixture-beta-package": "^1.0.0-beta.1", - "@typescript-eslint/parser": "^7.8.0", + "@typescript-eslint/parser": "^8.0.0-alpha.12", "ava": "^6.1.3", "c8": "^9.1.0", "chalk": "^5.3.0", diff --git a/rules/ast/is-reference-identifier.js b/rules/ast/is-reference-identifier.js index 7ebf894119..0794a3acd7 100644 --- a/rules/ast/is-reference-identifier.js +++ b/rules/ast/is-reference-identifier.js @@ -120,11 +120,18 @@ function isNotReference(node) { return parent.parameters.includes(node); } + // `@typescript-eslint/parse` v7 // `type Foo = { [Identifier in keyof string]: number; };` case 'TSTypeParameter': { return parent.name === node; } + // `@typescript-eslint/parse` v8 + // `type Foo = { [Identifier in keyof string]: number; };` + case 'TSMappedType': { + return parent.key === node; + } + // `type Identifier = Foo` case 'TSTypeAliasDeclaration': { return parent.id === node; From 3a282acdf8ef3c9407c88a09ff1a86894ea0a8bf Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Thu, 23 May 2024 15:15:52 +0800 Subject: [PATCH 06/14] Add "bug" label to bug reports --- .github/ISSUE_TEMPLATE/bug_report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a4b8dcc525..1856cb48a0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,7 @@ --- name: Bug report about: A rule isn't working as it should? +labels: bug --- From 8957a03032a59b82e6bbf557843a0af312affa15 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Sat, 25 May 2024 02:32:22 +0800 Subject: [PATCH 07/14] Add `no-negation-in-equality-check` rule (#2353) --- docs/rules/no-negation-in-equality-check.md | 30 ++ readme.md | 1 + rules/no-negation-in-equality-check.js | 104 +++++++ test/no-negation-in-equality-check.mjs | 50 ++++ .../no-negation-in-equality-check.mjs.md | 268 ++++++++++++++++++ .../no-negation-in-equality-check.mjs.snap | Bin 0 -> 799 bytes 6 files changed, 453 insertions(+) create mode 100644 docs/rules/no-negation-in-equality-check.md create mode 100644 rules/no-negation-in-equality-check.js create mode 100644 test/no-negation-in-equality-check.mjs create mode 100644 test/snapshots/no-negation-in-equality-check.mjs.md create mode 100644 test/snapshots/no-negation-in-equality-check.mjs.snap diff --git a/docs/rules/no-negation-in-equality-check.md b/docs/rules/no-negation-in-equality-check.md new file mode 100644 index 0000000000..248261ebeb --- /dev/null +++ b/docs/rules/no-negation-in-equality-check.md @@ -0,0 +1,30 @@ +# Disallow negated expression in equality check + +💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + +💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). + + + + +Using a negated expression in equality check is most likely a mistake. + +## Fail + +```js +if (!foo === bar) {} +``` + +```js +if (!foo !== bar) {} +``` + +## Pass + +```js +if (foo !== bar) {} +``` + +```js +if (!(foo === bar)) {} +``` diff --git a/readme.md b/readme.md index f21ccaae94..163a4b7572 100644 --- a/readme.md +++ b/readme.md @@ -145,6 +145,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [no-lonely-if](docs/rules/no-lonely-if.md) | Disallow `if` statements as the only statement in `if` blocks without `else`. | ✅ | 🔧 | | | [no-magic-array-flat-depth](docs/rules/no-magic-array-flat-depth.md) | Disallow a magic number as the `depth` argument in `Array#flat(…).` | ✅ | | | | [no-negated-condition](docs/rules/no-negated-condition.md) | Disallow negated conditions. | ✅ | 🔧 | | +| [no-negation-in-equality-check](docs/rules/no-negation-in-equality-check.md) | Disallow negated expression in equality check. | ✅ | | 💡 | | [no-nested-ternary](docs/rules/no-nested-ternary.md) | Disallow nested ternary expressions. | ✅ | 🔧 | | | [no-new-array](docs/rules/no-new-array.md) | Disallow `new Array()`. | ✅ | 🔧 | 💡 | | [no-new-buffer](docs/rules/no-new-buffer.md) | Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. | ✅ | 🔧 | 💡 | diff --git a/rules/no-negation-in-equality-check.js b/rules/no-negation-in-equality-check.js new file mode 100644 index 0000000000..9bcdbb8115 --- /dev/null +++ b/rules/no-negation-in-equality-check.js @@ -0,0 +1,104 @@ +'use strict'; +const { + fixSpaceAroundKeyword, + addParenthesizesToReturnOrThrowExpression, +} = require('./fix/index.js'); +const { + needsSemicolon, + isParenthesized, + isOnSameLine, +} = require('./utils/index.js'); + +const MESSAGE_ID_ERROR = 'no-negation-in-equality-check/error'; +const MESSAGE_ID_SUGGESTION = 'no-negation-in-equality-check/suggestion'; +const messages = { + [MESSAGE_ID_ERROR]: 'Negated expression in not allowed in equality check.', + [MESSAGE_ID_SUGGESTION]: 'Switch to \'{{operator}}\' check.', +}; + +const EQUALITY_OPERATORS = new Set([ + '===', + '!==', + '==', + '!=', +]); + +const isEqualityCheck = node => node.type === 'BinaryExpression' && EQUALITY_OPERATORS.has(node.operator); +const isNegatedExpression = node => node.type === 'UnaryExpression' && node.prefix && node.operator === '!'; + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => ({ + BinaryExpression(binaryExpression) { + const {operator, left} = binaryExpression; + + if ( + !isEqualityCheck(binaryExpression) + || !isNegatedExpression(left) + ) { + return; + } + + const {sourceCode} = context; + const bangToken = sourceCode.getFirstToken(left); + const negatedOperator = `${operator.startsWith('!') ? '=' : '!'}${operator.slice(1)}`; + + return { + node: bangToken, + messageId: MESSAGE_ID_ERROR, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + suggest: [ + { + messageId: MESSAGE_ID_SUGGESTION, + data: { + operator: negatedOperator, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + * fix(fixer) { + yield * fixSpaceAroundKeyword(fixer, binaryExpression, sourceCode); + + const tokenAfterBang = sourceCode.getTokenAfter(bangToken); + + const {parent} = binaryExpression; + if ( + (parent.type === 'ReturnStatement' || parent.type === 'ThrowStatement') + && !isParenthesized(binaryExpression, sourceCode) + ) { + const returnToken = sourceCode.getFirstToken(parent); + if (!isOnSameLine(returnToken, tokenAfterBang)) { + yield * addParenthesizesToReturnOrThrowExpression(fixer, parent, sourceCode); + } + } + + yield fixer.remove(bangToken); + + const previousToken = sourceCode.getTokenBefore(bangToken); + if (needsSemicolon(previousToken, sourceCode, tokenAfterBang.value)) { + yield fixer.insertTextAfter(bangToken, ';'); + } + + const operatorToken = sourceCode.getTokenAfter( + left, + token => token.type === 'Punctuator' && token.value === operator, + ); + yield fixer.replaceText(operatorToken, negatedOperator); + }, + }, + ], + }; + }, +}); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'problem', + docs: { + description: 'Disallow negated expression in equality check.', + recommended: true, + }, + + hasSuggestions: true, + messages, + }, +}; diff --git a/test/no-negation-in-equality-check.mjs b/test/no-negation-in-equality-check.mjs new file mode 100644 index 0000000000..70d0e58025 --- /dev/null +++ b/test/no-negation-in-equality-check.mjs @@ -0,0 +1,50 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + '!foo instanceof bar', + '+foo === bar', + '!(foo === bar)', + // We are not checking right side + 'foo === !bar', + ], + invalid: [ + '!foo === bar', + '!foo !== bar', + '!foo == bar', + '!foo != bar', + outdent` + function x() { + return!foo === bar; + } + `, + outdent` + function x() { + return! + foo === bar; + throw! + foo === bar; + } + `, + outdent` + foo + !(a) === b + `, + outdent` + foo + ![a, b].join('') === c + `, + outdent` + foo + ! [a, b].join('') === c + `, + outdent` + foo + !/* comment */[a, b].join('') === c + `, + '!!foo === bar', + ], +}); diff --git a/test/snapshots/no-negation-in-equality-check.mjs.md b/test/snapshots/no-negation-in-equality-check.mjs.md new file mode 100644 index 0000000000..6888103721 --- /dev/null +++ b/test/snapshots/no-negation-in-equality-check.mjs.md @@ -0,0 +1,268 @@ +# Snapshot report for `test/no-negation-in-equality-check.mjs` + +The actual snapshot is saved in `no-negation-in-equality-check.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): !foo === bar + +> Input + + `␊ + 1 | !foo === bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | !foo === bar␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | foo !== bar␊ + ` + +## invalid(2): !foo !== bar + +> Input + + `␊ + 1 | !foo !== bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | !foo !== bar␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '===' check.␊ + 1 | foo === bar␊ + ` + +## invalid(3): !foo == bar + +> Input + + `␊ + 1 | !foo == bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | !foo == bar␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!=' check.␊ + 1 | foo != bar␊ + ` + +## invalid(4): !foo != bar + +> Input + + `␊ + 1 | !foo != bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | !foo != bar␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '==' check.␊ + 1 | foo == bar␊ + ` + +## invalid(5): function x() { return!foo === bar; } + +> Input + + `␊ + 1 | function x() {␊ + 2 | return!foo === bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function x() {␊ + > 2 | return!foo === bar;␊ + | ^ Negated expression in not allowed in equality check.␊ + 3 | }␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | function x() {␊ + 2 | return foo !== bar;␊ + 3 | }␊ + ` + +## invalid(6): function x() { return! foo === bar; throw! foo === bar; } + +> Input + + `␊ + 1 | function x() {␊ + 2 | return!␊ + 3 | foo === bar;␊ + 4 | throw!␊ + 5 | foo === bar;␊ + 6 | }␊ + ` + +> Error 1/2 + + `␊ + 1 | function x() {␊ + > 2 | return!␊ + | ^ Negated expression in not allowed in equality check.␊ + 3 | foo === bar;␊ + 4 | throw!␊ + 5 | foo === bar;␊ + 6 | }␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | function x() {␊ + 2 | return (␊ + 3 | foo !== bar);␊ + 4 | throw!␊ + 5 | foo === bar;␊ + 6 | }␊ + ` + +> Error 2/2 + + `␊ + 1 | function x() {␊ + 2 | return!␊ + 3 | foo === bar;␊ + > 4 | throw!␊ + | ^ Negated expression in not allowed in equality check.␊ + 5 | foo === bar;␊ + 6 | }␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | function x() {␊ + 2 | return!␊ + 3 | foo === bar;␊ + 4 | throw (␊ + 5 | foo !== bar);␊ + 6 | }␊ + ` + +## invalid(7): foo !(a) === b + +> Input + + `␊ + 1 | foo␊ + 2 | !(a) === b␊ + ` + +> Error 1/1 + + `␊ + 1 | foo␊ + > 2 | !(a) === b␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | foo␊ + 2 | ;(a) !== b␊ + ` + +## invalid(8): foo ![a, b].join('') === c + +> Input + + `␊ + 1 | foo␊ + 2 | ![a, b].join('') === c␊ + ` + +> Error 1/1 + + `␊ + 1 | foo␊ + > 2 | ![a, b].join('') === c␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | foo␊ + 2 | ;[a, b].join('') !== c␊ + ` + +## invalid(9): foo ! [a, b].join('') === c + +> Input + + `␊ + 1 | foo␊ + 2 | ! [a, b].join('') === c␊ + ` + +> Error 1/1 + + `␊ + 1 | foo␊ + > 2 | ! [a, b].join('') === c␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | foo␊ + 2 | ; [a, b].join('') !== c␊ + ` + +## invalid(10): foo !/* comment */[a, b].join('') === c + +> Input + + `␊ + 1 | foo␊ + 2 | !/* comment */[a, b].join('') === c␊ + ` + +> Error 1/1 + + `␊ + 1 | foo␊ + > 2 | !/* comment */[a, b].join('') === c␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | foo␊ + 2 | ;/* comment */[a, b].join('') !== c␊ + ` + +## invalid(11): !!foo === bar + +> Input + + `␊ + 1 | !!foo === bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | !!foo === bar␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | !foo !== bar␊ + ` diff --git a/test/snapshots/no-negation-in-equality-check.mjs.snap b/test/snapshots/no-negation-in-equality-check.mjs.snap new file mode 100644 index 0000000000000000000000000000000000000000..9df853b5dd8c5053062066810663133c557a2e25 GIT binary patch literal 799 zcmV+)1K|8YRzV7zJB;7hCv|JseR#>Q^}G!uOog5yJogrtktD;{%=I9^lE)!LDl(8%!H) zLP57IGR8gcBYri~Et9?hV}OmrilW>*=cVkA4i2zOz3pq&S{wGqCN%~?2^5j071_q~ z`n@_3SH8LDIK=Tn1(EAaWRyWP9<+6u<&2Yi!#ZJmQCIm`zE%CO{Q;CZ9{)#8?(%lk_vCZ!F1%<;f}cr z$D;5LIpKHM-drvPpbk?gNKz^QTQG%V37)5UjpI|v%aeDKl6&+DFk2ZRvIG9>j-Q}t z_&SIxDnj35&|9(4ful|b4jD(Dg`Du-cY@0~0jF_-f7U`FLapIFoNucDG-Y3D#k%GT z=bDeHTyu_2$zII=>@X;C<@sh)Gr(FHvd(~g=74>O0hY!&oycJVrkRm0C$AL0tU1!A z6sd$)y;^4tlK7TdWa4D4%7cW)Bl-%7rM_YFNtZFip9AghRCADu_%cwq$juqhHeJE@drg`;^>LNi6;6(O5gX)@)-T~?coqLOD(w>g$q*h}VF d!>CgZ)sMw%=E_W?B!i+r=|7(B?|l6e0050%gNXnD literal 0 HcmV?d00001 From a449af99e863f103de101058abc7bf4eccb6297a Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Sat, 25 May 2024 18:01:43 +0800 Subject: [PATCH 08/14] `prefer-array-find`: Change `checkFromLast` default value to `true` (#2367) --- docs/rules/prefer-array-find.md | 25 +++++++++++++------------ rules/prefer-array-find.js | 6 +++--- test/prefer-array-find.mjs | 15 ++++++--------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/docs/rules/prefer-array-find.md b/docs/rules/prefer-array-find.md index 5b7065e03c..4f4b1db54f 100644 --- a/docs/rules/prefer-array-find.md +++ b/docs/rules/prefer-array-find.md @@ -17,10 +17,18 @@ This rule is fixable unless default values are used in declaration or assignment const item = array.filter(x => isUnicorn(x))[0]; ``` +```js +const item = array.filter(x => isUnicorn(x)).at(-1); +``` + ```js const item = array.filter(x => isUnicorn(x)).shift(); ``` +```js +const item = array.filter(x => isUnicorn(x)).pop(); +``` + ```js const [item] = array.filter(x => isUnicorn(x)); ``` @@ -50,25 +58,18 @@ Type: `object` ### checkFromLast Type: `boolean`\ -Default: `false` +Default: `true` -Pass `checkFromLast: true` to check cases searching from last. +Pass `checkFromLast: false` to disable check cases searching from last. -#### Fail +#### Pass ```js -// eslint unicorn/prefer-array-find: ["error", {"checkFromLast": true}] +// eslint unicorn/prefer-array-find: ["error", {"checkFromLast": false}] const item = array.filter(x => isUnicorn(x)).at(-1); ``` ```js -// eslint unicorn/prefer-array-find: ["error", {"checkFromLast": true}] +// eslint unicorn/prefer-array-find: ["error", {"checkFromLast": false}] const item = array.filter(x => isUnicorn(x)).pop(); ``` - -#### Pass - -```js -// eslint unicorn/prefer-array-find: ["error", {"checkFromLast": true}] -const item = array.findLast(x => isUnicorn(x)); -``` diff --git a/rules/prefer-array-find.js b/rules/prefer-array-find.js index 736ac4b8c5..85ff943cf1 100644 --- a/rules/prefer-array-find.js +++ b/rules/prefer-array-find.js @@ -180,7 +180,7 @@ const create = context => { const { checkFromLast, } = { - checkFromLast: false, + checkFromLast: true, ...context.options[0], }; @@ -428,8 +428,8 @@ const schema = [ properties: { checkFromLast: { type: 'boolean', - // TODO: Change default value to `true`, or remove the option when targeting Node.js 18. - default: false, + // TODO: Remove the option at some point. + default: true, }, }, }, diff --git a/test/prefer-array-find.mjs b/test/prefer-array-find.mjs index e886d293ee..a10a6fc7ee 100644 --- a/test/prefer-array-find.mjs +++ b/test/prefer-array-find.mjs @@ -923,15 +923,12 @@ test({ ], }); -// Check from last -const checkFromLastOptions = [{checkFromLast: true}]; - -// Default to false +// `checkFromLast` default to true test({ valid: [ 'array.filter(foo).pop()', 'array.filter(foo).at(-1)', - ], + ].map(code => ({code, options: [{checkFromLast: false}]})), invalid: [], }); @@ -968,7 +965,7 @@ test({ 'array.filter().pop()', 'array.filter(foo, thisArgument, extraArgument).pop()', 'array.filter(...foo).pop()', - ].map(code => ({code, options: checkFromLastOptions})), + ], invalid: [ { code: 'array.filter(foo).pop()', @@ -1005,7 +1002,7 @@ test({ `, errors: [{messageId: ERROR_POP}], }, - ].map(test => ({...test, options: checkFromLastOptions})), + ], }); // `.at(-1)` @@ -1058,7 +1055,7 @@ test({ 'array.filter().at(-1)', 'array.filter(foo, thisArgument, extraArgument).at(-1)', 'array.filter(...foo).at(-1)', - ].map(code => ({code, options: checkFromLastOptions})), + ], invalid: [ { code: 'array.filter(foo).at(-1)', @@ -1099,7 +1096,7 @@ test({ `, errors: [{messageId: ERROR_AT_MINUS_ONE}], }, - ].map(test => ({...test, options: checkFromLastOptions})), + ], }); // `.at(0)` From d812ad1fa93b294d0225469115538350aa3637c8 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Sat, 25 May 2024 20:14:08 +0800 Subject: [PATCH 09/14] `prefer-includes`: Check `.lastIndexOf()` (#2368) --- docs/rules/prefer-includes.md | 60 +++--- readme.md | 2 +- rules/prefer-includes.js | 11 +- test/prefer-includes.mjs | 24 +-- test/snapshots/prefer-includes.mjs.md | 251 +++++++++++++++++++++++- test/snapshots/prefer-includes.mjs.snap | Bin 1588 -> 1888 bytes 6 files changed, 294 insertions(+), 54 deletions(-) diff --git a/docs/rules/prefer-includes.md b/docs/rules/prefer-includes.md index f23d27aec3..4c8f48d323 100644 --- a/docs/rules/prefer-includes.md +++ b/docs/rules/prefer-includes.md @@ -1,4 +1,4 @@ -# Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence +# Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). @@ -7,7 +7,7 @@ -All built-ins have `.includes()` in addition to `.indexOf()`. Prefer `.includes()` over comparing the value of `.indexOf()`. +All built-ins have `.includes()` in addition to `.indexOf()` and `.lastIndexOf()`. Prefer `.includes()` over comparing the value of `.indexOf()` and `.lastIndexOf()`. [`Array#some()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) is intended for more complex needs. If you are just looking for the index where the given item is present, the code can be simplified to use [`Array#includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). This applies to any search with a literal, a variable, or any expression that doesn't have any explicit side effects. However, if the expression you are looking for relies on an item related to the function (its arguments, the function self, etc.), the case is still valid. @@ -16,99 +16,103 @@ This rule is fixable, unless the search expression in `Array#some()` has side ef ## Fail ```js -[].indexOf('foo') !== -1; +array.indexOf('foo') !== -1; ``` ```js -x.indexOf('foo') != -1; +array.indexOf('foo') !== -1; ``` ```js -str.indexOf('foo') > -1; +string.lastIndexOf('foo') !== -1; ``` ```js -'foobar'.indexOf('foo') >= 0; +array.lastIndexOf('foo') !== -1; ``` ```js -x.indexOf('foo') === -1 +foo.indexOf('foo') != -1; ``` ```js -const isFound = foo.some(x => x === 'foo'); +foo.indexOf('foo') >= 0; ``` ```js -const isFound = foo.some(x => 'foo' === x); +foo.indexOf('foo') > -1; ``` ```js -const isFound = foo.some(x => { - return x === 'foo'; -}); +foo.indexOf('foo') === -1 ``` -## Pass +```js +foo.some(x => x === 'foo'); +``` ```js -const str = 'foobar'; +foo.some(x => 'foo' === x); ``` ```js -str.indexOf('foo') !== -n; +foo.some(x => { + return x === 'foo'; +}); ``` +## Pass + ```js -str.indexOf('foo') !== 1; +foo.indexOf('foo') !== -n; ``` ```js -!str.indexOf('foo') === 1; +foo.indexOf('foo') !== 1; ``` ```js -!str.indexOf('foo') === -n; +foo.indexOf('foo') === 1; ``` ```js -str.includes('foo'); +foo.includes('foo'); ``` ```js -[1,2,3].includes(4); +foo.includes(4); ``` ```js -const isFound = foo.includes('foo'); +foo.includes('foo'); ``` ```js -const isFound = foo.some(x => x == undefined); +foo.some(x => x == undefined); ``` ```js -const isFound = foo.some(x => x !== 'foo'); +foo.some(x => x !== 'foo'); ``` ```js -const isFound = foo.some((x, index) => x === index); +foo.some((x, index) => x === index); ``` ```js -const isFound = foo.some(x => (x === 'foo') && isValid()); +foo.some(x => (x === 'foo') && isValid()); ``` ```js -const isFound = foo.some(x => y === 'foo'); +foo.some(x => y === 'foo'); ``` ```js -const isFound = foo.some(x => y.x === 'foo'); +foo.some(x => y.x === 'foo'); ``` ```js -const isFound = foo.some(x => { +foo.some(x => { const bar = getBar(); return x === bar; }); diff --git a/readme.md b/readme.md index 163a4b7572..9d715b4905 100644 --- a/readme.md +++ b/readme.md @@ -188,7 +188,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | ✅ | | 💡 | | [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | ✅ | | | | [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | ✅ | 🔧 | 💡 | -| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 | +| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 | | [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | 🔧 | | | [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | ✅ | 🔧 | | | [prefer-logical-operator-over-ternary](docs/rules/prefer-logical-operator-over-ternary.md) | Prefer using a logical operator over a ternary. | ✅ | | 💡 | diff --git a/rules/prefer-includes.js b/rules/prefer-includes.js index 0cfd14a6c2..e9e878beb0 100644 --- a/rules/prefer-includes.js +++ b/rules/prefer-includes.js @@ -5,9 +5,9 @@ const {isLiteral} = require('./ast/index.js'); const MESSAGE_ID = 'prefer-includes'; const messages = { - [MESSAGE_ID]: 'Use `.includes()`, rather than `.indexOf()`, when checking for existence.', + [MESSAGE_ID]: 'Use `.includes()`, rather than `.{{method}}()`, when checking for existence.', }; -// Ignore {_,lodash,underscore}.indexOf +// Ignore `{_,lodash,underscore}.{indexOf,lastIndexOf}` const ignoredVariables = new Set(['_', 'lodash', 'underscore']); const isIgnoredTarget = node => node.type === 'Identifier' && ignoredVariables.has(node.name); const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1; @@ -30,6 +30,9 @@ const getProblem = (context, node, target, argumentsNodes) => { return { node: memberExpressionNode.property, messageId: MESSAGE_ID, + data: { + method: node.left.callee.property.name, + }, fix(fixer) { const replacement = `${isNegativeResult(node) ? '!' : ''}${targetSource}.includes(${argumentsSource.join(', ')})`; return fixer.replaceText(node, replacement); @@ -49,7 +52,7 @@ const create = context => { context.on('BinaryExpression', node => { const {left, right, operator} = node; - if (!isMethodNamed(left, 'indexOf')) { + if (!isMethodNamed(left, 'indexOf') && !isMethodNamed(left, 'lastIndexOf')) { return; } @@ -86,7 +89,7 @@ module.exports = { meta: { type: 'suggestion', docs: { - description: 'Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence.', + description: 'Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence.', recommended: true, }, fixable: 'code', diff --git a/test/prefer-includes.mjs b/test/prefer-includes.mjs index 0ea72fd7fe..963d87149e 100644 --- a/test/prefer-includes.mjs +++ b/test/prefer-includes.mjs @@ -5,20 +5,22 @@ const {test} = getTester(import.meta); test.snapshot({ valid: [ - 'str.indexOf(\'foo\') !== -n', - 'str.indexOf(\'foo\') !== 1', - 'str.indexOf(\'foo\') === -2', - '!str.indexOf(\'foo\') === 1', - '!str.indexOf(\'foo\') === -n', + ...[ + 'str.indexOf(\'foo\') !== -n', + 'str.indexOf(\'foo\') !== 1', + 'str.indexOf(\'foo\') === -2', + '!str.indexOf(\'foo\') === 1', + '!str.indexOf(\'foo\') === -n', + 'null.indexOf(\'foo\') !== 1', + 'something.indexOf(foo, 0, another) !== -1', + '_.indexOf(foo, bar) !== -1', + 'lodash.indexOf(foo, bar) !== -1', + 'underscore.indexOf(foo, bar) !== -1', + ].flatMap(code => [code, code.replace('.indexOf', '.lastIndexOf')]), 'str.includes(\'foo\')', '\'foobar\'.includes(\'foo\')', '[1,2,3].includes(4)', - 'null.indexOf(\'foo\') !== 1', 'f(0) < 0', - 'something.indexOf(foo, 0, another) !== -1', - '_.indexOf(foo, bar) !== -1', - 'lodash.indexOf(foo, bar) !== -1', - 'underscore.indexOf(foo, bar) !== -1', ], invalid: [ '\'foobar\'.indexOf(\'foo\') !== -1', @@ -32,7 +34,7 @@ test.snapshot({ '(a || b).indexOf(\'foo\') === -1', 'foo.indexOf(bar, 0) !== -1', 'foo.indexOf(bar, 1) !== -1', - ], + ].flatMap(code => [code, code.replace('.indexOf', '.lastIndexOf')]), }); const {snapshot, typescript} = tests({ diff --git a/test/snapshots/prefer-includes.mjs.md b/test/snapshots/prefer-includes.mjs.md index 92011629b7..97207ce93e 100644 --- a/test/snapshots/prefer-includes.mjs.md +++ b/test/snapshots/prefer-includes.mjs.md @@ -25,7 +25,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(2): str.indexOf('foo') != -1 +## invalid(2): 'foobar'.lastIndexOf('foo') !== -1 + +> Input + + `␊ + 1 | 'foobar'.lastIndexOf('foo') !== -1␊ + ` + +> Output + + `␊ + 1 | 'foobar'.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | 'foobar'.lastIndexOf('foo') !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(3): str.indexOf('foo') != -1 > Input @@ -46,7 +67,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(3): str.indexOf('foo') > -1 +## invalid(4): str.lastIndexOf('foo') != -1 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') != -1␊ + ` + +> Output + + `␊ + 1 | str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') != -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(5): str.indexOf('foo') > -1 > Input @@ -67,7 +109,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(4): str.indexOf('foo') == -1 +## invalid(6): str.lastIndexOf('foo') > -1 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') > -1␊ + ` + +> Output + + `␊ + 1 | str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') > -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(7): str.indexOf('foo') == -1 > Input @@ -88,7 +151,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(5): 'foobar'.indexOf('foo') >= 0 +## invalid(8): str.lastIndexOf('foo') == -1 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') == -1␊ + ` + +> Output + + `␊ + 1 | !str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') == -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(9): 'foobar'.indexOf('foo') >= 0 > Input @@ -109,7 +193,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(6): [1,2,3].indexOf(4) !== -1 +## invalid(10): 'foobar'.lastIndexOf('foo') >= 0 + +> Input + + `␊ + 1 | 'foobar'.lastIndexOf('foo') >= 0␊ + ` + +> Output + + `␊ + 1 | 'foobar'.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | 'foobar'.lastIndexOf('foo') >= 0␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(11): [1,2,3].indexOf(4) !== -1 > Input @@ -130,7 +235,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(7): str.indexOf('foo') < 0 +## invalid(12): [1,2,3].lastIndexOf(4) !== -1 + +> Input + + `␊ + 1 | [1,2,3].lastIndexOf(4) !== -1␊ + ` + +> Output + + `␊ + 1 | [1,2,3].includes(4)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [1,2,3].lastIndexOf(4) !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(13): str.indexOf('foo') < 0 > Input @@ -151,7 +277,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(8): ''.indexOf('foo') < 0 +## invalid(14): str.lastIndexOf('foo') < 0 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') < 0␊ + ` + +> Output + + `␊ + 1 | !str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') < 0␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(15): ''.indexOf('foo') < 0 > Input @@ -172,7 +319,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(9): (a || b).indexOf('foo') === -1 +## invalid(16): ''.lastIndexOf('foo') < 0 + +> Input + + `␊ + 1 | ''.lastIndexOf('foo') < 0␊ + ` + +> Output + + `␊ + 1 | !''.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | ''.lastIndexOf('foo') < 0␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(17): (a || b).indexOf('foo') === -1 > Input @@ -193,7 +361,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(10): foo.indexOf(bar, 0) !== -1 +## invalid(18): (a || b).lastIndexOf('foo') === -1 + +> Input + + `␊ + 1 | (a || b).lastIndexOf('foo') === -1␊ + ` + +> Output + + `␊ + 1 | !(a || b).includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | (a || b).lastIndexOf('foo') === -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(19): foo.indexOf(bar, 0) !== -1 > Input @@ -214,7 +403,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(11): foo.indexOf(bar, 1) !== -1 +## invalid(20): foo.lastIndexOf(bar, 0) !== -1 + +> Input + + `␊ + 1 | foo.lastIndexOf(bar, 0) !== -1␊ + ` + +> Output + + `␊ + 1 | foo.includes(bar)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.lastIndexOf(bar, 0) !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(21): foo.indexOf(bar, 1) !== -1 > Input @@ -235,6 +445,27 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` +## invalid(22): foo.lastIndexOf(bar, 1) !== -1 + +> Input + + `␊ + 1 | foo.lastIndexOf(bar, 1) !== -1␊ + ` + +> Output + + `␊ + 1 | foo.includes(bar, 1)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.lastIndexOf(bar, 1) !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + ## invalid(1): values.some(x => x === "foo") > Input diff --git a/test/snapshots/prefer-includes.mjs.snap b/test/snapshots/prefer-includes.mjs.snap index dd84e89630d7c91ac1afa011e0da3df6927367c1..b5991d96583a7b8f3fe6296f51a7af0199c83684 100644 GIT binary patch literal 1888 zcmV-m2cP&sRzVzI(lQ>Pb%${Or1lAxi#ux(x)A%(Bq0uKKo_Ipy58#3Sfd2;FKG$~A*lC^| zM?Ux5&-d$d&vpD;quMrWH?5zS4|Ka_xAk78W!%x)t$JEjwqT`UnpLf{l4}`ty?3#Z zW+zq@SSpucRc%>@cH3&2w)NRTy{_5X<+bM$&nMU?c`Wfl@?;7?h5djiDZQ;#^|pS& zxZSmRoE(Y+-Z-^(x2{_}5RcHf*tJI?eiTD2e33|1!sG$~un+F%gW(6>w{+MY4Xf;C zp`+PNy#sbrGX_5mqp@E;Z0ZKontJVK%h-d4*#W)RvTWU`={cTi>wKqUc0kRm4)&ab zy(nUDYnFW>9QsX!EHjQw8Yv?D2MGTY4}N(*v;^T~5Ti~l#R*4>lClMs-SJw56o7^% zyPm?Xpa?Ic2*Jc3p{w^faOwCX%<-8#?T}sLWJwp0bc<+qCj>UK7_%q*6Y})Kgn(F@ zga>nsMn3M~K200BLXuuZj9!=fxDpBEFVWR^ZRY846)}`^AW;tNR`?|2(Lv9%M4NmU`9Q$rJ*5YMS~w;*}23QAbGr86y8U zOh4^IKStZ1bX8y*%4=%2m@Qp*#a)wcXz~c!5{D)Y5Pqo~h2S$^qmh4ykbmkRmuG-k z$7l4>mc1w-7S;2?TtbZA7-K{RnLX-X5bAHnQPWwFxaZMctCEf44pmjM2-Iux)?|mS zC5K!!AEL`YmNzClsCdkwyH%`JZxK;20y1M5qg5=;X+4#IWTI<$_K*L&>UhAX*W)JoWGVcAv zh-{E=dG;f&rHd+MY4y;SR{dDh zt=~9n7UVFlR*#6Qi!xXHP@cfmQIej-*gm*pG`1rCr+!CkcXcagnYZ+G56Ttj4c3(9 zhG{M_{y@ChyhxZ$$Ht1lNNlm;xxW?awI96hH~~VUcB<0$5|mbkZq+)9lwpo_q>meeiwS zNu&TaSb))7D06upTueI~#|0r~6fRBV;Va1(QKH9^;S;R{U}vsgjKJ5i3F^IdE{m^N zu*PoeCfD2gE#0scxC{JV=Vu0^SGA6(BT7bFX3UR}F>fS=cG_=vv74Tm;iku$TAlGHb*a0zr(1)Wt9f+` z-qUZlwVEzYr)4+sv~!ZZ43WGmhwJ_#x5Ix(kbe`Q{7{*K9V#4SpP9;v{l_O<%ojZ` zzi!C)N31#ydjz6)p`R-TN22aV`X#OLcEuY6_biW*i1(zeq&KCw+mwR%9*~%yBtZiI z7DARK@XKOFCxGN1jZjI&Ex|eQ)gbrwZWw8{z`6Imkge9)>#`f%%C&W4&u%Kv_o{Lg zaeUh;BRITMbVH5e+{%<{m(XbEgGX~=1`UIC{S)bWkdO>inQU5aDV%-X3*NpB-cJf6 zyWMPA*VOBz+V3L%=O$O%3GOHITWFA-2}G7e$J+jmw0$~-w!$#;kTio{c9InPhH0k7 zDtd34Cw;>pghp4AsD3UiC;gWm9w@4kceMJl@kInt^vd}Ry;$XKz_h{B20)J+g!W7m ze$)ncfPxFqHT635m0V40w+BC8tlrRTb`~_lWY3`AVn{lr!_o2H*Z%{ANRwagH~;|RAh!_! literal 1588 zcmV-42Fv+DRzV1ER<(rQ&C5Tb$= z(srw&I?1H*ZfaMyvjmzTv6MR!AP|Tb1bsr{i6!~j(2j!}t`mR2P#^15--c9>2E z=|E7Y)@s*@&4idjW4G-LB!1#0mOhWfDq(B^0O*4Md$0e4cWnZPgJqS&G+3I`Bo;VL z&2V4!)##h|o5X-xlhhvS#t}433rI(|9b(i-hMB7GSe9vlnpHjQH$3clr3AKP1?>|8 z7lV?`7ok>=!t+RBJms&@>@I^WU4MXgeq^tEWD8=l6(QJF3q6y;HwtppSvvL6la{I0^!TVK!Q146jS~VQU2JY zEVlq7o*&^AkMM@LT5OAwIgQx7B(D|Q;xxWwrT79(|4?2jwo&fR4{F?lx=HIqU{48| zzKWcB(c9Xh$>GyAo(NYIK0~uRUQG}#?gO14PrvoOrIaA4LAMK4CHPsmNb5UVMMPL! zQ{tS^#J%qrV-{9^6aJASr<9;!n!aRJv(k{07vLO%wZdS@6-2-jNaH!A!F;Z2mKgLm zXzoXh^N95qQ6>g>RS{nG(seai(H6vUc}io$~CVuIcHvEk(`ko^X6ROkIVtnPA%fJErU;mt)75*8F`wO5f{tN zguUdu7}lhDhhd99V)I5e+D6UMO#_l0C3HA;5WNjSI6}zIBrLz8uzV{DOBmDU9iB7( zn4Zmo3dvlWS*_3qdB|i_P=HjHt^{lhNjfUhQHPG!=%`$w!*&1RhWoHwrsGXI?$?8C z3U1MV3LU9ANM(a@{s+pNw!;}qAQM$5jq5+9YqEGP zhhLh-v!`r{a=3|1xIYCG{LqWm9!ks)P+~raaxL_z<@qjpY6&+v*4XNlf3dsmqa$Lw zo~v241n-cOmR2LYJG#@vZf7LN7lHA9IQ;C-vvhcZ0`gxZR6bNDV229F&~K_?#rek^ z&S&%9cF*v)f082wt&aU3LD0Kwq8*ePeMsVzM7vQ%TGu7*N>Bv%EZ?IbK9;tUL6hd) zrsVirLf$@!fdc#`ge)z65yv;ZGJ@s3q8n-zZ!3$Ub{DO-Gk!H6=D1~WT>nCGJ&#Bhs$9%1 zzZ6ct?u|db^{+KUgxzWC_P%-`jP~1z|DB6R+Y=uZ$Bws}zPjb`O7ZJp0FN*5t(n@snr-up6s^nd*zHIm-f+z;V`I{fk zyzm^Nxwj2~t{b@aOygc^?ha6J0oqp&pr>SNTC3&0-mN|$H75<4Vbc5O*O<~<+A*o$ zPJ>NqrcuAmW-HJWB+m}(y m#dmGyXOt_$>uPN?RQZE+@*(LIE=MQ0um1 Date: Mon, 27 May 2024 16:20:11 +0800 Subject: [PATCH 10/14] Clean `run-rules-on-codebase` script (#2369) --- test/run-rules-on-codebase/lint.mjs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/run-rules-on-codebase/lint.mjs b/test/run-rules-on-codebase/lint.mjs index 098641b8ab..bf72648400 100644 --- a/test/run-rules-on-codebase/lint.mjs +++ b/test/run-rules-on-codebase/lint.mjs @@ -23,12 +23,8 @@ const { }); const configs = [ - // TODO: Use `eslintPluginUnicorn.configs.all` instead when we change preset to flat config + eslintPluginUnicorn.configs['flat/all'], { - plugins: { - unicorn: eslintPluginUnicorn, - }, - rules: eslintPluginUnicorn.configs.all.rules, linterOptions: { reportUnusedDisableDirectives: false, }, @@ -38,8 +34,6 @@ const configs = [ 'coverage', 'test/integration/fixtures', 'test/integration/fixtures-local', - // Ignore this file self temporarily, disabling `n/file-extension-in-import` comment cause error - 'test/run-rules-on-codebase/lint.mjs', 'rules/utils/lodash.js', ], }, @@ -55,10 +49,6 @@ const configs = [ 'unicorn/consistent-function-scoping': 'off', // Annoying 'unicorn/no-keyword-prefix': 'off', - 'unicorn/no-unsafe-regex': 'off', - // Not ready yet - 'unicorn/prefer-string-replace-all': 'off', - 'unicorn/prefer-at': 'off', }, }, { From 1490bb62e33c55d765483c0ac565959c4ffc378e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Wed, 12 Jun 2024 14:30:34 +0300 Subject: [PATCH 11/14] Tests: Update `eslint-remote-tester` to v4 (#2376) --- .github/workflows/smoke-test.yml | 9 ++-- package.json | 6 +-- test/smoke/eslint-remote-tester.config.js | 36 ------------- test/smoke/eslint-remote-tester.config.mjs | 63 ++++++++++++++++++++++ 4 files changed, 69 insertions(+), 45 deletions(-) delete mode 100644 test/smoke/eslint-remote-tester.config.js create mode 100644 test/smoke/eslint-remote-tester.config.mjs diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 68e4352394..d8a56aa0cb 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -11,11 +11,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - - run: | - npm install - npm link - npm link eslint-plugin-unicorn - - uses: AriPerkkio/eslint-remote-tester-run-action@v4 + - run: npm install + - uses: AriPerkkio/eslint-remote-tester-run-action@v5 with: issue-title: "Results of weekly scheduled smoke test" - eslint-remote-tester-config: test/smoke/eslint-remote-tester.config.js + eslint-remote-tester-config: test/smoke/eslint-remote-tester.config.mjs diff --git a/package.json b/package.json index 054f432b51..8d17191171 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "lint:markdown": "markdownlint \"**/*.md\"", "lint:package-json": "npmPkgJsonLint .", "run-rules-on-codebase": "node ./test/run-rules-on-codebase/lint.mjs", - "smoke": "eslint-remote-tester --config ./test/smoke/eslint-remote-tester.config.js", + "smoke": "eslint-remote-tester --config ./test/smoke/eslint-remote-tester.config.mjs", "test": "npm-run-all --continue-on-error lint test:*", "test:js": "c8 ava" }, @@ -81,8 +81,8 @@ "eslint-doc-generator": "1.7.0", "eslint-plugin-eslint-plugin": "^6.1.0", "eslint-plugin-internal-rules": "file:./scripts/internal-rules/", - "eslint-remote-tester": "^3.0.1", - "eslint-remote-tester-repositories": "^1.0.1", + "eslint-remote-tester": "^4.0.0", + "eslint-remote-tester-repositories": "^2.0.0", "espree": "^10.0.1", "execa": "^8.0.1", "listr": "^0.14.3", diff --git a/test/smoke/eslint-remote-tester.config.js b/test/smoke/eslint-remote-tester.config.js deleted file mode 100644 index 7dbfba9971..0000000000 --- a/test/smoke/eslint-remote-tester.config.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -const {getRepositories, getPathIgnorePattern} = require('eslint-remote-tester-repositories'); - -module.exports = { - /** Repositories to scan */ - repositories: getRepositories({randomize: true}), - - /** Optional pattern used to exclude paths */ - pathIgnorePattern: getPathIgnorePattern(), - - /** Extensions of files under scanning */ - extensions: ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts', 'jsx', 'tsx', 'vue'], - - /** Maximum amount of tasks ran concurrently */ - concurrentTasks: 3, - - /** Optional boolean flag used to enable caching of cloned repositories. For CIs it's ideal to disable caching. Defaults to true. */ - cache: false, - - /** Optional setting for log level. Valid values are verbose, info, warn, error. Defaults to verbose. */ - logLevel: 'info', - - /** ESLint configuration */ - eslintrc: { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - project: [], - }, - extends: ['plugin:unicorn/all'], - }, -}; diff --git a/test/smoke/eslint-remote-tester.config.mjs b/test/smoke/eslint-remote-tester.config.mjs new file mode 100644 index 0000000000..f9ca31aec0 --- /dev/null +++ b/test/smoke/eslint-remote-tester.config.mjs @@ -0,0 +1,63 @@ +import { + getRepositories, + getPathIgnorePattern, +} from 'eslint-remote-tester-repositories'; +import typescriptParser from '@typescript-eslint/parser'; +import vueParser from 'vue-eslint-parser'; +import eslintPluginUnicorn from '../../index.js'; + +/** @type {import('eslint-remote-tester').Config} */ +const config = { + /** Repositories to scan */ + repositories: getRepositories({randomize: true}), + + /** Optional pattern used to exclude paths */ + pathIgnorePattern: getPathIgnorePattern(), + + /** Extensions of files under scanning */ + extensions: ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts', 'jsx', 'tsx', 'vue'], + + /** Maximum amount of tasks ran concurrently */ + concurrentTasks: 3, + + /** Optional boolean flag used to enable caching of cloned repositories. For CIs it's ideal to disable caching. Defaults to true. */ + cache: false, + + /** Optional setting for log level. Valid values are verbose, info, warn, error. Defaults to verbose. */ + logLevel: 'info', + + /** ESLint configuration */ + eslintConfig: [ + eslintPluginUnicorn.configs['flat/all'], + { + rules: { + // This rule crashing on replace string inside `jsx` or `Unicode escape sequence` + 'unicorn/string-content': 'off', + }, + }, + { + files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.tsx'], + languageOptions: { + parser: typescriptParser, + parserOptions: { + project: [], + }, + }, + }, + { + files: ['**/*.vue'], + languageOptions: { + parser: vueParser, + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaFeatures: { + jsx: true, + }, + project: [], + }, + }, + }, + ], +}; + +export default config; From ac8536e540ceeed0fe24740cdc00551f49f7f603 Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Fri, 14 Jun 2024 14:13:24 +0200 Subject: [PATCH 12/14] Add name to flat configs (#2377) Co-authored-by: fisker Cheung --- index.js | 17 ++++++++++------- test/package.mjs | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 0f754a2077..77bc1b1a42 100644 --- a/index.js +++ b/index.js @@ -47,9 +47,12 @@ const allRules = Object.fromEntries( ]), ); -const createConfig = (rules, isLegacyConfig = false) => ({ - ...(isLegacyConfig ? legacyConfigBase : flatConfigBase), - plugins: isLegacyConfig ? ['unicorn'] : {unicorn}, +const createConfig = (rules, flatConfigName = false) => ({ + ...( + flatConfigName + ? {...flatConfigBase, name: flatConfigName, plugins: {unicorn}} + : {...legacyConfigBase, plugins: ['unicorn']} + ), rules: {...externalRules, ...rules}, }); @@ -65,10 +68,10 @@ const unicorn = { }; const configs = { - recommended: createConfig(recommendedRules, /* isLegacyConfig */ true), - all: createConfig(allRules, /* isLegacyConfig */ true), - 'flat/recommended': createConfig(recommendedRules), - 'flat/all': createConfig(allRules), + recommended: createConfig(recommendedRules), + all: createConfig(allRules), + 'flat/recommended': createConfig(recommendedRules, 'unicorn/flat/recommended'), + 'flat/all': createConfig(allRules, 'unicorn/flat/all'), }; module.exports = {...unicorn, configs}; diff --git a/test/package.mjs b/test/package.mjs index 2dad8894fd..33cd39cfe9 100644 --- a/test/package.mjs +++ b/test/package.mjs @@ -171,11 +171,11 @@ function getCompactConfig(config) { test('flat configs', t => { t.deepEqual( - getCompactConfig(eslintPluginUnicorn.configs.recommended), + {...getCompactConfig(eslintPluginUnicorn.configs.recommended), name: 'unicorn/flat/recommended'}, {...eslintPluginUnicorn.configs['flat/recommended'], plugins: undefined}, ); t.deepEqual( - getCompactConfig(eslintPluginUnicorn.configs.all), + {...getCompactConfig(eslintPluginUnicorn.configs.all), name: 'unicorn/flat/all'}, {...eslintPluginUnicorn.configs['flat/all'], plugins: undefined}, ); }); From 10568ab392207010ca0da4bbb8e4720e3c23d704 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Fri, 14 Jun 2024 20:35:03 +0800 Subject: [PATCH 13/14] `prefer-array-some`: Check `Array#{findIndex,findLastIndex}()` (#2370) Co-authored-by: Sindre Sorhus --- docs/rules/prefer-array-some.md | 24 +- readme.md | 2 +- rules/prefer-array-some.js | 71 ++++- test/prefer-array-some.mjs | 34 +++ test/snapshots/prefer-array-some.mjs.md | 336 ++++++++++++++++++++++ test/snapshots/prefer-array-some.mjs.snap | Bin 1121 -> 1589 bytes 6 files changed, 453 insertions(+), 14 deletions(-) diff --git a/docs/rules/prefer-array-some.md b/docs/rules/prefer-array-some.md index cc11083488..e7b1899483 100644 --- a/docs/rules/prefer-array-some.md +++ b/docs/rules/prefer-array-some.md @@ -1,4 +1,4 @@ -# Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)` +# Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast,findIndex,findLastIndex}(…)` 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). @@ -17,7 +17,11 @@ We only check `.filter().length > 0` and `.filter().length !== 0`. These two non - Comparing the result of [`Array#find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) or [`Array#findLast()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLast) with `undefined`. -This rule is fixable for `.filter(…).length` check and has a suggestion for `.{find,findLast}(…)`. +- Using [`Array#findIndex()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) or [`Array#findLastIndex()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLastIndex) to ensure at least one element in the array passes a given check. + +This rule is fixable for `.filter(…).length` checks and `.{findIndex,findLastIndex}(…)`. + +This rule provides a suggestion for `.{find,findLast}(…)`. ## Fail @@ -44,11 +48,11 @@ const foo = array.find(element => isUnicorn(element)) ? bar : baz; ``` ```js -const hasUnicorn = array.find(element => isUnicorn(element) !== undefined; +const hasUnicorn = array.find(element => isUnicorn(element)) !== undefined; ``` ```js -const hasUnicorn = array.find(element => isUnicorn(element) != null; +const hasUnicorn = array.find(element => isUnicorn(element)) != null; ``` ```js @@ -62,11 +66,19 @@ const foo = array.findLast(element => isUnicorn(element)) ? bar : baz; ``` ```js -const hasUnicorn = array.findLast(element => isUnicorn(element) !== undefined; +const hasUnicorn = array.findLast(element => isUnicorn(element)) !== undefined; +``` + +```js +const hasUnicorn = array.findLast(element => isUnicorn(element)) != null; +``` + +```js +const hasUnicorn = array.findIndex(element => isUnicorn(element)) !== -1; ``` ```js -const hasUnicorn = array.findLast(element => isUnicorn(element) != null; +const hasUnicorn = array.findLastIndex(element => isUnicorn(element)) !== -1; ``` ```vue diff --git a/readme.md b/readme.md index 9d715b4905..bdfcb60e9a 100644 --- a/readme.md +++ b/readme.md @@ -176,7 +176,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [prefer-array-flat](docs/rules/prefer-array-flat.md) | Prefer `Array#flat()` over legacy techniques to flatten arrays. | ✅ | 🔧 | | | [prefer-array-flat-map](docs/rules/prefer-array-flat-map.md) | Prefer `.flatMap(…)` over `.map(…).flat()`. | ✅ | 🔧 | | | [prefer-array-index-of](docs/rules/prefer-array-index-of.md) | Prefer `Array#{indexOf,lastIndexOf}()` over `Array#{findIndex,findLastIndex}()` when looking for the index of an item. | ✅ | 🔧 | 💡 | -| [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`. | ✅ | 🔧 | 💡 | +| [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast,findIndex,findLastIndex}(…)`. | ✅ | 🔧 | 💡 | | [prefer-at](docs/rules/prefer-at.md) | Prefer `.at()` method for index access and `String#charAt()`. | ✅ | 🔧 | 💡 | | [prefer-blob-reading-methods](docs/rules/prefer-blob-reading-methods.md) | Prefer `Blob#arrayBuffer()` over `FileReader#readAsArrayBuffer(…)` and `Blob#text()` over `FileReader#readAsText(…)`. | ✅ | | | | [prefer-code-point](docs/rules/prefer-code-point.md) | Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`. | ✅ | | 💡 | diff --git a/rules/prefer-array-some.js b/rules/prefer-array-some.js index 563dd39b79..3da1cf5409 100644 --- a/rules/prefer-array-some.js +++ b/rules/prefer-array-some.js @@ -40,10 +40,14 @@ const isCheckingUndefined = node => && isLiteral(node.parent.right, null) ) ); +const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1; +const isLiteralZero = node => isLiteral(node, 0); /** @param {import('eslint').Rule.RuleContext} context */ -const create = context => ({ - CallExpression(callExpression) { +const create = context => { + // `.find(…)` + // `.findLast(…)` + context.on('CallExpression', callExpression => { if (!isMethodCall(callExpression, { methods: ['find', 'findLast'], minimumArguments: 1, @@ -86,8 +90,61 @@ const create = context => ({ }, ], }; - }, - BinaryExpression(binaryExpression) { + }); + + // These operators also used in `prefer-includes`, try to reuse the code in future + // `.{findIndex,findLastIndex}(…) !== -1` + // `.{findIndex,findLastIndex}(…) != -1` + // `.{findIndex,findLastIndex}(…) > -1` + // `.{findIndex,findLastIndex}(…) === -1` + // `.{findIndex,findLastIndex}(…) == -1` + // `.{findIndex,findLastIndex}(…) >= 0` + // `.{findIndex,findLastIndex}(…) < 0` + context.on('BinaryExpression', binaryExpression => { + const {left, right, operator} = binaryExpression; + + if (!( + isMethodCall(left, { + methods: ['findIndex', 'findLastIndex'], + argumentsLength: 1, + optionalCall: false, + optionalMember: false, + }) + && ( + (['!==', '!=', '>', '===', '=='].includes(operator) && isNegativeOne(right)) + || (['>=', '<'].includes(operator) && isLiteralZero(right)) + ) + )) { + return; + } + + const methodNode = left.callee.property; + return { + node: methodNode, + messageId: ERROR_ID_ARRAY_SOME, + data: {method: methodNode.name}, + * fix(fixer) { + if (['===', '==', '<'].includes(operator)) { + yield fixer.insertTextBefore(binaryExpression, '!'); + } + + yield fixer.replaceText(methodNode, 'some'); + + const operatorToken = context.sourceCode.getTokenAfter( + left, + token => token.type === 'Punctuator' && token.value === operator, + ); + const [start] = operatorToken.range; + const [, end] = binaryExpression.range; + + yield fixer.removeRange([start, end]); + }, + }; + }); + + // `.filter(…).length > 0` + // `.filter(…).length !== 0` + context.on('BinaryExpression', binaryExpression => { if (!( // We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule. (binaryExpression.operator === '>' || binaryExpression.operator === '!==') @@ -139,8 +196,8 @@ const create = context => ({ // The `BinaryExpression` always ends with a number or `)`, no need check for ASI }, }; - }, -}); + }); +}; /** @type {import('eslint').Rule.RuleModule} */ module.exports = { @@ -148,7 +205,7 @@ module.exports = { meta: { type: 'suggestion', docs: { - description: 'Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`.', + description: 'Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast,findIndex,findLastIndex}(…)`.', recommended: true, }, fixable: 'code', diff --git a/test/prefer-array-some.mjs b/test/prefer-array-some.mjs index 8dadeabcd5..181cbf5143 100644 --- a/test/prefer-array-some.mjs +++ b/test/prefer-array-some.mjs @@ -210,6 +210,40 @@ test.snapshot({ ], }); +// `.{findIndex,findLastIndex}(…) !== -1` +// `.{findIndex,findLastIndex}(…) != -1` +// `.{findIndex,findLastIndex}(…) > -1` +// `.{findIndex,findLastIndex}(…) === -1` +// `.{findIndex,findLastIndex}(…) == -1` +// `.{findIndex,findLastIndex}(…) >= 0` +// `.{findIndex,findLastIndex}(…) < 0` +test.snapshot({ + valid: [ + 'foo.notMatchedMethod(bar) !== -1', + 'new foo.findIndex(bar) !== -1', + 'foo.findIndex(bar, extraArgument) !== -1', + 'foo.findIndex(bar) instanceof -1', + 'foo.findIndex(...bar) !== -1', + // We are not ignoring ``{_,lodash,underscore}.{findIndex,findLastIndex}` + // but it doesn't make sense to use them with one argument + '_.findIndex(bar)', + '_.findIndex(foo, bar)', + ], + invalid: [ + ...[ + 'foo.findIndex(bar) !== -1', + 'foo.findIndex(bar) != -1', + 'foo.findIndex(bar) > - 1', + 'foo.findIndex(bar) === -1', + 'foo.findIndex(bar) == - 1', + 'foo.findIndex(bar) >= 0', + 'foo.findIndex(bar) < 0', + ].flatMap(code => [code, code.replace('findIndex', 'findLastIndex')]), + 'foo.findIndex(bar) !== (( - 1 ))', + 'foo.findIndex(element => element.bar === 1) !== (( - 1 ))', + ], +}); + test.vue({ valid: [], invalid: [ diff --git a/test/snapshots/prefer-array-some.mjs.md b/test/snapshots/prefer-array-some.mjs.md index 4bc92a86e3..d0a5d9a363 100644 --- a/test/snapshots/prefer-array-some.mjs.md +++ b/test/snapshots/prefer-array-some.mjs.md @@ -184,6 +184,342 @@ Generated by [AVA](https://avajs.dev). 15 | );␊ ` +## invalid(1): foo.findIndex(bar) !== -1 + +> Input + + `␊ + 1 | foo.findIndex(bar) !== -1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) !== -1␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(2): foo.findLastIndex(bar) !== -1 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) !== -1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) !== -1␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(3): foo.findIndex(bar) != -1 + +> Input + + `␊ + 1 | foo.findIndex(bar) != -1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) != -1␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(4): foo.findLastIndex(bar) != -1 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) != -1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) != -1␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(5): foo.findIndex(bar) > - 1 + +> Input + + `␊ + 1 | foo.findIndex(bar) > - 1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) > - 1␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(6): foo.findLastIndex(bar) > - 1 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) > - 1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) > - 1␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(7): foo.findIndex(bar) === -1 + +> Input + + `␊ + 1 | foo.findIndex(bar) === -1␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) === -1␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(8): foo.findLastIndex(bar) === -1 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) === -1␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) === -1␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(9): foo.findIndex(bar) == - 1 + +> Input + + `␊ + 1 | foo.findIndex(bar) == - 1␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) == - 1␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(10): foo.findLastIndex(bar) == - 1 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) == - 1␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) == - 1␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(11): foo.findIndex(bar) >= 0 + +> Input + + `␊ + 1 | foo.findIndex(bar) >= 0␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) >= 0␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(12): foo.findLastIndex(bar) >= 0 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) >= 0␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) >= 0␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(13): foo.findIndex(bar) < 0 + +> Input + + `␊ + 1 | foo.findIndex(bar) < 0␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) < 0␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(14): foo.findLastIndex(bar) < 0 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) < 0␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) < 0␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(15): foo.findIndex(bar) !== (( - 1 )) + +> Input + + `␊ + 1 | foo.findIndex(bar) !== (( - 1 ))␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) !== (( - 1 ))␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(16): foo.findIndex(element => element.bar === 1) !== (( - 1 )) + +> Input + + `␊ + 1 | foo.findIndex(element => element.bar === 1) !== (( - 1 ))␊ + ` + +> Output + + `␊ + 1 | foo.some(element => element.bar === 1) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(element => element.bar === 1) !== (( - 1 ))␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + ## invalid(1): foo.find(fn) == null > Input diff --git a/test/snapshots/prefer-array-some.mjs.snap b/test/snapshots/prefer-array-some.mjs.snap index 67bff2893c423ea1abaa10e1fe41a7c76c8b1de3..a7086be00a030019fdea346fc42c2dfaab80dd4a 100644 GIT binary patch literal 1589 zcmV-52Fm$CRzVvTv9)bEJA2;uuRJ8a>09KeX4;2_Wj0k^TLrTPs^PlEak*vMO_f+= zpV&Us>d-r^)u8m`heuzPG!0HaH9gxn@Y;^=eetl_G<;*@ol6tXPtafC^27^;6$LZo*0O&&eYcv`S_`oGC;=)$hbM}e)`)7}| zEpQIm6{eS6DJ#ls&Oha&&h9SpeABT(pVL?2LvmmlI|SK<2d3XfV=JeiW%mY#)_mJ_ z9hX{+sBkx;!o0Q$JC5!7&~hB8!NB0c4cInZScPrl40= zOAgk~vgabOC=JU1Y~lqWO{?!BS}lYXCNKag;BPX!rpNwAH#80A<^pK%wM`Gaw$rhi zaCiM4xa7cb{ZP*l)o{c{SK*6|MVNb*RKS_+^-bI_eKsLVzSHI4s|oMoL~p zN|@&@TMOk{>m5H9{2B?Kg#AKhJ=q8v4LNab$DTbQt^=X`PMhrPLCbaa8Bdt{kQX9g zwPb%5jjKolnuc}(IXBY>TBamdaKAx<-9F$3_QwqG$EiN0CjNq+rD+gpd(bxgO>#)w z%{Fm~#%_hiH4W-irb3VC-}Q7r9-~_P8|^pAauC!5l^q7vAUI};9-RdF9Gz9evtAWp z8A~s)62Y>T-4}R3pIHf>VFG$36bRj-mvnY1xXng5Ri}s12${$90vl#1_K~C)?<(6lO7?O-!Gu)&*XAp#6Hyr3oAs=G=oTl*X&XoR&-Y&))djp*0!g(y&x zrGG@`?--sx0Dk=bAytwqhNnny{|ttF`y3nx!7^6X2hZtb!{COK3uPI(0x~M*jiQYN zw`5ElB@xvKxN9;Za>1KN9!&S+Vcr0qAVI4i4adsDMLrAbFbl)5kjeiyEDTJKoP|{} z3kBe1VqyLOW@4cb#9Ud}>&r`g7S@LC%Qaz7nL#RE3RM*IY|OPWHRfe86GZ@KYRsbm zF;iz2134Ntu87#k&+80q6eaao*f`R-|Kf#g$vlM%Tm=okB&T2Q;2j~!PvrCy z0GKQLW_v&i<|uT+uBx;agQlh7tRjb0`QlAaPp&C_hy>g6HJo?|qoU@m9-`G0qUmq8 zW%`wtzIy3>p5n42#GW@@|K&{zzxl16q>&Tp?Owg1H5HhhDmXV z>Qxk1ufI_^9&^J{yinCETk%@t#9_GNRqU4`mrzLl5QXHTB}lf~RZYib*xFUhrL7b% zVC@v!%Xsqd~DlI0#u8kW|vYisFLg|=zCC+x%J1A`RXfw>Yv~xrH nE(+;Nw1xGY7C75hpXRuSC=>%2A_mC7jcM^OIs}aY2QL5sv;^_Y literal 1121 zcmV-n1fKgrRzVud7<1RVUE?nuytNy*^p&+`b z|IQg%Palg200000000B!Sj~>pRuoQ#qH4VBrmHSKhtm31B9qL(3=g4 zsub`f*AolRb!0okFa!y;T~w92>H}0|!M1Nu^$mCh#0$UzShm&uapD`>naoTYmBEUX z>z{M(J?Hz*cYOW3yXgn*JJBzXXzay4-EDeo$ML<6RVNL2E-WVuoqJW+V;zh7bepmm zO&h~uvx&-!-yZ*1Aq4jycoB1YQ8$RA?;mwKPV8)aexmeli67;YrT5C~22jV~;EoOI zJDb#}U$S05macD>FQkj?>MfYH%vwOY6qQkRCfU5$cmYCU`SnA}FNBd!R) z;!4#p&dm5QzU^;q(J1x;hWb*yf$OyAJ8i1Xi@RRj)z((cq*mOUI1 z2^E$}1MPrCG29?P6H_M_7qRJtXkgRXyO8C^hQ^JrQ~*bxDVHogjVw+=mKn@8&Yt7I zqHJ2GU{f9lSz7&?&}u~~jGO?O#D6O0^&;_~+#m!?OER@Lx?Y5+8}$7Su3Wu=koJNw zR(^K5hh4F{TE2;ToZvokbnA&mm%mD>>N0?346rDcZ2&9$WO3T%8^Bp^FgXeXIG1v( zMPmS~MIMIMYZ&m*4B$Kl$koqQlBAcVVM8s=*&r4c4{(a7ra;f{PrMZ+xgZd0%rPB6k^4cJ9@F zJTm-AGu%`CV$$sCM$l?aCN2xunLQc?Q1-|3Mi|s<%0Pw1FV)2*d0VV-(dNTsg(6~pK`c`a`65Z)E851NS_=M{YYGH^rb~bv z*GCa8rz6s6ezUA?e-_S{|KphK>0m|j%q;6@4s^*DDbmfw4FcICT}IGsg7tkr`)2ov zCOsc0&C0XQBk4aHpf5^FxpK`%g7qeE1xy~>n3!jQIGt`rgxw|4Jnn7k^&WaQ*+0_LN18yic>L}Y3jGL?K}_Jii62F>dNXx>(AhU#^892@fp z>SY$I*WVf(&vW50kCl2IOuWwJ;BYkYGWV;H6B;Bhiy%2}1#&R$TFv5eRP8cn(pJo4 zs9jS*^H~DT8sB`PDYr%G+z`=pEsKkZ-1l5Wg#{HfP`O8=O3nx2uQk~0WIK5%@41kE np+UNyd;~t^3^^Zu-(=iX7EGCjgaMP1JEFzEqD(x_NE!eDv+g01 From fce6ce390cc156282b507107427fc27322955039 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 14 Jun 2024 14:47:09 +0200 Subject: [PATCH 14/14] 54.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8d17191171..205dbb79bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-unicorn", - "version": "53.0.0", + "version": "54.0.0", "description": "More than 100 powerful ESLint rules", "license": "MIT", "repository": "sindresorhus/eslint-plugin-unicorn", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy