Skip to content

feat(css/lint): useBaseline#9318

Merged
ematipico merged 10 commits intomainfrom
feat/use-baseline
Mar 11, 2026
Merged

feat(css/lint): useBaseline#9318
ematipico merged 10 commits intomainfrom
feat/use-baseline

Conversation

@ematipico
Copy link
Member

@ematipico ematipico commented Mar 3, 2026

Summary

This PR implements the rule useBaseline, inspired by github.com/eslint/css/blob/main/docs/rules/use-baseline.md

I used AI to help me generate the codegen and implement the rule. However, the logic of the rule was a bit sloppy and unoptimized, so I made a few changes.

I added all the options from ESLint. Considering how new the rule is, I thought it made sense. However, I changed how the property values are handled. Instead of a key:value list, I went for an object instead.

Test Plan

Added various tests. There's a small problem with this rule: every time we update the metadata, there's a risk that tests need to be updated. Same for the docs.

Docs

Added in the rule

@changeset-bot
Copy link

changeset-bot bot commented Mar 3, 2026

🦋 Changeset detected

Latest commit: b04d728

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 A-Tooling Area: internal tools L-CSS Language: CSS and super languages A-Diagnostic Area: diagnostocis labels Mar 3, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Parser conformance results on

js/262

Test result main count This PR count Difference
Total 53006 53006 0
Passed 51786 51786 0
Failed 1178 1178 0
Panics 42 42 0
Coverage 97.70% 97.70% 0.00%

jsx/babel

Test result main count This PR count Difference
Total 38 38 0
Passed 37 37 0
Failed 1 1 0
Panics 0 0 0
Coverage 97.37% 97.37% 0.00%

markdown/commonmark

Test result main count This PR count Difference
Total 652 652 0
Passed 652 652 0
Failed 0 0 0
Panics 0 0 0
Coverage 100.00% 100.00% 0.00%

symbols/microsoft

Test result main count This PR count Difference
Total 5466 5466 0
Passed 1915 1915 0
Failed 3551 3551 0
Panics 0 0 0
Coverage 35.03% 35.03% 0.00%

ts/babel

Test result main count This PR count Difference
Total 636 636 0
Passed 568 568 0
Failed 68 68 0
Panics 0 0 0
Coverage 89.31% 89.31% 0.00%

ts/microsoft

Test result main count This PR count Difference
Total 18875 18875 0
Passed 13014 13014 0
Failed 5860 5860 0
Panics 1 1 0
Coverage 68.95% 68.95% 0.00%

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 3, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 156 skipped benchmarks1


Comparing feat/use-baseline (b04d728) with main (59c086a)

Open in CodSpeed

Footnotes

  1. 156 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 Mar 3, 2026
@ematipico ematipico force-pushed the feat/use-baseline branch from 6526dc2 to d1f881f Compare March 4, 2026 08:22
@ematipico ematipico marked this pull request as ready for review March 4, 2026 08:51
@ematipico ematipico requested review from a team March 4, 2026 08:51
@dyc3
Copy link
Contributor

dyc3 commented Mar 4, 2026

There's a small problem with this rule: every time we update the metadata, there's a risk that tests need to be updated. Same for the docs.

Maybe we could have an agent kick off a pr to keep it up to date every month or so. Or at least have a standard prompt to use for the task.

Copy link
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.

Very exciting!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

Walkthrough

Adds a nursery CSS lint rule useBaseline with its options and state types, plus many new tests and options files exercising property, value, function, selector, media-condition and at-rule checks and allow-lists. Introduces CSS baseline data generation: an xtask codegen command and justfile target that fetch web-features and emit PHF-based lookup tables into a generated runtime module. Adds a RuleSource::EslintCss variant and wiring for its display, namespace and URL handling. Adds a new use_baseline rule options module and related configuration types.

Suggested reviewers

  • dyc3
  • arendjr
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(css/lint): useBaseline' accurately summarises the main change—a new CSS lint rule implementation.
Description check ✅ Passed The description relates to the changeset, explaining the rule implementation, AI assistance disclosure, design decisions (property-value handling), and test coverage.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/use-baseline

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

Caution

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

⚠️ Outside diff range comments (1)
xtask/codegen/src/main.rs (1)

66-79: ⚠️ Potential issue | 🟠 Major

Handle License and CssBaseline commands gracefully when external_data is disabled.

