diff --git a/crates/biome_configuration/src/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs index c1ba03b9630..fe41a19cea6 100644 --- a/crates/biome_configuration/src/linter/rules.rs +++ b/crates/biome_configuration/src/linter/rules.rs @@ -2712,6 +2712,9 @@ pub struct Nursery { #[doc = "Enforce the use of new for all builtins, except String, Number, Boolean, Symbol and BigInt."] #[serde(skip_serializing_if = "Option::is_none")] pub use_consistent_new_builtin: Option>, + #[doc = "Disallow a missing generic family keyword within font families."] + #[serde(skip_serializing_if = "Option::is_none")] + pub use_generic_font_names: Option>, #[doc = "Disallows package private imports."] #[serde(skip_serializing_if = "Option::is_none")] pub use_import_restrictions: Option>, @@ -2757,6 +2760,7 @@ impl Nursery { "noUselessUndefinedInitialization", "useArrayLiterals", "useConsistentNewBuiltin", + "useGenericFontNames", "useImportRestrictions", "useSortedClasses", ]; @@ -2771,6 +2775,7 @@ impl Nursery { "noFlatMapIdentity", "noImportantInKeyframe", "noUnknownUnit", + "useGenericFontNames", ]; const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]), @@ -2783,6 +2788,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -2808,6 +2814,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -2929,16 +2936,21 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } + if let Some(rule) = self.use_sorted_classes.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -3048,16 +3060,21 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } + if let Some(rule) = self.use_sorted_classes.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3178,6 +3195,10 @@ impl Nursery { .use_consistent_new_builtin .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "useGenericFontNames" => self + .use_generic_font_names + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "useImportRestrictions" => self .use_import_restrictions .as_ref() diff --git a/crates/biome_css_analyze/src/keywords.rs b/crates/biome_css_analyze/src/keywords.rs index 7485b368763..0161c5898e6 100644 --- a/crates/biome_css_analyze/src/keywords.rs +++ b/crates/biome_css_analyze/src/keywords.rs @@ -1,6 +1,7 @@ pub const BASIC_KEYWORDS: [&str; 5] = ["initial", "inherit", "revert", "revert-layer", "unset"]; -pub const _SYSTEM_FONT_KEYWORDS: [&str; 6] = [ +// https://drafts.csswg.org/css-fonts/#system-family-name-value +pub const SYSTEM_FAMILY_NAME_KEYWORDS: [&str; 6] = [ "caption", "icon", "menu", diff --git a/crates/biome_css_analyze/src/lib.rs b/crates/biome_css_analyze/src/lib.rs index 3f52f427a56..ffff6001f94 100644 --- a/crates/biome_css_analyze/src/lib.rs +++ b/crates/biome_css_analyze/src/lib.rs @@ -122,13 +122,12 @@ mod tests { String::from_utf8(buffer).unwrap() } - const SOURCE: &str = r#".something {} -"#; + const SOURCE: &str = r#"@font-face { font-family: Gentium; }"#; let parsed = parse_css(SOURCE, CssParserOptions::default()); let mut error_ranges: Vec = Vec::new(); - let rule_filter = RuleFilter::Rule("nursery", "noDuplicateKeys"); + let rule_filter = RuleFilter::Rule("nursery", "noMissingGenericFamilyKeyword"); let options = AnalyzerOptions::default(); analyze( &parsed.tree(), diff --git a/crates/biome_css_analyze/src/lint/nursery.rs b/crates/biome_css_analyze/src/lint/nursery.rs index 6daa2ac3394..3fb4c1408b7 100644 --- a/crates/biome_css_analyze/src/lint/nursery.rs +++ b/crates/biome_css_analyze/src/lint/nursery.rs @@ -8,6 +8,7 @@ pub mod no_duplicate_font_names; pub mod no_duplicate_selectors_keyframe_block; pub mod no_important_in_keyframe; pub mod no_unknown_unit; +pub mod use_generic_font_names; declare_group! { pub Nursery { @@ -19,6 +20,7 @@ declare_group! { self :: no_duplicate_selectors_keyframe_block :: NoDuplicateSelectorsKeyframeBlock , self :: no_important_in_keyframe :: NoImportantInKeyframe , self :: no_unknown_unit :: NoUnknownUnit , + self :: use_generic_font_names :: UseGenericFontNames , ] } } diff --git a/crates/biome_css_analyze/src/lint/nursery/use_generic_font_names.rs b/crates/biome_css_analyze/src/lint/nursery/use_generic_font_names.rs new file mode 100644 index 00000000000..769306d398f --- /dev/null +++ b/crates/biome_css_analyze/src/lint/nursery/use_generic_font_names.rs @@ -0,0 +1,170 @@ +use biome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic, RuleSource}; +use biome_console::markup; +use biome_css_syntax::{ + AnyCssAtRule, AnyCssGenericComponentValue, AnyCssValue, CssAtRule, + CssGenericComponentValueList, CssGenericProperty, CssSyntaxKind, +}; +use biome_rowan::{AstNode, SyntaxNodeCast, TextRange}; + +use crate::utils::{ + find_font_family, is_css_variable, is_font_family_keyword, is_system_family_name_keyword, +}; + +declare_rule! { + /// Disallow a missing generic family keyword within font families. + /// + /// The generic font family can be: + /// - placed anywhere in the font family list + /// - omitted if a keyword related to property inheritance or a system font is used + /// + /// This rule checks the font and font-family properties. + /// The following special situations are ignored: + /// - Property with a keyword value such as `inherit`, `initial`. + /// - The last value being a CSS variable. + /// - `font-family` property in an `@font-face` rule. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```css,expect_diagnostic + /// a { font-family: Arial; } + /// ``` + /// + /// ```css,expect_diagnostic + /// a { font: normal 14px/32px -apple-system, BlinkMacSystemFont; } + /// ``` + /// + /// ### Valid + /// + /// ```css + /// a { font-family: "Lucida Grande", "Arial", sans-serif; } + /// ``` + /// + /// ```css + /// a { font-family: inherit; } + /// ``` + /// + /// ```css + /// a { font-family: sans-serif; } + /// ``` + /// + /// ```css + /// a { font-family: var(--font); } + /// ``` + /// + /// ```css + /// @font-face { font-family: Gentium; } + /// ``` + /// + pub UseGenericFontNames { + version: "next", + name: "useGenericFontNames", + recommended: true, + sources: &[RuleSource::Stylelint("font-family-no-missing-generic-family-keyword")], + } +} + +impl Rule for UseGenericFontNames { + type Query = Ast; + type State = TextRange; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Option { + let node = ctx.query(); + let property_name = node.name().ok()?.text().to_lowercase(); + + // Ignore `@font-face`. See more detail: https://drafts.csswg.org/css-fonts/#font-face-rule + if is_in_font_face_at_rule(node) { + return None; + } + + let is_font_family = property_name == "font-family"; + let is_font = property_name == "font"; + + if !is_font_family && !is_font { + return None; + } + + // handle shorthand font property with special value + // e.g: { font: caption }, { font: inherit } + let properties = node.value(); + if is_font && is_shorthand_font_property_with_keyword(&properties) { + return None; + } + + let font_families = if is_font { + find_font_family(properties) + } else { + collect_font_family_properties(properties) + }; + + if font_families.is_empty() { + return None; + } + + if has_generic_font_family_property(&font_families) { + return None; + } + + // Ignore the last value if it's a CSS variable now. + let last_value = font_families.last()?; + if is_css_variable(&last_value.text()) { + return None; + } + + Some(last_value.range()) + } + + fn diagnostic(_: &RuleContext, span: &Self::State) -> Option { + Some( + RuleDiagnostic::new( + rule_category!(), + span, + markup! { + "Generic font family missing." + }, + ) + .note(markup! { + "Consider adding a generic font family as a fallback." + }) + .footer_list( + markup! { + "For examples and more information, see" " the MDN Web Docs" + }, + &["serif", "sans-serif", "monospace", "etc."], + ), + ) + } +} + +fn is_in_font_face_at_rule(node: &CssGenericProperty) -> bool { + node.syntax() + .ancestors() + .find(|n| n.kind() == CssSyntaxKind::CSS_AT_RULE) + .and_then(|n| n.cast::()) + .and_then(|n| n.rule().ok()) + .is_some_and(|n| matches!(n, AnyCssAtRule::CssFontFaceAtRule(_))) +} + +fn is_shorthand_font_property_with_keyword(properties: &CssGenericComponentValueList) -> bool { + properties.into_iter().len() == 1 + && properties + .into_iter() + .any(|p| is_system_family_name_keyword(&p.text())) +} + +fn has_generic_font_family_property(nodes: &[AnyCssValue]) -> bool { + nodes.iter().any(|n| is_font_family_keyword(&n.text())) +} + +fn collect_font_family_properties(properties: CssGenericComponentValueList) -> Vec { + properties + .into_iter() + .filter_map(|v| match v { + AnyCssGenericComponentValue::AnyCssValue(value) => Some(value), + _ => None, + }) + .collect() +} diff --git a/crates/biome_css_analyze/src/options.rs b/crates/biome_css_analyze/src/options.rs index 0be8ac2a7af..f6f52b17766 100644 --- a/crates/biome_css_analyze/src/options.rs +++ b/crates/biome_css_analyze/src/options.rs @@ -12,3 +12,5 @@ pub type NoDuplicateSelectorsKeyframeBlock = < lint :: nursery :: no_duplicate_s pub type NoImportantInKeyframe = < lint :: nursery :: no_important_in_keyframe :: NoImportantInKeyframe as biome_analyze :: Rule > :: Options ; pub type NoUnknownUnit = ::Options; +pub type UseGenericFontNames = + ::Options; diff --git a/crates/biome_css_analyze/src/utils.rs b/crates/biome_css_analyze/src/utils.rs index a26f4c08ff1..5e99792cea9 100644 --- a/crates/biome_css_analyze/src/utils.rs +++ b/crates/biome_css_analyze/src/utils.rs @@ -1,7 +1,7 @@ use crate::keywords::{ BASIC_KEYWORDS, FONT_FAMILY_KEYWORDS, FONT_SIZE_KEYWORDS, FONT_STRETCH_KEYWORDS, FONT_STYLE_KEYWORDS, FONT_VARIANTS_KEYWORDS, FONT_WEIGHT_ABSOLUTE_KEYWORDS, - FONT_WEIGHT_NUMERIC_KEYWORDS, LINE_HEIGHT_KEYWORDS, + FONT_WEIGHT_NUMERIC_KEYWORDS, LINE_HEIGHT_KEYWORDS, SYSTEM_FAMILY_NAME_KEYWORDS, }; use biome_css_syntax::{AnyCssGenericComponentValue, AnyCssValue, CssGenericComponentValueList}; use biome_rowan::{AstNode, SyntaxNodeCast}; @@ -10,6 +10,10 @@ pub fn is_font_family_keyword(value: &str) -> bool { BASIC_KEYWORDS.contains(&value) || FONT_FAMILY_KEYWORDS.contains(&value) } +pub fn is_system_family_name_keyword(value: &str) -> bool { + BASIC_KEYWORDS.contains(&value) || SYSTEM_FAMILY_NAME_KEYWORDS.contains(&value) +} + // check if the value is a shorthand keyword used in `font` property pub fn is_font_shorthand_keyword(value: &str) -> bool { BASIC_KEYWORDS.contains(&value) @@ -27,7 +31,7 @@ pub fn is_css_variable(value: &str) -> bool { value.to_lowercase().starts_with("var(") } -// Get the font-families within a `font` shorthand property value. +/// Get the font-families within a `font` shorthand property value. pub fn find_font_family(value: CssGenericComponentValueList) -> Vec { let mut font_families: Vec = Vec::new(); for v in value { diff --git a/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/invalid.css b/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/invalid.css new file mode 100644 index 00000000000..07c2c7683a2 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/invalid.css @@ -0,0 +1,7 @@ +a { font-family: Arial; } +a { font-family: 'Arial', "Lucida Grande", Arial; } +a { fOnT-fAmIlY: ' Lucida Grande '; } +a { font-family: Times; } +a { FONT: italic 300 16px/30px Arial, " Arial"; } +a { font: normal 14px/32px -apple-system, BlinkMacSystemFont; } +a { font: 1em Lucida Grande, Arial, "sans-serif" } \ No newline at end of file diff --git a/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/invalid.css.snap new file mode 100644 index 00000000000..1eab83cfb45 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/invalid.css.snap @@ -0,0 +1,177 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: invalid.css +--- +# Input +```css +a { font-family: Arial; } +a { font-family: 'Arial', "Lucida Grande", Arial; } +a { fOnT-fAmIlY: ' Lucida Grande '; } +a { font-family: Times; } +a { FONT: italic 300 16px/30px Arial, " Arial"; } +a { font: normal 14px/32px -apple-system, BlinkMacSystemFont; } +a { font: 1em Lucida Grande, Arial, "sans-serif" } +``` + +# Diagnostics +``` +invalid.css:1:18 lint/nursery/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Generic font family missing. + + > 1 │ a { font-family: Arial; } + │ ^^^^^ + 2 │ a { font-family: 'Arial', "Lucida Grande", Arial; } + 3 │ a { fOnT-fAmIlY: ' Lucida Grande '; } + + i Consider adding a generic font family as a fallback. + + i For examples and more information, see the MDN Web Docs + + - serif + - sans-serif + - monospace + - etc. + + +``` + +``` +invalid.css:2:44 lint/nursery/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Generic font family missing. + + 1 │ a { font-family: Arial; } + > 2 │ a { font-family: 'Arial', "Lucida Grande", Arial; } + │ ^^^^^ + 3 │ a { fOnT-fAmIlY: ' Lucida Grande '; } + 4 │ a { font-family: Times; } + + i Consider adding a generic font family as a fallback. + + i For examples and more information, see the MDN Web Docs + + - serif + - sans-serif + - monospace + - etc. + + +``` + +``` +invalid.css:3:18 lint/nursery/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Generic font family missing. + + 1 │ a { font-family: Arial; } + 2 │ a { font-family: 'Arial', "Lucida Grande", Arial; } + > 3 │ a { fOnT-fAmIlY: ' Lucida Grande '; } + │ ^^^^^^^^^^^^^^^^^^ + 4 │ a { font-family: Times; } + 5 │ a { FONT: italic 300 16px/30px Arial, " Arial"; } + + i Consider adding a generic font family as a fallback. + + i For examples and more information, see the MDN Web Docs + + - serif + - sans-serif + - monospace + - etc. + + +``` + +``` +invalid.css:4:18 lint/nursery/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Generic font family missing. + + 2 │ a { font-family: 'Arial', "Lucida Grande", Arial; } + 3 │ a { fOnT-fAmIlY: ' Lucida Grande '; } + > 4 │ a { font-family: Times; } + │ ^^^^^ + 5 │ a { FONT: italic 300 16px/30px Arial, " Arial"; } + 6 │ a { font: normal 14px/32px -apple-system, BlinkMacSystemFont; } + + i Consider adding a generic font family as a fallback. + + i For examples and more information, see the MDN Web Docs + + - serif + - sans-serif + - monospace + - etc. + + +``` + +``` +invalid.css:5:39 lint/nursery/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Generic font family missing. + + 3 │ a { fOnT-fAmIlY: ' Lucida Grande '; } + 4 │ a { font-family: Times; } + > 5 │ a { FONT: italic 300 16px/30px Arial, " Arial"; } + │ ^^^^^^^^ + 6 │ a { font: normal 14px/32px -apple-system, BlinkMacSystemFont; } + 7 │ a { font: 1em Lucida Grande, Arial, "sans-serif" } + + i Consider adding a generic font family as a fallback. + + i For examples and more information, see the MDN Web Docs + + - serif + - sans-serif + - monospace + - etc. + + +``` + +``` +invalid.css:6:43 lint/nursery/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Generic font family missing. + + 4 │ a { font-family: Times; } + 5 │ a { FONT: italic 300 16px/30px Arial, " Arial"; } + > 6 │ a { font: normal 14px/32px -apple-system, BlinkMacSystemFont; } + │ ^^^^^^^^^^^^^^^^^^ + 7 │ a { font: 1em Lucida Grande, Arial, "sans-serif" } + + i Consider adding a generic font family as a fallback. + + i For examples and more information, see the MDN Web Docs + + - serif + - sans-serif + - monospace + - etc. + + +``` + +``` +invalid.css:7:37 lint/nursery/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Generic font family missing. + + 5 │ a { FONT: italic 300 16px/30px Arial, " Arial"; } + 6 │ a { font: normal 14px/32px -apple-system, BlinkMacSystemFont; } + > 7 │ a { font: 1em Lucida Grande, Arial, "sans-serif" } + │ ^^^^^^^^^^^^ + + i Consider adding a generic font family as a fallback. + + i For examples and more information, see the MDN Web Docs + + - serif + - sans-serif + - monospace + - etc. + + +``` diff --git a/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/valid.css b/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/valid.css new file mode 100644 index 00000000000..a44ac4c6a1c --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/valid.css @@ -0,0 +1,25 @@ +a { font-family: "Lucida Grande", "Arial", sans-serif; } +a { font: 1em "Lucida Grande", 'Arial', sans-serif; } +a { font: 1em "Lucida Grande", 'Arial', "sans-serif", sans-serif; } +a { font-family: Times, serif; } +b { font-family: inherit; } +b { font-family: inherit; } +b { font-family: initial; } +b { font-family: unset; } +b { font-family: serif; } +b { font-family: sans-serif; } +b { font-family: Courier, monospace; } +b { font: 1em/1.5 "Whatever Fanciness", cursive; } +a { font-family: Helvetica Neue, sans-serif, Apple Color Emoji; } +@font-face { font-family: Gentium; } +@FONT-FACE { font-family: Gentium; } +a { font: inherit; } +a { font: caption; } +a { font: icon; } +a { font: menu; } +a { font: message-box; } +a { font: small-caption; } +a { font: status-bar; } +a { font-family: var(--font); } +a { font-family: revert } +a { font-family: revert-layer } \ No newline at end of file diff --git a/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/valid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/valid.css.snap new file mode 100644 index 00000000000..274cb35722f --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/useGenericFontNames/valid.css.snap @@ -0,0 +1,32 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: valid.css +--- +# Input +```css +a { font-family: "Lucida Grande", "Arial", sans-serif; } +a { font: 1em "Lucida Grande", 'Arial', sans-serif; } +a { font: 1em "Lucida Grande", 'Arial', "sans-serif", sans-serif; } +a { font-family: Times, serif; } +b { font-family: inherit; } +b { font-family: inherit; } +b { font-family: initial; } +b { font-family: unset; } +b { font-family: serif; } +b { font-family: sans-serif; } +b { font-family: Courier, monospace; } +b { font: 1em/1.5 "Whatever Fanciness", cursive; } +a { font-family: Helvetica Neue, sans-serif, Apple Color Emoji; } +@font-face { font-family: Gentium; } +@FONT-FACE { font-family: Gentium; } +a { font: inherit; } +a { font: caption; } +a { font: icon; } +a { font: menu; } +a { font: message-box; } +a { font: small-caption; } +a { font: status-bar; } +a { font-family: var(--font); } +a { font-family: revert } +a { font-family: revert-layer } +``` diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index e2e39b13509..b4b17102d65 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -124,6 +124,7 @@ define_categories! { "lint/nursery/noFlatMapIdentity": "https://biomejs.dev/linter/rules/no-flat-map-identity", "lint/nursery/noImportantInKeyframe": "https://biomejs.dev/linter/rules/no-important-in-keyframe", "lint/nursery/noMisplacedAssertion": "https://biomejs.dev/linter/rules/no-misplaced-assertion", + "lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword", "lint/nursery/noNodejsModules": "https://biomejs.dev/linter/rules/no-nodejs-modules", "lint/nursery/noReactSpecificProps": "https://biomejs.dev/linter/rules/no-react-specific-props", "lint/nursery/noRestrictedImports": "https://biomejs.dev/linter/rules/no-restricted-imports", @@ -133,6 +134,7 @@ define_categories! { "lint/nursery/noUnknownUnit": "https://biomejs.dev/linter/rules/no-unknown-unit", "lint/nursery/useBiomeSuppressionComment": "https://biomejs.dev/linter/rules/use-biome-suppression-comment", "lint/nursery/useConsistentNewBuiltin": "https://biomejs.dev/linter/rules/use-consistent-new-builtin", + "lint/nursery/useGenericFontNames": "https://biomejs.dev/linter/rules/use-generic-font-names", "lint/nursery/useImportRestrictions": "https://biomejs.dev/linter/rules/use-import-restrictions", "lint/nursery/useSortedClasses": "https://biomejs.dev/linter/rules/use-sorted-classes", "lint/performance/noAccumulatingSpread": "https://biomejs.dev/linter/rules/no-accumulating-spread", diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 997b8b4c642..bacb9882a2f 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -996,6 +996,10 @@ export interface Nursery { * Enforce the use of new for all builtins, except String, Number, Boolean, Symbol and BigInt. */ useConsistentNewBuiltin?: RuleConfiguration_for_Null; + /** + * Disallow a missing generic family keyword within font families. + */ + useGenericFontNames?: RuleConfiguration_for_Null; /** * Disallows package private imports. */ @@ -1983,6 +1987,7 @@ export type Category = | "lint/nursery/noFlatMapIdentity" | "lint/nursery/noImportantInKeyframe" | "lint/nursery/noMisplacedAssertion" + | "lint/nursery/noMissingGenericFamilyKeyword" | "lint/nursery/noNodejsModules" | "lint/nursery/noReactSpecificProps" | "lint/nursery/noRestrictedImports" @@ -1992,6 +1997,7 @@ export type Category = | "lint/nursery/noUnknownUnit" | "lint/nursery/useBiomeSuppressionComment" | "lint/nursery/useConsistentNewBuiltin" + | "lint/nursery/useGenericFontNames" | "lint/nursery/useImportRestrictions" | "lint/nursery/useSortedClasses" | "lint/performance/noAccumulatingSpread" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 0919bafaa10..7b69c0e28f4 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1584,6 +1584,13 @@ { "type": "null" } ] }, + "useGenericFontNames": { + "description": "Disallow a missing generic family keyword within font families.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "useImportRestrictions": { "description": "Disallows package private imports.", "anyOf": [