Skip to content

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

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

feat: prefer-array-some from eslint-plugin-unicorn#9056
ruidosujeira wants to merge 5 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: cca98d5

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 analyser. Implements detection and automated-fix/suggest logic in Rust for patterns like filter(...).length > 0, findIndex(... ) !== -1, findLastIndex(...) !== -1, and find/findLast used in boolean or existence checks, rewriting them to .some() where appropriate. Adds rule options type, test fixtures (valid.js and invalid.js), and a changeset entry for the package release metadata.

Suggested reviewers

  • dyc3
  • ematipico
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: porting the prefer-array-some rule from eslint-plugin-unicorn to Biome.
Description check ✅ Passed The description clearly details the rule's functionality, autofix cases, suggestion-only cases, test coverage, and references the linked issue.
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #8820: implements the useArraySome rule with autofix support for filter/findIndex patterns and suggestions for find/findLast existence checks, preserves arguments including thisArg, includes comprehensive tests, and registers as a nursery rule.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the useArraySome nursery rule: new lint implementation, test files, rule options, and changeset entry with no unrelated modifications.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ 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

No actionable comments were generated in the recent review. 🎉


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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 (cca98d5) 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.

name: "useArraySome",
language: "js",
recommended: false,
sources: &[RuleSource::EslintUnicorn("prefer-array-some").same()],
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

Use .inspired() rather than .same() — the behaviour diverges from the upstream rule.

The unicorn prefer-array-some rule also flags patterns like findIndex(...) >= 0, findIndex(...) > -1, reversed operands (e.g. 0 < filter(...).length, -1 !== findIndex(...)), logical-NOT contexts (!arr.find(fn)), and others not covered here. Since this implementation is a subset, .same() over-promises compatibility.

Proposed fix
-        sources: &[RuleSource::EslintUnicorn("prefer-array-some").same()],
+        sources: &[RuleSource::EslintUnicorn("prefer-array-some").inspired()],

Based on learnings: "For rules derived from other linters, use the sources metadata with RuleSource::Eslint and either .same() for identical behavior or .inspired() for different behavior/options".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sources: &[RuleSource::EslintUnicorn("prefer-array-some").same()],
sources: &[RuleSource::EslintUnicorn("prefer-array-some").inspired()],
🤖 Prompt for AI Agents
In `@crates/biome_js_analyze/src/lint/nursery/use_array_some.rs` at line 43,
Replace the over-promising source marker by changing
RuleSource::EslintUnicorn("prefer-array-some").same() to .inspired() in the
sources array so the rule metadata correctly indicates it differs from upstream;
locate the sources: &[RuleSource::EslintUnicorn("prefer-array-some").same()]
entry and update it to use .inspired() instead of .same().

return detect_find_index_comparison_pattern(call, &method_name);
}

if method_name == "find" || method_name == "findLast" {
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) {
  // ...
}

"@biomejs/biome": patch
---

Added the nursery rule `useArraySome` to prefer `.some()` over verbose existence checks like `filter(...).length > 0` and `findIndex(...) !== -1`, with suggestions for `find`/`findLast` existence checks.
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

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