Skip to content

feat: prefer-array-some from eslint-plugin-unicorn#9056

Open
ruidosujeira wants to merge 9 commits intobiomejs:mainfrom
ruidosujeira:feat/8820-prefer-array-some
Open

feat: prefer-array-some from eslint-plugin-unicorn#9056
ruidosujeira wants to merge 9 commits intobiomejs:mainfrom
ruidosujeira:feat/8820-prefer-array-some

Conversation

@ruidosujeira
Copy link
Contributor

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.

@changeset-bot
Copy link

changeset-bot bot commented Feb 13, 2026

🦋 Changeset detected

Latest commit: 12579c5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

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

@github-actions github-actions bot added A-CLI Area: CLI A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis labels Feb 13, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

Walkthrough

Adds a new nursery lint rule useArraySome to the Biome JS analyzer. Implements detection and fix/suggestion logic in Rust for patterns such as filter(...).length > 0, findIndex(...) !== -1, findLastIndex(...) !== -1, and find/findLast used in boolean/existence checks, suggesting or rewriting them to .some() where appropriate. Introduces a public UseArraySomeState enum and a rule declaration, an empty UseArraySomeOptions options struct, test fixtures (valid.js, invalid.js), and a changeset entry for the package metadata.

Suggested reviewers

  • dyc3
  • ematipico
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the main change: implementing a new lint rule that ports the prefer-array-some functionality from eslint-plugin-unicorn.
Description check ✅ Passed The PR description thoroughly explains the rule's purpose, autofixes, suggestions, test coverage, and verification steps—all directly related to the changeset.
Linked Issues check ✅ Passed The PR successfully implements all coding objectives from issue #8820: the useArraySome rule with autofixes for filter/findIndex/findLastIndex patterns and suggestions for find/findLast, preserves thisArg, includes comprehensive tests, and is registered as a nursery rule.
Out of Scope Changes check ✅ Passed All changes are strictly in scope: the new lint rule implementation, supporting infrastructure (rule options, tests), and a single changeset entry—nothing extraneous.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).length or -1 !== items.findIndex(x => x > 1)) since detect_find_index_comparison_pattern already 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 wiring type Options = UseArraySomeOptions instead of ().

The UseArraySomeOptions struct exists in biome_rule_options but isn't referenced here. The codegen typically expects type Options = UseArraySomeOptions even 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-some covers additional patterns (e.g. filter(...).length in boolean context without a comparison, indexOfincludes inside .some()). Since this implementation is a subset, .inspired() is the more accurate qualifier.

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."


158-188: detect_filter_length_pattern only matches when the .length is on the left side of the comparison.

Patterns like 0 < items.filter(x => x > 1).length won't be caught because left_is_expression is only checked against left. The detect_find_index_comparison_pattern already 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: The is_number_literal_value(&expression, -1.0) check on line 287 is likely dead code.

JS parsers don't produce negative number literals; -1 is always a unary minus applied to 1. 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.

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 13, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 95 skipped benchmarks1


Comparing ruidosujeira:feat/8820-prefer-array-some (12579c5) with main (5ac2ad6)

Open in CodSpeed

Footnotes

  1. 95 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_pattern only matches when the length expression is on the left-hand side.

Reversed comparisons such as 0 < arr.filter(fn).length or 0 !== arr.filter(fn).length are silently ignored because:

  1. Line 168 unconditionally requires the left operand to be the .length expression.
  2. Operators like LessThan are 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_operator helper that maps ><, >=<=, and keeps !=/!== unchanged.)


190-220: Missing common findIndex patterns: >= 0 and > -1.

The unicorn rule also flags findIndex(...) >= 0 and findIndex(...) > -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 for find/findLast is not detected.

!arr.find(fn) is a common existence-check pattern that the upstream unicorn rule flags, but is_in_boolean_context only covers if/while/for/ternary test positions. A JsUnaryExpression with ! 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_expression only traverses parenthesised expressions.

