feat: prefer-array-some from eslint-plugin-unicorn#9056
feat: prefer-array-some from eslint-plugin-unicorn#9056ruidosujeira wants to merge 9 commits intobiomejs:mainfrom
Conversation
🦋 Changeset detectedLatest commit: 12579c5 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 | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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.
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) {
// ...
}
.changeset/tame-pumas-bathe.md
Outdated
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
There was a problem hiding this comment.
🤖 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 forfilter(...).length.
detect_find_index_comparison_patternchecks both operand positions (lines 219–223), but this function only checks the left side. Patterns like0 < arr.filter(fn).lengthor1 <= arr.filter(fn).lengthwill be silently missed.If you'd like symmetry with the
findIndexpath, you'd need to check the right operand and mirror the operator (e.g.0 < x↔x > 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.
There was a problem hiding this comment.
🤖 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,
-1is always parsed as a unary minus applied to a1literal — 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(...).lengthonly flagslengthon the left — reversed comparisons like0 < arr.filter(fn).lengthare silently ignored.Not a bug since the rule uses
.inspired(), but worth noting as a gap if you plan to expand coverage later. ThefindIndexdetection (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
JsBinaryExpressionor 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.
There was a problem hiding this comment.
🤖 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
-1as a unary negation of the literal1, not as a single negative number literal. Theas_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_expressionis a slightly misleading name.It's also used to check the right operand (e.g. line 220, 246). Something like
is_same_nodeorexpression_matches_nodewould 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_patterndoesn'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 checksleft == .lengthandright == literal. Patterns like0 < arr.filter(fn).lengthor1 <= arr.filter(fn).lengthwill 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.
There was a problem hiding this comment.
nit: you could probably simplify this a little with a let-else. something like:
let UseArraySomeState::Fix { call, replace_node } = state else {
return None;
};There was a problem hiding this comment.
nit: do we need the state to be cloneable?
There was a problem hiding this comment.
nit: simplify wording
| "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." |
| 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." | ||
| }), |
There was a problem hiding this comment.
follow the rule pillars. we need to tell the user:
- what the error is
- why it is an error
- how to fix it
this is missing the "why"
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
crates/biome_js_analyze/src/lint/nursery/use_array_some.rs (2)
60-70:UseArraySomeStateneed not bepub—pub(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_expressionis 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&leftand&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 = (); |
There was a problem hiding this comment.
🧩 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
`()`.
| pattern: "find/findLast used as boolean", | ||
| }); |
There was a problem hiding this comment.
Diagnostic copy is incomplete and slightly awkward in two spots.
- The
.note()at line 129 only lists.find()and.filter()—findLast(),findIndex()andfindLastIndex()are silently omitted, which will confuse users who trigger the rule via those methods. 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.
| 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, |
There was a problem hiding this comment.
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.
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.