feat(lint): add noDrizzleDeleteWithoutWhere and noDrizzleUpdateWithoutWhere rules#9525
Conversation
…outWhere rules Add two new nursery lint rules for Drizzle ORM that prevent accidental full-table deletes and updates by requiring a `.where()` clause. Both rules are configurable via `drizzleObjectName` option, which specifies the variable names that represent Drizzle ORM instances. Also adds `RuleDomain::Drizzle` and `RuleSource::EslintDrizzle` to support the new domain and link back to the original eslint-plugin-drizzle source. This PR was developed with the assistance of Claude Code. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…noDrizzleUpdateWithoutWhere Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: c1c1b8d 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 |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds Drizzle support to the analyzer surface: a new Possibly related PRs
Suggested labels
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)
📝 Coding Plan
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: 4
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/no_drizzle_update_without_where.rs (1)
118-157: Consider extracting shared helpers to avoid duplication.
get_identifier_nameandhas_where_in_chainare duplicated between this rule andno_drizzle_delete_without_where. Consider extracting them to a shared module (e.g.,drizzle_utils.rs) to reduce maintenance burden.🤖 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/no_drizzle_update_without_where.rs` around lines 118 - 157, Extract the duplicated helpers get_identifier_name and has_where_in_chain into a new shared module (e.g., create drizzle_utils.rs) that exports both functions; replace the copies in this file (no_drizzle_update_without_where.rs) and the other rule file (no_drizzle_delete_without_where.rs) with imports from the new module (adjust use/... paths accordingly), ensure the functions keep the same signatures and any required visibility (pub(crate) or pub) and remove the duplicated implementations so both rules call the shared helpers.
🤖 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/no_drizzle_delete_without_where.rs`:
- Around line 111-113: The inline backtick template syntax inside the
.note(markup! { ... }) may not render correctly; update the message in the
.note(markup! { ... }) call (in the no_drizzle_delete_without_where rule) to
either wrap code fragments with HTML <code> tags (e.g. <code>.where()</code> and
<code>.where(sql`1=1`)</code>) or replace backticks with HTML entities (`)
so the `.where()` and the `sql`1=1`` fragment render reliably; adjust the string
passed to markup! accordingly.
In `@crates/biome_js_analyze/src/lint/nursery/no_drizzle_update_without_where.rs`:
- Around line 111-113: The markup! note string in the
no_drizzle_update_without_where rule contains nested backticks and a
template-literal-like fragment (`sql`1=1``) that may not render; update the
message constructed in .note(markup! { ... })—either escape or remove the inner
backticks and rephrase the example (e.g. use plain text like "use .where(sql
\"1=1\") to explicitly update all rows" or "use .where(sql(\"1=1\"))") so the
markup! macro renders correctly; locate the .note(markup! { ... }) call in the
no_drizzle_update_without_where.rs file to apply the change.
In
`@crates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/invalid/delete.js`:
- Around line 12-13: The test in invalid/delete.js is labeled as a negative case
("should NOT trigger") but resides in the invalid/ folder, which expects
diagnostics; move the case using database.delete(users) (and its accompanying
comment) out of the invalid/ directory into the valid/ specs (or create a
corresponding valid test file) so test suite semantics match the comment and
snapshot expectations; ensure the test filename and its snapshot are placed
under the valid/ folder so it won't be treated as an expected-diagnostic case.
In
`@crates/biome_js_analyze/tests/specs/nursery/noDrizzleUpdateWithoutWhere/invalid/update.js`:
- Around line 12-13: The test case currently asserts that "different drizzle
object name should NOT trigger" but is placed in the invalid spec set; move this
test file out of the invalid suite into the valid/specs area (or the equivalent
valid directory) so its semantics match its placement, ensuring the test that
calls database.update(users).set({ name: "John" }) is categorized with valid
tests and leaving any surrounding test metadata/assertions unchanged.
---
Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/no_drizzle_update_without_where.rs`:
- Around line 118-157: Extract the duplicated helpers get_identifier_name and
has_where_in_chain into a new shared module (e.g., create drizzle_utils.rs) that
exports both functions; replace the copies in this file
(no_drizzle_update_without_where.rs) and the other rule file
(no_drizzle_delete_without_where.rs) with imports from the new module (adjust
use/... paths accordingly), ensure the functions keep the same signatures and
any required visibility (pub(crate) or pub) and remove the duplicated
implementations so both rules call the shared helpers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 318a23a4-814c-4f11-bb19-7ed678eade81
⛔ Files ignored due to path filters (5)
crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/invalid/delete.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/valid/delete.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noDrizzleUpdateWithoutWhere/invalid/update.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noDrizzleUpdateWithoutWhere/valid/update.js.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (14)
crates/biome_analyze/src/rule.rscrates/biome_js_analyze/src/lint/nursery/no_drizzle_delete_without_where.rscrates/biome_js_analyze/src/lint/nursery/no_drizzle_update_without_where.rscrates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/invalid/delete.jscrates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/invalid/delete.options.jsoncrates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/valid/delete.jscrates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/valid/delete.options.jsoncrates/biome_js_analyze/tests/specs/nursery/noDrizzleUpdateWithoutWhere/invalid/update.jscrates/biome_js_analyze/tests/specs/nursery/noDrizzleUpdateWithoutWhere/invalid/update.options.jsoncrates/biome_js_analyze/tests/specs/nursery/noDrizzleUpdateWithoutWhere/valid/update.jscrates/biome_js_analyze/tests/specs/nursery/noDrizzleUpdateWithoutWhere/valid/update.options.jsoncrates/biome_rule_options/src/lib.rscrates/biome_rule_options/src/no_drizzle_delete_without_where.rscrates/biome_rule_options/src/no_drizzle_update_without_where.rs
crates/biome_js_analyze/src/lint/nursery/no_drizzle_delete_without_where.rs
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/no_drizzle_update_without_where.rs
Show resolved
Hide resolved
crates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/invalid/delete.js
Outdated
Show resolved
Hide resolved
crates/biome_js_analyze/tests/specs/nursery/noDrizzleUpdateWithoutWhere/invalid/update.js
Outdated
Show resolved
Hide resolved
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract shared helpers get_identifier_name and has_where_in_chain to crates/biome_js_analyze/src/frameworks/drizzle.rs to avoid duplication - Use <Emphasis> markup tags instead of raw backticks in diagnostic messages - Move database.delete/update test cases from invalid/ to valid/ fixtures Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/frameworks/drizzle.rs`:
- Around line 18-27: The helper currently returns true for any
JsStaticMemberExpression with a member named "where" (using
JsStaticMemberExpression, member(), as_js_name(), value_token(),
token_text_trimmed()), but it must only match an actual call like `.where(...)`;
update the check to verify that the JsStaticMemberExpression is itself the
callee of a JsCallExpression (e.g., inspect the parent/ancestor and use
JsCallExpression::cast_ref to ensure the member expression is being invoked)
before returning true so bare member access like `.where;` does not count as a
where call.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 47cd33ba-7121-4e4a-9203-2f3f67ed47e7
⛔ Files ignored due to path filters (2)
crates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/invalid/delete.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noDrizzleUpdateWithoutWhere/invalid/update.js.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (6)
crates/biome_js_analyze/src/frameworks/drizzle.rscrates/biome_js_analyze/src/frameworks/mod.rscrates/biome_js_analyze/src/lint/nursery/no_drizzle_delete_without_where.rscrates/biome_js_analyze/src/lint/nursery/no_drizzle_update_without_where.rscrates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/invalid/delete.jscrates/biome_js_analyze/tests/specs/nursery/noDrizzleUpdateWithoutWhere/invalid/update.js
🚧 Files skipped from review as they are similar to previous changes (2)
- crates/biome_js_analyze/tests/specs/nursery/noDrizzleDeleteWithoutWhere/invalid/delete.js
- crates/biome_js_analyze/src/lint/nursery/no_drizzle_delete_without_where.rs
Verify that the JsStaticMemberExpression with member "where" is the callee of a JsCallExpression before returning true, so bare property access like .where (without parentheses) does not incorrectly suppress the diagnostic. Added test cases covering this scenario. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merging this PR will not alter performance
Comparing Footnotes
|
dyc3
left a comment
There was a problem hiding this comment.
This will need a website PR to document the new domain.
crates/biome_js_analyze/src/lint/nursery/no_drizzle_delete_without_where.rs
Outdated
Show resolved
Hide resolved
crates/biome_rule_options/src/no_drizzle_delete_without_where.rs
Outdated
Show resolved
Hide resolved
crates/biome_rule_options/src/no_drizzle_update_without_where.rs
Outdated
Show resolved
Hide resolved
- Link EslintDrizzle rule URLs to specific anchors on the docs page - Lower drizzle-orm minimum version requirement from >=0.29.0 to >=0.9.0 - Split changeset into one per rule - Mark both rules as recommended - Change drizzleObjectName option type to Option<Box<[Box<str>]>> with manual Merge impl Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Hey @dyc3, since both the rule pages and domains.mdx on the website are auto-generated from source, is a manual website PR still needed here? |
|
Ah I had forgotten we automated that. Nevermind it then. |
dyc3
left a comment
There was a problem hiding this comment.
Looks good. Just need to fix the CI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Closes #8114.
Adds two new nursery lint rules for Drizzle ORM, mirroring the rules from
eslint-plugin-drizzle:noDrizzleDeleteWithoutWhere— disallows calling.delete()without a.where()clause, which would delete allrows in the table.
noDrizzleUpdateWithoutWhere— disallows calling.update().set()without a.where()clause, which would updateall rows in the table.
Both rules are opt-in and require configuring the
drizzleObjectNameoption with the variable names that representDrizzle ORM instances in the project. This is necessary because Drizzle does not enforce a specific variable name for the
database client.
{ "linter": { "rules": { "nursery": { "noDrizzleDeleteWithoutWhere": { "level": "error", "options": { "drizzleObjectName": ["db"] } } } } } }The rules also support the drizzle domain, which will be automatically enabled when drizzle-orm is detected as a project
dependency:
{ "linter": { "domains": { "drizzle": "all" } } }Infrastructure changes
Test Plan
Snapshot tests covering:
cargo test -p biome_js_analyze -- "specs::nursery::no_drizzle"
test result: ok. 8 passed
Docs
Documentation is inline in the rule source via rustdoc, including: