-
-
Notifications
You must be signed in to change notification settings - Fork 471
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(biome_css_analyze): implement
noDuplicateFontNames
(#2308)
- Loading branch information
1 parent
43f17a3
commit ce223aa
Showing
19 changed files
with
652 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
pub const BASIC_KEYWORDS: [&str; 5] = ["initial", "inherit", "revert", "revert-layer", "unset"]; | ||
|
||
pub const _SYSTEM_FONT_KEYWORDS: [&str; 6] = [ | ||
"caption", | ||
"icon", | ||
"menu", | ||
"message-box", | ||
"small-caption", | ||
"status-bar", | ||
]; | ||
|
||
pub const FONT_FAMILY_KEYWORDS: [&str; 10] = [ | ||
"serif", | ||
"sans-serif", | ||
"cursive", | ||
"fantasy", | ||
"monospace", | ||
"system-ui", | ||
"ui-serif", | ||
"ui-sans-serif", | ||
"ui-monospace", | ||
"ui-rounded", | ||
]; | ||
|
||
pub const FONT_WEIGHT_ABSOLUTE_KEYWORDS: [&str; 2] = ["normal", "bold"]; | ||
pub const FONT_WEIGHT_NUMERIC_KEYWORDS: [&str; 9] = [ | ||
"100", "200", "300", "400", "500", "600", "700", "800", "900", | ||
]; | ||
pub const FONT_STYLE_KEYWORDS: [&str; 3] = ["normal", "italic", "oblique"]; | ||
pub const FONT_VARIANTS_KEYWORDS: [&str; 35] = [ | ||
"normal", | ||
"none", | ||
"historical-forms", | ||
"none", | ||
"common-ligatures", | ||
"no-common-ligatures", | ||
"discretionary-ligatures", | ||
"no-discretionary-ligatures", | ||
"historical-ligatures", | ||
"no-historical-ligatures", | ||
"contextual", | ||
"no-contextual", | ||
"small-caps", | ||
"all-small-caps", | ||
"petite-caps", | ||
"all-petite-caps", | ||
"unicase", | ||
"titling-caps", | ||
"lining-nums", | ||
"oldstyle-nums", | ||
"proportional-nums", | ||
"tabular-nums", | ||
"diagonal-fractions", | ||
"stacked-fractions", | ||
"ordinal", | ||
"slashed-zero", | ||
"jis78", | ||
"jis83", | ||
"jis90", | ||
"jis04", | ||
"simplified", | ||
"traditional", | ||
"full-width", | ||
"proportional-width", | ||
"ruby", | ||
]; | ||
|
||
pub const FONT_STRETCH_KEYWORDS: [&str; 8] = [ | ||
"semi-condensed", | ||
"condensed", | ||
"extra-condensed", | ||
"ultra-condensed", | ||
"semi-expanded", | ||
"expanded", | ||
"extra-expanded", | ||
"ultra-expanded", | ||
]; | ||
|
||
pub const FONT_SIZE_KEYWORDS: [&str; 9] = [ | ||
"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "larger", "smaller", | ||
]; | ||
|
||
pub const LINE_HEIGHT_KEYWORDS: [&str; 1] = ["normal"]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
crates/biome_css_analyze/src/lint/nursery/no_duplicate_font_names.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
use std::collections::HashSet; | ||
|
||
use biome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic, RuleSource}; | ||
use biome_console::markup; | ||
use biome_css_syntax::{AnyCssGenericComponentValue, AnyCssValue, CssGenericProperty}; | ||
use biome_rowan::{AstNode, TextRange}; | ||
|
||
use crate::utils::{find_font_family, is_font_family_keyword}; | ||
|
||
declare_rule! { | ||
/// Disallow duplicate names within font families. | ||
/// | ||
/// This rule checks the `font` and `font-family` properties for duplicate font names. | ||
/// | ||
/// This rule ignores var(--custom-property) variable syntaxes now. | ||
/// | ||
/// | ||
/// ## Examples | ||
/// | ||
/// ### Invalid | ||
/// | ||
/// ```css,expect_diagnostic | ||
/// a { font-family: "Lucida Grande", 'Arial', sans-serif, sans-serif; } | ||
/// ``` | ||
/// | ||
/// ```css,expect_diagnostic | ||
/// a { font-family: 'Arial', "Lucida Grande", Arial, sans-serif; } | ||
/// ``` | ||
/// | ||
/// ```css,expect_diagnostic | ||
/// a { FONT: italic 300 16px/30px Arial, " Arial", serif; } | ||
/// ``` | ||
/// | ||
/// ### Valid | ||
/// | ||
/// ```css | ||
/// a { font-family: "Lucida Grande", "Arial", sans-serif; } | ||
/// ``` | ||
/// | ||
/// ```css | ||
/// b { font: normal 14px/32px -apple-system, BlinkMacSystemFont, sans-serif; } | ||
/// ``` | ||
pub NoDuplicateFontNames { | ||
version: "next", | ||
name: "noDuplicateFontNames", | ||
recommended: true, | ||
sources: &[RuleSource::Stylelint("font-family-no-duplicate-names")], | ||
} | ||
} | ||
|
||
pub struct RuleState { | ||
value: String, | ||
span: TextRange, | ||
} | ||
|
||
impl Rule for NoDuplicateFontNames { | ||
type Query = Ast<CssGenericProperty>; | ||
type State = RuleState; | ||
type Signals = Option<Self::State>; | ||
type Options = (); | ||
|
||
fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { | ||
let node = ctx.query(); | ||
let property_name = node.name().ok()?.text().to_lowercase(); | ||
|
||
let is_font_family = property_name == "font-family"; | ||
let is_font = property_name == "font"; | ||
|
||
if !is_font_family && !is_font { | ||
return None; | ||
} | ||
|
||
let mut unquoted_family_names: HashSet<String> = HashSet::new(); | ||
let mut family_names: HashSet<String> = HashSet::new(); | ||
let value_list = node.value(); | ||
let font_families = if is_font { | ||
find_font_family(value_list) | ||
} else { | ||
value_list | ||
.into_iter() | ||
.filter_map(|v| match v { | ||
AnyCssGenericComponentValue::AnyCssValue(value) => Some(value), | ||
_ => None, | ||
}) | ||
.collect() | ||
}; | ||
|
||
for css_value in font_families { | ||
match css_value { | ||
// A generic family name like `sans-serif` or unquoted font name. | ||
AnyCssValue::CssIdentifier(val) => { | ||
let font_name = val.text(); | ||
|
||
// check the case: "Arial", Arial | ||
// we ignore the case of the font name is a keyword(context: https://github.com/stylelint/stylelint/issues/1284) | ||
// e.g "sans-serif", sans-serif | ||
if family_names.contains(&font_name) && !is_font_family_keyword(&font_name) { | ||
return Some(RuleState { | ||
value: font_name, | ||
span: val.range(), | ||
}); | ||
} | ||
|
||
// check the case: sans-self, sans-self | ||
if unquoted_family_names.contains(&font_name) { | ||
return Some(RuleState { | ||
value: font_name, | ||
span: val.range(), | ||
}); | ||
} | ||
unquoted_family_names.insert(font_name); | ||
} | ||
// A font family name. e.g "Lucida Grande", "Arial". | ||
AnyCssValue::CssString(val) => { | ||
let normalized_font_name: String = val | ||
.text() | ||
.chars() | ||
.filter(|&c| c != '\'' && c != '\"' && !c.is_whitespace()) | ||
.collect(); | ||
|
||
if family_names.contains(&normalized_font_name) | ||
|| unquoted_family_names.contains(&normalized_font_name) | ||
{ | ||
return Some(RuleState { | ||
value: normalized_font_name, | ||
span: val.range(), | ||
}); | ||
} | ||
family_names.insert(normalized_font_name); | ||
} | ||
_ => continue, | ||
} | ||
} | ||
None | ||
} | ||
|
||
fn diagnostic(_: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> { | ||
let span = state.span; | ||
Some( | ||
RuleDiagnostic::new( | ||
rule_category!(), | ||
span, | ||
markup! { | ||
"Unexpected duplicate font name: "<Emphasis>{ state.value }</Emphasis> | ||
}, | ||
) | ||
.note(markup! { | ||
"Remove duplicate font names within the property" | ||
}), | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.