diff --git a/.changeset/fix-use-generic-font-names-css-variable.md b/.changeset/fix-use-generic-font-names-css-variable.md new file mode 100644 index 000000000000..207535c02b08 --- /dev/null +++ b/.changeset/fix-use-generic-font-names-css-variable.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fix [`useGenericFontNames`](https://biomejs.dev/linter/rules/use-generic-font-names/) false positive when a CSS variable is used as the last value in `font-family` or `font`. The rule now correctly ignores cases like `font-family: "Noto Serif", var(--serif)` and `font: 1em Arial, var(--fallback)`. diff --git a/crates/biome_css_analyze/src/lint/a11y/use_generic_font_names.rs b/crates/biome_css_analyze/src/lint/a11y/use_generic_font_names.rs index 408da1c4e6e8..45b270864de8 100644 --- a/crates/biome_css_analyze/src/lint/a11y/use_generic_font_names.rs +++ b/crates/biome_css_analyze/src/lint/a11y/use_generic_font_names.rs @@ -102,6 +102,13 @@ impl Rule for UseGenericFontNames { return None; } + // Check if the last value in the original properties is a CSS variable. + // This must be done before collect_font_family_properties/find_font_family + // which filter out functions. + if is_last_value_css_variable(&properties) { + return None; + } + let font_families = if is_font { find_font_family(properties) } else { @@ -116,15 +123,7 @@ impl Rule for UseGenericFontNames { return None; } - // Ignore the last value if it's a CSS variable now. let last_value = font_families.last()?; - if last_value - .to_string() - .is_some_and(|s| is_css_variable(s.as_ref())) - { - return None; - } - Some(last_value.range()) } @@ -192,3 +191,18 @@ fn collect_font_family_properties(properties: CssGenericComponentValueList) -> V }) .collect() } + +/// Check if the last value in the properties list is a CSS variable. +/// This handles cases like `font-family: "Noto Serif", var(--serif)` +/// and `font: 1em "Noto Serif", var(--serif)`. +fn is_last_value_css_variable(properties: &CssGenericComponentValueList) -> bool { + // Find the last non-delimiter value + properties + .into_iter() + .filter_map(|v| match v { + AnyCssGenericComponentValue::AnyCssValue(_) => Some(v), + AnyCssGenericComponentValue::CssGenericDelimiter(_) => None, + }) + .last() + .is_some_and(|v| is_css_variable(&v.to_trimmed_text().text().to_ascii_lowercase_cow())) +} diff --git a/crates/biome_css_analyze/tests/specs/a11y/useGenericFontNames/valid.css b/crates/biome_css_analyze/tests/specs/a11y/useGenericFontNames/valid.css index 583b1e707308..697ad9499b43 100644 --- a/crates/biome_css_analyze/tests/specs/a11y/useGenericFontNames/valid.css +++ b/crates/biome_css_analyze/tests/specs/a11y/useGenericFontNames/valid.css @@ -22,5 +22,9 @@ a { font: message-box; } a { font: small-caption; } a { font: status-bar; } a { font-family: var(--font); } +a { font-family: "Noto Serif", var(--serif); } +a { font-family: Arial, var(--fallback); } +a { font: 1em "Noto Serif", var(--serif); } +a { font: 14px/1.5 Arial, var(--fallback); } a { font-family: revert } a { font-family: revert-layer } \ No newline at end of file diff --git a/crates/biome_css_analyze/tests/specs/a11y/useGenericFontNames/valid.css.snap b/crates/biome_css_analyze/tests/specs/a11y/useGenericFontNames/valid.css.snap index 0b301e7ba25a..28ef235ae5ce 100644 --- a/crates/biome_css_analyze/tests/specs/a11y/useGenericFontNames/valid.css.snap +++ b/crates/biome_css_analyze/tests/specs/a11y/useGenericFontNames/valid.css.snap @@ -28,6 +28,10 @@ a { font: message-box; } a { font: small-caption; } a { font: status-bar; } a { font-family: var(--font); } +a { font-family: "Noto Serif", var(--serif); } +a { font-family: Arial, var(--fallback); } +a { font: 1em "Noto Serif", var(--serif); } +a { font: 14px/1.5 Arial, var(--fallback); } a { font-family: revert } a { font-family: revert-layer } ```