feat: prefer-array-some from eslint-plugin-unicorn#9056
feat: prefer-array-some from eslint-plugin-unicorn#9056ruidosujeira wants to merge 5 commits intobiomejs:mainfrom
Conversation
🦋 Changeset detectedLatest commit: cca98d5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughAdds a new nursery lint rule Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @.changeset/tame-pumas-bathe.md:
- Around line 1-5: The changeset header currently uses "minor" but nursery rules
targeting main must use "patch"; update the frontmatter value from
"@biomejs/biome: minor" to "@biomejs/biome: patch" in the changeset that
documents the new nursery rule useArraySome, and re-run any release/changeset
validation to ensure the PR uses a patch changeset for this nursery rule.
In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs`:
- Around line 38-45: The rule declaration for UseArraySome sets version to
"2.3.7" but per project convention nursery rules must use version: "next";
update the UseArraySome rule's version field to "next" (leave the rest—name,
language, sources, fix_kind—unchanged) so the declare_lint_rule! metadata uses
the placeholder version that will be replaced at release time.
🧹 Nitpick comments (5)
crates/biome_js_analyze/tests/specs/nursery/useArraySome/invalid.js (1)
1-21: Good coverage of core patterns.You might also consider adding a reversed-operand case (e.g.
0 < items.filter(x => x > 1).lengthor-1 !== items.findIndex(x => x > 1)) sincedetect_find_index_comparison_patternalready handles both sides — would be nice to have a snapshot proving it works.crates/biome_js_analyze/src/lint/nursery/use_array_some.rs (4)
61-65: Consider wiringtype Options = UseArraySomeOptionsinstead of().The
UseArraySomeOptionsstruct exists inbiome_rule_optionsbut isn't referenced here. The codegen typically expectstype Options = UseArraySomeOptionseven when the struct is empty — keeps things consistent and future-proof if options are added later.
43-43: Consider.inspired()instead of.same()for the source mapping.The original
prefer-array-somecovers additional patterns (e.g.filter(...).lengthin boolean context without a comparison,indexOf→includesinside.some()). Since this implementation is a subset,.inspired()is the more accurate qualifier.Based on learnings: "For rules derived from other linters, use the
sourcesmetadata withRuleSource::Eslintand either.same()for identical behavior or.inspired()for different behavior/options."
158-188:detect_filter_length_patternonly matches when the.lengthis on the left side of the comparison.Patterns like
0 < items.filter(x => x > 1).lengthwon't be caught becauseleft_is_expressionis only checked againstleft. Thedetect_find_index_comparison_patternalready handles both sides — worth considering parity here.Not a bug (the rule simply won't fire for reversed operands), but it's an asymmetry worth noting.
285-304: Theis_number_literal_value(&expression, -1.0)check on line 287 is likely dead code.JS parsers don't produce negative number literals;
-1is always a unary minus applied to1. The unary-expression path below (lines 291–303) is the one that does the real work. The initial check is harmless but could be misleading.
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs`:
- Line 43: Replace the over-promising source marker by changing
RuleSource::EslintUnicorn("prefer-array-some").same() to .inspired() in the
sources array so the rule metadata correctly indicates it differs from upstream;
locate the sources: &[RuleSource::EslintUnicorn("prefer-array-some").same()]
entry and update it to use .inspired() instead of .same().
🧹 Nitpick comments (4)
crates/biome_js_analyze/src/lint/nursery/use_array_some.rs (4)
158-188:detect_filter_length_patternonly matches when the length expression is on the left-hand side.Reversed comparisons such as
0 < arr.filter(fn).lengthor0 !== arr.filter(fn).lengthare silently ignored because:
- Line 168 unconditionally requires the left operand to be the
.lengthexpression.- Operators like
LessThanare absent from the match on line 174.Contrast with
detect_find_index_comparison_pattern, which already handles both operand positions. Parity here would be nice.Sketch of a more symmetric approach
let left = comparison.left().ok()?; + let right = comparison.right().ok()?; + let operator = comparison.operator().ok()?; + + let left_is_length = left_is_expression(&left, parent_member.syntax()); + let right_is_length = left_is_expression(&right, parent_member.syntax()); + - if !left_is_expression(&left, parent_member.syntax()) { - return None; - } - - let right = comparison.right().ok()?; - let operator = comparison.operator().ok()?; - let matches = match operator { - JsBinaryOperator::GreaterThan => is_number_literal_value(&right, 0.0), - JsBinaryOperator::GreaterThanOrEqual => is_number_literal_value(&right, 1.0), - JsBinaryOperator::StrictInequality | JsBinaryOperator::Inequality => { - is_number_literal_value(&right, 0.0) - } - _ => false, - }; + let other = if left_is_length { + &right + } else if right_is_length { + &left + } else { + return None; + }; + + // Normalise: treat `0 < length` the same as `length > 0`, etc. + let normalised_op = if right_is_length { flip_operator(operator) } else { operator }; + + let matches = match normalised_op { + JsBinaryOperator::GreaterThan => is_number_literal_value(other, 0.0), + JsBinaryOperator::GreaterThanOrEqual => is_number_literal_value(other, 1.0), + JsBinaryOperator::StrictInequality | JsBinaryOperator::Inequality => { + is_number_literal_value(other, 0.0) + } + _ => false, + };(You'd need a small
flip_operatorhelper that maps>↔<,>=↔<=, and keeps!=/!==unchanged.)
190-220: Missing commonfindIndexpatterns:>= 0and> -1.The unicorn rule also flags
findIndex(...) >= 0andfindIndex(...) > -1(and the reversed forms). These are arguably more common in the wild than!== -1. Worth adding for completeness — especially if the metadata claims.same()behaviour.
79-90: Logical-NOT context forfind/findLastis not detected.
!arr.find(fn)is a common existence-check pattern that the upstream unicorn rule flags, butis_in_boolean_contextonly coversif/while/for/ternary test positions. AJsUnaryExpressionwith!operator wrapping the call would slip through.Not blocking — fine as a follow-up — but worth a tracking comment or TODO.
253-265:nearest_parent_binary_expressiononly traverses parenthesised expressions.This is fine for the current patterns, but note it would silently fail for
(arr.filter(fn)).length > 0because the parent of the call node would be aJsParenthesizedExpression, not aJsStaticMemberExpression, sodetect_filter_length_patternwould returnNonebefore this function is even reached. An edge case, but worth being aware of.
| name: "useArraySome", | ||
| language: "js", | ||
| recommended: false, | ||
| sources: &[RuleSource::EslintUnicorn("prefer-array-some").same()], |
There was a problem hiding this comment.
Use .inspired() rather than .same() — the behaviour diverges from the upstream rule.
The unicorn prefer-array-some rule also flags patterns like findIndex(...) >= 0, findIndex(...) > -1, reversed operands (e.g. 0 < filter(...).length, -1 !== findIndex(...)), logical-NOT contexts (!arr.find(fn)), and others not covered here. Since this implementation is a subset, .same() over-promises compatibility.
Proposed fix
- sources: &[RuleSource::EslintUnicorn("prefer-array-some").same()],
+ sources: &[RuleSource::EslintUnicorn("prefer-array-some").inspired()],Based on learnings: "For rules derived from other linters, use the sources metadata with RuleSource::Eslint and either .same() for identical behavior or .inspired() for different behavior/options".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sources: &[RuleSource::EslintUnicorn("prefer-array-some").same()], | |
| sources: &[RuleSource::EslintUnicorn("prefer-array-some").inspired()], |
🤖 Prompt for AI Agents
In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` at line 43,
Replace the over-promising source marker by changing
RuleSource::EslintUnicorn("prefer-array-some").same() to .inspired() in the
sources array so the rule metadata correctly indicates it differs from upstream;
locate the sources: &[RuleSource::EslintUnicorn("prefer-array-some").same()]
entry and update it to use .inspired() instead of .same().
| return detect_find_index_comparison_pattern(call, &method_name); | ||
| } | ||
|
|
||
| if method_name == "find" || method_name == "findLast" { |
There was a problem hiding this comment.
Minor nit - this could (and arguably should) be made into an if let chain.
if (method_name == "find" || method_name == "findLast")
&& let Some(state) = detect_find_existence_comparison_pattern(call, &method_name) {
// ...
}| "@biomejs/biome": patch | ||
| --- | ||
|
|
||
| Added the nursery rule `useArraySome` to prefer `.some()` over verbose existence checks like `filter(...).length > 0` and `findIndex(...) !== -1`, with suggestions for `find`/`findLast` existence checks. |
There was a problem hiding this comment.
Just throwing it out there - the rule also looks like it would trigger on ES2025's `Iterator.prototype.find.
IDK whether ESlint plugin unicorn does this itself, but might be worth documenting regardless.
| /// ``` | ||
| /// | ||
| /// ```js,expect_diagnostic | ||
| /// array.findIndex(predicate) !== -1; |
There was a problem hiding this comment.
I'd personally like to see a few more examples here showing the null equality checks and support for findLast variations
Summary
This PR introduces a new nursery rule useArraySome, porting the behavior of unicorn/prefer-array-some (issue #8820).
The rule prefers .some() when checking for the existence of matching elements and provides:
Autofix for:
filter(...).length > 0, !== 0, >= 1
findIndex(...) !== -1
findLastIndex(...) !== -1
Suggestion (no autofix) for:
find(...) / findLast(...) used only as existence checks (truthy checks, ternaries, !== undefined, != null).
The fix preserves arguments, including thisArg, and only replaces the method name with some.
Closes #8820.
Test Plan
Added comprehensive valid.js and invalid.js test cases.
Verified autofix output through snapshots (including preservation of thisArg).
Ran rule tests and full test suite locally.
Confirmed no regressions in existing rules.
Docs
The rule is registered as a nursery rule and documented with examples and expected fixes. No additional website documentation changes required at this stage.