When the external_data feature is off, the match arms exist but are empty (the function calls are #[cfg]'d out). Users can invoke these commands and get silent success with no work done—confusing UX.

Either explicitly error with bail!() as suggested, or feature-gate the enum variants themselves for compile-time consistency.

Possible direction
+use anyhow::bail;

         TaskCommand::License => {
             #[cfg(feature = "external_data")]
             generate_license(Overwrite)?;
+            #[cfg(not(feature = "external_data"))]
+            bail!("`license` requires the `external_data` feature");
         }
@@
         TaskCommand::CssBaseline => {
             #[cfg(feature = "external_data")]
             generate_css_baseline(Overwrite)?;
+            #[cfg(not(feature = "external_data"))]
+            bail!("`css-baseline` requires the `external_data` feature");
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xtask/codegen/src/main.rs` around lines 66 - 79, The match arms for
TaskCommand::License and TaskCommand::CssBaseline are compiled empty when the
external_data feature is disabled, causing silent no-ops; update handling so the
commands fail fast or are unavailable: either (A) move the #[cfg(feature =
"external_data")] gating to the TaskCommand enum (remove the variants when
feature is off) so callers cannot invoke them at compile time, or (B) keep the
variants but inside the TaskCommand::License and TaskCommand::CssBaseline arms
call bail!() (or return an Err) with a clear message referencing
generate_license and generate_css_baseline and stating that the external_data
feature is required; pick one approach and apply consistently so TaskCommand and
main.rs behavior align.
♻️ Duplicate comments (2)
xtask/codegen/src/generate_css_baseline.rs (1)

284-404: ⚠️ Potential issue | 🔴 Critical

Critical: binary_search requires KNOWN to be sorted (it currently isn’t).

The trailing anchor / anchor-size entries make the slice unsorted, so KNOWN.binary_search(...) is unreliable and can silently drop functions from the generated baseline tables.

Given this is codegen (not a hot path), the simplest fix is to ditch the sorted invariant:

Proposed patch
 fn is_known_css_function(name: &str) -> bool {
@@
-    KNOWN.binary_search(&name).is_ok()
+    KNOWN.contains(&name)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xtask/codegen/src/generate_css_baseline.rs` around lines 284 - 404, The KNOWN
slice in is_known_css_function is not sorted so using KNOWN.binary_search(...)
is incorrect; update is_known_css_function to use KNOWN.contains(&name) instead
of binary_search (or alternatively sort the KNOWN array if you prefer to keep
binary_search) so lookup is reliable; locate the is_known_css_function function
and replace the binary_search call with contains.
crates/biome_css_analyze/src/lint/nursery/use_baseline.rs (1)

570-608: ⚠️ Potential issue | 🟠 Major

Map CssFunctionAtRule to "function".

CssFunctionAtRule currently returns None, preventing baseline checks and allowlist configuration for the @function at-rule. Since "function" has a baseline entry (Limited tier) and the CSS Mixins spec defines @function as a valid at-rule, it should map to Some("function") like other spec-compliant at-rules.

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

In `@crates/biome_css_analyze/src/lint/nursery/use_baseline.rs` around lines 570 -
608, The function at_rule_name currently maps AnyCssAtRule::CssFunctionAtRule to
None, which prevents baseline/allowlist handling for the `@function` at-rule;
update at_rule_name so that the CssFunctionAtRule arm returns Some("function")
(i.e., change the match arm for AnyCssAtRule::CssFunctionAtRule from being
grouped with internal/framework at-rules to return Some("function")) to enable
baseline checks for "function".
🧹 Nitpick comments (3)
crates/biome_css_analyze/src/keywords.rs (1)

5756-5917: NAMED_COLORS currently mixes colour and non-colour keywords.

Line 5764 ("auto"), Line 5866 ("none"), and CSS-wide keywords (for example Line 5823 and Line 5910) are not named colours. Keeping them here makes the exemption set broader than the constant name suggests.

💡 Suggested split to keep intent explicit
+pub(crate) const CSS_WIDE_KEYWORDS: &[&str] = &[
+    "inherit",
+    "initial",
+    "revert",
+    "revert-layer",
+    "unset",
+];
+
+pub(crate) const UNIVERSAL_NON_COLOR_KEYWORDS: &[&str] = &["auto", "none"];
+
 pub(crate) const NAMED_COLORS: &[&str] = &[
     "aliceblue",
     "antiquewhite",
     "aqua",
     "aquamarine",
-    "auto",
     "azure",
@@
-    "inherit",
-    "initial",
@@
-    "none",
@@
-    "revert",
-    "revert-layer",
@@
-    "unset",
 ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_analyze/src/keywords.rs` around lines 5756 - 5917, The
NAMED_COLORS constant mixes true color names with non-color/CSS-wide keywords;
split it into two explicit constants: keep only actual color token strings in
NAMED_COLORS and move non-color entries (e.g., "auto", "none", CSS-wide keywords
like "inherit", "initial", "unset", "revert", "revert-layer", and any other
non-color tokens) into a new constant (e.g., CSS_WIDE_KEYWORDS or
NON_COLOR_KEYWORDS). Update any code that referenced NAMED_COLORS for exemption
checks to consult both lists where appropriate (use NAMED_COLORS for
color-specific logic and the new constant for CSS-wide/non-color exemptions) and
rename references if needed to preserve semantics.
xtask/codegen/src/generate_css_baseline.rs (2)

186-195: update_best should probably also merge year when tiers tie.

Right now, equal-tier entries keep the first-seen year. For year-based cut-offs, you likely want deterministic and “most permissive” behaviour:

  • If both are Newly, keep the earliest known year (min), or keep Known over Unknown.

This is low-risk and makes the generated data more stable against upstream duplication.

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

In `@xtask/codegen/src/generate_css_baseline.rs` around lines 186 - 195, The
update_best function currently replaces existing entries only when the new
status has a higher tier; change its collision handling so when tiers are equal
(using tier_rank/status.tier) it merges year and knowledge state
deterministically: for Newly prefer the earliest year (min of status.year and
existing.year), and treat Known as more permissive than Unknown (prefer Known
over Unknown when one is Known), then update the existing Status accordingly;
adjust the closure inside map.entry(...).and_modify(...) in update_best to
perform this tie-handling/merge instead of leaving the first-seen year
untouched.

561-562: Generated-file header command string looks too vague.

reformat_with_command(..., "baseline") will tell contributors to run baseline, but the actual task seems to be CssBaseline (likely cargo xtask codegen css-baseline / just gen-css-baseline). Minor, but this text tends to live forever.

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

In `@xtask/codegen/src/generate_css_baseline.rs` around lines 561 - 562, The
generated-file header uses a vague command string "baseline" which misleads
contributors; update the call to
xtask_glue::reformat_with_command(tokens.to_string(), "baseline") to use the
precise task name used to regenerate the file (e.g. "CssBaseline" or the exact
xtask/just invocation such as "cargo xtask codegen css-baseline" or "just
gen-css-baseline") so the header shows the correct regeneration command; locate
the call to reformat_with_command (the content variable assignment using tokens)
and replace the second argument with the canonical command string used by the
repository.
🤖 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_analyze/src/rule.rs`:
- Line 235: The Eslint CSS rule uses the wrong namespace and doc URL: update the
variant Self::EslintCss to produce the namespace "css" (not "eslint/css") and
change the documentation link constant used for EslintCss to point to the
correct GitHub path using "blob/main" and the ".md" file extension (e.g.,
.../blob/main/.../rule-name.md) so the canonical rule format and URL resolve
correctly; locate the namespace formatting logic around the Self::EslintCss
match arm and the documentation URL constant/format used for EslintCss and
adjust those strings accordingly.

In `@crates/biome_css_analyze/src/lint/nursery/use_baseline.rs`:
- Around line 405-416: The lookup fails when users provide mixed-case
keys/values because is_allowed_property_value lowercases the runtime key but
allow_property_values entries are not normalised; update UseBaselineOptions
(during deserialisation and/or its merge method) to normalise the
allow_property_values HashMap by converting all keys and values to a consistent
case (e.g., ASCII lowercase) when building the map so runtime matching in
is_allowed_property_value works case-insensitively; ensure you target the
UseBaselineOptions deserialisation/merge path that produces the
HashMap<String,String> used by is_allowed_property_value and preserve existing
semantics otherwise.
- Around line 381-495: is_inside_positive_supports is too broad: it treats any
node inside any positive `@supports` as exempt even if the `@supports` condition
doesn't test the same property/value; update is_inside_positive_supports (and
callers like check_property) to locate the nearest CssSupportsAtRule ancestor,
parse its condition AST (use CssSupportsFeatureDeclaration and
CssSupportsFeatureSelector / related nodes), extract tested property names and
values, and only return true when the supports condition explicitly tests the
current property or its value; leave behavior unchanged for negated conditions
(CssSupportsNotCondition) and fall back to false if no matching feature is
found.

In `@crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.css`:
- Around line 39-45: The empty `@supports` selector(:has()) {} block should be
replaced with a non-empty trivial rule to avoid Stylelint's block-no-empty; edit
the `@supports` selector(:has()) { ... } block and add a harmless placeholder
declaration (for example a :root or a simple selector with a no-op custom
property or initial value) so the guard remains scoped but is not empty,
ensuring the semantics ("this guard doesn’t apply elsewhere") are preserved
without triggering lint errors.

In `@crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.css`:
- Around line 42-65: Tests use the non-existent CSS property background-filter
inside several rules (e.g., the rule blocks that read "a { accent-color: auto;
background-filter: auto }" and the nested/supports variants), which trips
property-no-unknown; update every occurrence of background-filter to the correct
backdrop-filter so the `@supports` blocks actually exercise backdrop-filter usage
(check the rules within the compound `@supports`, nested `@supports`, and the
case-insensitive `@SUPPORTS/`@SuPpOrTs blocks).

In `@crates/biome_rule_options/src/use_baseline.rs`:
- Around line 89-91: The field allow_property_values currently uses
HashMap<String, String>, which only permits a single value per CSS property;
change its type to HashMap<String, Vec<String>> so each property maps to an
array of allowed values (matching upstream ESLint's Record<string, string[]>),
keep or adjust the #[serde(skip_serializing_if = "HashMap::is_empty")] attribute
as needed, and update any code that builds, reads, or serializes/deserializes
allow_property_values to treat values as Vec<String> (constructors, defaults,
tests, and any places referencing allow_property_values must push/extend vectors
instead of assigning single strings).

In `@justfile`:
- Around line 59-61: The gen-css-baseline justfile recipe fails because
generate_css_baseline() is behind #[cfg(feature = "external_data")]; update the
recipe to enable that feature when invoking the xtask_codegen binary (i.e., run
cargo with the external_data feature enabled for package xtask_codegen) so the
feature-gated function is compiled and the css-baseline command succeeds.

In `@xtask/codegen/Cargo.toml`:
- Line 64: Make ureq an optional dependency and tie it to the existing
external_data feature, and add cfg guards around the unconditional imports.
Update Cargo.toml to declare ureq as optional and ensure the external_data
feature includes "ureq" (instead of listing ureq feature strings directly), then
add #[cfg(feature = "external_data")] before each unconditional use ureq;
statement in unicode.rs, generate_license.rs, and generate_css_baseline.rs so
the crate builds when the external_data feature is disabled.

In `@xtask/codegen/src/generate_css_baseline.rs`:
- Around line 10-11: The network fetch using WEB_FEATURES_DATA_URL currently
calls get(...).call() with no timeout and can block CI; update the HTTP client
creation to use
ureq::AgentBuilder::new().timeout(Duration::from_secs(...)).build() (or
equivalent) and replace the plain get(...) calls to use that agent so calls time
out; additionally pin or document the web-features source by changing
WEB_FEATURES_DATA_URL to a specific versioned URL (or add a comment explaining
why unpkg "latest" is acceptable) so generated output is stable over time.

---

Outside diff comments:
In `@xtask/codegen/src/main.rs`:
- Around line 66-79: The match arms for TaskCommand::License and
TaskCommand::CssBaseline are compiled empty when the external_data feature is
disabled, causing silent no-ops; update handling so the commands fail fast or
are unavailable: either (A) move the #[cfg(feature = "external_data")] gating to
the TaskCommand enum (remove the variants when feature is off) so callers cannot
invoke them at compile time, or (B) keep the variants but inside the
TaskCommand::License and TaskCommand::CssBaseline arms call bail!() (or return
an Err) with a clear message referencing generate_license and
generate_css_baseline and stating that the external_data feature is required;
pick one approach and apply consistently so TaskCommand and main.rs behavior
align.

---

Duplicate comments:
In `@crates/biome_css_analyze/src/lint/nursery/use_baseline.rs`:
- Around line 570-608: The function at_rule_name currently maps
AnyCssAtRule::CssFunctionAtRule to None, which prevents baseline/allowlist
handling for the `@function` at-rule; update at_rule_name so that the
CssFunctionAtRule arm returns Some("function") (i.e., change the match arm for
AnyCssAtRule::CssFunctionAtRule from being grouped with internal/framework
at-rules to return Some("function")) to enable baseline checks for "function".

In `@xtask/codegen/src/generate_css_baseline.rs`:
- Around line 284-404: The KNOWN slice in is_known_css_function is not sorted so
using KNOWN.binary_search(...) is incorrect; update is_known_css_function to use
KNOWN.contains(&name) instead of binary_search (or alternatively sort the KNOWN
array if you prefer to keep binary_search) so lookup is reliable; locate the
is_known_css_function function and replace the binary_search call with contains.

---

Nitpick comments:
In `@crates/biome_css_analyze/src/keywords.rs`:
- Around line 5756-5917: The NAMED_COLORS constant mixes true color names with
non-color/CSS-wide keywords; split it into two explicit constants: keep only
actual color token strings in NAMED_COLORS and move non-color entries (e.g.,
"auto", "none", CSS-wide keywords like "inherit", "initial", "unset", "revert",
"revert-layer", and any other non-color tokens) into a new constant (e.g.,
CSS_WIDE_KEYWORDS or NON_COLOR_KEYWORDS). Update any code that referenced
NAMED_COLORS for exemption checks to consult both lists where appropriate (use
NAMED_COLORS for color-specific logic and the new constant for
CSS-wide/non-color exemptions) and rename references if needed to preserve
semantics.

In `@xtask/codegen/src/generate_css_baseline.rs`:
- Around line 186-195: The update_best function currently replaces existing
entries only when the new status has a higher tier; change its collision
handling so when tiers are equal (using tier_rank/status.tier) it merges year
and knowledge state deterministically: for Newly prefer the earliest year (min
of status.year and existing.year), and treat Known as more permissive than
Unknown (prefer Known over Unknown when one is Known), then update the existing
Status accordingly; adjust the closure inside map.entry(...).and_modify(...) in
update_best to perform this tie-handling/merge instead of leaving the first-seen
year untouched.
- Around line 561-562: The generated-file header uses a vague command string
"baseline" which misleads contributors; update the call to
xtask_glue::reformat_with_command(tokens.to_string(), "baseline") to use the
precise task name used to regenerate the file (e.g. "CssBaseline" or the exact
xtask/just invocation such as "cargo xtask codegen css-baseline" or "just
gen-css-baseline") so the header shows the correct regeneration command; locate
the call to reformat_with_command (the content variable assignment using tokens)
and replace the second argument with the canonical command string used by the
repository.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4b6a9c56-1e8f-4c6f-a0da-163d532c45f9

📥 Commits

Reviewing files that changed from the base of the PR and between 1022662 and 11e4a32.

⛔ Files ignored due to path filters (15)
  • Cargo.lock is excluded by !**/*.lock and included by **
  • 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_css_analyze/tests/specs/nursery/useBaseline/invalid.allow-lists.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.newly.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2021.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2022.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.allow-lists.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.newly.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.year.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs 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 (30)
  • .changeset/add-use-baseline-css-rule.md
  • crates/biome_analyze/src/rule.rs
  • crates/biome_css_analyze/Cargo.toml
  • crates/biome_css_analyze/src/baseline_data.rs
  • crates/biome_css_analyze/src/keywords.rs
  • crates/biome_css_analyze/src/lib.rs
  • crates/biome_css_analyze/src/lint/nursery/use_baseline.rs
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.allow-lists.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.allow-lists.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.newly.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.newly.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2021.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2021.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2022.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2022.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.allow-lists.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.allow-lists.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.newly.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.newly.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.year.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.year.options.json
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/use_baseline.rs
  • justfile
  • xtask/codegen/Cargo.toml
  • xtask/codegen/src/generate_css_baseline.rs
  • xtask/codegen/src/lib.rs
  • xtask/codegen/src/main.rs

Comment on lines +381 to +495
/// Check whether a syntax node is inside a positive (non-negated) `@supports`
/// condition. If so, the property/value is feature-gated and should not be
/// flagged.
fn is_inside_positive_supports(syntax: &biome_css_syntax::CssSyntaxNode) -> bool {
for anc in syntax.ancestors().skip(1) {
// If we encounter a `not` condition on the way up, it means we're
// inside `@supports not (...)` — not suppressed.
if CssSupportsNotCondition::can_cast(anc.kind()) {
return false;
}
// If we reach the top-level @supports at-rule (via its parent CssAtRule),
// we are inside a positive @supports.
if CssSupportsAtRule::can_cast(anc.kind()) {
return true;
}
}
false
}

fn in_allow_list(name: &TokenText, list: &[String]) -> bool {
list.iter()
.any(|item| item.eq_ignore_ascii_case(&name.to_ascii_lowercase_cow()))
}

fn is_allowed_property_value(
key: &TokenText,
value: &TokenText,
list: &HashMap<String, String>,
) -> bool {
let this_value = list.get(key.to_ascii_lowercase_cow().as_ref());
this_value.is_some_and(|this_value| {
value
.to_ascii_lowercase_cow()
.eq_ignore_ascii_case(this_value)
})
}

fn is_named_color(name: &str) -> bool {
NAMED_COLORS.binary_search(&name).is_ok()
}

fn check_property(
prop: &CssGenericProperty,
options: &UseBaselineOptions,
) -> Option<UseBaselineState> {
use biome_css_syntax::AnyCssValue;

// If inside a positive @supports, the feature is gated — skip
if is_inside_positive_supports(prop.syntax()) {
return None;
}

// Check the property name itself
let name_node = prop.name().ok()?;
let prop_name = name_node.as_css_identifier()?.value_token().ok()?;
let prop_text = prop_name.token_text_trimmed();

// Skip custom properties (--foo)
if prop_text.text().starts_with("--") {
return None;
}

// Skip if in allow list
if in_allow_list(&prop_text, options.allow_properties.as_slice()) {
return None;
}

// Check property name baseline
if let Some(status) = find_baseline(
&BASELINE_PROPERTIES,
&prop_text.text().to_ascii_lowercase_cow(),
) && !is_acceptable(options, status.tier, status.year)
{
return Some(UseBaselineState {
feature_name: prop_text.into(),
category: "property",
range: prop_name.text_trimmed_range(),
});
}
// If not in our data, assume it's fine (unknown = not checked)

// Check property values (identifier keywords only)
for component in prop.value() {
if let Some(value) = component.as_any_css_value()
&& let AnyCssValue::CssIdentifier(ident) = value
{
let tok = ident.value_token().ok()?;
let val_text = tok.token_text_trimmed();

// Skip named colors
if is_named_color(&val_text.to_ascii_lowercase_cow()) {
continue;
}

// Skip if in property value allow list
if is_allowed_property_value(&prop_text, &val_text, &options.allow_property_values) {
continue;
}

if let Some(status) = find_property_value_baseline(
&prop_text.to_ascii_lowercase_cow(),
&val_text.to_ascii_lowercase_cow(),
) && !is_acceptable(options, status.tier, status.year)
{
return Some(UseBaselineState {
feature_name: (prop_text, val_text).into(),
category: "property value",
range: tok.text_trimmed_range(),
});
}
}
}

None
}
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1. Check the full implementation of use_baseline.rs to understand the current logic
wc -l crates/biome_css_analyze/src/lint/nursery/use_baseline.rs

Repository: biomejs/biome

Length of output: 118


🏁 Script executed:

#!/bin/bash
# 2. Look at the CST node types related to `@supports`
rg -n "CssSupports" crates/biome_css_syntax/src --type rust | head -30

Repository: biomejs/biome

Length of output: 3636


🏁 Script executed:

#!/bin/bash
# 3. Check the test fixtures for use_baseline
find crates/biome_css_analyze/tests -name "*use_baseline*" -type f

Repository: biomejs/biome

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Read the specific lines from use_baseline.rs
sed -n '381,495p' crates/biome_css_analyze/src/lint/nursery/use_baseline.rs

Repository: biomejs/biome

Length of output: 3811


🏁 Script executed:

#!/bin/bash
# Search for test fixtures more broadly
find crates/biome_css_analyze/tests -name "*.rs" -o -name "*.md" | head -20

Repository: biomejs/biome

Length of output: 146


🏁 Script executed:

#!/bin/bash
# Check the structure of CssSupportsFeatureDeclaration and related nodes
rg -A10 "pub struct CssSupportsFeatureDeclaration" crates/biome_css_syntax/src/generated/nodes.rs
rg -A10 "pub struct CssSupportsFeatureSelector" crates/biome_css_syntax/src/generated/nodes.rs

Repository: biomejs/biome

Length of output: 1782


🏁 Script executed:

#!/bin/bash
# Check the spec test structure for use_baseline
find crates/biome_css_analyze/tests -type d -name "specs" | head -5

Repository: biomejs/biome

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Find test spec files for use_baseline
find crates/biome_css_analyze/tests/specs -name "*use_baseline*" -o -name "*baseline*" 2>/dev/null | head -10

Repository: biomejs/biome

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Get a broader view of test structure
ls -la crates/biome_css_analyze/tests/specs/

Repository: biomejs/biome

Length of output: 570


🏁 Script executed:

#!/bin/bash
# Check nursery specs
ls -la crates/biome_css_analyze/tests/specs/nursery/

Repository: biomejs/biome

Length of output: 427


🏁 Script executed:

#!/bin/bash
# Find use_baseline test file
find crates/biome_css_analyze/tests/specs/nursery -name "*use_baseline*" -o -name "*baseline*"

Repository: biomejs/biome

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Check the useBaseline test directory
ls -la crates/biome_css_analyze/tests/specs/nursery/useBaseline/

Repository: biomejs/biome

Length of output: 1990


🏁 Script executed:

#!/bin/bash
# Read the test spec file for useBaseline
find crates/biome_css_analyze/tests/specs/nursery/useBaseline -name "*.md" -o -name "*.json"

Repository: biomejs/biome

Length of output: 657


🏁 Script executed:

#!/bin/bash
# Read the main valid.css test file
cat crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.css

Repository: biomejs/biome

Length of output: 2689


🏁 Script executed:

#!/bin/bash
# Read the main invalid.css test file
cat crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.css

Repository: biomejs/biome

Length of output: 1657


🏁 Script executed:

#!/bin/bash
# Check the snapshot files to see what diagnostics are expected
head -100 crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.css.snap

Repository: biomejs/biome

Length of output: 2862


🏁 Script executed:

#!/bin/bash
# Check the invalid snapshot to see what diagnostics are expected
head -150 crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.css.snap

Repository: biomejs/biome

Length of output: 5398


🏁 Script executed:

#!/bin/bash
# Check what specific diagnostics are expected from the test files
grep -c "lint/nursery/useBaseline" crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.css.snap

Repository: biomejs/biome

Length of output: 59


🏁 Script executed:

#!/bin/bash
# Get the full invalid snapshot to see all expected diagnostics
cat crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.css.snap | tail -200

Repository: biomejs/biome

Length of output: 8102


🏁 Script executed:

#!/bin/bash
# Check if there are other functions that check `@supports` logic
rg -n "is_inside_positive_supports|CssSupportsAtRule|CssSupportsNotCondition" crates/biome_css_analyze/src/lint/nursery/use_baseline.rs

Repository: biomejs/biome

Length of output: 487


🏁 Script executed:

#!/bin/bash
# Check the full use_baseline.rs to see all the logic around `@supports`
head -380 crates/biome_css_analyze/src/lint/nursery/use_baseline.rs | tail -100

Repository: biomejs/biome

Length of output: 3492


🏁 Script executed:

#!/bin/bash
# Read more of the implementation to see if property values and functions are checked separately
sed -n '1,380p' crates/biome_css_analyze/src/lint/nursery/use_baseline.rs | grep -A 50 "pub fn rule"

Repository: biomejs/biome

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Look at the rule implementation structure
rg -n "impl UseBaseline|pub fn rule" crates/biome_css_analyze/src/lint/nursery/use_baseline.rs -A 30 | head -80

Repository: biomejs/biome

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Search for the declare_lint_rule macro for UseBaseline
rg -n "declare_lint_rule" crates/biome_css_analyze/src/lint/nursery/use_baseline.rs -A 20

Repository: biomejs/biome

Length of output: 1992


🏁 Script executed:

#!/bin/bash
# Get the beginning of the file to see the rule declaration
head -150 crates/biome_css_analyze/src/lint/nursery/use_baseline.rs

Repository: biomejs/biome

Length of output: 4693


🏁 Script executed:

#!/bin/bash
# Check if there are multiple rule checking handlers or just one
rg -n "impl Rule for UseBaseline|fn check_" crates/biome_css_analyze/src/lint/nursery/use_baseline.rs

Repository: biomejs/biome

Length of output: 366


🏁 Script executed:

#!/bin/bash
# Read around where other node types are checked (at-rules, functions, etc)
sed -n '500,635p' crates/biome_css_analyze/src/lint/nursery/use_baseline.rs

Repository: biomejs/biome

Length of output: 4873


🏁 Script executed:

#!/bin/bash
# Read the Rule implementation
sed -n '224,300p' crates/biome_css_analyze/src/lint/nursery/use_baseline.rs

Repository: biomejs/biome

Length of output: 3030


The @supports exemption is too broad for properties alone—consider matching against the actual condition.

The concern is valid: is_inside_positive_supports() exempts ANY property name inside ANY positive @supports block without checking whether the condition actually tests that feature. However, this applies only to properties; functions, pseudo-classes, pseudo-elements, and media conditions are still flagged inside @supports blocks, as shown by the test fixtures (e.g., @supports (accent-color: auto) { accent-color: abs(...) } flags the function).

The current approach is permissive by design (per the rule docs: "Code inside @supports blocks is exempt"), but it could be more selective. A property should only be exempted if the nearest @supports condition actually tests that property or its value—not just any ancestor @supports. The test case at line 24 of invalid.css (@supports (clip-path: fill-box) { clip-path: stroke-box }) shows this gap: the condition guards fill-box, but stroke-box is still flagged because property values are checked separately.

If you want stricter matching, you'd need to extract features from the @supports condition (via CssSupportsFeatureDeclaration and CssSupportsFeatureSelector) and only suppress matching uses. That's more complex but more precise.

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

In `@crates/biome_css_analyze/src/lint/nursery/use_baseline.rs` around lines 381 -
495, is_inside_positive_supports is too broad: it treats any node inside any
positive `@supports` as exempt even if the `@supports` condition doesn't test the
same property/value; update is_inside_positive_supports (and callers like
check_property) to locate the nearest CssSupportsAtRule ancestor, parse its
condition AST (use CssSupportsFeatureDeclaration and CssSupportsFeatureSelector
/ related nodes), extract tested property names and values, and only return true
when the supports condition explicitly tests the current property or its value;
leave behavior unchanged for negated conditions (CssSupportsNotCondition) and
fall back to false if no matching feature is found.

"pulldown-cmark",
]
license = ["serde", "serde_json", "ureq/default", "ureq/json"]
external_data = ["serde", "serde_json", "ureq/default", "ureq/json"]
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check ureq declaration and feature wiring
echo "=== Checking ureq dependency and external_data feature ==="
rg -n "^\s*ureq\s*=|^external_data\s*=" xtask/codegen/Cargo.toml -A3 -B2

# Check if ureq is used in source and whether it's feature-gated
echo -e "\n=== Checking ureq usage in source ==="
rg -n "ureq::|use ureq|cfg\(feature\s*=\s*\"external_data\"\)" xtask/codegen/src --max-count 20

Repository: biomejs/biome

Length of output: 1201


ureq is non-optional but needs to be tied to external_data — and worse, it's imported unconditionally in three source files.

The dependency is always pulled in even when external_data is disabled. Additionally, ureq is imported without feature guards in unicode.rs, generate_license.rs, and generate_css_baseline.rs, which means disabling the feature breaks the build. Make ureq optional and gate it properly:

Suggested refactor
-ureq                  = "3.1.4"
+ureq                  = { version = "3.1.4", optional = true, default-features = false }

-external_data = ["serde", "serde_json", "ureq/default", "ureq/json"]
+external_data = ["dep:serde", "dep:serde_json", "dep:ureq", "ureq/json"]

You'll also need to add #[cfg(feature = "external_data")] guards to the unconditional use ureq imports.

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

In `@xtask/codegen/Cargo.toml` at line 64, Make ureq an optional dependency and
tie it to the existing external_data feature, and add cfg guards around the
unconditional imports. Update Cargo.toml to declare ureq as optional and ensure
the external_data feature includes "ureq" (instead of listing ureq feature
strings directly), then add #[cfg(feature = "external_data")] before each
unconditional use ureq; statement in unicode.rs, generate_license.rs, and
generate_css_baseline.rs so the crate builds when the external_data feature is
disabled.

Comment on lines +10 to +11
const WEB_FEATURES_DATA_URL: &str = "https://unpkg.com/web-features/data.json";

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file structure
fd "generate_css_baseline.rs" --type f

Repository: biomejs/biome

Length of output: 99


🏁 Script executed:

# Check the content at the specified lines
head -50 xtask/codegen/src/generate_css_baseline.rs

Repository: biomejs/biome

Length of output: 1343


🏁 Script executed:

# Also check lines 532-565 to see the other location mentioned
wc -l xtask/codegen/src/generate_css_baseline.rs

Repository: biomejs/biome

Length of output: 103


🌐 Web query:

ureq 3.1.4 HTTP request timeout configuration best practices

💡 Result:

What ureq 3.1.4 gives you (timeouts, defaults, precedence)

In ureq 3.x you can configure timeouts on the Agent (recommended for consistency) and, depending on API, sometimes per-request.

Key knobs on AgentBuilder:

  • timeout_connect: socket connect timeout. Default: 30s. If you set both timeout_connect and timeout, timeout_connect wins for the connect phase. [1]
  • timeout_read / timeout_write: per-socket-operation read/write timeouts. Default: none (so reads/writes can block forever). If you also set timeout, timeout takes precedence over these. [1]
  • timeout: overall request timeout including DNS resolution, connection time, redirects, and reading the response body; note that slow DNS may still make you exceed the timeout because DNS can’t be interrupted with available APIs. [1]

ureq also has a more granular timeout model (documented in 3.2.0) that splits timeouts by phase (resolve, connect, send_request, recv_response, recv_body, plus global and redirect-related per_call). This is useful as a mental model even if you stick to the AgentBuilder methods. [2]

Best-practice configuration patterns

  1. Always set a finite “overall” timeout

    • Use AgentBuilder::timeout(...) as a hard upper bound so a request can’t hang forever (especially important because default read/write timeouts are “none”). [1]
  2. Set a shorter connect timeout than your overall timeout

    • Connect failures should usually be detected quickly (e.g., 250ms–2s in low-latency environments; higher if you expect cold networks/VPN/mobile).
    • In ureq, timeout_connect is also the one that can override timeout for the connect phase, so set it intentionally. [1]
  3. Decide whether you need per-read/per-write timeouts

    • If you have an overall timeout, you may not need timeout_read/write.
    • If you stream large bodies or want “stall detection” (fail if no progress), add timeout_read (and possibly timeout_write) to cap individual socket operations—just remember timeout() overrides them when both are set. [1]
  4. Account for redirects explicitly

    • Because the overall timeout includes redirects, a low global timeout can fail on redirect-heavy flows. [1]
    • If you adopt the granular model, per_call exists specifically for “call-by-call when following redirects,” which can help avoid a single redirect hop consuming the whole budget. [2]
  5. Be aware of DNS behavior

    • ureq notes DNS may not be interruptible, so avoid extremely tight global budgets if you rely on system DNS and occasionally see slow resolution; alternatively use a custom resolver where appropriate. [1]
  6. Consider upgrading if you see timeout edge cases

    • The ureq changelog indicates fixes around timeout_read behavior landed after earlier 3.x versions; if timeout correctness is critical, validate on your exact version or upgrade. [3]

Example (sane defaults for many API clients)

use std::time::Duration;

let agent = ureq::builder()
    .timeout_connect(Duration::from_secs(2))  // fail fast on connect
    .timeout(Duration::from_secs(10))         // hard cap for whole request
    .build();

Sources: AgentBuilder timeout docs and defaults [1]; granular per-phase timeout model (3.2.0 docs) [2]; changelog notes about timeout fixes [3].

References:
[1] ureq AgentBuilder docs (timeout/timeout_connect/timeout_read/timeout_write semantics, defaults, precedence, DNS note). (world.pages.gitlab.gnome.org)
[2] ureq config::Timeouts (phase-specific timeout breakdown, redirect per_call). (docs.rs)
[3] ureq changelog (timeout-related fixes in later 3.x). (docs.rs)

Citations:


🏁 Script executed:

# Get the network request code section (lines 532-565)
sed -n '532,565p' xtask/codegen/src/generate_css_baseline.rs

Repository: biomejs/biome

Length of output: 1203


🏁 Script executed:

# Also check around line 10 context and any other network calls
grep -n "ureq\|get\|timeout\|request" xtask/codegen/src/generate_css_baseline.rs

Repository: biomejs/biome

Length of output: 381


🏁 Script executed:

# Check Cargo.toml to see ureq version
fd "Cargo.toml" xtask/codegen --exec grep -l ureq {} \;

Repository: biomejs/biome

Length of output: 81


🏁 Script executed:

# Check ureq version in xtask/codegen/Cargo.toml
cat xtask/codegen/Cargo.toml | grep -A2 -B2 ureq

Repository: biomejs/biome

Length of output: 389


🏁 Script executed:

# Let's also verify if there are any other network calls in the file or if this is the only one
wc -l xtask/codegen/src/generate_css_baseline.rs && tail -50 xtask/codegen/src/generate_css_baseline.rs

Repository: biomejs/biome

Length of output: 1717


Add explicit HTTP timeouts (and consider pinning the web-features source).

Network requests without timeouts can hang CI/dev workflows. The code uses get(WEB_FEATURES_DATA_URL).call() without any timeout configuration; ureq 3.1.4 requires explicit setup (e.g. via AgentBuilder::timeout()) to prevent indefinite blocking. Also, unpkg.com/web-features/data.json is a "latest" endpoint, so output can drift over time—pin the version or document the intent.

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

In `@xtask/codegen/src/generate_css_baseline.rs` around lines 10 - 11, The network
fetch using WEB_FEATURES_DATA_URL currently calls get(...).call() with no
timeout and can block CI; update the HTTP client creation to use
ureq::AgentBuilder::new().timeout(Duration::from_secs(...)).build() (or
equivalent) and replace the plain get(...) calls to use that agent so calls time
out; additionally pin or document the web-features source by changing
WEB_FEATURES_DATA_URL to a specific versioned URL (or add a comment explaining
why unpkg "latest" is acceptable) so generated output is stable over time.

@ematipico ematipico requested a review from dyc3 March 6, 2026 13:19
@ematipico ematipico force-pushed the feat/use-baseline branch from 72cdbf2 to d2e9a72 Compare March 6, 2026 13:32
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

♻️ Duplicate comments (1)
crates/biome_css_analyze/src/lint/nursery/use_baseline.rs (1)

385-399: ⚠️ Potential issue | 🟠 Major

@supports not (...) blocks are likely being treated as positive supports.

Line 389 relies on ancestor scanning for CssSupportsNotCondition, but declarations in the block body usually won’t have that condition node as an ancestor. In practice, Line 395 may return true for any CssSupportsAtRule, suppressing diagnostics in @supports not fallback blocks.

#!/bin/bash
set -euo pipefail

# 1) Inspect AST node APIs for supports at-rules and not-conditions.
rg -n "pub struct CssSupportsAtRule|impl CssSupportsAtRule|fn prelude\\(|fn condition\\(|fn block\\(" crates/biome_css_syntax/src/generated/nodes.rs -A20 -B2
rg -n "pub struct CssSupportsNotCondition|impl CssSupportsNotCondition" crates/biome_css_syntax/src/generated/nodes.rs -A20 -B2

# 2) Re-open the current suppression logic.
rg -n "fn is_inside_positive_supports|CssSupportsNotCondition|CssSupportsAtRule" crates/biome_css_analyze/src/lint/nursery/use_baseline.rs -C5

# 3) Check if there is explicit fixture coverage for `@supports` not.
rg -n "@supports\\s+not" crates/biome_css_analyze/tests/specs/nursery/useBaseline -C2

Expected verification outcome: if CssSupportsNotCondition is only part of the prelude subtree and fixtures lack @supports not coverage, this branch is a real false-negative risk.

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

In `@crates/biome_css_analyze/src/lint/nursery/use_baseline.rs` around lines 385 -
399, The current is_inside_positive_supports function incorrectly assumes a
CssSupportsNotCondition will appear among ancestors of a declaration; instead
locate the enclosing CssSupportsAtRule (use CssSupportsAtRule::can_cast while
walking ancestors to find the at-rule node) then inspect its prelude/condition
subtree (use CssSupportsAtRule::prelude or condition accessor and search that
subtree) for any CssSupportsNotCondition nodes; update
is_inside_positive_supports to return false if the at-rule's condition subtree
contains a CssSupportsNotCondition and only return true when the at-rule exists
and its condition subtree has no not-condition.
🤖 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/add-use-baseline-css-rule.md:
- Line 5: Change the wording in the changelog so it says the rule "reports
diagnostics" instead of "warns" and add a brief note that the rule does not set
an explicit severity in declare_lint_rule!, so it defaults to
Severity::Information (RuleMetadata::default()). Update the sentence describing
the new nursery lint rule `useBaseline` to reflect that it reports diagnostics
for CSS properties, values, at-rules, media conditions, functions, or
pseudo-selectors not in the configured Baseline tier and mention the default
severity behavior via `declare_lint_rule!` / `RuleMetadata::default()`.

In
`@crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.allow-lists.css`:
- Around line 16-17: The test case is masked by the `@supports` wrapper so the
intended rejection of "clip-path: stroke-box" never runs; update the fixture in
invalid.allow-lists.css so the property is checked outside a positive `@supports`
block (e.g., move "a { clip-path: stroke-box; }" out of "@supports (clip-path:
fill-box) { ... }" or change the supports condition to a negative/irrelevant
one) so the check in use_baseline.rs (the short-circuit at the property-checking
path) will exercise the negative allowPropertyValues case.

---

Duplicate comments:
In `@crates/biome_css_analyze/src/lint/nursery/use_baseline.rs`:
- Around line 385-399: The current is_inside_positive_supports function
incorrectly assumes a CssSupportsNotCondition will appear among ancestors of a
declaration; instead locate the enclosing CssSupportsAtRule (use
CssSupportsAtRule::can_cast while walking ancestors to find the at-rule node)
then inspect its prelude/condition subtree (use CssSupportsAtRule::prelude or
condition accessor and search that subtree) for any CssSupportsNotCondition
nodes; update is_inside_positive_supports to return false if the at-rule's
condition subtree contains a CssSupportsNotCondition and only return true when
the at-rule exists and its condition subtree has no not-condition.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 760af6cd-ac08-40a4-b921-c5f9f8a67e20

📥 Commits

Reviewing files that changed from the base of the PR and between 11e4a32 and f231f9f.

⛔ Files ignored due to path filters (13)
  • Cargo.lock is excluded by !**/*.lock and included by **
  • 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_css_analyze/tests/specs/nursery/useBaseline/invalid.allow-lists.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.newly.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2021.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2022.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.allow-lists.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.newly.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.year.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
📒 Files selected for processing (23)
  • .changeset/add-use-baseline-css-rule.md
  • crates/biome_analyze/src/rule.rs
  • crates/biome_css_analyze/Cargo.toml
  • crates/biome_css_analyze/src/baseline_data.rs
  • crates/biome_css_analyze/src/keywords.rs
  • crates/biome_css_analyze/src/lib.rs
  • crates/biome_css_analyze/src/lint/nursery/use_baseline.rs
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.allow-lists.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.allow-lists.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.newly.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.newly.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2021.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2021.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2022.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2022.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.allow-lists.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.allow-lists.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.newly.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.newly.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.year.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.year.options.json
✅ Files skipped from review due to trivial changes (1)
  • crates/biome_css_analyze/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (11)
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.newly.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2022.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2021.options.json
  • crates/biome_css_analyze/src/lib.rs
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/valid.allow-lists.options.json
  • crates/biome_analyze/src/rule.rs
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.allow-lists.options.json
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.newly.options.json
  • crates/biome_css_analyze/src/keywords.rs
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2021.css
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.year-2022.css

@github-actions github-actions bot added the L-JavaScript Language: JavaScript and super languages label Mar 6, 2026
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

🧹 Nitpick comments (2)
crates/biome_css_analyze/src/lint/nursery/use_baseline.rs (2)

623-627: Minor: same redundant conversion as in_allow_list.

name here is &'static str from at_rule_name, and eq_ignore_ascii_case doesn't require pre-lowercasing.

♻️ Suggested simplification
     if options
         .allow_at_rules
         .iter()
-        .any(|item| item.eq_ignore_ascii_case(&name.to_ascii_lowercase_cow()))
+        .any(|item| item.eq_ignore_ascii_case(name))
     {
         return None;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_analyze/src/lint/nursery/use_baseline.rs` around lines 623 -
627, The condition pre-lowercases `name` unnecessarily before calling
`eq_ignore_ascii_case`, causing redundant conversion; update the check in the
block that iterates `options.allow_at_rules` to call `eq_ignore_ascii_case`
directly with `name` (e.g., `item.eq_ignore_ascii_case(name)`) instead of
`item.eq_ignore_ascii_case(&name.to_ascii_lowercase_cow())`, and mirror the same
simplification used in `in_allow_list` so no extra allocation or lowercase
conversion occurs.

401-404: Minor: redundant case conversion.

eq_ignore_ascii_case already handles case-insensitive comparison, so the to_ascii_lowercase_cow() call is unnecessary overhead.

♻️ Suggested simplification
 fn in_allow_list(name: &TokenText, list: &[String]) -> bool {
     list.iter()
-        .any(|item| item.eq_ignore_ascii_case(&name.to_ascii_lowercase_cow()))
+        .any(|item| item.eq_ignore_ascii_case(name.text()))
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_analyze/src/lint/nursery/use_baseline.rs` around lines 401 -
404, The helper in_allow_list currently lowercases the TokenText before calling
eq_ignore_ascii_case, which is redundant; update in_allow_list (and any call
sites using to_ascii_lowercase_cow) to call eq_ignore_ascii_case directly with
the original TokenText (i.e., replace
item.eq_ignore_ascii_case(&name.to_ascii_lowercase_cow()) with
item.eq_ignore_ascii_case(&name) or item.eq_ignore_ascii_case(name) as
appropriate), removing the to_ascii_lowercase_cow() call to avoid unnecessary
allocation and overhead.
🤖 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_redundant_default_export.rs`:
- Line 18: The rustdoc summary contains a copy-paste gremlin and an unclosed
backtick: replace the broken sentence in the doc comment with a correct
description for this rule (e.g., "This rule reports the nursery lint rule
`no_redundant_default_export` when a `default` export references the same
identifier as a named export."), close all backticks, and remove the stray
"useBase" token so the rustdoc for no_redundant_default_export.rs and the rule
name `no_redundant_default_export` render correctly.

---

Nitpick comments:
In `@crates/biome_css_analyze/src/lint/nursery/use_baseline.rs`:
- Around line 623-627: The condition pre-lowercases `name` unnecessarily before
calling `eq_ignore_ascii_case`, causing redundant conversion; update the check
in the block that iterates `options.allow_at_rules` to call
`eq_ignore_ascii_case` directly with `name` (e.g.,
`item.eq_ignore_ascii_case(name)`) instead of
`item.eq_ignore_ascii_case(&name.to_ascii_lowercase_cow())`, and mirror the same
simplification used in `in_allow_list` so no extra allocation or lowercase
conversion occurs.
- Around line 401-404: The helper in_allow_list currently lowercases the
TokenText before calling eq_ignore_ascii_case, which is redundant; update
in_allow_list (and any call sites using to_ascii_lowercase_cow) to call
eq_ignore_ascii_case directly with the original TokenText (i.e., replace
item.eq_ignore_ascii_case(&name.to_ascii_lowercase_cow()) with
item.eq_ignore_ascii_case(&name) or item.eq_ignore_ascii_case(name) as
appropriate), removing the to_ascii_lowercase_cow() call to avoid unnecessary
allocation and overhead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ef7693f1-cb84-4664-b753-8206a21381f4

📥 Commits

Reviewing files that changed from the base of the PR and between 8d2ef95 and b04d728.

⛔ Files ignored due to path filters (1)
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.allow-lists.css.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (4)
  • .changeset/add-use-baseline-css-rule.md
  • crates/biome_css_analyze/src/lint/nursery/use_baseline.rs
  • crates/biome_css_analyze/tests/specs/nursery/useBaseline/invalid.allow-lists.css
  • crates/biome_js_analyze/src/lint/nursery/no_redundant_default_export.rs

/// Checks if a default export exports the same symbol as a named export.
///
/// This rule warns when a `default` export references the same identifier as a named export.
/// This rule reports new nursery lint rule `useBase when a `default` export references the same identifier as a named export.
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

Fix the rustdoc sentence (copy-paste gremlin + broken backtick).

Line 18 mentions useBase and has an unclosed inline code span, so the rule docs are misleading and render oddly.

Suggested patch
-    /// This rule reports new nursery lint rule `useBase when a `default` export references the same identifier as a named export.
+    /// This rule reports when a `default` export references the same identifier as a named export.
🤖 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_redundant_default_export.rs` at
line 18, The rustdoc summary contains a copy-paste gremlin and an unclosed
backtick: replace the broken sentence in the doc comment with a correct
description for this rule (e.g., "This rule reports the nursery lint rule
`no_redundant_default_export` when a `default` export references the same
identifier as a named export."), close all backticks, and remove the stray
"useBase" token so the rustdoc for no_redundant_default_export.rs and the rule
name `no_redundant_default_export` render correctly.

// CSS named colors are CssIdentifier tokens in property values.
// We exclude them from property-value baseline checks because they're
// universally supported regardless of baseline data.
pub(crate) const NAMED_COLORS: &[&str] = &[
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess it wasn't worth it to make this a set?

Copy link
Member Author

Choose a reason for hiding this comment

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

Argh, I missed it. Good catch

Copy link
Member Author

Choose a reason for hiding this comment

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

So, looking at where NAMED_COLORS is actually placed, I think we should keep it as an array for now because all const inside the file are arrays.

If we want to use phf, I think we can send a PR to change all of them

@ematipico ematipico merged commit 3ac98eb into main Mar 11, 2026
36 checks passed
@ematipico ematipico deleted the feat/use-baseline branch March 11, 2026 08:42
@github-actions github-actions bot mentioned this pull request Mar 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 A-Tooling Area: internal tools L-CSS Language: CSS and super languages L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants