Skip to content

feat(lint/js): add useStringStartsEndsWith#9796

Merged
dyc3 merged 1 commit intomainfrom
dyc3/useStringStartsEndsWith
Apr 11, 2026
Merged

feat(lint/js): add useStringStartsEndsWith#9796
dyc3 merged 1 commit intomainfrom
dyc3/useStringStartsEndsWith

Conversation

@dyc3
Copy link
Copy Markdown
Contributor

@dyc3 dyc3 commented Apr 4, 2026

Summary

This adds useStringStartsEndsWith, which is a port of https://typescript-eslint.io/rules/prefer-string-starts-ends-with/

I chose to use type info (like the source rule) because my hunch is that there would be way too many false positives otherwise. Also, I chose to split the invalid tests into a bunch of files because the rule is very complex, and it makes it easier to spot whether diagnostics are getting emitted.

generated by gpt 5.4, but heavily guided to stop it from doing dumb things.

closes #7653
supersedes and closes #8582

Test Plan

all the tests are from the source rule, excluding the ones depending on the source rule's option

Docs

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 4, 2026

🦋 Changeset detected

Latest commit: 46e3636

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-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis labels Apr 4, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 4, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 196 skipped benchmarks1


Comparing dyc3/useStringStartsEndsWith (46e3636) with main (1d09f0f)

Open in CodSpeed

Footnotes

  1. 196 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.

@github-actions github-actions Bot added the A-CLI Area: CLI label Apr 4, 2026
@dyc3 dyc3 force-pushed the dyc3/useStringStartsEndsWith branch from 6cc3176 to 5c6f564 Compare April 4, 2026 22:42
@dyc3 dyc3 marked this pull request as ready for review April 4, 2026 23:06
@dyc3 dyc3 force-pushed the dyc3/useStringStartsEndsWith branch from c6082cc to 328c0e5 Compare April 4, 2026 23:10
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 44794dff-7357-45bc-90ce-564dd7b5434d

📥 Commits

Reviewing files that changed from the base of the PR and between c09b088 and 46e3636.

⛔ Files ignored due to path filters (14)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidCharAt.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidIndex.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidNoFix.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidRegex.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSearch.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSlice.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/valid.ts.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (11)
  • .changeset/rude-apples-prove.md
  • crates/biome_js_analyze/src/lint/nursery/use_string_starts_ends_with.rs
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidCharAt.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidIndex.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidNoFix.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidRegex.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSearch.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSlice.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/valid.ts
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/use_string_starts_ends_with.rs
✅ Files skipped from review due to trivial changes (7)
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidNoFix.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidCharAt.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidIndex.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/valid.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSearch.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSlice.ts
  • crates/biome_rule_options/src/use_string_starts_ends_with.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidRegex.ts

Walkthrough

A new nursery lint rule useStringStartsEndsWith was added to the Biome JS/TS analyser. It detects string prefix/suffix patterns expressed via binary comparisons, indexed/charAt access, indexOf/lastIndexOf, match/test with anchored regex, slice/substring, and similar idioms, requiring type information to confirm string receivers. The rule emits diagnostics and, where safe, offers autofixes that rewrite to startsWith()/endsWith(). Supporting additions include rule declarations, a RuleState type and fix plan logic, an empty options struct and module export, and multiple valid/invalid test fixtures.

Suggested labels

A-Type-Inference

Suggested reviewers

  • ematipico
  • Netail
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding a new lint rule useStringStartsEndsWith to the JavaScript linter.
Description check ✅ Passed The description is related to the changeset, explaining the motivation (porting from typescript-eslint), design decisions (using type info), and test organisation, with disclosure of AI assistance.
Linked Issues check ✅ Passed The PR implements the primary objectives from both linked issues: ports the typescript-eslint prefer-string-starts-ends-with rule to Biome [#7653, #8582] with comprehensive pattern matching and type-based analysis.
Out of Scope Changes check ✅ Passed All changes are in scope: new lint rule implementation, test fixtures, rule options configuration, and changeset entry. No unrelated modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dyc3/useStringStartsEndsWith

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

@dyc3 dyc3 requested review from a team April 5, 2026 03:29
@dyc3 dyc3 force-pushed the dyc3/useStringStartsEndsWith branch from 328c0e5 to 0abca04 Compare April 6, 2026 14:31
Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
.changeset/rude-apples-prove.md (1)

5-7: Changeset description is clear and user-focused.

The description accurately conveys the rule's purpose and its type-aware behaviour. A minor clarity improvement: consider adding a comma after "strings" to make the compound structure clearer.

📝 Optional clarity tweak
-The rule uses type information, so it only reports on strings and skips array lookups such as `items[0] === "a"`.
+The rule uses type information, so it only reports on strings, and skips array lookups such as `items[0] === "a"`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.changeset/rude-apples-prove.md around lines 5 - 7, The changeset sentence
describing the rule's type-aware behavior needs a comma for clarity: update the
sentence that mentions the rule "uses type information" so that the clause reads
"so it only reports on strings, and skips array lookups such as `items[0] ===
\"a\"`" — modify the text referring to useStringStartsEndsWith to insert the
comma after "strings" to improve readability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.changeset/rude-apples-prove.md:
- Around line 5-7: The changeset sentence describing the rule's type-aware
behavior needs a comma for clarity: update the sentence that mentions the rule
"uses type information" so that the clause reads "so it only reports on strings,
and skips array lookups such as `items[0] === \"a\"`" — modify the text
referring to useStringStartsEndsWith to insert the comma after "strings" to
improve readability.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 16cea3ab-836c-48e7-b2c0-84534420d57f

📥 Commits

Reviewing files that changed from the base of the PR and between 328c0e5 and 0abca04.

⛔ Files ignored due to path filters (14)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidCharAt.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidIndex.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidNoFix.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidRegex.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSearch.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSlice.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/valid.ts.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (11)
  • .changeset/rude-apples-prove.md
  • crates/biome_js_analyze/src/lint/nursery/use_string_starts_ends_with.rs
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidCharAt.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidIndex.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidNoFix.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidRegex.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSearch.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSlice.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/valid.ts
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/use_string_starts_ends_with.rs
✅ Files skipped from review due to trivial changes (9)
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSlice.ts
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSearch.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidRegex.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidNoFix.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidIndex.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidCharAt.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/valid.ts
  • crates/biome_rule_options/src/use_string_starts_ends_with.rs

Copy link
Copy Markdown
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

It's a very big rule with a lot of methods, so I am going to approve with some loose suggestions:

  • Let's try to docstrings all methods (some aren't, even though they might seem straightforward)
  • Let's try to frame/comment on weird logics that aren't framed in the docstrings (I left one comment)
  • Let's try using plain language (less technical) in the diagnostics
  • Remember to add https://github.com/You-saku as a co-author, because they added the first PR in the first place

