Skip to content

feat(linter): preset#9813

Merged
ematipico merged 12 commits intonextfrom
feat/rule-set
Apr 20, 2026
Merged

feat(linter): preset#9813
ematipico merged 12 commits intonextfrom
feat/rule-set

Conversation

@ematipico
Copy link
Copy Markdown
Member

@ematipico ematipico commented Apr 5, 2026

Summary

Closes #5764

Adds a new preset configuration, next to the recommended configuration. As for now, the preset yields the same semantics of recommended: true and recommended: false, plus, it adds an all preset, which brings back the old all: true field, but it has different semantics: nursery rules aren't enabled regardless of its value.

I added a migration to move to the new field, as recommended will fade.

The reason why preset is a string and not a list is that:

  • It would add complexity for no reason
  • We don't have enough presets where users can compose different ones
  • Other linters tend to lean towards "one preset at a time"

AI was used only to create tests and to help me understand the macros.

Test Plan

Added a new series of tests. Made sure

Docs

I will open a new PR

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 5, 2026

🦋 Changeset detected

Latest commit: d5103e7

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

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

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

@ematipico ematipico requested review from a team April 5, 2026 17:45
@github-actions github-actions Bot added A-CLI Area: CLI A-Project Area: project A-Linter Area: linter A-Tooling Area: internal tools labels Apr 5, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 5, 2026

Merging this PR will degrade performance by 72.63%

⚡ 3 improved benchmarks
❌ 7 regressed benchmarks
✅ 239 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
css_analyzer[tachyons_11778168428173736564.css] 179.9 ms 199 ms -9.59%
css_analyzer[foundation_11602414662825430680.css] 242.5 ms 280.3 ms -13.47%
css_analyzer[pure_9395922602181450299.css] 24.1 ms 27.3 ms -11.88%
html_analyzer[real/wikipedia-Unix.html] 166.1 ms 572.5 ms -70.99%
html_analyzer[index_1033418810622582172.html] 468.6 µs 1,285.8 µs -63.56%
html_analyzer[real/wikipedia-fr-Guerre_de_Canudos.html] 450.2 ms 1,429.2 ms -68.5%
html_analyzer[real/wikipedia-JavaScript.html] 189.1 ms 691 ms -72.63%
execute_optimized[or_pattern] 590.2 µs 543.8 µs +8.52%
execute_optimized[code_snippet] 453.7 µs 416.1 µs +9.03%
execute_optimized[where_clause] 454.7 µs 412.2 µs +10.31%

Comparing feat/rule-set (d5103e7) with next (1828261)

Open in CodSpeed

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 5, 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

Walkthrough

Adds a preset configuration under linter.rules with values "recommended", "all", and "none", applicable at top level and per rule group. Introduces RulePreset and PresetConfig types, extends rule metadata and generated configuration structs to carry presets, updates analyzer and macro codepaths to compute enabled rules from presets, and deprecates the boolean recommended (validator emits diagnostics and a migration rule rewrites recommendedpreset). Nursery rules remain opt‑in. Adds tests, snapshots, and service/settings accessors for the new preset semantics.

Suggested reviewers

  • dyc3
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(linter): preset' clearly and concisely describes the main change: adding a new preset configuration feature to the linter.
Description check ✅ Passed The description explains the PR's motivation (issue #5764), summarises the new preset configuration semantics, mentions the migration, and documents test coverage and AI assistance.
Linked Issues check ✅ Passed The PR successfully implements the objective from issue #5764 by adding a preset: "all" option to enable all rules, with nursery rules intentionally remaining opt-in.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the preset feature and its migration. No unrelated modifications detected; domain-level preset support is correctly noted as out of scope.

✏️ 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 feat/rule-set

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

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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/biome_configuration_macros/src/group_struct.rs (1)

255-305: ⚠️ Potential issue | 🟠 Major

preset: "all" is ignored for configured groups.

Both collect_preset_rules() branches only handle the recommended path. So suspicious: { preset: "all" } enables nothing, and a top-level preset: "all" also stops applying to suspicious as soon as that group has any nested config. The effective preset needs to flow through preset_as_filters(...) here, not just the recommended special-case.

Also applies to: 391-412

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