This is fine for the current patterns, but note it would silently fail for (arr.filter(fn)).length > 0 because the parent of the call node would be a JsParenthesizedExpression, not a JsStaticMemberExpression, so detect_filter_length_pattern would return None before this function is even reached. An edge case, but worth being aware of.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
  // ...
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd personally like to see a few more examples here showing the null equality checks and support for findLast variations

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs`:
- Around line 180-195: The comparison logic in use_array_some.rs only inspects
the left operand of nearest_parent_binary_expression(parent_member.syntax()), so
reversed forms like `0 < arr.filter(...).length` are missed; update the logic in
the function that currently computes comparison/left/right/operator (used by
detect_find_index_comparison_pattern) to also check the case where the array
expression is on the right: if left_is_expression(&left, parent_member.syntax())
fails, test whether right_is_expression(&right, parent_member.syntax()) (or
mirror the existing left check against right) and then mirror the operator
(e.g., treat JsBinaryOperator::LessThan as GreaterThan when swapping sides,
LessThanOrEqual ↔ GreaterThanOrEqual, etc.) so the same numeric literal checks
(is_number_literal_value(&other, 0.0/1.0)) apply for reversed comparisons.
🧹 Nitpick comments (1)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs`:
- Around line 180-195: The comparison logic in use_array_some.rs only inspects
the left operand of nearest_parent_binary_expression(parent_member.syntax()), so
reversed forms like `0 < arr.filter(...).length` are missed; update the logic in
the function that currently computes comparison/left/right/operator (used by
detect_find_index_comparison_pattern) to also check the case where the array
expression is on the right: if left_is_expression(&left, parent_member.syntax())
fails, test whether right_is_expression(&right, parent_member.syntax()) (or
mirror the existing left check against right) and then mirror the operator
(e.g., treat JsBinaryOperator::LessThan as GreaterThan when swapping sides,
LessThanOrEqual ↔ GreaterThanOrEqual, etc.) so the same numeric literal checks
(is_number_literal_value(&other, 0.0/1.0)) apply for reversed comparisons.
crates/biome_js_analyze/src/lint/nursery/use_array_some.rs (1)

180-195: Reversed comparisons are not handled for filter(...).length.

detect_find_index_comparison_pattern checks both operand positions (lines 219–223), but this function only checks the left side. Patterns like 0 < arr.filter(fn).length or 1 <= arr.filter(fn).length will be silently missed.

If you'd like symmetry with the findIndex path, you'd need to check the right operand and mirror the operator (e.g. 0 < xx > 0). Not blocking, but worth a follow-up.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 180
- 195, The comparison logic in use_array_some.rs only inspects the left operand
of nearest_parent_binary_expression(parent_member.syntax()), so reversed forms
like `0 < arr.filter(...).length` are missed; update the logic in the function
that currently computes comparison/left/right/operator (used by
detect_find_index_comparison_pattern) to also check the case where the array
expression is on the right: if left_is_expression(&left, parent_member.syntax())
fails, test whether right_is_expression(&right, parent_member.syntax()) (or
mirror the existing left check against right) and then mirror the operator
(e.g., treat JsBinaryOperator::LessThan as GreaterThan when swapping sides,
LessThanOrEqual ↔ GreaterThanOrEqual, etc.) so the same numeric literal checks
(is_number_literal_value(&other, 0.0/1.0)) apply for reversed comparisons.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs`:
- Around line 299-318: The initial call to is_number_literal_value(&expression,
-1.0) in is_negative_one_literal is dead because JS parses -1 as a unary minus
applied to 1, so remove that check and simplify the function: clone and
omit_parentheses, then match expression.as_js_unary_expression(), verify
unary.operator() is JsUnaryOperator::Minus, and finally check unary.argument()
is a numeric literal value 1.0 using is_number_literal_value; keep the existing
early returns and error handling unchanged.
- Around line 186-195: The current comparison handling in the match block only
checks the right side (using comparison.right(), operator, and
is_number_literal_value) so reversed comparisons like 0 < arr.filter(...).length
are ignored; update the logic in the use_array_some rule (look for
variables/idents comparison, right, operator, matches, and
is_number_literal_value) to also inspect the left side when the right side is a
numeric literal: compute matches for both (literal on right with expression on
left) and (literal on left with expression on right) mirroring the approach used
in the findIndex detection so operators like
GreaterThan/GreaterThanOrEqual/Inequality are evaluated in both directions and
produce the same boolean result regardless of operand order.
- Around line 267-279: The traversal in nearest_parent_binary_expression can run
unbounded; add a simple depth guard (e.g., a MAX_PAREN_DEPTH constant or local
counter) and increment it each loop iteration, returning None when the limit is
reached, while keeping the existing logic that checks JsBinaryExpression::cast
and biome_js_syntax::JsParenthesizedExpression::can_cast; use the existing
node.parent() calls and preserve the early returns so behavior is unchanged for
normal inputs but protected against runaway parent chains.
🧹 Nitpick comments (3)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs`:
- Around line 299-318: The initial call to is_number_literal_value(&expression,
-1.0) in is_negative_one_literal is dead because JS parses -1 as a unary minus
applied to 1, so remove that check and simplify the function: clone and
omit_parentheses, then match expression.as_js_unary_expression(), verify
unary.operator() is JsUnaryOperator::Minus, and finally check unary.argument()
is a numeric literal value 1.0 using is_number_literal_value; keep the existing
early returns and error handling unchanged.
- Around line 186-195: The current comparison handling in the match block only
checks the right side (using comparison.right(), operator, and
is_number_literal_value) so reversed comparisons like 0 < arr.filter(...).length
are ignored; update the logic in the use_array_some rule (look for
variables/idents comparison, right, operator, matches, and
is_number_literal_value) to also inspect the left side when the right side is a
numeric literal: compute matches for both (literal on right with expression on
left) and (literal on left with expression on right) mirroring the approach used
in the findIndex detection so operators like
GreaterThan/GreaterThanOrEqual/Inequality are evaluated in both directions and
produce the same boolean result regardless of operand order.
- Around line 267-279: The traversal in nearest_parent_binary_expression can run
unbounded; add a simple depth guard (e.g., a MAX_PAREN_DEPTH constant or local
counter) and increment it each loop iteration, returning None when the limit is
reached, while keeping the existing logic that checks JsBinaryExpression::cast
and biome_js_syntax::JsParenthesizedExpression::can_cast; use the existing
node.parent() calls and preserve the early returns so behavior is unchanged for
normal inputs but protected against runaway parent chains.
crates/biome_js_analyze/src/lint/nursery/use_array_some.rs (3)

299-318: is_number_literal_value(&expression, -1.0) on line 301 is effectively dead code.

In JS AST, -1 is always parsed as a unary minus applied to a 1 literal — there's no such thing as a negative number literal token. The check on line 301 will never succeed; the unary fallback below does all the actual work. Not harmful, but it's misleading.

Proposed simplification
 fn is_negative_one_literal(expression: &AnyJsExpression) -> bool {
     let expression = expression.clone().omit_parentheses();
-    if is_number_literal_value(&expression, -1.0) {
-        return true;
-    }
-
     let unary = match expression.as_js_unary_expression() {
         Some(unary) => unary,
         None => return false,
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 299
- 318, The initial call to is_number_literal_value(&expression, -1.0) in
is_negative_one_literal is dead because JS parses -1 as a unary minus applied to
1, so remove that check and simplify the function: clone and omit_parentheses,
then match expression.as_js_unary_expression(), verify unary.operator() is
JsUnaryOperator::Minus, and finally check unary.argument() is a numeric literal
value 1.0 using is_number_literal_value; keep the existing early returns and
error handling unchanged.

186-195: filter(...).length only flags length on the left — reversed comparisons like 0 < arr.filter(fn).length are silently ignored.

Not a bug since the rule uses .inspired(), but worth noting as a gap if you plan to expand coverage later. The findIndex detection (line 219–223) does handle both sides, so this asymmetry might surprise future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 186
- 195, The current comparison handling in the match block only checks the right
side (using comparison.right(), operator, and is_number_literal_value) so
reversed comparisons like 0 < arr.filter(...).length are ignored; update the
logic in the use_array_some rule (look for variables/idents comparison, right,
operator, matches, and is_number_literal_value) to also inspect the left side
when the right side is a numeric literal: compute matches for both (literal on
right with expression on left) and (literal on left with expression on right)
mirroring the approach used in the findIndex detection so operators like
GreaterThan/GreaterThanOrEqual/Inequality are evaluated in both directions and
produce the same boolean result regardless of operand order.

267-279: nearest_parent_binary_expression — consider guarding against runaway traversal.

The loop walks upward through parenthesised expressions until it finds a JsBinaryExpression or a non-paren node. In practice this terminates quickly, but a belt-and-suspenders depth cap (or a comment explaining why unbounded is safe) would help future readers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 267
- 279, The traversal in nearest_parent_binary_expression can run unbounded; add
a simple depth guard (e.g., a MAX_PAREN_DEPTH constant or local counter) and
increment it each loop iteration, returning None when the limit is reached,
while keeping the existing logic that checks JsBinaryExpression::cast and
biome_js_syntax::JsParenthesizedExpression::can_cast; use the existing
node.parent() calls and preserve the early returns so behavior is unchanged for
normal inputs but protected against runaway parent chains.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs`:
- Around line 299-303: The direct numeric-literal check using
is_number_literal_value(&expression, -1.0) inside is_negative_one_literal is
effectively dead because the parser represents -1 as a UnaryExpression, so
remove that branch (or replace it with a brief comment noting it's unreachable)
and rely on the subsequent unary-negation logic that inspects
AnyJsUnaryExpression and as_js_number_literal_expression(); update
is_negative_one_literal to only perform the unary check (or keep the call but
comment why it will never match) to avoid confusion around
is_number_literal_value and -1 handling.
- Around line 281-287: Rename the helper function left_is_expression to a
neutral name like is_same_node or expression_matches_node to reflect that it can
be used for either operand; update the function signature (left_is_expression ->
is_same_node) and replace all call sites (e.g., uses around lines where it
checks the right operand) to the new name, keeping the implementation that
compares AnyJsExpression.omit_parentheses().syntax().eq(&JsSyntaxNode) unchanged
and run tests to ensure no references remain to left_is_expression.
- Around line 172-202: detect_filter_length_pattern currently only handles cases
where the ".length" member is on the left side of the binary expression; add
symmetric handling for when the ".length" member is on the right (e.g., "0 <
arr.filter(...).length") by checking both comparison.left() and
comparison.right() and determining which side is the parent_member (use
left_is_expression on each side), then map operators appropriately (or swap
operands) so that the same numeric checks (is_number_literal_value 0.0 or 1.0)
apply regardless of side; mirror the logic used in
detect_find_index_comparison_pattern to locate the member, identify the literal
side, compute matches for JsBinaryOperator variants, and return the same
UseArraySomeState::Fix with call, replace_node (the binary expression), and
pattern when matched.
🧹 Nitpick comments (3)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs`:
- Around line 299-303: The direct numeric-literal check using
is_number_literal_value(&expression, -1.0) inside is_negative_one_literal is
effectively dead because the parser represents -1 as a UnaryExpression, so
remove that branch (or replace it with a brief comment noting it's unreachable)
and rely on the subsequent unary-negation logic that inspects
AnyJsUnaryExpression and as_js_number_literal_expression(); update
is_negative_one_literal to only perform the unary check (or keep the call but
comment why it will never match) to avoid confusion around
is_number_literal_value and -1 handling.
- Around line 281-287: Rename the helper function left_is_expression to a
neutral name like is_same_node or expression_matches_node to reflect that it can
be used for either operand; update the function signature (left_is_expression ->
is_same_node) and replace all call sites (e.g., uses around lines where it
checks the right operand) to the new name, keeping the implementation that
compares AnyJsExpression.omit_parentheses().syntax().eq(&JsSyntaxNode) unchanged
and run tests to ensure no references remain to left_is_expression.
- Around line 172-202: detect_filter_length_pattern currently only handles cases
where the ".length" member is on the left side of the binary expression; add
symmetric handling for when the ".length" member is on the right (e.g., "0 <
arr.filter(...).length") by checking both comparison.left() and
comparison.right() and determining which side is the parent_member (use
left_is_expression on each side), then map operators appropriately (or swap
operands) so that the same numeric checks (is_number_literal_value 0.0 or 1.0)
apply regardless of side; mirror the logic used in
detect_find_index_comparison_pattern to locate the member, identify the literal
side, compute matches for JsBinaryOperator variants, and return the same
UseArraySomeState::Fix with call, replace_node (the binary expression), and
pattern when matched.
crates/biome_js_analyze/src/lint/nursery/use_array_some.rs (3)

299-303: is_number_literal_value(&expression, -1.0) on line 301 is likely dead code.

JS parsers (including Biome's) represent -1 as a unary negation of the literal 1, not as a single negative number literal. The as_js_number_literal_expression() path won't match -1, so this branch should never be true. The unary check below (lines 305-318) is the one doing the real work.

Not harmful, but worth a brief comment or removal to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 299
- 303, The direct numeric-literal check using
is_number_literal_value(&expression, -1.0) inside is_negative_one_literal is
effectively dead because the parser represents -1 as a UnaryExpression, so
remove that branch (or replace it with a brief comment noting it's unreachable)
and rely on the subsequent unary-negation logic that inspects
AnyJsUnaryExpression and as_js_number_literal_expression(); update
is_negative_one_literal to only perform the unary check (or keep the call but
comment why it will never match) to avoid confusion around
is_number_literal_value and -1 handling.

281-287: Nit: left_is_expression is a slightly misleading name.

It's also used to check the right operand (e.g. line 220, 246). Something like is_same_node or expression_matches_node would read more naturally at the call sites.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 281
- 287, Rename the helper function left_is_expression to a neutral name like
is_same_node or expression_matches_node to reflect that it can be used for
either operand; update the function signature (left_is_expression ->
is_same_node) and replace all call sites (e.g., uses around lines where it
checks the right operand) to the new name, keeping the implementation that
compares AnyJsExpression.omit_parentheses().syntax().eq(&JsSyntaxNode) unchanged
and run tests to ensure no references remain to left_is_expression.

172-202: detect_filter_length_pattern doesn't handle reversed operands (e.g. 0 < arr.filter(fn).length).

detect_find_index_comparison_pattern (line 219-223) checks both sides of the binary expression, but this function only checks left == .length and right == literal. Patterns like 0 < arr.filter(fn).length or 1 <= arr.filter(fn).length will silently pass undetected.

Consider adding symmetric checks, mirroring the approach in detect_find_index_comparison_pattern.

Sketch of the idea
     let comparison = nearest_parent_binary_expression(parent_member.syntax())?;
     let left = comparison.left().ok()?;
-    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 left_is_len = left_is_expression(&left, parent_member.syntax());
+    let right_is_len = left_is_expression(&right, parent_member.syntax());
+
+    let matches = if left_is_len {
+        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,
+        }
+    } else if right_is_len {
+        match operator {
+            JsBinaryOperator::LessThan => is_number_literal_value(&left, 0.0),
+            JsBinaryOperator::LessThanOrEqual => is_number_literal_value(&left, 1.0),
+            JsBinaryOperator::StrictInequality | JsBinaryOperator::Inequality => {
+                is_number_literal_value(&left, 0.0)
+            }
+            _ => false,
+        }
+    } else {
+        false
+    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 172
- 202, detect_filter_length_pattern currently only handles cases where the
".length" member is on the left side of the binary expression; add symmetric
handling for when the ".length" member is on the right (e.g., "0 <
arr.filter(...).length") by checking both comparison.left() and
comparison.right() and determining which side is the parent_member (use
left_is_expression on each side), then map operators appropriately (or swap
operands) so that the same numeric checks (is_number_literal_value 0.0 or 1.0)
apply regardless of side; mirror the logic used in
detect_find_index_comparison_pattern to locate the member, identify the literal
side, compute matches for JsBinaryOperator variants, and return the same
UseArraySomeState::Fix with call, replace_node (the binary expression), and
pattern when matched.

Comment on lines 136 to 141
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you could probably simplify this a little with a let-else. something like:

let UseArraySomeState::Fix { call, replace_node } = state else {
    return None;
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: do we need the state to be cloneable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: simplify wording

Suggested change
"Use "<Emphasis>".some()"</Emphasis>" when you only need to know whether at least one element matches."
"Use "<Emphasis>".some()"</Emphasis>" when you only need to know if any element matches."

Comment on lines 121 to 131
Some(
RuleDiagnostic::new(
rule_category!(),
range,
markup! {
"Prefer "<Emphasis>".some()"</Emphasis>" over "<Emphasis>{pattern}</Emphasis>" to check for existence."
},
)
.note(markup! {
"Use "<Emphasis>".some()"</Emphasis>" when you only need to know whether at least one element matches."
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

follow the rule pillars. we need to tell the user:

  1. what the error is
  2. why it is an error
  3. how to fix it

this is missing the "why"

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
crates/biome_js_analyze/src/lint/nursery/use_array_some.rs (2)

60-70: UseArraySomeState need not be pubpub(crate) is sufficient.

Rule state types are crate-internal; no external consumer needs to name this type.

♻️ Proposed fix
-pub enum UseArraySomeState {
+pub(crate) enum UseArraySomeState {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 60 -
70, Change the exported visibility of the rule state enum to crate-only: modify
the declaration of UseArraySomeState from `pub enum UseArraySomeState { ... }`
to `pub(crate) enum UseArraySomeState { ... }` (keeping the variants Fix and
Suggest and their fields unchanged) so the type remains available within the
crate but is not publicly exported.

283-289: left_is_expression is a misleading name — it's used for both sides.

The function simply tests whether an expression's unwrapped syntax node equals target; it has nothing inherently "left" about it. Lines 221–222 then call it for both &left and &right, requiring the caller to mentally rename it on the spot.

♻️ Suggested rename
-fn left_is_expression(expression: &AnyJsExpression, target: &JsSyntaxNode) -> bool {
+fn expression_matches_node(expression: &AnyJsExpression, target: &JsSyntaxNode) -> bool {

And update all three call-sites (detect_filter_length_pattern, detect_find_index_comparison_pattern, detect_find_existence_comparison_pattern) accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 283
- 289, The function left_is_expression is misnamed because it simply checks
whether an expression's unwrapped syntax node equals a target and is used for
both sides; rename left_is_expression to a neutral, descriptive name such as
expression_matches_target (or is_expression_target) and update all call-sites
that reference it—detect_filter_length_pattern,
detect_find_index_comparison_pattern, and
detect_find_existence_comparison_pattern—to use the new name; ensure the
function signature and any imports/usages are adjusted accordingly and run
tests/compile to confirm no remaining references to left_is_expression.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs`:
- Around line 174-196: detect_filter_length_pattern currently only handles cases
where the member expression (parent_member) is the left operand of the binary
comparison; update it to detect when parent_member is on either side of the
comparison by checking comparison.left() and comparison.right() and normalizing
which side is the array.length expression and which side is the numeric literal.
Use nearest_parent_binary_expression to get the comparison, then determine if
parent_member.syntax() matches the left or the right; if it matches the right,
swap roles (and interpret operators accordingly, e.g., treat GreaterThan as
LessThan when operands are swapped) before calling left_is_expression,
is_number_literal_value, and constructing the UseArraySomeState so that mirrored
forms like 0 < arr.filter(...).length or 0 !== arr.filter(...).length are
handled. Ensure existing helper functions (left_is_expression,
is_number_literal_value) are reused and no behavior changes for the original
left-side case.
- Around line 101-102: Update the diagnostic text in use_array_some.rs: change
the pattern value (currently "find/findLast used as boolean") to a clearer
wording like "find/findIndex/findLast/findLastIndex used as boolean" or
"find-family used as boolean" and update the .note() call (the diagnostic note
that currently only mentions .find() and .filter()) to list all relevant methods
(.find(), .findLast(), .findIndex(), .findLastIndex(), and .filter()) so users
triggered by any of those will see accurate guidance; locate and edit the
pattern field and the .note() invocation near the diagnostic construction in the
file to make these textual changes.
- Line 76: Replace the empty options alias with the generated rule options type:
change the rule's type alias `Options = ();` to `Options = UseArraySomeOptions`
and ensure the `UseArraySomeOptions` type from the `biome_rule_options` crate is
imported/available in `use_array_some.rs`; update any references to `Options`
accordingly so the rule uses the structured `UseArraySomeOptions` instead of
`()`.

---

Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs`:
- Around line 60-70: Change the exported visibility of the rule state enum to
crate-only: modify the declaration of UseArraySomeState from `pub enum
UseArraySomeState { ... }` to `pub(crate) enum UseArraySomeState { ... }`
(keeping the variants Fix and Suggest and their fields unchanged) so the type
remains available within the crate but is not publicly exported.
- Around line 283-289: The function left_is_expression is misnamed because it
simply checks whether an expression's unwrapped syntax node equals a target and
is used for both sides; rename left_is_expression to a neutral, descriptive name
such as expression_matches_target (or is_expression_target) and update all
call-sites that reference it—detect_filter_length_pattern,
detect_find_index_comparison_pattern, and
detect_find_existence_comparison_pattern—to use the new name; ensure the
function signature and any imports/usages are adjusted accordingly and run
tests/compile to confirm no remaining references to left_is_expression.

type Query = Ast<JsCallExpression>;
type State = UseArraySomeState;
type Signals = Option<Self::State>;
type Options = ();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether UseArraySomeOptions is defined and whether it's expected to be
# used in the rule implementation.

echo "=== Checking biome_rule_options for UseArraySomeOptions ==="
fd "use_array_some.rs" crates/biome_rule_options --exec cat {}

echo ""
echo "=== Cross-references in the rule file ==="
rg -n "UseArraySomeOptions" crates/biome_js_analyze/

Repository: biomejs/biome

Length of output: 507


Wire the generated UseArraySomeOptions type instead of ().

The crates/biome_rule_options crate already defines a properly-structured UseArraySomeOptions struct for this rule. The rule should use type Options = UseArraySomeOptions; rather than type Options = ();—this keeps the codegen pipeline consistent and is standard practice for Biome lint rules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` at line 76,
Replace the empty options alias with the generated rule options type: change the
rule's type alias `Options = ();` to `Options = UseArraySomeOptions` and ensure
the `UseArraySomeOptions` type from the `biome_rule_options` crate is
imported/available in `use_array_some.rs`; update any references to `Options`
accordingly so the rule uses the structured `UseArraySomeOptions` instead of
`()`.

Comment on lines +101 to +102
pattern: "find/findLast used as boolean",
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Diagnostic copy is incomplete and slightly awkward in two spots.

  1. The .note() at line 129 only lists .find() and .filter()findLast(), findIndex() and findLastIndex() are silently omitted, which will confuse users who trigger the rule via those methods.
  2. pattern: "find/findLast used as boolean" produces the message "Prefer .some() over find/findLast used as boolean to check for existence." — the wording is a bit rough.
✏️ Suggested fixes
-                pattern: "find/findLast used as boolean",
+                pattern: "find/findLast in a boolean context",
             .note(markup! {
-                <Emphasis>".some()"</Emphasis>" is more readable and semantically expressive than using "<Emphasis>".find()"</Emphasis>" or "<Emphasis>".filter()"</Emphasis>" just to check for element existence."
+                <Emphasis>".some()"</Emphasis>" is more readable and semantically expressive than using "<Emphasis>".find()"</Emphasis>", "<Emphasis>".findLast()"</Emphasis>", "<Emphasis>".findIndex()"</Emphasis>", "<Emphasis>".findLastIndex()"</Emphasis>", or "<Emphasis>".filter()"</Emphasis>" just to check for element existence."
             })

Also applies to: 128-133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 101
- 102, Update the diagnostic text in use_array_some.rs: change the pattern value
(currently "find/findLast used as boolean") to a clearer wording like
"find/findIndex/findLast/findLastIndex used as boolean" or "find-family used as
boolean" and update the .note() call (the diagnostic note that currently only
mentions .find() and .filter()) to list all relevant methods (.find(),
.findLast(), .findIndex(), .findLastIndex(), and .filter()) so users triggered
by any of those will see accurate guidance; locate and edit the pattern field
and the .note() invocation near the diagnostic construction in the file to make
these textual changes.

Comment on lines +174 to +196
fn detect_filter_length_pattern(call: &JsCallExpression) -> Option<UseArraySomeState> {
let parent_member = call.syntax().parent().and_then(|parent| {
let member = parent.cast::<biome_js_syntax::JsStaticMemberExpression>()?;
let member_name = member.member().ok()?;
let name = member_name.as_js_name()?;
(name.value_token().ok()?.text_trimmed() == "length").then_some(member)
})?;

let comparison = nearest_parent_binary_expression(parent_member.syntax())?;
let left = comparison.left().ok()?;
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

detect_filter_length_pattern only catches comparisons with filter(...).length on the left.

Line 184 bails out if the member expression isn't the left operand, so mirrored forms like 0 < arr.filter(fn).length or 0 !== arr.filter(fn).length go undetected. Given that this is an .inspired() rule this is a conscious scope decision — just flagging it so future contributors know it's a known gap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` around lines 174
- 196, detect_filter_length_pattern currently only handles cases where the
member expression (parent_member) is the left operand of the binary comparison;
update it to detect when parent_member is on either side of the comparison by
checking comparison.left() and comparison.right() and normalizing which side is
the array.length expression and which side is the numeric literal. Use
nearest_parent_binary_expression to get the comparison, then determine if
parent_member.syntax() matches the left or the right; if it matches the right,
swap roles (and interpret operators accordingly, e.g., treat GreaterThan as
LessThan when operands are swapped) before calling left_is_expression,
is_number_literal_value, and constructing the UseArraySomeState so that mirrored
forms like 0 < arr.filter(...).length or 0 !== arr.filter(...).length are
handled. Ensure existing helper functions (left_is_expression,
is_number_literal_value) are reused and no behavior changes for the original
left-side case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📎 Port prefer-array-some from eslint-plugin-unicorn

3 participants

Comments