Comment thread crates/biome_js_analyze/src/lint/nursery/use_string_starts_ends_with.rs Outdated
@dyc3 dyc3 force-pushed the dyc3/useStringStartsEndsWith branch from 0abca04 to c09b088 Compare April 11, 2026 14:24
Copy link
Copy Markdown
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

🤖 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_string_starts_ends_with.rs`:
- Around line 668-687: normalize_binary_expression currently returns
BinaryComparison preserving original left/right; to handle commuted equality
checks, detect when left is a literal/null/number/string or a simple numeric
literal expression (e.g., JsLiteral, JsNullLiteral, numeric/string literal) and
right is a non-literal/complex expression, then swap left and right before
constructing BinaryComparison so the "interesting" expression is always on left;
preserve the negated flag for Inequality/StrictInequality cases and keep using
JsBinaryExpression/operator to decide negation; update
normalize_binary_expression to perform this swap (referencing
normalize_binary_expression, BinaryComparison, JsBinaryExpression, operator,
left, right).
- Around line 361-385: The matcher allows indexOf/lastIndexOf calls with extra
arguments which change semantics; in match_index_of_expression check that
call.arguments list has exactly one argument (i.e., bail out unless the call has
length == 1) before creating the FixPlan, and make the identical change in the
corresponding matcher for lastIndexOf (the other function around lines
~394–421), ensuring you inspect the call variable returned by string_method_call
and return None if the argument count is not exactly one.
- Around line 1109-1138: The EndsWith rewrite is incorrectly matching slice
calls that provide an explicit end bound; update the two EndsWith branches to
require the slice's second argument to be either absent or equal to the string
length. Concretely, in the branch that inspects the minus binary (the one
calling matches_length_minus_value and checking second.is_none()), and in the
later branch that matches left/right against length (the one using
ensure_expression_match and matches_length_expression), add a guard that second
is None OR matches the length member (use
matches_length_expression/ensure_expression_match against
length_member(object.clone())). This ensures the transformation to
PreferredMethod::EndsWith only happens when the slice end is the full string
length (or unspecified).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 227041e3-c380-4624-a79b-235ef072a2f6

📥 Commits

Reviewing files that changed from the base of the PR and between 0abca04 and c09b088.

⛔ Files ignored due to path filters (14)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidCharAt.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidIndex.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidNoFix.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidRegex.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSearch.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSlice.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/valid.ts.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (11)
  • .changeset/rude-apples-prove.md
  • crates/biome_js_analyze/src/lint/nursery/use_string_starts_ends_with.rs
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidCharAt.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidIndex.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidNoFix.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidRegex.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSearch.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSlice.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/valid.ts
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/use_string_starts_ends_with.rs
✅ Files skipped from review due to trivial changes (9)
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSearch.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidRegex.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidNoFix.ts
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidSlice.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/valid.ts
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidIndex.ts
  • crates/biome_rule_options/src/use_string_starts_ends_with.rs
  • crates/biome_js_analyze/tests/specs/nursery/useStringStartsEndsWith/invalidCharAt.ts

@dyc3 dyc3 force-pushed the dyc3/useStringStartsEndsWith branch from c09b088 to 46e3636 Compare April 11, 2026 15:21
@dyc3 dyc3 merged commit f1c1363 into main Apr 11, 2026
21 checks passed
@dyc3 dyc3 deleted the dyc3/useStringStartsEndsWith branch April 11, 2026 15:51
@github-actions github-actions Bot mentioned this pull request Apr 11, 2026
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-string-starts-ends-with from typescript-eslint

2 participants