In `@crates/biome_configuration_macros/src/group_struct.rs` around lines 255 -
305, collect_preset_rules currently only applies the "recommended" preset path
and ignores PresetConfig::All for configured groups; update collect_preset_rules
(and the similar branch at the other location) to compute the effective preset
for the group (e.g. prefer self.preset if Some(...) else parent_preset) and,
when that effective preset is PresetConfig::All or FromAnalyzer(...), extend
enabled_rules with Self::preset_as_filters(effective_preset) in addition to the
existing recommended_rules_as_filters() handling; use the existing helpers
is_preset_recommended()/is_recommended_unset() only for the
recommended-special-case so the All path flows through preset_as_filters instead
of being skipped.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/huge-walls-float.md:
- Around line 7-10: The "all" preset description currently overpromises by
implying it enables every Biome rule while the implementation still keeps
nursery rules opt-in; update the text for the `"all"` preset (and the other
occurrences referenced around lines 24-30) to explicitly state that `"all"`
enables all non-nursery rules or "all stable rules" and that
nursery/experimental rules remain opt-in, e.g., replace "it enables all Biome
rules" with language like "it enables all stable/non-nursery Biome rules;
nursery (experimental) rules remain opt-in" so readers get the correct
expectation.

In `@crates/biome_analyze/src/rule.rs`:
- Around line 50-51: The new pub field rule_presets: &'static [RulePreset] on
RuleMetadata is never populated by the existing fluent builder used by
declare_*_rule!, so add a builder setter method to RuleMetadata (e.g., a
rule_presets(...) fluent method) that accepts &'static [RulePreset] and sets the
internal field; update the RuleMetadata::default/constructor to initialize
rule_presets to &[] and ensure any places that build metadata via the fluent API
(the declare_*_rule! macro usages) can call .rule_presets(...) to populate it so
the field is no longer left empty by default.

In `@crates/biome_cli/tests/cases/preset.rs`:
- Around line 358-398: The test group_preset_all_with_rule_off only triggers
noDebugger (which is explicitly "off"), so it can pass even if the "suspicious"
preset handling is broken; update the test by adding a second non-recommended
rule under "suspicious" in the config JSON (in the Utf8Path "biome.json"
inserted in the fs) and make test.js include code that triggers that rule (in
addition to DEBUGGER_BEFORE) so the run_cli call will produce a diagnostic when
the "preset": "all" behavior is working; keep the existing noDebugger:"off"
entry and assert_cli_snapshot as before.

In `@crates/biome_configuration/src/analyzer/linter/mod.rs`:
- Line 55: Remove the stray dbg!(&self) call that prints the parsed linter
config; locate the dbg! invocation in the module (mod.rs) inside the
Analyzer/Linter implementation (the dbg!(&self) line) and delete it so the
linter no longer writes user config to stderr and muddies CLI or
machine-readable output.

In `@crates/biome_migrate/src/analyzers.rs`:
- Around line 49-50: The Recommended analyzer is being registered
unconditionally via registry.record_rule::<Recommended>() which causes it to
match any JSON member named "recommended"; remove or comment out that
registration here (registry.record_rule::<Recommended>()) and instead only
register Recommended after its matcher is made path-scoped in
crates/biome_migrate/src/analyzers/recommended.rs (or add a TODO noting to
register once path-scoped). This prevents the analyzer from rewriting unrelated
keys outside linter.rules until Recommended's matcher is updated to be
path-aware.

In `@crates/biome_migrate/src/analyzers/recommended.rs`:
- Around line 69-84: The fixer currently misapplies trivia: when constructing
the json_member for "preset" use name.trailing_trivia().pieces() for the
string's trailing trivia instead of name.leading_trivia(), and fix the colon
token trivia by assigning its leading trivia to the token's leading_trivia and
its trailing trivia to the token's trailing_trivia (do not call
with_leading_trivia_pieces twice). Update the json_string_literal("preset") call
in the json_member_name to use
.with_trailing_trivia_pieces(name.trailing_trivia().pieces()), and set the
token(T![:]) to use node.colon_token().ok()?.leading_trivia().pieces() for
leading and node.colon_token().ok()?.trailing_trivia().pieces() for trailing (or
capture colon_token() once into a local to avoid duplicate calls) so inline
comments/spacing around the member are preserved.

In `@crates/biome_service/src/settings.rs`:
- Around line 634-646: The logic in recommended_enabled() is reading raw fields
and using or(Some(...)) which causes an empty rules object to resolve
incorrectly; instead derive the flag from the effective preset resolution used
by Rules (use the preset() or effective_preset() accessor on rules to get the
resolved preset) and check that preset.is_recommended(); update
recommended_enabled() to use rules.as_ref().map(|rules|
rules.preset().is_recommended()).unwrap_or(true) (and make the analogous change
for the other related method at lines 649-654 so both use the effective preset
accessor rather than or(Some(...))).

---

Outside diff comments:
In `@crates/biome_configuration_macros/src/group_struct.rs`:
- Around line 255-305: collect_preset_rules currently only applies the
"recommended" preset path and ignores PresetConfig::All for configured groups;
update collect_preset_rules (and the similar branch at the other location) to
compute the effective preset for the group (e.g. prefer self.preset if Some(...)
else parent_preset) and, when that effective preset is PresetConfig::All or
FromAnalyzer(...), extend enabled_rules with
Self::preset_as_filters(effective_preset) in addition to the existing
recommended_rules_as_filters() handling; use the existing helpers
is_preset_recommended()/is_recommended_unset() only for the
recommended-special-case so the All path flows through preset_as_filters instead
of being skipped.
🪄 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: 3cee5f18-ada8-451d-8d26-df92e062fbc7

📥 Commits

Reviewing files that changed from the base of the PR and between 01f8473 and 41f178e.

⛔ Files ignored due to path filters (28)
  • crates/biome_cli/tests/snapshots/main_cases_migrate_v2/should_migrate_aws_config.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_migrate_v2/should_migrate_nested_config.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_migrate_v2/should_successfully_migrate_ariakit.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_migrate_v2/should_successfully_migrate_knip.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_migrate_v2/should_successfully_migrate_sentry.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/group_preset_all_with_rule_off.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/group_preset_none_disables_group_rules.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/group_preset_none_with_rule_enabled.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/group_preset_recommended_enables_group_rules.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/nursery_preset_all_does_not_enable_rules.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/nursery_rule_enabled_individually.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/preset_all_does_not_enable_nursery_rules.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/preset_all_enables_all_rules.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/preset_none_disables_all_rules.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/preset_recommended_does_not_enable_nursery_rules.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/preset_recommended_enables_recommended_rules.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/preset_recommended_same_as_recommended_true.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/recommended_true_does_not_enable_nursery_rules.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_init/creates_config_file.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_init/creates_config_file_when_biome_installed_via_package_manager.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_init/creates_config_jsonc_file.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_init/enables_vcs_and_ignore_dist.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_lint/linter_shows_the_default_severity_of_rule_on.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_configuration/incorrect_rule_name.snap is excluded by !**/*.snap and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/tests/invalid/preset.json.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 (16)
  • .changeset/huge-walls-float.md
  • crates/biome_analyze/src/lib.rs
  • crates/biome_analyze/src/rule.rs
  • crates/biome_cli/tests/cases/mod.rs
  • crates/biome_cli/tests/cases/preset.rs
  • crates/biome_configuration/src/analyzer/assist/actions.rs
  • crates/biome_configuration/src/analyzer/linter/mod.rs
  • crates/biome_configuration/src/analyzer/mod.rs
  • crates/biome_configuration/src/analyzer/presets.rs
  • crates/biome_configuration/src/lib.rs
  • crates/biome_configuration/tests/invalid/preset.json
  • crates/biome_configuration_macros/src/group_struct.rs
  • crates/biome_migrate/src/analyzers.rs
  • crates/biome_migrate/src/analyzers/recommended.rs
  • crates/biome_service/src/settings.rs
  • xtask/codegen/src/generate_configuration.rs

Comment thread .changeset/huge-walls-float.md
Comment thread crates/biome_analyze/src/rule.rs
Comment thread crates/biome_cli/tests/cases/preset.rs
Comment thread crates/biome_configuration/src/analyzer/linter/mod.rs Outdated
Comment thread crates/biome_configuration/src/analyzer/presets.rs Outdated
Comment thread crates/biome_migrate/src/analyzers.rs
Comment on lines +69 to +84
let new_value = json_string_value(
json_string_literal(new_value)
.with_leading_trivia_pieces(value.leading_trivia().pieces())
.with_trailing_trivia_pieces(value.trailing_trivia().pieces()),
);
let member = json_member(
json_member_name(
json_string_literal("preset")
.with_leading_trivia_pieces(name.leading_trivia().pieces())
.with_trailing_trivia_pieces(name.leading_trivia().pieces()),
)
.into(),
token(T![:])
.with_leading_trivia_pieces(node.colon_token().ok()?.leading_trivia().pieces())
.with_leading_trivia_pieces(node.colon_token().ok()?.trailing_trivia().pieces()),
AnyJsonValue::JsonStringValue(new_value.clone()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The fixer drops trivia around "recommended":.

Line 78 uses name.leading_trivia() as trailing trivia, and Line 83 assigns the colon’s leading trivia twice. Inline comments or spacing around the member will be lost or moved by the migration.

Small fix
         let member = json_member(
             json_member_name(
                 json_string_literal("preset")
                     .with_leading_trivia_pieces(name.leading_trivia().pieces())
-                    .with_trailing_trivia_pieces(name.leading_trivia().pieces()),
+                    .with_trailing_trivia_pieces(name.trailing_trivia().pieces()),
             )
             .into(),
             token(T![:])
                 .with_leading_trivia_pieces(node.colon_token().ok()?.leading_trivia().pieces())
-                .with_leading_trivia_pieces(node.colon_token().ok()?.trailing_trivia().pieces()),
+                .with_trailing_trivia_pieces(node.colon_token().ok()?.trailing_trivia().pieces()),
             AnyJsonValue::JsonStringValue(new_value.clone()),
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_migrate/src/analyzers/recommended.rs` around lines 69 - 84, The
fixer currently misapplies trivia: when constructing the json_member for
"preset" use name.trailing_trivia().pieces() for the string's trailing trivia
instead of name.leading_trivia(), and fix the colon token trivia by assigning
its leading trivia to the token's leading_trivia and its trailing trivia to the
token's trailing_trivia (do not call with_leading_trivia_pieces twice). Update
the json_string_literal("preset") call in the json_member_name to use
.with_trailing_trivia_pieces(name.trailing_trivia().pieces()), and set the
token(T![:]) to use node.colon_token().ok()?.leading_trivia().pieces() for
leading and node.colon_token().ok()?.trailing_trivia().pieces() for trailing (or
capture colon_token() once into a local to avoid duplicate calls) so inline
comments/spacing around the member are preserved.

Comment thread crates/biome_service/src/settings.rs
Copy link
Copy Markdown
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

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

Maybe for a follow up PR, but I do think there should be some way for users to opt in to all the nursery rules. Maybe an all-with-nursery preset.

How will this interact with domains? should domains have the preset field as well?

@Conaclos
Copy link
Copy Markdown
Member

Conaclos commented Apr 5, 2026

I am slightly concerned by the all preset because it could enable conflicting rules. Usually linters tend to exclude conflicting rules of their all preset. Perhaps it is ok, because we should not have so many such rules? And domains already gate specific rules.

How will this interact with domains?

I guess this should run as today: preset should not affect rules under a domain.

should domains have the preset field as well?

Domains have already a preset concept. We could extend it to support a all preset.
For example:

{
  "linter": {
    "domains": {
      "project": "all"
    }
  }
}

Regarding the unit tests, it could be nice to have some tests for combining group and root preset. For example:

{
  "linter": {
    "rules": {
      "preset": "none",
      "suspicious": {
        "preset": "all",
      }
    }
  }
}

In the past we had some bugs there.

@ematipico
Copy link
Copy Markdown
Member Author

Maybe for a follow up PR, but I do think there should be some way for users to opt in to all the nursery rules. Maybe an all-with-nursery preset.

How will this interact with domains? should domains have the preset field as well?

While I understand the sentiment, nursery rules are opt-in by design, where users opt-in singularly. Nursery rules must stay a convenience for us maintainers, and less for users.

Still, that's out of scope of this PR.

@ematipico
Copy link
Copy Markdown
Member Author

I am slightly concerned by the all preset because it could enable conflicting rules. Usually linters tend to exclude conflicting rules of their all preset. Perhaps it is ok, because we should not have so many such rules? And domains already gate specific rules.

Users have already answered this question in the relative issue. They enable all rules and remove what they need.

@ematipico ematipico requested review from a team and dyc3 April 6, 2026 09:44
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/biome_cli/src/commands/rage.rs (1)

239-243: ⚠️ Potential issue | 🟡 Minor

Use a neutral label for non-error diagnostics.

Line 241 always prints "Error", which is now misleading when a diagnostic is a warning/hint/info.

Suggested tweak
-                                {KeyValuePair::new("Error", markup!{
+                                {KeyValuePair::new("Diagnostic", markup!{
                                      {format!{"{}", PrintDescription(&diagnostic)}}
                                  })}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_cli/src/commands/rage.rs` around lines 239 - 243, The code
currently hardcodes the label "Error" when rendering each diagnostic; update the
KeyValuePair creation to use a neutral or level-aware label instead — e.g.,
replace KeyValuePair::new("Error", ...) with something like
KeyValuePair::new(diagnostic.level().to_string().as_str() or "Diagnostic", ...)
so warnings/hints/info are labeled correctly; ensure you still pass the same
PrintDescription(&diagnostic) value for the message.
🧹 Nitpick comments (1)
crates/biome_configuration/src/analyzer/presets.rs (1)

93-99: Consider a more informative error message.

When RulePreset::from_str fails, the diagnostic shows "Invalid rule preset." which is less helpful than the serde error that lists valid values. You could construct a richer message here to match the serde path.

That said, this is minor and won't block anything.

💡 Optional: Improve error message
                     match result {
                         Ok(preset) => return Some(PresetConfig::FromAnalyzer(preset)),
                         Err(err) => {
-                            ctx.report(DeserializationDiagnostic::new(err).with_range(range));
+                            ctx.report(
+                                DeserializationDiagnostic::new(err)
+                                    .with_range(range)
+                                    .note_with_list("Accepted values:", &["recommended", "all", "none"]),
+                            );
                         }
                     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_configuration/src/analyzer/presets.rs` around lines 93 - 99, The
diagnostic created when RulePreset::from_str(&value) returns Err should include
the serde/parsing error text so users see the valid values; modify the Err
branch (where ctx.report is called) to build a richer message that embeds
err.to_string() (or formats "Invalid rule preset: {err}") into the
DeserializationDiagnostic before calling ctx.report, keeping the existing
.with_range(range) and returning PresetConfig::FromAnalyzer on Ok(preset).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@crates/biome_cli/src/commands/rage.rs`:
- Around line 239-243: The code currently hardcodes the label "Error" when
rendering each diagnostic; update the KeyValuePair creation to use a neutral or
level-aware label instead — e.g., replace KeyValuePair::new("Error", ...) with
something like KeyValuePair::new(diagnostic.level().to_string().as_str() or
"Diagnostic", ...) so warnings/hints/info are labeled correctly; ensure you
still pass the same PrintDescription(&diagnostic) value for the message.

---

Nitpick comments:
In `@crates/biome_configuration/src/analyzer/presets.rs`:
- Around line 93-99: The diagnostic created when RulePreset::from_str(&value)
returns Err should include the serde/parsing error text so users see the valid
values; modify the Err branch (where ctx.report is called) to build a richer
message that embeds err.to_string() (or formats "Invalid rule preset: {err}")
into the DeserializationDiagnostic before calling ctx.report, keeping the
existing .with_range(range) and returning PresetConfig::FromAnalyzer on
Ok(preset).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e82c57e7-e1d6-47e6-9876-79300a6201bb

📥 Commits

Reviewing files that changed from the base of the PR and between 6444812 and 2503536.

⛔ Files ignored due to path filters (14)
  • Cargo.lock is excluded by !**/*.lock and included by **
  • crates/biome_cli/tests/snapshots/main_cases_overrides_linter/does_override_recommended.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/customising_one_group_still_enables_recommended.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/empty_rules_object_still_enables_recommended.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/group_preset_all_with_rule_off.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_preset/preset_none_with_group_preset_all.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/doesnt_enable_rules_when_recommended_is_false.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_lint/should_not_choke_on_recursive_function_call.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_rage/with_configuration.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_rage/with_formatter_configuration.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_rage/with_jsonc_configuration.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_rage/with_linter_configuration.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_rage/with_malformed_configuration.snap is excluded by !**/*.snap and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
📒 Files selected for processing (9)
  • crates/biome_analyze/src/rule.rs
  • crates/biome_cli/src/commands/rage.rs
  • crates/biome_cli/tests/cases/preset.rs
  • crates/biome_configuration/Cargo.toml
  • crates/biome_configuration/src/analyzer/linter/mod.rs
  • crates/biome_configuration/src/analyzer/presets.rs
  • crates/biome_configuration_macros/src/group_struct.rs
  • crates/biome_deserialize/src/diagnostics.rs
  • xtask/codegen/src/generate_configuration.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/biome_configuration/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/biome_analyze/src/rule.rs
  • crates/biome_configuration/src/analyzer/linter/mod.rs

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)
crates/biome_configuration/src/analyzer/presets.rs (1)

34-49: Unify parsing logic to avoid serde/visitor drift.

You currently parse presets in two places with slightly different logic/messages. A tiny shared parser would keep behaviour consistent and future-proof this when RulePreset grows. One parser to rule them all.

♻️ Proposed refactor
 impl PresetConfig {
+    fn parse_str(value: &str) -> Result<Self, &'static str> {
+        match value {
+            "all" => Ok(Self::All),
+            "none" => Ok(Self::None),
+            _ => RulePreset::from_str(value).map(Self::FromAnalyzer),
+        }
+    }
+
     pub const fn is_all(&self) -> bool {
         matches!(self, Self::All)
     }
@@
 impl<'de> Deserialize<'de> for PresetConfig {
@@
-        let s = <String as Deserialize>::deserialize(deserializer)?;
-        match s.as_str() {
-            "recommended" => Ok(Self::FromAnalyzer(RulePreset::Recommended)),
-            "all" => Ok(Self::All),
-            "none" => Ok(Self::None),
-            _ => Err(serde::de::Error::custom(format!(
-                "Invalid preset value: \"{s}\". Expected \"recommended\", \"all\", or \"none\""
-            ))),
-        }
+        let s = <String as Deserialize>::deserialize(deserializer)?;
+        Self::parse_str(&s).map_err(|_| {
+            serde::de::Error::custom(format!(
+                "Invalid preset value: \"{s}\". Expected \"recommended\", \"all\", or \"none\""
+            ))
+        })
     }
 }
@@
             fn visit_str(
@@
-                if value == "none" {
-                    return Some(PresetConfig::None);
-                } else if value == "all" {
-                    return Some(PresetConfig::All);
-                } else {
-                    let result = RulePreset::from_str(&value);
-                    match result {
-                        Ok(preset) => return Some(PresetConfig::FromAnalyzer(preset)),
-                        Err(err) => {
-                            ctx.report(DeserializationDiagnostic::new(err).with_range(range));
-                        }
-                    }
-                }
-
-                None
+                match PresetConfig::parse_str(&value) {
+                    Ok(preset) => Some(preset),
+                    Err(err) => {
+                        ctx.report(DeserializationDiagnostic::new(err).with_range(range));
+                        None
+                    }
+                }
             }
         }

Also applies to: 71-107

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

In `@crates/biome_configuration/src/analyzer/presets.rs` around lines 34 - 49, The
PresetConfig deserialize impl duplicates parsing logic already used elsewhere;
create a single parser (e.g., implement std::str::FromStr for RulePreset or add
a private fn parse_rule_preset(s: &str) -> Result<RulePreset, String>) and use
it from PresetConfig::deserialize and the other parsing site (the code
referenced around lines 71-107) so messages and accepted values stay consistent;
update Deserialize for PresetConfig to call that shared parser and map its error
into serde::de::Error::custom.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/biome_configuration/src/analyzer/presets.rs`:
- Around line 34-49: The PresetConfig deserialize impl duplicates parsing logic
already used elsewhere; create a single parser (e.g., implement
std::str::FromStr for RulePreset or add a private fn parse_rule_preset(s: &str)
-> Result<RulePreset, String>) and use it from PresetConfig::deserialize and the
other parsing site (the code referenced around lines 71-107) so messages and
accepted values stay consistent; update Deserialize for PresetConfig to call
that shared parser and map its error into serde::de::Error::custom.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a4473e67-02ba-4e6c-9943-8c358e568abb

📥 Commits

Reviewing files that changed from the base of the PR and between 2503536 and 2e20751.

📒 Files selected for processing (3)
  • crates/biome_cli/tests/snapshots/main_cases_preset/customising_one_group_still_enables_recommended.snap.new
  • crates/biome_cli/tests/snapshots/main_cases_preset/empty_rules_object_still_enables_recommended.snap.new
  • crates/biome_configuration/src/analyzer/presets.rs
✅ Files skipped from review due to trivial changes (2)
  • crates/biome_cli/tests/snapshots/main_cases_preset/customising_one_group_still_enables_recommended.snap.new
  • crates/biome_cli/tests/snapshots/main_cases_preset/empty_rules_object_still_enables_recommended.snap.new

@ematipico ematipico added this to the Biome v2.5 milestone Apr 8, 2026
@ematipico
Copy link
Copy Markdown
Member Author

@dyc3 @Conaclos can I please a review?

This PR risks to fallback quickly, so please let me know if there are things that need to be addressed

@ematipico ematipico force-pushed the feat/rule-set branch 2 times, most recently from 25f4e1b to b959845 Compare April 19, 2026 15:46
@dyc3
Copy link
Copy Markdown
Contributor

dyc3 commented Apr 19, 2026

Sorry for the delay. I don't feel like my concerns about how this interacts with domains is completely addressed.

  • Should a top level preset enable rules with domains? Probably not unconditionally, we risk activating rules that conflict.
  • Should domains have their own presets configured separately? (as in, you would set both top level preset and domain preset, and they would not intersect)
  • Should a top level preset only enable rules with domains if the domain is enabled? For example, if you enable top level recommended preset and the vue domain is enabled, that would enable recommended rules in that domain.

@ematipico
Copy link
Copy Markdown
Member Author

Sorry for the delay. I don't feel like my concerns about how this interacts with domains is completely addressed.

  • Should a top level preset enable rules with domains? Probably not unconditionally, we risk activating rules that conflict.

That's already the case with the all preset. That's something users are aware of. So to answer your question, yes.

  • Should domains have their own presets configured separately? (as in, you would set both top level preset and domain preset, and they would not intersect)

No, it would increase complexity a lot on our part, and in time of writing, it would provide little gains to users.

  • Should a top level preset only enable rules with domains if the domain is enabled? For example, if you enable top level recommended preset and the vue domain is enabled, that would enable recommended rules in that domain.

It depends on the preset (time of writing). The all preset enables all rules regardless. The recommended preset works the same as the existing flag.

So to give you a more generic answer, I think presets don't have predefined rules, but they have the rules we decide and document.

For example, wild idea, if in the future we add a monorepo preset, it will enable rules from certain domains even though the relative domains aren't enabled.

@dyc3
Copy link
Copy Markdown
Contributor

dyc3 commented Apr 19, 2026

So to give you a more generic answer, I think presets don't have predefined rules, but they have the rules we decide and document.

Ok, that makes sense to me 👍

@ematipico ematipico merged commit 69aadc2 into next Apr 20, 2026
36 of 37 checks passed
@ematipico ematipico deleted the feat/rule-set branch April 20, 2026 17:07
@Conaclos
Copy link
Copy Markdown
Member

That's already the case with the all preset. That's something users are aware of. So to answer your question, yes.

What do you mean. Setting preset to all enables also the rules from the disabled domains or domains set to recommended?

@ematipico
Copy link
Copy Markdown
Member Author

That's already the case with the all preset. That's something users are aware of. So to answer your question, yes.

What do you mean. Setting preset to all enables also the rules from the disabled domains or domains set to recommended?

I think things aren't that complex. With all you enable all rules, then you disable the ones you don't need. Or domains you don't need.

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

Labels

A-CLI Area: CLI A-Linter Area: linter A-Project Area: project A-Tooling Area: internal tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants