From 53e104f0a276dc8dece10c2519578d8ef18f5863 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Fri, 29 Dec 2023 04:19:41 +0100 Subject: [PATCH 01/82] wip --- .../src/categories.rs | 1 + .../src/semantic_analyzers/nursery.rs | 2 + .../nursery/use_sorted_classes.rs | 1083 +++++++++++++++++ .../specs/nursery/useSortedClasses/invalid.js | 1 + .../specs/nursery/useSortedClasses/valid.js | 1 + .../src/configuration/linter/rules.rs | 31 +- .../src/configuration/parse/json/rules.rs | 8 + .../@biomejs/backend-jsonrpc/src/workspace.ts | 5 + .../@biomejs/biome/configuration_schema.json | 7 + tmptest.js | 0 .../components/generated/NumberOfRules.astro | 2 +- .../src/content/docs/linter/rules/index.mdx | 1 + .../docs/linter/rules/use-sorted-classes.md | 50 + 13 files changed, 1185 insertions(+), 7 deletions(-) create mode 100644 crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.js create mode 100644 tmptest.js create mode 100644 website/src/content/docs/linter/rules/use-sorted-classes.md diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 866fd4c326c1..425b914afb20 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -123,6 +123,7 @@ define_categories! { "lint/nursery/useNumberProperties": "https://biomejs.dev/linter/rules/use-number-properties", "lint/nursery/useRegexLiterals": "https://biomejs.dev/linter/rules/use-regex-literals", "lint/nursery/useShorthandFunctionType": "https://biomejs.dev/lint/rules/use-shorthand-function-type", + "lint/nursery/useSortedClasses": "https://biomejs.dev/linter/rules/use-sorted-classes", "lint/nursery/useValidAriaRole": "https://biomejs.dev/lint/rules/use-valid-aria-role", "lint/performance/noAccumulatingSpread": "https://biomejs.dev/linter/rules/no-accumulating-spread", "lint/performance/noDelete": "https://biomejs.dev/linter/rules/no-delete", diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs index 168ef23a649b..55849e710d39 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs @@ -9,6 +9,7 @@ pub(crate) mod no_unused_imports; pub(crate) mod use_export_type; pub(crate) mod use_for_of; pub(crate) mod use_number_properties; +pub(crate) mod use_sorted_classes; declare_group! { pub (crate) Nursery { @@ -21,6 +22,7 @@ declare_group! { self :: use_export_type :: UseExportType , self :: use_for_of :: UseForOf , self :: use_number_properties :: UseNumberProperties , + self :: use_sorted_classes :: UseSortedClasses , ] } } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs new file mode 100644 index 000000000000..9fd701dc80e8 --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -0,0 +1,1083 @@ +use std::collections::HashMap; + +use biome_analyze::{ + context::RuleContext, declare_rule, ActionCategory, AddVisitor, AnalyzerOptions, FixKind, + Phases, QueryMatch, Queryable, Rule, RuleDiagnostic, RuleKey, ServiceBag, Visitor, + VisitorContext, +}; +use biome_console::markup; +use biome_deserialize::{ + Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor, Text, + VisitableType, +}; +use biome_diagnostics::Applicability; +use biome_js_factory::make::{js_string_literal, js_string_literal_expression, jsx_string}; +use biome_js_syntax::{ + JsCallArguments, JsCallExpression, JsLanguage, JsStringLiteralExpression, + JsTemplateChunkElement, JsxAttribute, JsxString, +}; +use biome_rowan::{AstNode, BatchMutationExt, Language, SyntaxNode, TextRange, WalkEvent}; + +use crate::JsRuleAction; + +// TODO: variants +// TODO: extensibility +// TODO: preset generation script +// TODO: automatic config sync +// TODO: remove duplicated classes + +// rule metadata +// ------------- + +declare_rule! { + /// Enforce the sorting of CSS classes. + /// + /// TODO: description + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```jsx,expect_diagnostic + ///
; + /// ``` + /// + /// ## Valid + /// + /// ```js + /// // TODO: examples + /// ``` + /// + pub(crate) UseSortedClasses { + version: "next", + name: "useSortedClasses", + recommended: false, + fix_kind: FixKind::Safe, + } +} + +// utils +// ----- + +fn get_callee_name(call_expression: &JsCallExpression) -> Option { + Some( + call_expression + .callee() + .ok()? + .as_js_identifier_expression()? + .name() + .ok()? + .name() + .ok()? + .to_string(), + ) +} + +fn get_attribute_name(attribute: &JsxAttribute) -> Option { + Some(attribute.name().ok()?.as_jsx_name()?.to_string()) +} + +fn is_call_expression_of_valid_function( + call_expression: &JsCallExpression, + functions: &Vec, +) -> bool { + match get_callee_name(&call_expression) { + Some(name) => functions.contains(&name.to_string()), + None => false, + } +} + +const CLASS_ATTRIBUTE_NAMES: [&str; 2] = ["class", "className"]; + +fn is_valid_attribute(attribute: &JsxAttribute, attributes: &Vec) -> bool { + match get_attribute_name(&attribute) { + Some(name) => { + attributes.contains(&name.to_string()) || CLASS_ATTRIBUTE_NAMES.contains(&name.as_str()) + } + None => false, + } +} + +fn get_rule_options(analyzer_options: &AnalyzerOptions) -> UseSortedClassesOptions { + match analyzer_options + .configuration + .rules + .get_rule_options::(&RuleKey::new("nursery", "useSortedClasses")) + { + Some(options) => options.clone(), + None => UseSortedClassesOptions::default(), + } +} + +// attributes visitor +// ------------------ + +#[derive(Default)] +struct StringLiteralInAttributeVisitor { + in_valid_attribute: bool, +} + +impl Visitor for StringLiteralInAttributeVisitor { + type Language = JsLanguage; + + fn visit( + &mut self, + event: &WalkEvent>, + mut ctx: VisitorContext, + ) { + let options = get_rule_options(ctx.options); + match event { + WalkEvent::Enter(node) => { + // When entering an attribute node, track if we are in a valid attribute. + if let Some(attribute) = JsxAttribute::cast_ref(node) { + self.in_valid_attribute = is_valid_attribute(&attribute, &options.attributes); + } + + // When entering a JSX string node, and we are in a valid attribute, emit. + if let Some(jsx_string) = JsxString::cast_ref(node) { + if self.in_valid_attribute { + ctx.match_query(ClassStringLike::JsxString(jsx_string)); + } + } + + // When entering a string literal node, and we are in a valid attribute, emit. + if let Some(string_literal) = JsStringLiteralExpression::cast_ref(node) { + if self.in_valid_attribute { + ctx.match_query(ClassStringLike::StringLiteral(string_literal)); + } + } + } + WalkEvent::Leave(node) => { + // When leaving an attribute node, reset in_valid_attribute. + if JsxAttribute::cast_ref(node).is_some() { + self.in_valid_attribute = false; + } + } + } + } +} + +// functions (call expression) visitor +// ----------------------------------- + +#[derive(Default)] +struct StringLiteralInCallExpressionVisitor { + in_valid_function: bool, + in_arguments: bool, +} + +impl Visitor for StringLiteralInCallExpressionVisitor { + type Language = JsLanguage; + + fn visit( + &mut self, + event: &WalkEvent>, + mut ctx: VisitorContext, + ) { + let options = get_rule_options(ctx.options); + if options.functions.is_empty() { + return; + } + match event { + WalkEvent::Enter(node) => { + // When entering a call expression node, track if we are in a valid function and reset + // in_arguments. + if let Some(call_expression) = JsCallExpression::cast_ref(&node) { + self.in_valid_function = + is_call_expression_of_valid_function(&call_expression, &options.functions); + self.in_arguments = false; + } + + // When entering a call arguments node, set in_arguments. + if JsCallArguments::cast_ref(&node).is_some() { + self.in_arguments = true; + } + + // When entering a string literal node, and we are in a valid function and in arguments, emit. + if let Some(string_literal) = JsStringLiteralExpression::cast_ref(&node) { + if self.in_valid_function && self.in_arguments { + ctx.match_query(ClassStringLike::StringLiteral(string_literal)); + } + } + } + WalkEvent::Leave(node) => { + // When leaving a call arguments node, reset in_arguments. + if JsCallArguments::cast_ref(&node).is_some() { + self.in_arguments = false; + } + } + } + } +} + +// functions (template chunk) visitor +// ---------------------------------- + +// TODO: template chunk visitor + +// custom query +// ------------ + +#[derive(Clone)] +pub(crate) enum ClassStringLike { + StringLiteral(JsStringLiteralExpression), + JsxString(JsxString), + TemplateChunk(JsTemplateChunkElement), +} + +impl ClassStringLike { + fn range(&self) -> TextRange { + match self { + ClassStringLike::StringLiteral(string_literal) => string_literal.range(), + ClassStringLike::JsxString(jsx_string) => jsx_string.range(), + ClassStringLike::TemplateChunk(template_chunk) => template_chunk.range(), + } + } + + fn value(&self) -> Option { + match self { + ClassStringLike::StringLiteral(string_literal) => { + Some(string_literal.inner_string_text().ok()?.to_string()) + } + ClassStringLike::JsxString(jsx_string) => { + Some(jsx_string.inner_string_text().ok()?.to_string()) + } + ClassStringLike::TemplateChunk(template_chunk) => Some(template_chunk.to_string()), + } + } +} + +impl QueryMatch for ClassStringLike { + fn text_range(&self) -> TextRange { + self.range() + } +} + +impl Queryable for ClassStringLike { + type Input = Self; + type Language = JsLanguage; + type Output = ClassStringLike; + type Services = (); + + fn build_visitor( + analyzer: &mut impl AddVisitor, + _: &::Root, + ) { + analyzer.add_visitor(Phases::Syntax, || { + StringLiteralInAttributeVisitor::default() + }); + analyzer.add_visitor(Phases::Syntax, || { + StringLiteralInCallExpressionVisitor::default() + }); + } + + fn unwrap_match(_: &ServiceBag, query: &Self::Input) -> Self::Output { + query.clone() + } +} + +// class sorting +// ------------- + +fn get_class_order_match(spec: &String, class_name: &str) -> Option { + if spec.ends_with("$") && class_name == &spec[..spec.len() - 1] { + return Some(true); + } + if class_name.starts_with(spec) && class_name != spec.as_str() { + return Some(false); + } + None +} + +fn find_class_order_index(class_order: &Vec, class_name: &str) -> Option { + let mut matched = false; + let mut match_index: usize = 0; + let mut last_size: usize = 0; + for (i, spec) in class_order.iter().enumerate() { + match get_class_order_match(spec, class_name) { + Some(true) => return Some(i), + Some(false) => { + let spec_size = spec.chars().count(); + if spec_size > last_size { + match_index = i; + last_size = spec_size; + matched = true; + } + } + _ => {} + } + } + if matched { + Some(match_index) + } else { + None + } +} + +// TODO: detect arbitrary css (e.g. [background:red]), put at the end +fn sort_class_name(class_name: &str, class_order: &Vec) -> String { + let classes = class_name.split_whitespace().collect::>(); + let mut unordered_classes: Vec<&str> = Vec::new(); + let mut class_order_map: HashMap> = HashMap::new(); + for class in classes { + match find_class_order_index(class_order, class) { + Some(index) => { + class_order_map + .entry(index) + .or_insert_with(Vec::new) + .push(class); + } + None => { + unordered_classes.push(class); + } + } + } + let mut sorted_classes: Vec<&str> = unordered_classes; + for i in 0..class_order.len() { + if let Some(classes) = class_order_map.get(&i) { + let mut abc_classes = classes.clone(); + abc_classes.sort_unstable(); + sorted_classes.extend(abc_classes); + } + } + return sorted_classes.join(" "); +} + +// rule +// ---- + +impl Rule for UseSortedClasses { + type Query = ClassStringLike; + type State = String; + type Signals = Option; + type Options = UseSortedClassesOptions; + + fn run(ctx: &RuleContext) -> Option { + let value = ctx.query().value()?; + let options = &ctx.options(); + let class_order = get_class_order(&options.preset, &options.class_order); + let sorted_value = sort_class_name(value.as_str(), &class_order); + if value != sorted_value { + Some(sorted_value) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + Some( + RuleDiagnostic::new(rule_category!(), ctx.query().range(), "TODO: title").note( + markup! { + "TODO: description." + }, + ), + ) + } + + fn action(ctx: &RuleContext, state: &Self::State) -> Option { + let mut mutation = ctx.root().begin(); + match ctx.query() { + ClassStringLike::StringLiteral(string_literal) => { + let replacement = js_string_literal_expression(js_string_literal(&state)); + mutation.replace_node(string_literal.clone(), replacement); + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message: markup! { + "TODO: message." + } + .to_owned(), + mutation, + }) + } + ClassStringLike::JsxString(jsx_string_node) => { + let replacement = jsx_string(js_string_literal(&state)); + mutation.replace_node(jsx_string_node.clone(), replacement); + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message: markup! { + "TODO: message." + } + .to_owned(), + mutation, + }) + } + _ => None, + } + } +} + +// options +// ------- + +#[derive(Debug, Default, Clone)] +pub struct UseSortedClassesOptions { + pub preset: ClassOrderPreset, + pub attributes: Vec, + pub functions: Vec, + pub class_order: Vec, +} + +impl Deserializable for UseSortedClassesOptions { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + value.deserialize(UseSortedClassesOptionsVisitor, name, diagnostics) + } +} + +struct UseSortedClassesOptionsVisitor; +impl DeserializationVisitor for UseSortedClassesOptionsVisitor { + type Output = UseSortedClassesOptions; + + const EXPECTED_TYPE: VisitableType = VisitableType::MAP; + + fn visit_map( + self, + members: impl Iterator>, + _range: TextRange, + _name: &str, + diagnostics: &mut Vec, + ) -> Option { + let mut result = Self::Output::default(); + for (key, value) in members.flatten() { + let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { + continue; + }; + match key_text.text() { + "preset" => { + // TODO: actually retrieve the value + result.preset = ClassOrderPreset::Tailwind; + } + "attributes" => { + if let Some(attributes) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + result.attributes = attributes; + } + } + "functions" => { + if let Some(functions) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + result.functions = functions; + } + } + "class_order" => { + if let Some(class_order) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + result.class_order = class_order; + } + } + unknown_key => { + const ALLOWED_KEYS: &[&str] = &["behaviorExceptions"]; + diagnostics.push(DeserializationDiagnostic::new_unknown_key( + unknown_key, + key.range(), + ALLOWED_KEYS, + )) + } + } + } + Some(result) + } +} + +// presets +// ------- + +#[derive(Debug, Default, Clone)] +pub enum ClassOrderPreset { + NoPreset, + #[default] + Tailwind, +} + +fn get_class_order(preset: &ClassOrderPreset, class_order: &Vec) -> Vec { + if !class_order.is_empty() { + return class_order.clone(); + } + match preset { + ClassOrderPreset::NoPreset => { + panic!("TODO: no preset error message") + } + ClassOrderPreset::Tailwind => { + vec![ + // TAILWIND-PRESET-START + String::from("container$"), + String::from("sr-only$"), + String::from("not-sr-only$"), + String::from("pointer-events-none$"), + String::from("pointer-events-auto$"), + String::from("visible$"), + String::from("invisible$"), + String::from("collapse$"), + String::from("static$"), + String::from("fixed$"), + String::from("absolute$"), + String::from("relative$"), + String::from("sticky$"), + String::from("inset-"), + String::from("inset-x-"), + String::from("inset-y-"), + String::from("start-"), + String::from("end-"), + String::from("top-"), + String::from("right-"), + String::from("bottom-"), + String::from("left-"), + String::from("isolate$"), + String::from("isolation-auto$"), + String::from("z-"), + String::from("order-"), + String::from("col-"), + String::from("col-start-"), + String::from("col-end-"), + String::from("row-"), + String::from("row-start-"), + String::from("row-end-"), + String::from("float-start$"), + String::from("float-end$"), + String::from("float-right$"), + String::from("float-left$"), + String::from("float-none$"), + String::from("clear-start$"), + String::from("clear-end$"), + String::from("clear-left$"), + String::from("clear-right$"), + String::from("clear-both$"), + String::from("clear-none$"), + String::from("m-"), + String::from("mx-"), + String::from("my-"), + String::from("ms-"), + String::from("me-"), + String::from("mt-"), + String::from("mr-"), + String::from("mb-"), + String::from("ml-"), + String::from("box-border$"), + String::from("box-content$"), + String::from("line-clamp-"), + String::from("line-clamp-none$"), + String::from("block$"), + String::from("inline-block$"), + String::from("inline$"), + String::from("flex$"), + String::from("inline-flex$"), + String::from("table$"), + String::from("inline-table$"), + String::from("table-caption$"), + String::from("table-cell$"), + String::from("table-column$"), + String::from("table-column-group$"), + String::from("table-footer-group$"), + String::from("table-header-group$"), + String::from("table-row-group$"), + String::from("table-row$"), + String::from("flow-root$"), + String::from("grid$"), + String::from("inline-grid$"), + String::from("contents$"), + String::from("list-item$"), + String::from("hidden$"), + String::from("aspect-"), + String::from("size-"), + String::from("h-"), + String::from("max-h-"), + String::from("min-h-"), + String::from("w-"), + String::from("min-w-"), + String::from("max-w-"), + String::from("flex-shrink$"), + String::from("flex-shrink-"), + String::from("shrink$"), + String::from("shrink-"), + String::from("flex-grow$"), + String::from("flex-grow-"), + String::from("grow$"), + String::from("grow-"), + String::from("basis-"), + String::from("table-auto$"), + String::from("table-fixed$"), + String::from("caption-top$"), + String::from("caption-bottom$"), + String::from("border-collapse$"), + String::from("border-separate$"), + String::from("border-spacing-"), + String::from("border-spacing-x-"), + String::from("border-spacing-y-"), + String::from("origin-"), + String::from("translate-x-"), + String::from("translate-y-"), + String::from("rotate-"), + String::from("skew-x-"), + String::from("skew-y-"), + String::from("scale-"), + String::from("scale-x-"), + String::from("scale-y-"), + String::from("transform$"), + String::from("transform-cpu$"), + String::from("transform-gpu$"), + String::from("transform-none$"), + String::from("animate-"), + String::from("cursor-"), + String::from("touch-auto$"), + String::from("touch-none$"), + String::from("touch-pan-x$"), + String::from("touch-pan-left$"), + String::from("touch-pan-right$"), + String::from("touch-pan-y$"), + String::from("touch-pan-up$"), + String::from("touch-pan-down$"), + String::from("touch-pinch-zoom$"), + String::from("touch-manipulation$"), + String::from("select-none$"), + String::from("select-text$"), + String::from("select-all$"), + String::from("select-auto$"), + String::from("resize-none$"), + String::from("resize-y$"), + String::from("resize-x$"), + String::from("resize$"), + String::from("snap-none$"), + String::from("snap-x$"), + String::from("snap-y$"), + String::from("snap-both$"), + String::from("snap-mandatory$"), + String::from("snap-proximity$"), + String::from("snap-start$"), + String::from("snap-end$"), + String::from("snap-center$"), + String::from("snap-align-none$"), + String::from("snap-normal$"), + String::from("snap-always$"), + String::from("scroll-m-"), + String::from("scroll-mx-"), + String::from("scroll-my-"), + String::from("scroll-ms-"), + String::from("scroll-me-"), + String::from("scroll-mt-"), + String::from("scroll-mr-"), + String::from("scroll-mb-"), + String::from("scroll-ml-"), + String::from("scroll-p-"), + String::from("scroll-px-"), + String::from("scroll-py-"), + String::from("scroll-ps-"), + String::from("scroll-pe-"), + String::from("scroll-pt-"), + String::from("scroll-pr-"), + String::from("scroll-pb-"), + String::from("scroll-pl-"), + String::from("list-inside$"), + String::from("list-outside$"), + String::from("list-"), + String::from("list-image-"), + String::from("appearance-none$"), + String::from("appearance-auto$"), + String::from("columns-"), + String::from("break-before-auto$"), + String::from("break-before-avoid$"), + String::from("break-before-all$"), + String::from("break-before-avoid-page$"), + String::from("break-before-page$"), + String::from("break-before-left$"), + String::from("break-before-right$"), + String::from("break-before-column$"), + String::from("break-inside-auto$"), + String::from("break-inside-avoid$"), + String::from("break-inside-avoid-page$"), + String::from("break-inside-avoid-column$"), + String::from("break-after-auto$"), + String::from("break-after-avoid$"), + String::from("break-after-all$"), + String::from("break-after-avoid-page$"), + String::from("break-after-page$"), + String::from("break-after-left$"), + String::from("break-after-right$"), + String::from("break-after-column$"), + String::from("auto-cols-"), + String::from("grid-flow-row$"), + String::from("grid-flow-col$"), + String::from("grid-flow-dense$"), + String::from("grid-flow-row-dense$"), + String::from("grid-flow-col-dense$"), + String::from("auto-rows-"), + String::from("grid-cols-"), + String::from("grid-rows-"), + String::from("flex-row$"), + String::from("flex-row-reverse$"), + String::from("flex-col$"), + String::from("flex-col-reverse$"), + String::from("flex-wrap$"), + String::from("flex-wrap-reverse$"), + String::from("flex-nowrap$"), + String::from("place-content-center$"), + String::from("place-content-start$"), + String::from("place-content-end$"), + String::from("place-content-between$"), + String::from("place-content-around$"), + String::from("place-content-evenly$"), + String::from("place-content-baseline$"), + String::from("place-content-stretch$"), + String::from("place-items-start$"), + String::from("place-items-end$"), + String::from("place-items-center$"), + String::from("place-items-baseline$"), + String::from("place-items-stretch$"), + String::from("content-normal$"), + String::from("content-center$"), + String::from("content-start$"), + String::from("content-end$"), + String::from("content-between$"), + String::from("content-around$"), + String::from("content-evenly$"), + String::from("content-baseline$"), + String::from("content-stretch$"), + String::from("items-start$"), + String::from("items-end$"), + String::from("items-center$"), + String::from("items-baseline$"), + String::from("items-stretch$"), + String::from("justify-normal$"), + String::from("justify-start$"), + String::from("justify-end$"), + String::from("justify-center$"), + String::from("justify-between$"), + String::from("justify-around$"), + String::from("justify-evenly$"), + String::from("justify-stretch$"), + String::from("justify-items-start$"), + String::from("justify-items-end$"), + String::from("justify-items-center$"), + String::from("justify-items-stretch$"), + String::from("gap-"), + String::from("gap-x-"), + String::from("gap-y-"), + String::from("space-x-"), + String::from("space-y-"), + String::from("space-y-reverse$"), + String::from("space-x-reverse$"), + String::from("divide-x$"), + String::from("divide-x-"), + String::from("divide-y$"), + String::from("divide-y-"), + String::from("divide-y-reverse$"), + String::from("divide-x-reverse$"), + String::from("divide-solid$"), + String::from("divide-dashed$"), + String::from("divide-dotted$"), + String::from("divide-double$"), + String::from("divide-none$"), + String::from("divide-"), + String::from("divide-opacity-"), + String::from("place-self-auto$"), + String::from("place-self-start$"), + String::from("place-self-end$"), + String::from("place-self-center$"), + String::from("place-self-stretch$"), + String::from("self-auto$"), + String::from("self-start$"), + String::from("self-end$"), + String::from("self-center$"), + String::from("self-stretch$"), + String::from("self-baseline$"), + String::from("justify-self-auto$"), + String::from("justify-self-start$"), + String::from("justify-self-end$"), + String::from("justify-self-center$"), + String::from("justify-self-stretch$"), + String::from("overflow-auto$"), + String::from("overflow-hidden$"), + String::from("overflow-clip$"), + String::from("overflow-visible$"), + String::from("overflow-scroll$"), + String::from("overflow-x-auto$"), + String::from("overflow-y-auto$"), + String::from("overflow-x-hidden$"), + String::from("overflow-y-hidden$"), + String::from("overflow-x-clip$"), + String::from("overflow-y-clip$"), + String::from("overflow-x-visible$"), + String::from("overflow-y-visible$"), + String::from("overflow-x-scroll$"), + String::from("overflow-y-scroll$"), + String::from("overscroll-auto$"), + String::from("overscroll-contain$"), + String::from("overscroll-none$"), + String::from("overscroll-y-auto$"), + String::from("overscroll-y-contain$"), + String::from("overscroll-y-none$"), + String::from("overscroll-x-auto$"), + String::from("overscroll-x-contain$"), + String::from("overscroll-x-none$"), + String::from("scroll-auto$"), + String::from("scroll-smooth$"), + String::from("truncate$"), + String::from("overflow-ellipsis$"), + String::from("text-ellipsis$"), + String::from("text-clip$"), + String::from("hyphens-none$"), + String::from("hyphens-manual$"), + String::from("hyphens-auto$"), + String::from("whitespace-normal$"), + String::from("whitespace-nowrap$"), + String::from("whitespace-pre$"), + String::from("whitespace-pre-line$"), + String::from("whitespace-pre-wrap$"), + String::from("whitespace-break-spaces$"), + String::from("text-wrap$"), + String::from("text-nowrap$"), + String::from("text-balance$"), + String::from("text-pretty$"), + String::from("break-normal$"), + String::from("break-words$"), + String::from("break-all$"), + String::from("break-keep$"), + String::from("rounded$"), + String::from("rounded-"), + String::from("rounded-s$"), + String::from("rounded-s-"), + String::from("rounded-e$"), + String::from("rounded-e-"), + String::from("rounded-t$"), + String::from("rounded-t-"), + String::from("rounded-r$"), + String::from("rounded-r-"), + String::from("rounded-b$"), + String::from("rounded-b-"), + String::from("rounded-l$"), + String::from("rounded-l-"), + String::from("rounded-ss$"), + String::from("rounded-ss-"), + String::from("rounded-se$"), + String::from("rounded-se-"), + String::from("rounded-ee$"), + String::from("rounded-ee-"), + String::from("rounded-es$"), + String::from("rounded-es-"), + String::from("rounded-tl$"), + String::from("rounded-tl-"), + String::from("rounded-tr$"), + String::from("rounded-tr-"), + String::from("rounded-br$"), + String::from("rounded-br-"), + String::from("rounded-bl$"), + String::from("rounded-bl-"), + String::from("border$"), + String::from("border-"), + String::from("border-x$"), + String::from("border-x-"), + String::from("border-y$"), + String::from("border-y-"), + String::from("border-s$"), + String::from("border-s-"), + String::from("border-e$"), + String::from("border-e-"), + String::from("border-t$"), + String::from("border-t-"), + String::from("border-r$"), + String::from("border-r-"), + String::from("border-b$"), + String::from("border-b-"), + String::from("border-l$"), + String::from("border-l-"), + String::from("border-solid$"), + String::from("border-dashed$"), + String::from("border-dotted$"), + String::from("border-double$"), + String::from("border-hidden$"), + String::from("border-none$"), + String::from("border-opacity-"), + String::from("bg-"), + String::from("bg-opacity-"), + String::from("from-"), + String::from("via-"), + String::from("to-"), + String::from("decoration-slice$"), + String::from("decoration-clone$"), + String::from("box-decoration-slice$"), + String::from("box-decoration-clone$"), + String::from("bg-fixed$"), + String::from("bg-local$"), + String::from("bg-scroll$"), + String::from("bg-clip-border$"), + String::from("bg-clip-padding$"), + String::from("bg-clip-content$"), + String::from("bg-clip-text$"), + String::from("bg-repeat$"), + String::from("bg-no-repeat$"), + String::from("bg-repeat-x$"), + String::from("bg-repeat-y$"), + String::from("bg-repeat-round$"), + String::from("bg-repeat-space$"), + String::from("bg-origin-border$"), + String::from("bg-origin-padding$"), + String::from("bg-origin-content$"), + String::from("fill-"), + String::from("stroke-"), + String::from("object-contain$"), + String::from("object-cover$"), + String::from("object-fill$"), + String::from("object-none$"), + String::from("object-scale-down$"), + String::from("object-"), + String::from("p-"), + String::from("px-"), + String::from("py-"), + String::from("ps-"), + String::from("pe-"), + String::from("pt-"), + String::from("pr-"), + String::from("pb-"), + String::from("pl-"), + String::from("text-left$"), + String::from("text-center$"), + String::from("text-right$"), + String::from("text-justify$"), + String::from("text-start$"), + String::from("text-end$"), + String::from("indent-"), + String::from("align-baseline$"), + String::from("align-top$"), + String::from("align-middle$"), + String::from("align-bottom$"), + String::from("align-text-top$"), + String::from("align-text-bottom$"), + String::from("align-sub$"), + String::from("align-super$"), + String::from("align-"), + String::from("font-"), + String::from("text-"), + String::from("uppercase$"), + String::from("lowercase$"), + String::from("capitalize$"), + String::from("normal-case$"), + String::from("italic$"), + String::from("not-italic$"), + String::from("normal-nums$"), + String::from("ordinal$"), + String::from("slashed-zero$"), + String::from("lining-nums$"), + String::from("oldstyle-nums$"), + String::from("proportional-nums$"), + String::from("tabular-nums$"), + String::from("diagonal-fractions$"), + String::from("stacked-fractions$"), + String::from("leading-"), + String::from("tracking-"), + String::from("text-opacity-"), + String::from("underline$"), + String::from("overline$"), + String::from("line-through$"), + String::from("no-underline$"), + String::from("decoration-"), + String::from("decoration-solid$"), + String::from("decoration-double$"), + String::from("decoration-dotted$"), + String::from("decoration-dashed$"), + String::from("decoration-wavy$"), + String::from("underline-offset-"), + String::from("antialiased$"), + String::from("subpixel-antialiased$"), + String::from("placeholder-"), + String::from("placeholder-opacity-"), + String::from("caret-"), + String::from("accent-"), + String::from("opacity-"), + String::from("bg-blend-normal$"), + String::from("bg-blend-multiply$"), + String::from("bg-blend-screen$"), + String::from("bg-blend-overlay$"), + String::from("bg-blend-darken$"), + String::from("bg-blend-lighten$"), + String::from("bg-blend-color-dodge$"), + String::from("bg-blend-color-burn$"), + String::from("bg-blend-hard-light$"), + String::from("bg-blend-soft-light$"), + String::from("bg-blend-difference$"), + String::from("bg-blend-exclusion$"), + String::from("bg-blend-hue$"), + String::from("bg-blend-saturation$"), + String::from("bg-blend-color$"), + String::from("bg-blend-luminosity$"), + String::from("mix-blend-normal$"), + String::from("mix-blend-multiply$"), + String::from("mix-blend-screen$"), + String::from("mix-blend-overlay$"), + String::from("mix-blend-darken$"), + String::from("mix-blend-lighten$"), + String::from("mix-blend-color-dodge$"), + String::from("mix-blend-color-burn$"), + String::from("mix-blend-hard-light$"), + String::from("mix-blend-soft-light$"), + String::from("mix-blend-difference$"), + String::from("mix-blend-exclusion$"), + String::from("mix-blend-hue$"), + String::from("mix-blend-saturation$"), + String::from("mix-blend-color$"), + String::from("mix-blend-luminosity$"), + String::from("mix-blend-plus-lighter$"), + String::from("shadow$"), + String::from("shadow-"), + String::from("outline-none$"), + String::from("outline$"), + String::from("outline-dashed$"), + String::from("outline-dotted$"), + String::from("outline-double$"), + String::from("outline-offset-"), + String::from("ring$"), + String::from("ring-"), + String::from("ring-inset$"), + String::from("ring-opacity-"), + String::from("ring-offset-"), + String::from("blur$"), + String::from("blur-"), + String::from("brightness-"), + String::from("contrast-"), + String::from("drop-shadow$"), + String::from("drop-shadow-"), + String::from("grayscale$"), + String::from("grayscale-"), + String::from("hue-rotate-"), + String::from("invert$"), + String::from("invert-"), + String::from("saturate-"), + String::from("sepia$"), + String::from("sepia-"), + String::from("filter$"), + String::from("filter-none$"), + String::from("backdrop-blur$"), + String::from("backdrop-blur-"), + String::from("backdrop-brightness-"), + String::from("backdrop-contrast-"), + String::from("backdrop-grayscale$"), + String::from("backdrop-grayscale-"), + String::from("backdrop-hue-rotate-"), + String::from("backdrop-invert$"), + String::from("backdrop-invert-"), + String::from("backdrop-opacity-"), + String::from("backdrop-saturate-"), + String::from("backdrop-sepia$"), + String::from("backdrop-sepia-"), + String::from("backdrop-filter$"), + String::from("backdrop-filter-none$"), + String::from("transition$"), + String::from("transition-"), + String::from("delay-"), + String::from("duration-"), + String::from("ease-"), + String::from("will-change-"), + String::from("content-"), + String::from("forced-color-adjust-auto$"), + String::from("forced-color-adjust-none$"), + // TAILWIND-PRESET-END + ] + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.js new file mode 100644 index 000000000000..5e9c281c65aa --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.js @@ -0,0 +1 @@ +// TODO: invalid test diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.js b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.js new file mode 100644 index 000000000000..85130e78ecfd --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.js @@ -0,0 +1 @@ +// TODO: valid test diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index 756c7318a08c..3cb2513bbcac 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2558,6 +2558,9 @@ pub struct Nursery { #[doc = "Enforce using function types instead of object type with call signatures."] #[serde(skip_serializing_if = "Option::is_none")] pub use_shorthand_function_type: Option, + #[doc = "Enforce the sorting of CSS classes."] + #[serde(skip_serializing_if = "Option::is_none")] + pub use_sorted_classes: Option, #[doc = "Elements with ARIA roles must use a valid, non-abstract ARIA role."] #[serde(skip_serializing_if = "Option::is_none")] pub use_valid_aria_role: Option, @@ -2630,6 +2633,9 @@ impl MergeWith for Nursery { if let Some(use_shorthand_function_type) = other.use_shorthand_function_type { self.use_shorthand_function_type = Some(use_shorthand_function_type); } + if let Some(use_sorted_classes) = other.use_sorted_classes { + self.use_sorted_classes = Some(use_sorted_classes); + } if let Some(use_valid_aria_role) = other.use_valid_aria_role { self.use_valid_aria_role = Some(use_valid_aria_role); } @@ -2645,7 +2651,7 @@ impl MergeWith for Nursery { } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 23] = [ + pub(crate) const GROUP_RULES: [&'static str; 24] = [ "noAriaHiddenOnFocusable", "noDefaultExport", "noDuplicateJsonKeys", @@ -2668,6 +2674,7 @@ impl Nursery { "useNumberProperties", "useRegexLiterals", "useShorthandFunctionType", + "useSortedClasses", "useValidAriaRole", ]; const RECOMMENDED_RULES: [&'static str; 9] = [ @@ -2690,9 +2697,9 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 23] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 24] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -2716,6 +2723,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(&self) -> bool { @@ -2842,11 +2850,16 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.use_valid_aria_role.as_ref() { + 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[22])); } } + if let Some(rule) = self.use_valid_aria_role.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 { @@ -2961,11 +2974,16 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.use_valid_aria_role.as_ref() { + 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[22])); } } + if let Some(rule) = self.use_valid_aria_role.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"] @@ -2979,7 +2997,7 @@ impl Nursery { pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 9] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 23] { + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 24] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] @@ -3024,6 +3042,7 @@ impl Nursery { "useNumberProperties" => self.use_number_properties.as_ref(), "useRegexLiterals" => self.use_regex_literals.as_ref(), "useShorthandFunctionType" => self.use_shorthand_function_type.as_ref(), + "useSortedClasses" => self.use_sorted_classes.as_ref(), "useValidAriaRole" => self.use_valid_aria_role.as_ref(), _ => None, } diff --git a/crates/biome_service/src/configuration/parse/json/rules.rs b/crates/biome_service/src/configuration/parse/json/rules.rs index e6df7f054a0e..c546dc64c142 100644 --- a/crates/biome_service/src/configuration/parse/json/rules.rs +++ b/crates/biome_service/src/configuration/parse/json/rules.rs @@ -1041,6 +1041,13 @@ impl Deserializable for Nursery { diagnostics, ); } + "useSortedClasses" => { + result.use_sorted_classes = Deserializable::deserialize( + &value, + "useSortedClasses", + diagnostics, + ); + } "useValidAriaRole" => { result.use_valid_aria_role = Deserializable::deserialize( &value, @@ -1077,6 +1084,7 @@ impl Deserializable for Nursery { "useNumberProperties", "useRegexLiterals", "useShorthandFunctionType", + "useSortedClasses", "useValidAriaRole", ], )); diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 3cfd535f63d6..fc85c64210fd 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -931,6 +931,10 @@ export interface Nursery { * Enforce using function types instead of object type with call signatures. */ useShorthandFunctionType?: RuleConfiguration; + /** + * Enforce the sorting of CSS classes. + */ + useSortedClasses?: RuleConfiguration; /** * Elements with ARIA roles must use a valid, non-abstract ARIA role. */ @@ -1643,6 +1647,7 @@ export type Category = | "lint/nursery/useNumberProperties" | "lint/nursery/useRegexLiterals" | "lint/nursery/useShorthandFunctionType" + | "lint/nursery/useSortedClasses" | "lint/nursery/useValidAriaRole" | "lint/performance/noAccumulatingSpread" | "lint/performance/noDelete" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 0aca63c8827e..430666a0627b 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1384,6 +1384,13 @@ { "type": "null" } ] }, + "useSortedClasses": { + "description": "Enforce the sorting of CSS classes.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "useValidAriaRole": { "description": "Elements with ARIA roles must use a valid, non-abstract ARIA role.", "anyOf": [ diff --git a/tmptest.js b/tmptest.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/website/src/components/generated/NumberOfRules.astro b/website/src/components/generated/NumberOfRules.astro index 2d2d08a0b093..08347f094bc1 100644 --- a/website/src/components/generated/NumberOfRules.astro +++ b/website/src/components/generated/NumberOfRules.astro @@ -1,2 +1,2 @@ -

Biome's linter has a total of 188 rules

\ No newline at end of file +

Biome's linter has a total of 189 rules

\ No newline at end of file diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index 07efaf64f1c9..030361a1eda8 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -250,4 +250,5 @@ do not have access to those modules. | | | [useNumberProperties](/linter/rules/use-number-properties) | Use Number properties instead of global ones. | ⚠️ | | [useRegexLiterals](/linter/rules/use-regex-literals) | Enforce the use of the regular expression literals instead of the RegExp constructor if possible. | ⚠️ | | [useShorthandFunctionType](/linter/rules/use-shorthand-function-type) | Enforce using function types instead of object type with call signatures. | 🔧 | +| [useSortedClasses](/linter/rules/use-sorted-classes) | Enforce the sorting of CSS classes. | 🔧 | | [useValidAriaRole](/linter/rules/use-valid-aria-role) | Elements with ARIA roles must use a valid, non-abstract ARIA role. | ⚠️ | diff --git a/website/src/content/docs/linter/rules/use-sorted-classes.md b/website/src/content/docs/linter/rules/use-sorted-classes.md new file mode 100644 index 000000000000..18cf706fb636 --- /dev/null +++ b/website/src/content/docs/linter/rules/use-sorted-classes.md @@ -0,0 +1,50 @@ +--- +title: useSortedClasses (since vnext) +--- + +**Diagnostic Category: `lint/nursery/useSortedClasses`** + +:::caution +This rule is part of the [nursery](/linter/rules/#nursery) group. +::: + +Enforce the sorting of CSS classes. + +TODO: description + +## Examples + +### Invalid + +```jsx +

; +``` + +
nursery/useSortedClasses.js:1:12 lint/nursery/useSortedClasses  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   TODO: title
+  
+  > 1 │ <div class="px-2 foo px-4 bar" />;
+              ^^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   TODO: description.
+  
+   Safe fix: TODO: message.
+  
+    1  - <div·class="px-2·foo·px-4·bar"·/>;
+      1+ <div·class="foo·bar·px-2·px-4"·/>;
+    2 2  
+  
+
+ +## Valid + +```jsx +// TODO: examples +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options) From 0494556b5a43eae3588d926559cf54d7b3ebe359 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Fri, 29 Dec 2023 04:19:50 +0100 Subject: [PATCH 02/82] fixes --- .../nursery/use_sorted_classes.rs | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index 9fd701dc80e8..b05f6582353f 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -79,9 +79,9 @@ fn get_attribute_name(attribute: &JsxAttribute) -> Option { fn is_call_expression_of_valid_function( call_expression: &JsCallExpression, - functions: &Vec, + functions: &[String], ) -> bool { - match get_callee_name(&call_expression) { + match get_callee_name(call_expression) { Some(name) => functions.contains(&name.to_string()), None => false, } @@ -89,8 +89,8 @@ fn is_call_expression_of_valid_function( const CLASS_ATTRIBUTE_NAMES: [&str; 2] = ["class", "className"]; -fn is_valid_attribute(attribute: &JsxAttribute, attributes: &Vec) -> bool { - match get_attribute_name(&attribute) { +fn is_valid_attribute(attribute: &JsxAttribute, attributes: &[String]) -> bool { + match get_attribute_name(attribute) { Some(name) => { attributes.contains(&name.to_string()) || CLASS_ATTRIBUTE_NAMES.contains(&name.as_str()) } @@ -182,19 +182,19 @@ impl Visitor for StringLiteralInCallExpressionVisitor { WalkEvent::Enter(node) => { // When entering a call expression node, track if we are in a valid function and reset // in_arguments. - if let Some(call_expression) = JsCallExpression::cast_ref(&node) { + if let Some(call_expression) = JsCallExpression::cast_ref(node) { self.in_valid_function = is_call_expression_of_valid_function(&call_expression, &options.functions); self.in_arguments = false; } // When entering a call arguments node, set in_arguments. - if JsCallArguments::cast_ref(&node).is_some() { + if JsCallArguments::cast_ref(node).is_some() { self.in_arguments = true; } // When entering a string literal node, and we are in a valid function and in arguments, emit. - if let Some(string_literal) = JsStringLiteralExpression::cast_ref(&node) { + if let Some(string_literal) = JsStringLiteralExpression::cast_ref(node) { if self.in_valid_function && self.in_arguments { ctx.match_query(ClassStringLike::StringLiteral(string_literal)); } @@ -202,7 +202,7 @@ impl Visitor for StringLiteralInCallExpressionVisitor { } WalkEvent::Leave(node) => { // When leaving a call arguments node, reset in_arguments. - if JsCallArguments::cast_ref(&node).is_some() { + if JsCallArguments::cast_ref(node).is_some() { self.in_arguments = false; } } @@ -280,7 +280,7 @@ impl Queryable for ClassStringLike { // ------------- fn get_class_order_match(spec: &String, class_name: &str) -> Option { - if spec.ends_with("$") && class_name == &spec[..spec.len() - 1] { + if spec.ends_with('$') && class_name == &spec[..spec.len() - 1] { return Some(true); } if class_name.starts_with(spec) && class_name != spec.as_str() { @@ -289,7 +289,7 @@ fn get_class_order_match(spec: &String, class_name: &str) -> Option { None } -fn find_class_order_index(class_order: &Vec, class_name: &str) -> Option { +fn find_class_order_index(class_order: &[String], class_name: &str) -> Option { let mut matched = false; let mut match_index: usize = 0; let mut last_size: usize = 0; @@ -322,10 +322,7 @@ fn sort_class_name(class_name: &str, class_order: &Vec) -> String { for class in classes { match find_class_order_index(class_order, class) { Some(index) => { - class_order_map - .entry(index) - .or_insert_with(Vec::new) - .push(class); + class_order_map.entry(index).or_default().push(class); } None => { unordered_classes.push(class); @@ -340,7 +337,7 @@ fn sort_class_name(class_name: &str, class_order: &Vec) -> String { sorted_classes.extend(abc_classes); } } - return sorted_classes.join(" "); + sorted_classes.join(" ") } // rule @@ -378,7 +375,7 @@ impl Rule for UseSortedClasses { let mut mutation = ctx.root().begin(); match ctx.query() { ClassStringLike::StringLiteral(string_literal) => { - let replacement = js_string_literal_expression(js_string_literal(&state)); + let replacement = js_string_literal_expression(js_string_literal(state)); mutation.replace_node(string_literal.clone(), replacement); Some(JsRuleAction { category: ActionCategory::QuickFix, @@ -391,7 +388,7 @@ impl Rule for UseSortedClasses { }) } ClassStringLike::JsxString(jsx_string_node) => { - let replacement = jsx_string(js_string_literal(&state)); + let replacement = jsx_string(js_string_literal(state)); mutation.replace_node(jsx_string_node.clone(), replacement); Some(JsRuleAction { category: ActionCategory::QuickFix, From da3c54c936a6bf2059bccab0364ddb615dfb2183 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Sun, 31 Dec 2023 01:53:58 +0100 Subject: [PATCH 03/82] add layers and pre-process options --- .../nursery/use_sorted_classes.rs | 1379 +++++++++-------- 1 file changed, 758 insertions(+), 621 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index b05f6582353f..80dd14697443 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::vec; use biome_analyze::{ context::RuleContext, declare_rule, ActionCategory, AddVisitor, AnalyzerOptions, FixKind, @@ -17,6 +17,7 @@ use biome_js_syntax::{ JsTemplateChunkElement, JsxAttribute, JsxString, }; use biome_rowan::{AstNode, BatchMutationExt, Language, SyntaxNode, TextRange, WalkEvent}; +use rustc_hash::FxHashMap; use crate::JsRuleAction; @@ -26,6 +27,8 @@ use crate::JsRuleAction; // TODO: automatic config sync // TODO: remove duplicated classes +const CLASS_ATTRIBUTES: [&str; 2] = ["class", "className"]; + // rule metadata // ------------- @@ -87,18 +90,14 @@ fn is_call_expression_of_valid_function( } } -const CLASS_ATTRIBUTE_NAMES: [&str; 2] = ["class", "className"]; - fn is_valid_attribute(attribute: &JsxAttribute, attributes: &[String]) -> bool { match get_attribute_name(attribute) { - Some(name) => { - attributes.contains(&name.to_string()) || CLASS_ATTRIBUTE_NAMES.contains(&name.as_str()) - } + Some(name) => attributes.contains(&name.to_string()), None => false, } } -fn get_rule_options(analyzer_options: &AnalyzerOptions) -> UseSortedClassesOptions { +fn get_options_from_analyzer(analyzer_options: &AnalyzerOptions) -> UseSortedClassesOptions { match analyzer_options .configuration .rules @@ -125,7 +124,7 @@ impl Visitor for StringLiteralInAttributeVisitor { event: &WalkEvent>, mut ctx: VisitorContext, ) { - let options = get_rule_options(ctx.options); + let options = get_options_from_analyzer(ctx.options); match event { WalkEvent::Enter(node) => { // When entering an attribute node, track if we are in a valid attribute. @@ -174,7 +173,7 @@ impl Visitor for StringLiteralInCallExpressionVisitor { event: &WalkEvent>, mut ctx: VisitorContext, ) { - let options = get_rule_options(ctx.options); + let options = get_options_from_analyzer(ctx.options); if options.functions.is_empty() { return; } @@ -279,7 +278,7 @@ impl Queryable for ClassStringLike { // class sorting // ------------- -fn get_class_order_match(spec: &String, class_name: &str) -> Option { +fn get_utilities_match(spec: &String, class_name: &str) -> Option { if spec.ends_with('$') && class_name == &spec[..spec.len() - 1] { return Some(true); } @@ -289,12 +288,12 @@ fn get_class_order_match(spec: &String, class_name: &str) -> Option { None } -fn find_class_order_index(class_order: &[String], class_name: &str) -> Option { +fn find_utilities_index(utilities: &[String], class_name: &str) -> Option { let mut matched = false; let mut match_index: usize = 0; let mut last_size: usize = 0; - for (i, spec) in class_order.iter().enumerate() { - match get_class_order_match(spec, class_name) { + for (i, spec) in utilities.iter().enumerate() { + match get_utilities_match(spec, class_name) { Some(true) => return Some(i), Some(false) => { let spec_size = spec.chars().count(); @@ -315,14 +314,14 @@ fn find_class_order_index(class_order: &[String], class_name: &str) -> Option) -> String { +fn sort_class_name(class_name: &str, utilities: &Vec) -> String { let classes = class_name.split_whitespace().collect::>(); let mut unordered_classes: Vec<&str> = Vec::new(); - let mut class_order_map: HashMap> = HashMap::new(); + let mut utilities_map: FxHashMap> = FxHashMap::default(); for class in classes { - match find_class_order_index(class_order, class) { + match find_utilities_index(utilities, class) { Some(index) => { - class_order_map.entry(index).or_default().push(class); + utilities_map.entry(index).or_default().push(class); } None => { unordered_classes.push(class); @@ -330,8 +329,8 @@ fn sort_class_name(class_name: &str, class_order: &Vec) -> String { } } let mut sorted_classes: Vec<&str> = unordered_classes; - for i in 0..class_order.len() { - if let Some(classes) = class_order_map.get(&i) { + for i in 0..utilities.len() { + if let Some(classes) = utilities_map.get(&i) { let mut abc_classes = classes.clone(); abc_classes.sort_unstable(); sorted_classes.extend(abc_classes); @@ -352,8 +351,7 @@ impl Rule for UseSortedClasses { fn run(ctx: &RuleContext) -> Option { let value = ctx.query().value()?; let options = &ctx.options(); - let class_order = get_class_order(&options.preset, &options.class_order); - let sorted_value = sort_class_name(value.as_str(), &class_order); + let sorted_value = sort_class_name(value.as_str(), &options.utilities); if value != sorted_value { Some(sorted_value) } else { @@ -408,14 +406,112 @@ impl Rule for UseSortedClasses { // options // ------- -#[derive(Debug, Default, Clone)] +struct UtilityLayer { + layer: String, + classes: Vec, +} + +impl Deserializable for UtilityLayer { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + value.deserialize(UtilityLayerVisitor, name, diagnostics) + } +} + +struct UtilityLayerVisitor; +impl DeserializationVisitor for UtilityLayerVisitor { + type Output = UtilityLayer; + + const EXPECTED_TYPE: VisitableType = VisitableType::MAP; + + fn visit_map( + self, + members: impl Iterator>, + _range: TextRange, + _name: &str, + diagnostics: &mut Vec, + ) -> Option { + let mut layer: Option = None; + let mut classes: Option> = None; + const ALLOWED_OPTIONS: &[&str] = &["layer", "classes"]; + + for (key, value) in members.flatten() { + let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { + continue; + }; + match key_text.text() { + "layer" => { + if let Some(layer_option) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + layer = Some(layer_option); + } + } + "classes" => { + if let Some(classes_option) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + classes = Some(classes_option); + } + } + unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key( + unknown_key, + key.range(), + ALLOWED_OPTIONS, + )), + } + } + + let missing_layer = layer.is_none(); + let missing_classes = classes.is_none(); + + if missing_layer || missing_classes { + let mut missing_keys: Vec<&str> = Vec::new(); + if missing_layer { + missing_keys.push("layer"); + } + if missing_classes { + missing_keys.push("classes"); + } + let missing_keys = missing_keys.join(", "); + // TODO: how to actually handle this? + diagnostics.push(DeserializationDiagnostic::new(format!( + "Missing {}.", + missing_keys + ))); + + None + } else { + Some(UtilityLayer { + layer: layer.expect("TODO: error message (this should never happen)"), + classes: classes.expect("TODO: error message (this should never happen)"), + }) + } + } +} + +#[derive(Debug, Clone)] pub struct UseSortedClassesOptions { - pub preset: ClassOrderPreset, pub attributes: Vec, pub functions: Vec, - pub class_order: Vec, + pub utilities: Vec, +} + +impl Default for UseSortedClassesOptions { + fn default() -> Self { + UseSortedClassesOptions { + attributes: CLASS_ATTRIBUTES.iter().map(|&s| s.to_string()).collect(), + functions: Vec::new(), + utilities: Vec::new(), + } + } } +const ALLOWED_OPTIONS: &[&str] = &["attributes", "functions", "preset", "utilities"]; + impl Deserializable for UseSortedClassesOptions { fn deserialize( value: &impl DeserializableValue, @@ -439,21 +535,21 @@ impl DeserializationVisitor for UseSortedClassesOptionsVisitor { _name: &str, diagnostics: &mut Vec, ) -> Option { - let mut result = Self::Output::default(); + let mut result = UseSortedClassesOptions::default(); + let mut preset: UseSortedClassesPreset = UseSortedClassesPreset::TailwindCSS; + let mut utilities_option: Option> = None; + for (key, value) in members.flatten() { let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { continue; }; match key_text.text() { - "preset" => { - // TODO: actually retrieve the value - result.preset = ClassOrderPreset::Tailwind; - } "attributes" => { - if let Some(attributes) = + if let Some(attributes_option) = Deserializable::deserialize(&value, &key_text, diagnostics) { - result.attributes = attributes; + let attributes_option: Vec = attributes_option; // TODO: is there a better way to do this? + result.attributes.extend(attributes_option); } } "functions" => { @@ -463,23 +559,56 @@ impl DeserializationVisitor for UseSortedClassesOptionsVisitor { result.functions = functions; } } - "class_order" => { - if let Some(class_order) = + "preset" => { + if let Some(preset_option) = Deserializable::deserialize(&value, &key_text, diagnostics) { - result.class_order = class_order; + let preset_option: String = preset_option; // TODO: is there a better way to do this? + let preset_option = preset_option.as_str(); + match preset_option { + "tailwind-css" => { + preset = UseSortedClassesPreset::TailwindCSS; + } + "no-preset" => { + preset = UseSortedClassesPreset::None; + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_unknown_value( + preset_option, + value.range(), + ALLOWED_PRESETS, + )); + } + } } } - unknown_key => { - const ALLOWED_KEYS: &[&str] = &["behaviorExceptions"]; - diagnostics.push(DeserializationDiagnostic::new_unknown_key( - unknown_key, - key.range(), - ALLOWED_KEYS, - )) + "utilities" => { + if let Some(utilities_opt) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + utilities_option = Some(utilities_opt); + } } + unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key( + unknown_key, + key.range(), + ALLOWED_OPTIONS, + )), } } + + let resolved_utilities = match utilities_option { + Some(utilities) => utilities, + None => get_utilities_preset(&preset), + }; + result.utilities = resolved_utilities + .iter() + .flat_map(|layer| { + // TODO: extend layer here + layer.classes.clone() + }) + .collect(); + Some(result) } } @@ -488,593 +617,601 @@ impl DeserializationVisitor for UseSortedClassesOptionsVisitor { // ------- #[derive(Debug, Default, Clone)] -pub enum ClassOrderPreset { - NoPreset, +pub enum UseSortedClassesPreset { + None, #[default] - Tailwind, + TailwindCSS, } -fn get_class_order(preset: &ClassOrderPreset, class_order: &Vec) -> Vec { - if !class_order.is_empty() { - return class_order.clone(); - } +const ALLOWED_PRESETS: &[&str] = &["no-preset", "tailwind-css"]; + +// TODO: move to separate file? +fn get_utilities_preset(preset: &UseSortedClassesPreset) -> Vec { match preset { - ClassOrderPreset::NoPreset => { - panic!("TODO: no preset error message") + UseSortedClassesPreset::None => { + vec![] } - ClassOrderPreset::Tailwind => { + UseSortedClassesPreset::TailwindCSS => { + // TAILWIND-PRESET-START vec![ - // TAILWIND-PRESET-START - String::from("container$"), - String::from("sr-only$"), - String::from("not-sr-only$"), - String::from("pointer-events-none$"), - String::from("pointer-events-auto$"), - String::from("visible$"), - String::from("invisible$"), - String::from("collapse$"), - String::from("static$"), - String::from("fixed$"), - String::from("absolute$"), - String::from("relative$"), - String::from("sticky$"), - String::from("inset-"), - String::from("inset-x-"), - String::from("inset-y-"), - String::from("start-"), - String::from("end-"), - String::from("top-"), - String::from("right-"), - String::from("bottom-"), - String::from("left-"), - String::from("isolate$"), - String::from("isolation-auto$"), - String::from("z-"), - String::from("order-"), - String::from("col-"), - String::from("col-start-"), - String::from("col-end-"), - String::from("row-"), - String::from("row-start-"), - String::from("row-end-"), - String::from("float-start$"), - String::from("float-end$"), - String::from("float-right$"), - String::from("float-left$"), - String::from("float-none$"), - String::from("clear-start$"), - String::from("clear-end$"), - String::from("clear-left$"), - String::from("clear-right$"), - String::from("clear-both$"), - String::from("clear-none$"), - String::from("m-"), - String::from("mx-"), - String::from("my-"), - String::from("ms-"), - String::from("me-"), - String::from("mt-"), - String::from("mr-"), - String::from("mb-"), - String::from("ml-"), - String::from("box-border$"), - String::from("box-content$"), - String::from("line-clamp-"), - String::from("line-clamp-none$"), - String::from("block$"), - String::from("inline-block$"), - String::from("inline$"), - String::from("flex$"), - String::from("inline-flex$"), - String::from("table$"), - String::from("inline-table$"), - String::from("table-caption$"), - String::from("table-cell$"), - String::from("table-column$"), - String::from("table-column-group$"), - String::from("table-footer-group$"), - String::from("table-header-group$"), - String::from("table-row-group$"), - String::from("table-row$"), - String::from("flow-root$"), - String::from("grid$"), - String::from("inline-grid$"), - String::from("contents$"), - String::from("list-item$"), - String::from("hidden$"), - String::from("aspect-"), - String::from("size-"), - String::from("h-"), - String::from("max-h-"), - String::from("min-h-"), - String::from("w-"), - String::from("min-w-"), - String::from("max-w-"), - String::from("flex-shrink$"), - String::from("flex-shrink-"), - String::from("shrink$"), - String::from("shrink-"), - String::from("flex-grow$"), - String::from("flex-grow-"), - String::from("grow$"), - String::from("grow-"), - String::from("basis-"), - String::from("table-auto$"), - String::from("table-fixed$"), - String::from("caption-top$"), - String::from("caption-bottom$"), - String::from("border-collapse$"), - String::from("border-separate$"), - String::from("border-spacing-"), - String::from("border-spacing-x-"), - String::from("border-spacing-y-"), - String::from("origin-"), - String::from("translate-x-"), - String::from("translate-y-"), - String::from("rotate-"), - String::from("skew-x-"), - String::from("skew-y-"), - String::from("scale-"), - String::from("scale-x-"), - String::from("scale-y-"), - String::from("transform$"), - String::from("transform-cpu$"), - String::from("transform-gpu$"), - String::from("transform-none$"), - String::from("animate-"), - String::from("cursor-"), - String::from("touch-auto$"), - String::from("touch-none$"), - String::from("touch-pan-x$"), - String::from("touch-pan-left$"), - String::from("touch-pan-right$"), - String::from("touch-pan-y$"), - String::from("touch-pan-up$"), - String::from("touch-pan-down$"), - String::from("touch-pinch-zoom$"), - String::from("touch-manipulation$"), - String::from("select-none$"), - String::from("select-text$"), - String::from("select-all$"), - String::from("select-auto$"), - String::from("resize-none$"), - String::from("resize-y$"), - String::from("resize-x$"), - String::from("resize$"), - String::from("snap-none$"), - String::from("snap-x$"), - String::from("snap-y$"), - String::from("snap-both$"), - String::from("snap-mandatory$"), - String::from("snap-proximity$"), - String::from("snap-start$"), - String::from("snap-end$"), - String::from("snap-center$"), - String::from("snap-align-none$"), - String::from("snap-normal$"), - String::from("snap-always$"), - String::from("scroll-m-"), - String::from("scroll-mx-"), - String::from("scroll-my-"), - String::from("scroll-ms-"), - String::from("scroll-me-"), - String::from("scroll-mt-"), - String::from("scroll-mr-"), - String::from("scroll-mb-"), - String::from("scroll-ml-"), - String::from("scroll-p-"), - String::from("scroll-px-"), - String::from("scroll-py-"), - String::from("scroll-ps-"), - String::from("scroll-pe-"), - String::from("scroll-pt-"), - String::from("scroll-pr-"), - String::from("scroll-pb-"), - String::from("scroll-pl-"), - String::from("list-inside$"), - String::from("list-outside$"), - String::from("list-"), - String::from("list-image-"), - String::from("appearance-none$"), - String::from("appearance-auto$"), - String::from("columns-"), - String::from("break-before-auto$"), - String::from("break-before-avoid$"), - String::from("break-before-all$"), - String::from("break-before-avoid-page$"), - String::from("break-before-page$"), - String::from("break-before-left$"), - String::from("break-before-right$"), - String::from("break-before-column$"), - String::from("break-inside-auto$"), - String::from("break-inside-avoid$"), - String::from("break-inside-avoid-page$"), - String::from("break-inside-avoid-column$"), - String::from("break-after-auto$"), - String::from("break-after-avoid$"), - String::from("break-after-all$"), - String::from("break-after-avoid-page$"), - String::from("break-after-page$"), - String::from("break-after-left$"), - String::from("break-after-right$"), - String::from("break-after-column$"), - String::from("auto-cols-"), - String::from("grid-flow-row$"), - String::from("grid-flow-col$"), - String::from("grid-flow-dense$"), - String::from("grid-flow-row-dense$"), - String::from("grid-flow-col-dense$"), - String::from("auto-rows-"), - String::from("grid-cols-"), - String::from("grid-rows-"), - String::from("flex-row$"), - String::from("flex-row-reverse$"), - String::from("flex-col$"), - String::from("flex-col-reverse$"), - String::from("flex-wrap$"), - String::from("flex-wrap-reverse$"), - String::from("flex-nowrap$"), - String::from("place-content-center$"), - String::from("place-content-start$"), - String::from("place-content-end$"), - String::from("place-content-between$"), - String::from("place-content-around$"), - String::from("place-content-evenly$"), - String::from("place-content-baseline$"), - String::from("place-content-stretch$"), - String::from("place-items-start$"), - String::from("place-items-end$"), - String::from("place-items-center$"), - String::from("place-items-baseline$"), - String::from("place-items-stretch$"), - String::from("content-normal$"), - String::from("content-center$"), - String::from("content-start$"), - String::from("content-end$"), - String::from("content-between$"), - String::from("content-around$"), - String::from("content-evenly$"), - String::from("content-baseline$"), - String::from("content-stretch$"), - String::from("items-start$"), - String::from("items-end$"), - String::from("items-center$"), - String::from("items-baseline$"), - String::from("items-stretch$"), - String::from("justify-normal$"), - String::from("justify-start$"), - String::from("justify-end$"), - String::from("justify-center$"), - String::from("justify-between$"), - String::from("justify-around$"), - String::from("justify-evenly$"), - String::from("justify-stretch$"), - String::from("justify-items-start$"), - String::from("justify-items-end$"), - String::from("justify-items-center$"), - String::from("justify-items-stretch$"), - String::from("gap-"), - String::from("gap-x-"), - String::from("gap-y-"), - String::from("space-x-"), - String::from("space-y-"), - String::from("space-y-reverse$"), - String::from("space-x-reverse$"), - String::from("divide-x$"), - String::from("divide-x-"), - String::from("divide-y$"), - String::from("divide-y-"), - String::from("divide-y-reverse$"), - String::from("divide-x-reverse$"), - String::from("divide-solid$"), - String::from("divide-dashed$"), - String::from("divide-dotted$"), - String::from("divide-double$"), - String::from("divide-none$"), - String::from("divide-"), - String::from("divide-opacity-"), - String::from("place-self-auto$"), - String::from("place-self-start$"), - String::from("place-self-end$"), - String::from("place-self-center$"), - String::from("place-self-stretch$"), - String::from("self-auto$"), - String::from("self-start$"), - String::from("self-end$"), - String::from("self-center$"), - String::from("self-stretch$"), - String::from("self-baseline$"), - String::from("justify-self-auto$"), - String::from("justify-self-start$"), - String::from("justify-self-end$"), - String::from("justify-self-center$"), - String::from("justify-self-stretch$"), - String::from("overflow-auto$"), - String::from("overflow-hidden$"), - String::from("overflow-clip$"), - String::from("overflow-visible$"), - String::from("overflow-scroll$"), - String::from("overflow-x-auto$"), - String::from("overflow-y-auto$"), - String::from("overflow-x-hidden$"), - String::from("overflow-y-hidden$"), - String::from("overflow-x-clip$"), - String::from("overflow-y-clip$"), - String::from("overflow-x-visible$"), - String::from("overflow-y-visible$"), - String::from("overflow-x-scroll$"), - String::from("overflow-y-scroll$"), - String::from("overscroll-auto$"), - String::from("overscroll-contain$"), - String::from("overscroll-none$"), - String::from("overscroll-y-auto$"), - String::from("overscroll-y-contain$"), - String::from("overscroll-y-none$"), - String::from("overscroll-x-auto$"), - String::from("overscroll-x-contain$"), - String::from("overscroll-x-none$"), - String::from("scroll-auto$"), - String::from("scroll-smooth$"), - String::from("truncate$"), - String::from("overflow-ellipsis$"), - String::from("text-ellipsis$"), - String::from("text-clip$"), - String::from("hyphens-none$"), - String::from("hyphens-manual$"), - String::from("hyphens-auto$"), - String::from("whitespace-normal$"), - String::from("whitespace-nowrap$"), - String::from("whitespace-pre$"), - String::from("whitespace-pre-line$"), - String::from("whitespace-pre-wrap$"), - String::from("whitespace-break-spaces$"), - String::from("text-wrap$"), - String::from("text-nowrap$"), - String::from("text-balance$"), - String::from("text-pretty$"), - String::from("break-normal$"), - String::from("break-words$"), - String::from("break-all$"), - String::from("break-keep$"), - String::from("rounded$"), - String::from("rounded-"), - String::from("rounded-s$"), - String::from("rounded-s-"), - String::from("rounded-e$"), - String::from("rounded-e-"), - String::from("rounded-t$"), - String::from("rounded-t-"), - String::from("rounded-r$"), - String::from("rounded-r-"), - String::from("rounded-b$"), - String::from("rounded-b-"), - String::from("rounded-l$"), - String::from("rounded-l-"), - String::from("rounded-ss$"), - String::from("rounded-ss-"), - String::from("rounded-se$"), - String::from("rounded-se-"), - String::from("rounded-ee$"), - String::from("rounded-ee-"), - String::from("rounded-es$"), - String::from("rounded-es-"), - String::from("rounded-tl$"), - String::from("rounded-tl-"), - String::from("rounded-tr$"), - String::from("rounded-tr-"), - String::from("rounded-br$"), - String::from("rounded-br-"), - String::from("rounded-bl$"), - String::from("rounded-bl-"), - String::from("border$"), - String::from("border-"), - String::from("border-x$"), - String::from("border-x-"), - String::from("border-y$"), - String::from("border-y-"), - String::from("border-s$"), - String::from("border-s-"), - String::from("border-e$"), - String::from("border-e-"), - String::from("border-t$"), - String::from("border-t-"), - String::from("border-r$"), - String::from("border-r-"), - String::from("border-b$"), - String::from("border-b-"), - String::from("border-l$"), - String::from("border-l-"), - String::from("border-solid$"), - String::from("border-dashed$"), - String::from("border-dotted$"), - String::from("border-double$"), - String::from("border-hidden$"), - String::from("border-none$"), - String::from("border-opacity-"), - String::from("bg-"), - String::from("bg-opacity-"), - String::from("from-"), - String::from("via-"), - String::from("to-"), - String::from("decoration-slice$"), - String::from("decoration-clone$"), - String::from("box-decoration-slice$"), - String::from("box-decoration-clone$"), - String::from("bg-fixed$"), - String::from("bg-local$"), - String::from("bg-scroll$"), - String::from("bg-clip-border$"), - String::from("bg-clip-padding$"), - String::from("bg-clip-content$"), - String::from("bg-clip-text$"), - String::from("bg-repeat$"), - String::from("bg-no-repeat$"), - String::from("bg-repeat-x$"), - String::from("bg-repeat-y$"), - String::from("bg-repeat-round$"), - String::from("bg-repeat-space$"), - String::from("bg-origin-border$"), - String::from("bg-origin-padding$"), - String::from("bg-origin-content$"), - String::from("fill-"), - String::from("stroke-"), - String::from("object-contain$"), - String::from("object-cover$"), - String::from("object-fill$"), - String::from("object-none$"), - String::from("object-scale-down$"), - String::from("object-"), - String::from("p-"), - String::from("px-"), - String::from("py-"), - String::from("ps-"), - String::from("pe-"), - String::from("pt-"), - String::from("pr-"), - String::from("pb-"), - String::from("pl-"), - String::from("text-left$"), - String::from("text-center$"), - String::from("text-right$"), - String::from("text-justify$"), - String::from("text-start$"), - String::from("text-end$"), - String::from("indent-"), - String::from("align-baseline$"), - String::from("align-top$"), - String::from("align-middle$"), - String::from("align-bottom$"), - String::from("align-text-top$"), - String::from("align-text-bottom$"), - String::from("align-sub$"), - String::from("align-super$"), - String::from("align-"), - String::from("font-"), - String::from("text-"), - String::from("uppercase$"), - String::from("lowercase$"), - String::from("capitalize$"), - String::from("normal-case$"), - String::from("italic$"), - String::from("not-italic$"), - String::from("normal-nums$"), - String::from("ordinal$"), - String::from("slashed-zero$"), - String::from("lining-nums$"), - String::from("oldstyle-nums$"), - String::from("proportional-nums$"), - String::from("tabular-nums$"), - String::from("diagonal-fractions$"), - String::from("stacked-fractions$"), - String::from("leading-"), - String::from("tracking-"), - String::from("text-opacity-"), - String::from("underline$"), - String::from("overline$"), - String::from("line-through$"), - String::from("no-underline$"), - String::from("decoration-"), - String::from("decoration-solid$"), - String::from("decoration-double$"), - String::from("decoration-dotted$"), - String::from("decoration-dashed$"), - String::from("decoration-wavy$"), - String::from("underline-offset-"), - String::from("antialiased$"), - String::from("subpixel-antialiased$"), - String::from("placeholder-"), - String::from("placeholder-opacity-"), - String::from("caret-"), - String::from("accent-"), - String::from("opacity-"), - String::from("bg-blend-normal$"), - String::from("bg-blend-multiply$"), - String::from("bg-blend-screen$"), - String::from("bg-blend-overlay$"), - String::from("bg-blend-darken$"), - String::from("bg-blend-lighten$"), - String::from("bg-blend-color-dodge$"), - String::from("bg-blend-color-burn$"), - String::from("bg-blend-hard-light$"), - String::from("bg-blend-soft-light$"), - String::from("bg-blend-difference$"), - String::from("bg-blend-exclusion$"), - String::from("bg-blend-hue$"), - String::from("bg-blend-saturation$"), - String::from("bg-blend-color$"), - String::from("bg-blend-luminosity$"), - String::from("mix-blend-normal$"), - String::from("mix-blend-multiply$"), - String::from("mix-blend-screen$"), - String::from("mix-blend-overlay$"), - String::from("mix-blend-darken$"), - String::from("mix-blend-lighten$"), - String::from("mix-blend-color-dodge$"), - String::from("mix-blend-color-burn$"), - String::from("mix-blend-hard-light$"), - String::from("mix-blend-soft-light$"), - String::from("mix-blend-difference$"), - String::from("mix-blend-exclusion$"), - String::from("mix-blend-hue$"), - String::from("mix-blend-saturation$"), - String::from("mix-blend-color$"), - String::from("mix-blend-luminosity$"), - String::from("mix-blend-plus-lighter$"), - String::from("shadow$"), - String::from("shadow-"), - String::from("outline-none$"), - String::from("outline$"), - String::from("outline-dashed$"), - String::from("outline-dotted$"), - String::from("outline-double$"), - String::from("outline-offset-"), - String::from("ring$"), - String::from("ring-"), - String::from("ring-inset$"), - String::from("ring-opacity-"), - String::from("ring-offset-"), - String::from("blur$"), - String::from("blur-"), - String::from("brightness-"), - String::from("contrast-"), - String::from("drop-shadow$"), - String::from("drop-shadow-"), - String::from("grayscale$"), - String::from("grayscale-"), - String::from("hue-rotate-"), - String::from("invert$"), - String::from("invert-"), - String::from("saturate-"), - String::from("sepia$"), - String::from("sepia-"), - String::from("filter$"), - String::from("filter-none$"), - String::from("backdrop-blur$"), - String::from("backdrop-blur-"), - String::from("backdrop-brightness-"), - String::from("backdrop-contrast-"), - String::from("backdrop-grayscale$"), - String::from("backdrop-grayscale-"), - String::from("backdrop-hue-rotate-"), - String::from("backdrop-invert$"), - String::from("backdrop-invert-"), - String::from("backdrop-opacity-"), - String::from("backdrop-saturate-"), - String::from("backdrop-sepia$"), - String::from("backdrop-sepia-"), - String::from("backdrop-filter$"), - String::from("backdrop-filter-none$"), - String::from("transition$"), - String::from("transition-"), - String::from("delay-"), - String::from("duration-"), - String::from("ease-"), - String::from("will-change-"), - String::from("content-"), - String::from("forced-color-adjust-auto$"), - String::from("forced-color-adjust-none$"), - // TAILWIND-PRESET-END + UtilityLayer { + layer: String::from("components"), + classes: vec![String::from("container$")], + }, + UtilityLayer { + layer: String::from("utilities"), + classes: vec![ + String::from("sr-only$"), + String::from("not-sr-only$"), + String::from("pointer-events-none$"), + String::from("pointer-events-auto$"), + String::from("visible$"), + String::from("invisible$"), + String::from("collapse$"), + String::from("static$"), + String::from("fixed$"), + String::from("absolute$"), + String::from("relative$"), + String::from("sticky$"), + String::from("inset-"), + String::from("inset-x-"), + String::from("inset-y-"), + String::from("start-"), + String::from("end-"), + String::from("top-"), + String::from("right-"), + String::from("bottom-"), + String::from("left-"), + String::from("isolate$"), + String::from("isolation-auto$"), + String::from("z-"), + String::from("order-"), + String::from("col-"), + String::from("col-start-"), + String::from("col-end-"), + String::from("row-"), + String::from("row-start-"), + String::from("row-end-"), + String::from("float-start$"), + String::from("float-end$"), + String::from("float-right$"), + String::from("float-left$"), + String::from("float-none$"), + String::from("clear-start$"), + String::from("clear-end$"), + String::from("clear-left$"), + String::from("clear-right$"), + String::from("clear-both$"), + String::from("clear-none$"), + String::from("m-"), + String::from("mx-"), + String::from("my-"), + String::from("ms-"), + String::from("me-"), + String::from("mt-"), + String::from("mr-"), + String::from("mb-"), + String::from("ml-"), + String::from("box-border$"), + String::from("box-content$"), + String::from("line-clamp-"), + String::from("line-clamp-none$"), + String::from("block$"), + String::from("inline-block$"), + String::from("inline$"), + String::from("flex$"), + String::from("inline-flex$"), + String::from("table$"), + String::from("inline-table$"), + String::from("table-caption$"), + String::from("table-cell$"), + String::from("table-column$"), + String::from("table-column-group$"), + String::from("table-footer-group$"), + String::from("table-header-group$"), + String::from("table-row-group$"), + String::from("table-row$"), + String::from("flow-root$"), + String::from("grid$"), + String::from("inline-grid$"), + String::from("contents$"), + String::from("list-item$"), + String::from("hidden$"), + String::from("aspect-"), + String::from("size-"), + String::from("h-"), + String::from("max-h-"), + String::from("min-h-"), + String::from("w-"), + String::from("min-w-"), + String::from("max-w-"), + String::from("flex-shrink$"), + String::from("flex-shrink-"), + String::from("shrink$"), + String::from("shrink-"), + String::from("flex-grow$"), + String::from("flex-grow-"), + String::from("grow$"), + String::from("grow-"), + String::from("basis-"), + String::from("table-auto$"), + String::from("table-fixed$"), + String::from("caption-top$"), + String::from("caption-bottom$"), + String::from("border-collapse$"), + String::from("border-separate$"), + String::from("border-spacing-"), + String::from("border-spacing-x-"), + String::from("border-spacing-y-"), + String::from("origin-"), + String::from("translate-x-"), + String::from("translate-y-"), + String::from("rotate-"), + String::from("skew-x-"), + String::from("skew-y-"), + String::from("scale-"), + String::from("scale-x-"), + String::from("scale-y-"), + String::from("transform$"), + String::from("transform-cpu$"), + String::from("transform-gpu$"), + String::from("transform-none$"), + String::from("animate-"), + String::from("cursor-"), + String::from("touch-auto$"), + String::from("touch-none$"), + String::from("touch-pan-x$"), + String::from("touch-pan-left$"), + String::from("touch-pan-right$"), + String::from("touch-pan-y$"), + String::from("touch-pan-up$"), + String::from("touch-pan-down$"), + String::from("touch-pinch-zoom$"), + String::from("touch-manipulation$"), + String::from("select-none$"), + String::from("select-text$"), + String::from("select-all$"), + String::from("select-auto$"), + String::from("resize-none$"), + String::from("resize-y$"), + String::from("resize-x$"), + String::from("resize$"), + String::from("snap-none$"), + String::from("snap-x$"), + String::from("snap-y$"), + String::from("snap-both$"), + String::from("snap-mandatory$"), + String::from("snap-proximity$"), + String::from("snap-start$"), + String::from("snap-end$"), + String::from("snap-center$"), + String::from("snap-align-none$"), + String::from("snap-normal$"), + String::from("snap-always$"), + String::from("scroll-m-"), + String::from("scroll-mx-"), + String::from("scroll-my-"), + String::from("scroll-ms-"), + String::from("scroll-me-"), + String::from("scroll-mt-"), + String::from("scroll-mr-"), + String::from("scroll-mb-"), + String::from("scroll-ml-"), + String::from("scroll-p-"), + String::from("scroll-px-"), + String::from("scroll-py-"), + String::from("scroll-ps-"), + String::from("scroll-pe-"), + String::from("scroll-pt-"), + String::from("scroll-pr-"), + String::from("scroll-pb-"), + String::from("scroll-pl-"), + String::from("list-inside$"), + String::from("list-outside$"), + String::from("list-"), + String::from("list-image-"), + String::from("appearance-none$"), + String::from("appearance-auto$"), + String::from("columns-"), + String::from("break-before-auto$"), + String::from("break-before-avoid$"), + String::from("break-before-all$"), + String::from("break-before-avoid-page$"), + String::from("break-before-page$"), + String::from("break-before-left$"), + String::from("break-before-right$"), + String::from("break-before-column$"), + String::from("break-inside-auto$"), + String::from("break-inside-avoid$"), + String::from("break-inside-avoid-page$"), + String::from("break-inside-avoid-column$"), + String::from("break-after-auto$"), + String::from("break-after-avoid$"), + String::from("break-after-all$"), + String::from("break-after-avoid-page$"), + String::from("break-after-page$"), + String::from("break-after-left$"), + String::from("break-after-right$"), + String::from("break-after-column$"), + String::from("auto-cols-"), + String::from("grid-flow-row$"), + String::from("grid-flow-col$"), + String::from("grid-flow-dense$"), + String::from("grid-flow-row-dense$"), + String::from("grid-flow-col-dense$"), + String::from("auto-rows-"), + String::from("grid-cols-"), + String::from("grid-rows-"), + String::from("flex-row$"), + String::from("flex-row-reverse$"), + String::from("flex-col$"), + String::from("flex-col-reverse$"), + String::from("flex-wrap$"), + String::from("flex-wrap-reverse$"), + String::from("flex-nowrap$"), + String::from("place-content-center$"), + String::from("place-content-start$"), + String::from("place-content-end$"), + String::from("place-content-between$"), + String::from("place-content-around$"), + String::from("place-content-evenly$"), + String::from("place-content-baseline$"), + String::from("place-content-stretch$"), + String::from("place-items-start$"), + String::from("place-items-end$"), + String::from("place-items-center$"), + String::from("place-items-baseline$"), + String::from("place-items-stretch$"), + String::from("content-normal$"), + String::from("content-center$"), + String::from("content-start$"), + String::from("content-end$"), + String::from("content-between$"), + String::from("content-around$"), + String::from("content-evenly$"), + String::from("content-baseline$"), + String::from("content-stretch$"), + String::from("items-start$"), + String::from("items-end$"), + String::from("items-center$"), + String::from("items-baseline$"), + String::from("items-stretch$"), + String::from("justify-normal$"), + String::from("justify-start$"), + String::from("justify-end$"), + String::from("justify-center$"), + String::from("justify-between$"), + String::from("justify-around$"), + String::from("justify-evenly$"), + String::from("justify-stretch$"), + String::from("justify-items-start$"), + String::from("justify-items-end$"), + String::from("justify-items-center$"), + String::from("justify-items-stretch$"), + String::from("gap-"), + String::from("gap-x-"), + String::from("gap-y-"), + String::from("space-x-"), + String::from("space-y-"), + String::from("space-y-reverse$"), + String::from("space-x-reverse$"), + String::from("divide-x$"), + String::from("divide-x-"), + String::from("divide-y$"), + String::from("divide-y-"), + String::from("divide-y-reverse$"), + String::from("divide-x-reverse$"), + String::from("divide-solid$"), + String::from("divide-dashed$"), + String::from("divide-dotted$"), + String::from("divide-double$"), + String::from("divide-none$"), + String::from("divide-"), + String::from("divide-opacity-"), + String::from("place-self-auto$"), + String::from("place-self-start$"), + String::from("place-self-end$"), + String::from("place-self-center$"), + String::from("place-self-stretch$"), + String::from("self-auto$"), + String::from("self-start$"), + String::from("self-end$"), + String::from("self-center$"), + String::from("self-stretch$"), + String::from("self-baseline$"), + String::from("justify-self-auto$"), + String::from("justify-self-start$"), + String::from("justify-self-end$"), + String::from("justify-self-center$"), + String::from("justify-self-stretch$"), + String::from("overflow-auto$"), + String::from("overflow-hidden$"), + String::from("overflow-clip$"), + String::from("overflow-visible$"), + String::from("overflow-scroll$"), + String::from("overflow-x-auto$"), + String::from("overflow-y-auto$"), + String::from("overflow-x-hidden$"), + String::from("overflow-y-hidden$"), + String::from("overflow-x-clip$"), + String::from("overflow-y-clip$"), + String::from("overflow-x-visible$"), + String::from("overflow-y-visible$"), + String::from("overflow-x-scroll$"), + String::from("overflow-y-scroll$"), + String::from("overscroll-auto$"), + String::from("overscroll-contain$"), + String::from("overscroll-none$"), + String::from("overscroll-y-auto$"), + String::from("overscroll-y-contain$"), + String::from("overscroll-y-none$"), + String::from("overscroll-x-auto$"), + String::from("overscroll-x-contain$"), + String::from("overscroll-x-none$"), + String::from("scroll-auto$"), + String::from("scroll-smooth$"), + String::from("truncate$"), + String::from("overflow-ellipsis$"), + String::from("text-ellipsis$"), + String::from("text-clip$"), + String::from("hyphens-none$"), + String::from("hyphens-manual$"), + String::from("hyphens-auto$"), + String::from("whitespace-normal$"), + String::from("whitespace-nowrap$"), + String::from("whitespace-pre$"), + String::from("whitespace-pre-line$"), + String::from("whitespace-pre-wrap$"), + String::from("whitespace-break-spaces$"), + String::from("text-wrap$"), + String::from("text-nowrap$"), + String::from("text-balance$"), + String::from("text-pretty$"), + String::from("break-normal$"), + String::from("break-words$"), + String::from("break-all$"), + String::from("break-keep$"), + String::from("rounded$"), + String::from("rounded-"), + String::from("rounded-s$"), + String::from("rounded-s-"), + String::from("rounded-e$"), + String::from("rounded-e-"), + String::from("rounded-t$"), + String::from("rounded-t-"), + String::from("rounded-r$"), + String::from("rounded-r-"), + String::from("rounded-b$"), + String::from("rounded-b-"), + String::from("rounded-l$"), + String::from("rounded-l-"), + String::from("rounded-ss$"), + String::from("rounded-ss-"), + String::from("rounded-se$"), + String::from("rounded-se-"), + String::from("rounded-ee$"), + String::from("rounded-ee-"), + String::from("rounded-es$"), + String::from("rounded-es-"), + String::from("rounded-tl$"), + String::from("rounded-tl-"), + String::from("rounded-tr$"), + String::from("rounded-tr-"), + String::from("rounded-br$"), + String::from("rounded-br-"), + String::from("rounded-bl$"), + String::from("rounded-bl-"), + String::from("border$"), + String::from("border-"), + String::from("border-x$"), + String::from("border-x-"), + String::from("border-y$"), + String::from("border-y-"), + String::from("border-s$"), + String::from("border-s-"), + String::from("border-e$"), + String::from("border-e-"), + String::from("border-t$"), + String::from("border-t-"), + String::from("border-r$"), + String::from("border-r-"), + String::from("border-b$"), + String::from("border-b-"), + String::from("border-l$"), + String::from("border-l-"), + String::from("border-solid$"), + String::from("border-dashed$"), + String::from("border-dotted$"), + String::from("border-double$"), + String::from("border-hidden$"), + String::from("border-none$"), + String::from("border-opacity-"), + String::from("bg-"), + String::from("bg-opacity-"), + String::from("from-"), + String::from("via-"), + String::from("to-"), + String::from("decoration-slice$"), + String::from("decoration-clone$"), + String::from("box-decoration-slice$"), + String::from("box-decoration-clone$"), + String::from("bg-fixed$"), + String::from("bg-local$"), + String::from("bg-scroll$"), + String::from("bg-clip-border$"), + String::from("bg-clip-padding$"), + String::from("bg-clip-content$"), + String::from("bg-clip-text$"), + String::from("bg-repeat$"), + String::from("bg-no-repeat$"), + String::from("bg-repeat-x$"), + String::from("bg-repeat-y$"), + String::from("bg-repeat-round$"), + String::from("bg-repeat-space$"), + String::from("bg-origin-border$"), + String::from("bg-origin-padding$"), + String::from("bg-origin-content$"), + String::from("fill-"), + String::from("stroke-"), + String::from("object-contain$"), + String::from("object-cover$"), + String::from("object-fill$"), + String::from("object-none$"), + String::from("object-scale-down$"), + String::from("object-"), + String::from("p-"), + String::from("px-"), + String::from("py-"), + String::from("ps-"), + String::from("pe-"), + String::from("pt-"), + String::from("pr-"), + String::from("pb-"), + String::from("pl-"), + String::from("text-left$"), + String::from("text-center$"), + String::from("text-right$"), + String::from("text-justify$"), + String::from("text-start$"), + String::from("text-end$"), + String::from("indent-"), + String::from("align-baseline$"), + String::from("align-top$"), + String::from("align-middle$"), + String::from("align-bottom$"), + String::from("align-text-top$"), + String::from("align-text-bottom$"), + String::from("align-sub$"), + String::from("align-super$"), + String::from("align-"), + String::from("font-"), + String::from("text-"), + String::from("uppercase$"), + String::from("lowercase$"), + String::from("capitalize$"), + String::from("normal-case$"), + String::from("italic$"), + String::from("not-italic$"), + String::from("normal-nums$"), + String::from("ordinal$"), + String::from("slashed-zero$"), + String::from("lining-nums$"), + String::from("oldstyle-nums$"), + String::from("proportional-nums$"), + String::from("tabular-nums$"), + String::from("diagonal-fractions$"), + String::from("stacked-fractions$"), + String::from("leading-"), + String::from("tracking-"), + String::from("text-opacity-"), + String::from("underline$"), + String::from("overline$"), + String::from("line-through$"), + String::from("no-underline$"), + String::from("decoration-"), + String::from("decoration-solid$"), + String::from("decoration-double$"), + String::from("decoration-dotted$"), + String::from("decoration-dashed$"), + String::from("decoration-wavy$"), + String::from("underline-offset-"), + String::from("antialiased$"), + String::from("subpixel-antialiased$"), + String::from("placeholder-"), + String::from("placeholder-opacity-"), + String::from("caret-"), + String::from("accent-"), + String::from("opacity-"), + String::from("bg-blend-normal$"), + String::from("bg-blend-multiply$"), + String::from("bg-blend-screen$"), + String::from("bg-blend-overlay$"), + String::from("bg-blend-darken$"), + String::from("bg-blend-lighten$"), + String::from("bg-blend-color-dodge$"), + String::from("bg-blend-color-burn$"), + String::from("bg-blend-hard-light$"), + String::from("bg-blend-soft-light$"), + String::from("bg-blend-difference$"), + String::from("bg-blend-exclusion$"), + String::from("bg-blend-hue$"), + String::from("bg-blend-saturation$"), + String::from("bg-blend-color$"), + String::from("bg-blend-luminosity$"), + String::from("mix-blend-normal$"), + String::from("mix-blend-multiply$"), + String::from("mix-blend-screen$"), + String::from("mix-blend-overlay$"), + String::from("mix-blend-darken$"), + String::from("mix-blend-lighten$"), + String::from("mix-blend-color-dodge$"), + String::from("mix-blend-color-burn$"), + String::from("mix-blend-hard-light$"), + String::from("mix-blend-soft-light$"), + String::from("mix-blend-difference$"), + String::from("mix-blend-exclusion$"), + String::from("mix-blend-hue$"), + String::from("mix-blend-saturation$"), + String::from("mix-blend-color$"), + String::from("mix-blend-luminosity$"), + String::from("mix-blend-plus-lighter$"), + String::from("shadow$"), + String::from("shadow-"), + String::from("outline-none$"), + String::from("outline$"), + String::from("outline-dashed$"), + String::from("outline-dotted$"), + String::from("outline-double$"), + String::from("outline-offset-"), + String::from("ring$"), + String::from("ring-"), + String::from("ring-inset$"), + String::from("ring-opacity-"), + String::from("ring-offset-"), + String::from("blur$"), + String::from("blur-"), + String::from("brightness-"), + String::from("contrast-"), + String::from("drop-shadow$"), + String::from("drop-shadow-"), + String::from("grayscale$"), + String::from("grayscale-"), + String::from("hue-rotate-"), + String::from("invert$"), + String::from("invert-"), + String::from("saturate-"), + String::from("sepia$"), + String::from("sepia-"), + String::from("filter$"), + String::from("filter-none$"), + String::from("backdrop-blur$"), + String::from("backdrop-blur-"), + String::from("backdrop-brightness-"), + String::from("backdrop-contrast-"), + String::from("backdrop-grayscale$"), + String::from("backdrop-grayscale-"), + String::from("backdrop-hue-rotate-"), + String::from("backdrop-invert$"), + String::from("backdrop-invert-"), + String::from("backdrop-opacity-"), + String::from("backdrop-saturate-"), + String::from("backdrop-sepia$"), + String::from("backdrop-sepia-"), + String::from("backdrop-filter$"), + String::from("backdrop-filter-none$"), + String::from("transition$"), + String::from("transition-"), + String::from("delay-"), + String::from("duration-"), + String::from("ease-"), + String::from("will-change-"), + String::from("content-"), + String::from("forced-color-adjust-auto$"), + String::from("forced-color-adjust-none$"), + ], + }, ] + // TAILWIND-PRESET-END } } } From beec56bdc373ff0565204ab9c4c406698f4cbbba Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Sun, 31 Dec 2023 04:36:37 +0100 Subject: [PATCH 04/82] class parser! --- .../nursery/use_sorted_classes.rs | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index 80dd14697443..db7edc3974ee 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -108,6 +108,162 @@ fn get_options_from_analyzer(analyzer_options: &AnalyzerOptions) -> UseSortedCla } } +// class parser +// ------------ + +fn split_at_indexes<'a>(s: &'a str, indexes: &[usize]) -> Vec<&'a str> { + let mut segments = Vec::new(); + let mut start_offset = 0; + let mut start = 0; + + for &index in indexes { + if index > s.len() { + break; // Avoid panicking on out-of-bounds indexes + } + if index > start { + segments.push(&s[start + start_offset..index]); + } + start_offset = 1; + start = index; + } + + if start + start_offset < s.len() { + segments.push(&s[start + start_offset..]); + } + + segments +} + +#[derive(Debug, Clone, PartialEq)] +enum Quote { + Single, + Double, + Backtick, +} + +impl Quote { + fn from_char(c: char) -> Option { + match c { + '\'' => Some(Quote::Single), + '"' => Some(Quote::Double), + '`' => Some(Quote::Backtick), + _ => None, + } + } +} + +#[derive(Debug)] +enum CharKind { + Other, + Quote(Quote), + Backslash, +} + +#[derive(Debug)] +struct UtilitySegmentData { + arbitrary: bool, + text: String, +} + +#[derive(Debug)] +struct UtilityData { + variants: Vec, + utility: UtilitySegmentData, +} + +fn parse_class(class_name: &str) -> UtilityData { + // state + let mut arbitrary_block_depth = 0; + let mut at_arbitrary_block_start = false; + let mut quoted_arbitrary_block_type: Option = None; + let mut last_char = CharKind::Other; + let mut delimiter_indexes: Vec = Vec::new(); + + // loop + for (index, c) in class_name.chars().enumerate() { + let mut next_last_char = CharKind::Other; + let mut is_start_of_arbitrary_block = false; + match c { + '[' => { + if arbitrary_block_depth == 0 { + arbitrary_block_depth = 1; + at_arbitrary_block_start = true; + is_start_of_arbitrary_block = true; + } else if let None = quoted_arbitrary_block_type { + arbitrary_block_depth += 1; + } + } + '\'' | '"' | '`' => { + if at_arbitrary_block_start { + let quote = Quote::from_char(c) + .expect("TODO: error message (this should never happen)"); + quoted_arbitrary_block_type = Some(quote.clone()); + } else if let CharKind::Backslash = last_char { + // Ignore. + } else { + let quote = Quote::from_char(c) + .expect("TODO: error message (this should never happen)"); + next_last_char = CharKind::Quote(quote); + } + } + '\\' => { + if let CharKind::Backslash = last_char { + // Consider escaped backslashes as other characters. + } else { + next_last_char = CharKind::Backslash; + } + } + ']' => { + if arbitrary_block_depth > 0 { + match "ed_arbitrary_block_type { + // If in quoted arbitrary block... + Some(quote_type) => { + // and the last character was a quote... + if let CharKind::Quote(last_quote) = &last_char { + // of the same type as the current quote... + if quote_type == last_quote { + // then we are no longer in an arbitrary block. + arbitrary_block_depth = 0; + quoted_arbitrary_block_type = None; + } + } + } + None => { + arbitrary_block_depth -= 1; + quoted_arbitrary_block_type = None; + } + } + } + } + ':' => { + if arbitrary_block_depth == 0 { + delimiter_indexes.push(index); + } + } + _ => {} + }; + if arbitrary_block_depth < 0 { + panic!("TODO: error message (this should never happen)"); + }; + if at_arbitrary_block_start && !is_start_of_arbitrary_block { + at_arbitrary_block_start = false; + }; + last_char = next_last_char; + } + let mut variants: Vec = split_at_indexes(class_name, &delimiter_indexes) + .iter() + .map(|&s| UtilitySegmentData { + arbitrary: s.starts_with('['), + text: s.to_string(), + }) + .collect(); + let utility = variants + .pop() + .expect("TODO: error message (this should never happen)"); + + UtilityData { variants, utility } +} + // attributes visitor // ------------------ From d50aeab51d2c61b87d65dc059128426fcd4ac9a3 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Sun, 31 Dec 2023 17:55:22 +0100 Subject: [PATCH 05/82] split into modules --- .../nursery/use_sorted_classes.rs | 1280 +---------------- .../use_sorted_classes/class_like_visitor.rs | 224 +++ .../use_sorted_classes/class_parser.rs | 160 +++ .../nursery/use_sorted_classes/options.rs | 217 +++ .../nursery/use_sorted_classes/presets.rs | 598 ++++++++ .../nursery/use_sorted_classes/sort.rs | 62 + 6 files changed, 1271 insertions(+), 1270 deletions(-) create mode 100644 crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs create mode 100644 crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_parser.rs create mode 100644 crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs create mode 100644 crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs create mode 100644 crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index db7edc3974ee..abec2a497107 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -1,33 +1,22 @@ -use std::vec; +mod class_like_visitor; +mod class_parser; +mod options; +mod presets; +mod sort; use biome_analyze::{ - context::RuleContext, declare_rule, ActionCategory, AddVisitor, AnalyzerOptions, FixKind, - Phases, QueryMatch, Queryable, Rule, RuleDiagnostic, RuleKey, ServiceBag, Visitor, - VisitorContext, + context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, }; use biome_console::markup; -use biome_deserialize::{ - Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor, Text, - VisitableType, -}; use biome_diagnostics::Applicability; use biome_js_factory::make::{js_string_literal, js_string_literal_expression, jsx_string}; -use biome_js_syntax::{ - JsCallArguments, JsCallExpression, JsLanguage, JsStringLiteralExpression, - JsTemplateChunkElement, JsxAttribute, JsxString, -}; -use biome_rowan::{AstNode, BatchMutationExt, Language, SyntaxNode, TextRange, WalkEvent}; -use rustc_hash::FxHashMap; +use biome_rowan::BatchMutationExt; use crate::JsRuleAction; -// TODO: variants -// TODO: extensibility -// TODO: preset generation script -// TODO: automatic config sync -// TODO: remove duplicated classes - -const CLASS_ATTRIBUTES: [&str; 2] = ["class", "className"]; +use self::{ + class_like_visitor::ClassStringLike, options::UseSortedClassesOptions, sort::sort_class_name, +}; // rule metadata // ------------- @@ -59,442 +48,6 @@ declare_rule! { } } -// utils -// ----- - -fn get_callee_name(call_expression: &JsCallExpression) -> Option { - Some( - call_expression - .callee() - .ok()? - .as_js_identifier_expression()? - .name() - .ok()? - .name() - .ok()? - .to_string(), - ) -} - -fn get_attribute_name(attribute: &JsxAttribute) -> Option { - Some(attribute.name().ok()?.as_jsx_name()?.to_string()) -} - -fn is_call_expression_of_valid_function( - call_expression: &JsCallExpression, - functions: &[String], -) -> bool { - match get_callee_name(call_expression) { - Some(name) => functions.contains(&name.to_string()), - None => false, - } -} - -fn is_valid_attribute(attribute: &JsxAttribute, attributes: &[String]) -> bool { - match get_attribute_name(attribute) { - Some(name) => attributes.contains(&name.to_string()), - None => false, - } -} - -fn get_options_from_analyzer(analyzer_options: &AnalyzerOptions) -> UseSortedClassesOptions { - match analyzer_options - .configuration - .rules - .get_rule_options::(&RuleKey::new("nursery", "useSortedClasses")) - { - Some(options) => options.clone(), - None => UseSortedClassesOptions::default(), - } -} - -// class parser -// ------------ - -fn split_at_indexes<'a>(s: &'a str, indexes: &[usize]) -> Vec<&'a str> { - let mut segments = Vec::new(); - let mut start_offset = 0; - let mut start = 0; - - for &index in indexes { - if index > s.len() { - break; // Avoid panicking on out-of-bounds indexes - } - if index > start { - segments.push(&s[start + start_offset..index]); - } - start_offset = 1; - start = index; - } - - if start + start_offset < s.len() { - segments.push(&s[start + start_offset..]); - } - - segments -} - -#[derive(Debug, Clone, PartialEq)] -enum Quote { - Single, - Double, - Backtick, -} - -impl Quote { - fn from_char(c: char) -> Option { - match c { - '\'' => Some(Quote::Single), - '"' => Some(Quote::Double), - '`' => Some(Quote::Backtick), - _ => None, - } - } -} - -#[derive(Debug)] -enum CharKind { - Other, - Quote(Quote), - Backslash, -} - -#[derive(Debug)] -struct UtilitySegmentData { - arbitrary: bool, - text: String, -} - -#[derive(Debug)] -struct UtilityData { - variants: Vec, - utility: UtilitySegmentData, -} - -fn parse_class(class_name: &str) -> UtilityData { - // state - let mut arbitrary_block_depth = 0; - let mut at_arbitrary_block_start = false; - let mut quoted_arbitrary_block_type: Option = None; - let mut last_char = CharKind::Other; - let mut delimiter_indexes: Vec = Vec::new(); - - // loop - for (index, c) in class_name.chars().enumerate() { - let mut next_last_char = CharKind::Other; - let mut is_start_of_arbitrary_block = false; - match c { - '[' => { - if arbitrary_block_depth == 0 { - arbitrary_block_depth = 1; - at_arbitrary_block_start = true; - is_start_of_arbitrary_block = true; - } else if let None = quoted_arbitrary_block_type { - arbitrary_block_depth += 1; - } - } - '\'' | '"' | '`' => { - if at_arbitrary_block_start { - let quote = Quote::from_char(c) - .expect("TODO: error message (this should never happen)"); - quoted_arbitrary_block_type = Some(quote.clone()); - } else if let CharKind::Backslash = last_char { - // Ignore. - } else { - let quote = Quote::from_char(c) - .expect("TODO: error message (this should never happen)"); - next_last_char = CharKind::Quote(quote); - } - } - '\\' => { - if let CharKind::Backslash = last_char { - // Consider escaped backslashes as other characters. - } else { - next_last_char = CharKind::Backslash; - } - } - ']' => { - if arbitrary_block_depth > 0 { - match "ed_arbitrary_block_type { - // If in quoted arbitrary block... - Some(quote_type) => { - // and the last character was a quote... - if let CharKind::Quote(last_quote) = &last_char { - // of the same type as the current quote... - if quote_type == last_quote { - // then we are no longer in an arbitrary block. - arbitrary_block_depth = 0; - quoted_arbitrary_block_type = None; - } - } - } - None => { - arbitrary_block_depth -= 1; - quoted_arbitrary_block_type = None; - } - } - } - } - ':' => { - if arbitrary_block_depth == 0 { - delimiter_indexes.push(index); - } - } - _ => {} - }; - if arbitrary_block_depth < 0 { - panic!("TODO: error message (this should never happen)"); - }; - if at_arbitrary_block_start && !is_start_of_arbitrary_block { - at_arbitrary_block_start = false; - }; - last_char = next_last_char; - } - let mut variants: Vec = split_at_indexes(class_name, &delimiter_indexes) - .iter() - .map(|&s| UtilitySegmentData { - arbitrary: s.starts_with('['), - text: s.to_string(), - }) - .collect(); - let utility = variants - .pop() - .expect("TODO: error message (this should never happen)"); - - UtilityData { variants, utility } -} - -// attributes visitor -// ------------------ - -#[derive(Default)] -struct StringLiteralInAttributeVisitor { - in_valid_attribute: bool, -} - -impl Visitor for StringLiteralInAttributeVisitor { - type Language = JsLanguage; - - fn visit( - &mut self, - event: &WalkEvent>, - mut ctx: VisitorContext, - ) { - let options = get_options_from_analyzer(ctx.options); - match event { - WalkEvent::Enter(node) => { - // When entering an attribute node, track if we are in a valid attribute. - if let Some(attribute) = JsxAttribute::cast_ref(node) { - self.in_valid_attribute = is_valid_attribute(&attribute, &options.attributes); - } - - // When entering a JSX string node, and we are in a valid attribute, emit. - if let Some(jsx_string) = JsxString::cast_ref(node) { - if self.in_valid_attribute { - ctx.match_query(ClassStringLike::JsxString(jsx_string)); - } - } - - // When entering a string literal node, and we are in a valid attribute, emit. - if let Some(string_literal) = JsStringLiteralExpression::cast_ref(node) { - if self.in_valid_attribute { - ctx.match_query(ClassStringLike::StringLiteral(string_literal)); - } - } - } - WalkEvent::Leave(node) => { - // When leaving an attribute node, reset in_valid_attribute. - if JsxAttribute::cast_ref(node).is_some() { - self.in_valid_attribute = false; - } - } - } - } -} - -// functions (call expression) visitor -// ----------------------------------- - -#[derive(Default)] -struct StringLiteralInCallExpressionVisitor { - in_valid_function: bool, - in_arguments: bool, -} - -impl Visitor for StringLiteralInCallExpressionVisitor { - type Language = JsLanguage; - - fn visit( - &mut self, - event: &WalkEvent>, - mut ctx: VisitorContext, - ) { - let options = get_options_from_analyzer(ctx.options); - if options.functions.is_empty() { - return; - } - match event { - WalkEvent::Enter(node) => { - // When entering a call expression node, track if we are in a valid function and reset - // in_arguments. - if let Some(call_expression) = JsCallExpression::cast_ref(node) { - self.in_valid_function = - is_call_expression_of_valid_function(&call_expression, &options.functions); - self.in_arguments = false; - } - - // When entering a call arguments node, set in_arguments. - if JsCallArguments::cast_ref(node).is_some() { - self.in_arguments = true; - } - - // When entering a string literal node, and we are in a valid function and in arguments, emit. - if let Some(string_literal) = JsStringLiteralExpression::cast_ref(node) { - if self.in_valid_function && self.in_arguments { - ctx.match_query(ClassStringLike::StringLiteral(string_literal)); - } - } - } - WalkEvent::Leave(node) => { - // When leaving a call arguments node, reset in_arguments. - if JsCallArguments::cast_ref(node).is_some() { - self.in_arguments = false; - } - } - } - } -} - -// functions (template chunk) visitor -// ---------------------------------- - -// TODO: template chunk visitor - -// custom query -// ------------ - -#[derive(Clone)] -pub(crate) enum ClassStringLike { - StringLiteral(JsStringLiteralExpression), - JsxString(JsxString), - TemplateChunk(JsTemplateChunkElement), -} - -impl ClassStringLike { - fn range(&self) -> TextRange { - match self { - ClassStringLike::StringLiteral(string_literal) => string_literal.range(), - ClassStringLike::JsxString(jsx_string) => jsx_string.range(), - ClassStringLike::TemplateChunk(template_chunk) => template_chunk.range(), - } - } - - fn value(&self) -> Option { - match self { - ClassStringLike::StringLiteral(string_literal) => { - Some(string_literal.inner_string_text().ok()?.to_string()) - } - ClassStringLike::JsxString(jsx_string) => { - Some(jsx_string.inner_string_text().ok()?.to_string()) - } - ClassStringLike::TemplateChunk(template_chunk) => Some(template_chunk.to_string()), - } - } -} - -impl QueryMatch for ClassStringLike { - fn text_range(&self) -> TextRange { - self.range() - } -} - -impl Queryable for ClassStringLike { - type Input = Self; - type Language = JsLanguage; - type Output = ClassStringLike; - type Services = (); - - fn build_visitor( - analyzer: &mut impl AddVisitor, - _: &::Root, - ) { - analyzer.add_visitor(Phases::Syntax, || { - StringLiteralInAttributeVisitor::default() - }); - analyzer.add_visitor(Phases::Syntax, || { - StringLiteralInCallExpressionVisitor::default() - }); - } - - fn unwrap_match(_: &ServiceBag, query: &Self::Input) -> Self::Output { - query.clone() - } -} - -// class sorting -// ------------- - -fn get_utilities_match(spec: &String, class_name: &str) -> Option { - if spec.ends_with('$') && class_name == &spec[..spec.len() - 1] { - return Some(true); - } - if class_name.starts_with(spec) && class_name != spec.as_str() { - return Some(false); - } - None -} - -fn find_utilities_index(utilities: &[String], class_name: &str) -> Option { - let mut matched = false; - let mut match_index: usize = 0; - let mut last_size: usize = 0; - for (i, spec) in utilities.iter().enumerate() { - match get_utilities_match(spec, class_name) { - Some(true) => return Some(i), - Some(false) => { - let spec_size = spec.chars().count(); - if spec_size > last_size { - match_index = i; - last_size = spec_size; - matched = true; - } - } - _ => {} - } - } - if matched { - Some(match_index) - } else { - None - } -} - -// TODO: detect arbitrary css (e.g. [background:red]), put at the end -fn sort_class_name(class_name: &str, utilities: &Vec) -> String { - let classes = class_name.split_whitespace().collect::>(); - let mut unordered_classes: Vec<&str> = Vec::new(); - let mut utilities_map: FxHashMap> = FxHashMap::default(); - for class in classes { - match find_utilities_index(utilities, class) { - Some(index) => { - utilities_map.entry(index).or_default().push(class); - } - None => { - unordered_classes.push(class); - } - } - } - let mut sorted_classes: Vec<&str> = unordered_classes; - for i in 0..utilities.len() { - if let Some(classes) = utilities_map.get(&i) { - let mut abc_classes = classes.clone(); - abc_classes.sort_unstable(); - sorted_classes.extend(abc_classes); - } - } - sorted_classes.join(" ") -} - // rule // ---- @@ -558,816 +111,3 @@ impl Rule for UseSortedClasses { } } } - -// options -// ------- - -struct UtilityLayer { - layer: String, - classes: Vec, -} - -impl Deserializable for UtilityLayer { - fn deserialize( - value: &impl DeserializableValue, - name: &str, - diagnostics: &mut Vec, - ) -> Option { - value.deserialize(UtilityLayerVisitor, name, diagnostics) - } -} - -struct UtilityLayerVisitor; -impl DeserializationVisitor for UtilityLayerVisitor { - type Output = UtilityLayer; - - const EXPECTED_TYPE: VisitableType = VisitableType::MAP; - - fn visit_map( - self, - members: impl Iterator>, - _range: TextRange, - _name: &str, - diagnostics: &mut Vec, - ) -> Option { - let mut layer: Option = None; - let mut classes: Option> = None; - const ALLOWED_OPTIONS: &[&str] = &["layer", "classes"]; - - for (key, value) in members.flatten() { - let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { - continue; - }; - match key_text.text() { - "layer" => { - if let Some(layer_option) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - layer = Some(layer_option); - } - } - "classes" => { - if let Some(classes_option) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - classes = Some(classes_option); - } - } - unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key( - unknown_key, - key.range(), - ALLOWED_OPTIONS, - )), - } - } - - let missing_layer = layer.is_none(); - let missing_classes = classes.is_none(); - - if missing_layer || missing_classes { - let mut missing_keys: Vec<&str> = Vec::new(); - if missing_layer { - missing_keys.push("layer"); - } - if missing_classes { - missing_keys.push("classes"); - } - let missing_keys = missing_keys.join(", "); - // TODO: how to actually handle this? - diagnostics.push(DeserializationDiagnostic::new(format!( - "Missing {}.", - missing_keys - ))); - - None - } else { - Some(UtilityLayer { - layer: layer.expect("TODO: error message (this should never happen)"), - classes: classes.expect("TODO: error message (this should never happen)"), - }) - } - } -} - -#[derive(Debug, Clone)] -pub struct UseSortedClassesOptions { - pub attributes: Vec, - pub functions: Vec, - pub utilities: Vec, -} - -impl Default for UseSortedClassesOptions { - fn default() -> Self { - UseSortedClassesOptions { - attributes: CLASS_ATTRIBUTES.iter().map(|&s| s.to_string()).collect(), - functions: Vec::new(), - utilities: Vec::new(), - } - } -} - -const ALLOWED_OPTIONS: &[&str] = &["attributes", "functions", "preset", "utilities"]; - -impl Deserializable for UseSortedClassesOptions { - fn deserialize( - value: &impl DeserializableValue, - name: &str, - diagnostics: &mut Vec, - ) -> Option { - value.deserialize(UseSortedClassesOptionsVisitor, name, diagnostics) - } -} - -struct UseSortedClassesOptionsVisitor; -impl DeserializationVisitor for UseSortedClassesOptionsVisitor { - type Output = UseSortedClassesOptions; - - const EXPECTED_TYPE: VisitableType = VisitableType::MAP; - - fn visit_map( - self, - members: impl Iterator>, - _range: TextRange, - _name: &str, - diagnostics: &mut Vec, - ) -> Option { - let mut result = UseSortedClassesOptions::default(); - let mut preset: UseSortedClassesPreset = UseSortedClassesPreset::TailwindCSS; - let mut utilities_option: Option> = None; - - for (key, value) in members.flatten() { - let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { - continue; - }; - match key_text.text() { - "attributes" => { - if let Some(attributes_option) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - let attributes_option: Vec = attributes_option; // TODO: is there a better way to do this? - result.attributes.extend(attributes_option); - } - } - "functions" => { - if let Some(functions) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - result.functions = functions; - } - } - "preset" => { - if let Some(preset_option) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - let preset_option: String = preset_option; // TODO: is there a better way to do this? - let preset_option = preset_option.as_str(); - match preset_option { - "tailwind-css" => { - preset = UseSortedClassesPreset::TailwindCSS; - } - "no-preset" => { - preset = UseSortedClassesPreset::None; - } - _ => { - diagnostics.push(DeserializationDiagnostic::new_unknown_value( - preset_option, - value.range(), - ALLOWED_PRESETS, - )); - } - } - } - } - "utilities" => { - if let Some(utilities_opt) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - utilities_option = Some(utilities_opt); - } - } - unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key( - unknown_key, - key.range(), - ALLOWED_OPTIONS, - )), - } - } - - let resolved_utilities = match utilities_option { - Some(utilities) => utilities, - None => get_utilities_preset(&preset), - }; - result.utilities = resolved_utilities - .iter() - .flat_map(|layer| { - // TODO: extend layer here - layer.classes.clone() - }) - .collect(); - - Some(result) - } -} - -// presets -// ------- - -#[derive(Debug, Default, Clone)] -pub enum UseSortedClassesPreset { - None, - #[default] - TailwindCSS, -} - -const ALLOWED_PRESETS: &[&str] = &["no-preset", "tailwind-css"]; - -// TODO: move to separate file? -fn get_utilities_preset(preset: &UseSortedClassesPreset) -> Vec { - match preset { - UseSortedClassesPreset::None => { - vec![] - } - UseSortedClassesPreset::TailwindCSS => { - // TAILWIND-PRESET-START - vec![ - UtilityLayer { - layer: String::from("components"), - classes: vec![String::from("container$")], - }, - UtilityLayer { - layer: String::from("utilities"), - classes: vec![ - String::from("sr-only$"), - String::from("not-sr-only$"), - String::from("pointer-events-none$"), - String::from("pointer-events-auto$"), - String::from("visible$"), - String::from("invisible$"), - String::from("collapse$"), - String::from("static$"), - String::from("fixed$"), - String::from("absolute$"), - String::from("relative$"), - String::from("sticky$"), - String::from("inset-"), - String::from("inset-x-"), - String::from("inset-y-"), - String::from("start-"), - String::from("end-"), - String::from("top-"), - String::from("right-"), - String::from("bottom-"), - String::from("left-"), - String::from("isolate$"), - String::from("isolation-auto$"), - String::from("z-"), - String::from("order-"), - String::from("col-"), - String::from("col-start-"), - String::from("col-end-"), - String::from("row-"), - String::from("row-start-"), - String::from("row-end-"), - String::from("float-start$"), - String::from("float-end$"), - String::from("float-right$"), - String::from("float-left$"), - String::from("float-none$"), - String::from("clear-start$"), - String::from("clear-end$"), - String::from("clear-left$"), - String::from("clear-right$"), - String::from("clear-both$"), - String::from("clear-none$"), - String::from("m-"), - String::from("mx-"), - String::from("my-"), - String::from("ms-"), - String::from("me-"), - String::from("mt-"), - String::from("mr-"), - String::from("mb-"), - String::from("ml-"), - String::from("box-border$"), - String::from("box-content$"), - String::from("line-clamp-"), - String::from("line-clamp-none$"), - String::from("block$"), - String::from("inline-block$"), - String::from("inline$"), - String::from("flex$"), - String::from("inline-flex$"), - String::from("table$"), - String::from("inline-table$"), - String::from("table-caption$"), - String::from("table-cell$"), - String::from("table-column$"), - String::from("table-column-group$"), - String::from("table-footer-group$"), - String::from("table-header-group$"), - String::from("table-row-group$"), - String::from("table-row$"), - String::from("flow-root$"), - String::from("grid$"), - String::from("inline-grid$"), - String::from("contents$"), - String::from("list-item$"), - String::from("hidden$"), - String::from("aspect-"), - String::from("size-"), - String::from("h-"), - String::from("max-h-"), - String::from("min-h-"), - String::from("w-"), - String::from("min-w-"), - String::from("max-w-"), - String::from("flex-shrink$"), - String::from("flex-shrink-"), - String::from("shrink$"), - String::from("shrink-"), - String::from("flex-grow$"), - String::from("flex-grow-"), - String::from("grow$"), - String::from("grow-"), - String::from("basis-"), - String::from("table-auto$"), - String::from("table-fixed$"), - String::from("caption-top$"), - String::from("caption-bottom$"), - String::from("border-collapse$"), - String::from("border-separate$"), - String::from("border-spacing-"), - String::from("border-spacing-x-"), - String::from("border-spacing-y-"), - String::from("origin-"), - String::from("translate-x-"), - String::from("translate-y-"), - String::from("rotate-"), - String::from("skew-x-"), - String::from("skew-y-"), - String::from("scale-"), - String::from("scale-x-"), - String::from("scale-y-"), - String::from("transform$"), - String::from("transform-cpu$"), - String::from("transform-gpu$"), - String::from("transform-none$"), - String::from("animate-"), - String::from("cursor-"), - String::from("touch-auto$"), - String::from("touch-none$"), - String::from("touch-pan-x$"), - String::from("touch-pan-left$"), - String::from("touch-pan-right$"), - String::from("touch-pan-y$"), - String::from("touch-pan-up$"), - String::from("touch-pan-down$"), - String::from("touch-pinch-zoom$"), - String::from("touch-manipulation$"), - String::from("select-none$"), - String::from("select-text$"), - String::from("select-all$"), - String::from("select-auto$"), - String::from("resize-none$"), - String::from("resize-y$"), - String::from("resize-x$"), - String::from("resize$"), - String::from("snap-none$"), - String::from("snap-x$"), - String::from("snap-y$"), - String::from("snap-both$"), - String::from("snap-mandatory$"), - String::from("snap-proximity$"), - String::from("snap-start$"), - String::from("snap-end$"), - String::from("snap-center$"), - String::from("snap-align-none$"), - String::from("snap-normal$"), - String::from("snap-always$"), - String::from("scroll-m-"), - String::from("scroll-mx-"), - String::from("scroll-my-"), - String::from("scroll-ms-"), - String::from("scroll-me-"), - String::from("scroll-mt-"), - String::from("scroll-mr-"), - String::from("scroll-mb-"), - String::from("scroll-ml-"), - String::from("scroll-p-"), - String::from("scroll-px-"), - String::from("scroll-py-"), - String::from("scroll-ps-"), - String::from("scroll-pe-"), - String::from("scroll-pt-"), - String::from("scroll-pr-"), - String::from("scroll-pb-"), - String::from("scroll-pl-"), - String::from("list-inside$"), - String::from("list-outside$"), - String::from("list-"), - String::from("list-image-"), - String::from("appearance-none$"), - String::from("appearance-auto$"), - String::from("columns-"), - String::from("break-before-auto$"), - String::from("break-before-avoid$"), - String::from("break-before-all$"), - String::from("break-before-avoid-page$"), - String::from("break-before-page$"), - String::from("break-before-left$"), - String::from("break-before-right$"), - String::from("break-before-column$"), - String::from("break-inside-auto$"), - String::from("break-inside-avoid$"), - String::from("break-inside-avoid-page$"), - String::from("break-inside-avoid-column$"), - String::from("break-after-auto$"), - String::from("break-after-avoid$"), - String::from("break-after-all$"), - String::from("break-after-avoid-page$"), - String::from("break-after-page$"), - String::from("break-after-left$"), - String::from("break-after-right$"), - String::from("break-after-column$"), - String::from("auto-cols-"), - String::from("grid-flow-row$"), - String::from("grid-flow-col$"), - String::from("grid-flow-dense$"), - String::from("grid-flow-row-dense$"), - String::from("grid-flow-col-dense$"), - String::from("auto-rows-"), - String::from("grid-cols-"), - String::from("grid-rows-"), - String::from("flex-row$"), - String::from("flex-row-reverse$"), - String::from("flex-col$"), - String::from("flex-col-reverse$"), - String::from("flex-wrap$"), - String::from("flex-wrap-reverse$"), - String::from("flex-nowrap$"), - String::from("place-content-center$"), - String::from("place-content-start$"), - String::from("place-content-end$"), - String::from("place-content-between$"), - String::from("place-content-around$"), - String::from("place-content-evenly$"), - String::from("place-content-baseline$"), - String::from("place-content-stretch$"), - String::from("place-items-start$"), - String::from("place-items-end$"), - String::from("place-items-center$"), - String::from("place-items-baseline$"), - String::from("place-items-stretch$"), - String::from("content-normal$"), - String::from("content-center$"), - String::from("content-start$"), - String::from("content-end$"), - String::from("content-between$"), - String::from("content-around$"), - String::from("content-evenly$"), - String::from("content-baseline$"), - String::from("content-stretch$"), - String::from("items-start$"), - String::from("items-end$"), - String::from("items-center$"), - String::from("items-baseline$"), - String::from("items-stretch$"), - String::from("justify-normal$"), - String::from("justify-start$"), - String::from("justify-end$"), - String::from("justify-center$"), - String::from("justify-between$"), - String::from("justify-around$"), - String::from("justify-evenly$"), - String::from("justify-stretch$"), - String::from("justify-items-start$"), - String::from("justify-items-end$"), - String::from("justify-items-center$"), - String::from("justify-items-stretch$"), - String::from("gap-"), - String::from("gap-x-"), - String::from("gap-y-"), - String::from("space-x-"), - String::from("space-y-"), - String::from("space-y-reverse$"), - String::from("space-x-reverse$"), - String::from("divide-x$"), - String::from("divide-x-"), - String::from("divide-y$"), - String::from("divide-y-"), - String::from("divide-y-reverse$"), - String::from("divide-x-reverse$"), - String::from("divide-solid$"), - String::from("divide-dashed$"), - String::from("divide-dotted$"), - String::from("divide-double$"), - String::from("divide-none$"), - String::from("divide-"), - String::from("divide-opacity-"), - String::from("place-self-auto$"), - String::from("place-self-start$"), - String::from("place-self-end$"), - String::from("place-self-center$"), - String::from("place-self-stretch$"), - String::from("self-auto$"), - String::from("self-start$"), - String::from("self-end$"), - String::from("self-center$"), - String::from("self-stretch$"), - String::from("self-baseline$"), - String::from("justify-self-auto$"), - String::from("justify-self-start$"), - String::from("justify-self-end$"), - String::from("justify-self-center$"), - String::from("justify-self-stretch$"), - String::from("overflow-auto$"), - String::from("overflow-hidden$"), - String::from("overflow-clip$"), - String::from("overflow-visible$"), - String::from("overflow-scroll$"), - String::from("overflow-x-auto$"), - String::from("overflow-y-auto$"), - String::from("overflow-x-hidden$"), - String::from("overflow-y-hidden$"), - String::from("overflow-x-clip$"), - String::from("overflow-y-clip$"), - String::from("overflow-x-visible$"), - String::from("overflow-y-visible$"), - String::from("overflow-x-scroll$"), - String::from("overflow-y-scroll$"), - String::from("overscroll-auto$"), - String::from("overscroll-contain$"), - String::from("overscroll-none$"), - String::from("overscroll-y-auto$"), - String::from("overscroll-y-contain$"), - String::from("overscroll-y-none$"), - String::from("overscroll-x-auto$"), - String::from("overscroll-x-contain$"), - String::from("overscroll-x-none$"), - String::from("scroll-auto$"), - String::from("scroll-smooth$"), - String::from("truncate$"), - String::from("overflow-ellipsis$"), - String::from("text-ellipsis$"), - String::from("text-clip$"), - String::from("hyphens-none$"), - String::from("hyphens-manual$"), - String::from("hyphens-auto$"), - String::from("whitespace-normal$"), - String::from("whitespace-nowrap$"), - String::from("whitespace-pre$"), - String::from("whitespace-pre-line$"), - String::from("whitespace-pre-wrap$"), - String::from("whitespace-break-spaces$"), - String::from("text-wrap$"), - String::from("text-nowrap$"), - String::from("text-balance$"), - String::from("text-pretty$"), - String::from("break-normal$"), - String::from("break-words$"), - String::from("break-all$"), - String::from("break-keep$"), - String::from("rounded$"), - String::from("rounded-"), - String::from("rounded-s$"), - String::from("rounded-s-"), - String::from("rounded-e$"), - String::from("rounded-e-"), - String::from("rounded-t$"), - String::from("rounded-t-"), - String::from("rounded-r$"), - String::from("rounded-r-"), - String::from("rounded-b$"), - String::from("rounded-b-"), - String::from("rounded-l$"), - String::from("rounded-l-"), - String::from("rounded-ss$"), - String::from("rounded-ss-"), - String::from("rounded-se$"), - String::from("rounded-se-"), - String::from("rounded-ee$"), - String::from("rounded-ee-"), - String::from("rounded-es$"), - String::from("rounded-es-"), - String::from("rounded-tl$"), - String::from("rounded-tl-"), - String::from("rounded-tr$"), - String::from("rounded-tr-"), - String::from("rounded-br$"), - String::from("rounded-br-"), - String::from("rounded-bl$"), - String::from("rounded-bl-"), - String::from("border$"), - String::from("border-"), - String::from("border-x$"), - String::from("border-x-"), - String::from("border-y$"), - String::from("border-y-"), - String::from("border-s$"), - String::from("border-s-"), - String::from("border-e$"), - String::from("border-e-"), - String::from("border-t$"), - String::from("border-t-"), - String::from("border-r$"), - String::from("border-r-"), - String::from("border-b$"), - String::from("border-b-"), - String::from("border-l$"), - String::from("border-l-"), - String::from("border-solid$"), - String::from("border-dashed$"), - String::from("border-dotted$"), - String::from("border-double$"), - String::from("border-hidden$"), - String::from("border-none$"), - String::from("border-opacity-"), - String::from("bg-"), - String::from("bg-opacity-"), - String::from("from-"), - String::from("via-"), - String::from("to-"), - String::from("decoration-slice$"), - String::from("decoration-clone$"), - String::from("box-decoration-slice$"), - String::from("box-decoration-clone$"), - String::from("bg-fixed$"), - String::from("bg-local$"), - String::from("bg-scroll$"), - String::from("bg-clip-border$"), - String::from("bg-clip-padding$"), - String::from("bg-clip-content$"), - String::from("bg-clip-text$"), - String::from("bg-repeat$"), - String::from("bg-no-repeat$"), - String::from("bg-repeat-x$"), - String::from("bg-repeat-y$"), - String::from("bg-repeat-round$"), - String::from("bg-repeat-space$"), - String::from("bg-origin-border$"), - String::from("bg-origin-padding$"), - String::from("bg-origin-content$"), - String::from("fill-"), - String::from("stroke-"), - String::from("object-contain$"), - String::from("object-cover$"), - String::from("object-fill$"), - String::from("object-none$"), - String::from("object-scale-down$"), - String::from("object-"), - String::from("p-"), - String::from("px-"), - String::from("py-"), - String::from("ps-"), - String::from("pe-"), - String::from("pt-"), - String::from("pr-"), - String::from("pb-"), - String::from("pl-"), - String::from("text-left$"), - String::from("text-center$"), - String::from("text-right$"), - String::from("text-justify$"), - String::from("text-start$"), - String::from("text-end$"), - String::from("indent-"), - String::from("align-baseline$"), - String::from("align-top$"), - String::from("align-middle$"), - String::from("align-bottom$"), - String::from("align-text-top$"), - String::from("align-text-bottom$"), - String::from("align-sub$"), - String::from("align-super$"), - String::from("align-"), - String::from("font-"), - String::from("text-"), - String::from("uppercase$"), - String::from("lowercase$"), - String::from("capitalize$"), - String::from("normal-case$"), - String::from("italic$"), - String::from("not-italic$"), - String::from("normal-nums$"), - String::from("ordinal$"), - String::from("slashed-zero$"), - String::from("lining-nums$"), - String::from("oldstyle-nums$"), - String::from("proportional-nums$"), - String::from("tabular-nums$"), - String::from("diagonal-fractions$"), - String::from("stacked-fractions$"), - String::from("leading-"), - String::from("tracking-"), - String::from("text-opacity-"), - String::from("underline$"), - String::from("overline$"), - String::from("line-through$"), - String::from("no-underline$"), - String::from("decoration-"), - String::from("decoration-solid$"), - String::from("decoration-double$"), - String::from("decoration-dotted$"), - String::from("decoration-dashed$"), - String::from("decoration-wavy$"), - String::from("underline-offset-"), - String::from("antialiased$"), - String::from("subpixel-antialiased$"), - String::from("placeholder-"), - String::from("placeholder-opacity-"), - String::from("caret-"), - String::from("accent-"), - String::from("opacity-"), - String::from("bg-blend-normal$"), - String::from("bg-blend-multiply$"), - String::from("bg-blend-screen$"), - String::from("bg-blend-overlay$"), - String::from("bg-blend-darken$"), - String::from("bg-blend-lighten$"), - String::from("bg-blend-color-dodge$"), - String::from("bg-blend-color-burn$"), - String::from("bg-blend-hard-light$"), - String::from("bg-blend-soft-light$"), - String::from("bg-blend-difference$"), - String::from("bg-blend-exclusion$"), - String::from("bg-blend-hue$"), - String::from("bg-blend-saturation$"), - String::from("bg-blend-color$"), - String::from("bg-blend-luminosity$"), - String::from("mix-blend-normal$"), - String::from("mix-blend-multiply$"), - String::from("mix-blend-screen$"), - String::from("mix-blend-overlay$"), - String::from("mix-blend-darken$"), - String::from("mix-blend-lighten$"), - String::from("mix-blend-color-dodge$"), - String::from("mix-blend-color-burn$"), - String::from("mix-blend-hard-light$"), - String::from("mix-blend-soft-light$"), - String::from("mix-blend-difference$"), - String::from("mix-blend-exclusion$"), - String::from("mix-blend-hue$"), - String::from("mix-blend-saturation$"), - String::from("mix-blend-color$"), - String::from("mix-blend-luminosity$"), - String::from("mix-blend-plus-lighter$"), - String::from("shadow$"), - String::from("shadow-"), - String::from("outline-none$"), - String::from("outline$"), - String::from("outline-dashed$"), - String::from("outline-dotted$"), - String::from("outline-double$"), - String::from("outline-offset-"), - String::from("ring$"), - String::from("ring-"), - String::from("ring-inset$"), - String::from("ring-opacity-"), - String::from("ring-offset-"), - String::from("blur$"), - String::from("blur-"), - String::from("brightness-"), - String::from("contrast-"), - String::from("drop-shadow$"), - String::from("drop-shadow-"), - String::from("grayscale$"), - String::from("grayscale-"), - String::from("hue-rotate-"), - String::from("invert$"), - String::from("invert-"), - String::from("saturate-"), - String::from("sepia$"), - String::from("sepia-"), - String::from("filter$"), - String::from("filter-none$"), - String::from("backdrop-blur$"), - String::from("backdrop-blur-"), - String::from("backdrop-brightness-"), - String::from("backdrop-contrast-"), - String::from("backdrop-grayscale$"), - String::from("backdrop-grayscale-"), - String::from("backdrop-hue-rotate-"), - String::from("backdrop-invert$"), - String::from("backdrop-invert-"), - String::from("backdrop-opacity-"), - String::from("backdrop-saturate-"), - String::from("backdrop-sepia$"), - String::from("backdrop-sepia-"), - String::from("backdrop-filter$"), - String::from("backdrop-filter-none$"), - String::from("transition$"), - String::from("transition-"), - String::from("delay-"), - String::from("duration-"), - String::from("ease-"), - String::from("will-change-"), - String::from("content-"), - String::from("forced-color-adjust-auto$"), - String::from("forced-color-adjust-none$"), - ], - }, - ] - // TAILWIND-PRESET-END - } - } -} diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs new file mode 100644 index 000000000000..b6b447b68903 --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs @@ -0,0 +1,224 @@ +use biome_analyze::{ + AddVisitor, AnalyzerOptions, Phases, QueryMatch, Queryable, RuleKey, ServiceBag, Visitor, + VisitorContext, +}; +use biome_js_syntax::{ + JsCallArguments, JsCallExpression, JsLanguage, JsStringLiteralExpression, + JsTemplateChunkElement, JsxAttribute, JsxString, +}; +use biome_rowan::{AstNode, Language, SyntaxNode, TextRange, WalkEvent}; + +use super::UseSortedClassesOptions; + +fn get_options_from_analyzer(analyzer_options: &AnalyzerOptions) -> UseSortedClassesOptions { + match analyzer_options + .configuration + .rules + .get_rule_options::(&RuleKey::new("nursery", "useSortedClasses")) + { + Some(options) => options.clone(), + None => UseSortedClassesOptions::default(), + } +} + +fn get_callee_name(call_expression: &JsCallExpression) -> Option { + Some( + call_expression + .callee() + .ok()? + .as_js_identifier_expression()? + .name() + .ok()? + .name() + .ok()? + .to_string(), + ) +} + +fn is_call_expression_of_valid_function( + call_expression: &JsCallExpression, + functions: &[String], +) -> bool { + match get_callee_name(call_expression) { + Some(name) => functions.contains(&name.to_string()), + None => false, + } +} + +fn get_attribute_name(attribute: &JsxAttribute) -> Option { + Some(attribute.name().ok()?.as_jsx_name()?.to_string()) +} + +fn is_valid_attribute(attribute: &JsxAttribute, attributes: &[String]) -> bool { + match get_attribute_name(attribute) { + Some(name) => attributes.contains(&name.to_string()), + None => false, + } +} + +// attributes visitor +// ------------------ + +#[derive(Default)] +struct StringLiteralInAttributeVisitor { + in_valid_attribute: bool, +} + +impl Visitor for StringLiteralInAttributeVisitor { + type Language = JsLanguage; + + fn visit( + &mut self, + event: &WalkEvent>, + mut ctx: VisitorContext, + ) { + let options = get_options_from_analyzer(ctx.options); + match event { + WalkEvent::Enter(node) => { + // When entering an attribute node, track if we are in a valid attribute. + if let Some(attribute) = JsxAttribute::cast_ref(node) { + self.in_valid_attribute = is_valid_attribute(&attribute, &options.attributes); + } + + // When entering a JSX string node, and we are in a valid attribute, emit. + if let Some(jsx_string) = JsxString::cast_ref(node) { + if self.in_valid_attribute { + ctx.match_query(ClassStringLike::JsxString(jsx_string)); + } + } + + // When entering a string literal node, and we are in a valid attribute, emit. + if let Some(string_literal) = JsStringLiteralExpression::cast_ref(node) { + if self.in_valid_attribute { + ctx.match_query(ClassStringLike::StringLiteral(string_literal)); + } + } + } + WalkEvent::Leave(node) => { + // When leaving an attribute node, reset in_valid_attribute. + if JsxAttribute::cast_ref(node).is_some() { + self.in_valid_attribute = false; + } + } + } + } +} + +// functions (call expression) visitor +// ----------------------------------- + +#[derive(Default)] +struct StringLiteralInCallExpressionVisitor { + in_valid_function: bool, + in_arguments: bool, +} + +impl Visitor for StringLiteralInCallExpressionVisitor { + type Language = JsLanguage; + + fn visit( + &mut self, + event: &WalkEvent>, + mut ctx: VisitorContext, + ) { + let options = get_options_from_analyzer(ctx.options); + if options.functions.is_empty() { + return; + } + match event { + WalkEvent::Enter(node) => { + // When entering a call expression node, track if we are in a valid function and reset + // in_arguments. + if let Some(call_expression) = JsCallExpression::cast_ref(node) { + self.in_valid_function = + is_call_expression_of_valid_function(&call_expression, &options.functions); + self.in_arguments = false; + } + + // When entering a call arguments node, set in_arguments. + if JsCallArguments::cast_ref(node).is_some() { + self.in_arguments = true; + } + + // When entering a string literal node, and we are in a valid function and in arguments, emit. + if let Some(string_literal) = JsStringLiteralExpression::cast_ref(node) { + if self.in_valid_function && self.in_arguments { + ctx.match_query(ClassStringLike::StringLiteral(string_literal)); + } + } + } + WalkEvent::Leave(node) => { + // When leaving a call arguments node, reset in_arguments. + if JsCallArguments::cast_ref(node).is_some() { + self.in_arguments = false; + } + } + } + } +} + +// functions (template chunk) visitor +// ---------------------------------- + +// TODO: template chunk visitor + +// query +// ----- + +#[derive(Clone)] +pub(crate) enum ClassStringLike { + StringLiteral(JsStringLiteralExpression), + JsxString(JsxString), + TemplateChunk(JsTemplateChunkElement), +} + +impl ClassStringLike { + pub fn range(&self) -> TextRange { + match self { + ClassStringLike::StringLiteral(string_literal) => string_literal.range(), + ClassStringLike::JsxString(jsx_string) => jsx_string.range(), + ClassStringLike::TemplateChunk(template_chunk) => template_chunk.range(), + } + } + + pub fn value(&self) -> Option { + match self { + ClassStringLike::StringLiteral(string_literal) => { + Some(string_literal.inner_string_text().ok()?.to_string()) + } + ClassStringLike::JsxString(jsx_string) => { + Some(jsx_string.inner_string_text().ok()?.to_string()) + } + ClassStringLike::TemplateChunk(template_chunk) => Some(template_chunk.to_string()), + } + } +} + +impl QueryMatch for ClassStringLike { + fn text_range(&self) -> TextRange { + self.range() + } +} + +impl Queryable for ClassStringLike { + type Input = Self; + type Language = JsLanguage; + type Output = ClassStringLike; + type Services = (); + + fn build_visitor( + analyzer: &mut impl AddVisitor, + _: &::Root, + ) { + analyzer.add_visitor(Phases::Syntax, || { + StringLiteralInAttributeVisitor::default() + }); + analyzer.add_visitor(Phases::Syntax, || { + StringLiteralInCallExpressionVisitor::default() + }); + } + + fn unwrap_match(_: &ServiceBag, query: &Self::Input) -> Self::Output { + query.clone() + } +} diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_parser.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_parser.rs new file mode 100644 index 000000000000..d36d3afa75a6 --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_parser.rs @@ -0,0 +1,160 @@ +/// Splits a string into segments based on a list of indexes. The characters at the indexes are not +/// included in the segments, as they are considered delimiters. +fn split_at_indexes<'a>(s: &'a str, indexes: &[usize]) -> Vec<&'a str> { + let mut segments = Vec::new(); + let mut start_offset = 0; + let mut start = 0; + + for &index in indexes { + if index > s.len() { + break; // Avoid panicking on out-of-bounds indexes + } + if index > start { + segments.push(&s[start + start_offset..index]); + } + start_offset = 1; + start = index; + } + + if start + start_offset < s.len() { + segments.push(&s[start + start_offset..]); + } + + segments +} + +#[derive(Debug, Clone, PartialEq)] +enum Quote { + Single, + Double, + Backtick, +} + +impl Quote { + fn from_char(c: char) -> Option { + match c { + '\'' => Some(Quote::Single), + '"' => Some(Quote::Double), + '`' => Some(Quote::Backtick), + _ => None, + } + } +} + +#[derive(Debug)] +enum CharKind { + Other, + Quote(Quote), + Backslash, +} + +/// Information about a segment of a CSS class (variant or utility). +#[derive(Debug)] +pub struct UtilitySegmentData { + arbitrary: bool, + text: String, +} + +/// Information about a CSS class. +#[derive(Debug)] +pub struct UtilityData { + variants: Vec, + utility: UtilitySegmentData, +} + +/// Parses a CSS class into a utility data structure, containing a list of variants and the +/// utility itself. +pub fn parse_class(class_name: &str) -> UtilityData { + // state + let mut arbitrary_block_depth = 0; + let mut at_arbitrary_block_start = false; + let mut quoted_arbitrary_block_type: Option = None; + let mut last_char = CharKind::Other; + let mut delimiter_indexes: Vec = Vec::new(); + + // loop + for (index, c) in class_name.chars().enumerate() { + let mut next_last_char = CharKind::Other; + let mut is_start_of_arbitrary_block = false; + match c { + '[' => { + if arbitrary_block_depth == 0 { + arbitrary_block_depth = 1; + at_arbitrary_block_start = true; + is_start_of_arbitrary_block = true; + } else if quoted_arbitrary_block_type.is_none() { + arbitrary_block_depth += 1; + } + } + '\'' | '"' | '`' => { + if at_arbitrary_block_start { + quoted_arbitrary_block_type = Quote::from_char(c); + if quoted_arbitrary_block_type.is_none() { + // Sanity check. + panic!("TODO: error message (this should never happen)"); + } + } else if let CharKind::Backslash = last_char { + // Escaped, ignore. + } else { + let quote = Quote::from_char(c) + .expect("TODO: error message (this should never happen)"); + next_last_char = CharKind::Quote(quote); + } + } + '\\' => { + if let CharKind::Backslash = last_char { + // Consider escaped backslashes as other characters. + } else { + next_last_char = CharKind::Backslash; + } + } + ']' => { + if arbitrary_block_depth > 0 { + match "ed_arbitrary_block_type { + // If in quoted arbitrary block... + Some(quote_type) => { + // and the last character was a quote... + if let CharKind::Quote(last_quote) = &last_char { + // of the same type as the current quote... + if quote_type == last_quote { + // then we are no longer in an arbitrary block. + arbitrary_block_depth = 0; + quoted_arbitrary_block_type = None; + } + } + } + None => { + arbitrary_block_depth -= 1; + quoted_arbitrary_block_type = None; + } + } + } + } + ':' => { + if arbitrary_block_depth == 0 { + delimiter_indexes.push(index); + } + } + _ => {} + }; + if arbitrary_block_depth < 0 { + panic!("TODO: error message (this should never happen)"); + }; + if at_arbitrary_block_start && !is_start_of_arbitrary_block { + at_arbitrary_block_start = false; + }; + last_char = next_last_char; + } + let mut variants: Vec = split_at_indexes(class_name, &delimiter_indexes) + .iter() + .map(|&s| UtilitySegmentData { + arbitrary: s.starts_with('['), + text: s.to_string(), + }) + .collect(); + let utility = variants + .pop() + .expect("TODO: error message (this should never happen)"); + + UtilityData { variants, utility } +} diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs new file mode 100644 index 000000000000..2c571ded72ce --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs @@ -0,0 +1,217 @@ +use biome_deserialize::{ + Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor, Text, + VisitableType, +}; +use biome_rowan::TextRange; + +use super::presets::{get_utilities_preset, UseSortedClassesPreset}; + +const CLASS_ATTRIBUTES: [&str; 2] = ["class", "className"]; +const ALLOWED_PRESETS: &[&str] = &["no-preset", "tailwind-css"]; + +pub struct UtilityLayer { + pub layer: String, + pub classes: Vec, +} + +impl Deserializable for UtilityLayer { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + value.deserialize(UtilityLayerVisitor, name, diagnostics) + } +} + +struct UtilityLayerVisitor; +impl DeserializationVisitor for UtilityLayerVisitor { + type Output = UtilityLayer; + + const EXPECTED_TYPE: VisitableType = VisitableType::MAP; + + fn visit_map( + self, + members: impl Iterator>, + _range: TextRange, + _name: &str, + diagnostics: &mut Vec, + ) -> Option { + let mut layer: Option = None; + let mut classes: Option> = None; + const ALLOWED_OPTIONS: &[&str] = &["layer", "classes"]; + + for (key, value) in members.flatten() { + let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { + continue; + }; + match key_text.text() { + "layer" => { + if let Some(layer_option) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + layer = Some(layer_option); + } + } + "classes" => { + if let Some(classes_option) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + classes = Some(classes_option); + } + } + unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key( + unknown_key, + key.range(), + ALLOWED_OPTIONS, + )), + } + } + + let missing_layer = layer.is_none(); + let missing_classes = classes.is_none(); + + if missing_layer || missing_classes { + let mut missing_keys: Vec<&str> = Vec::new(); + if missing_layer { + missing_keys.push("layer"); + } + if missing_classes { + missing_keys.push("classes"); + } + let missing_keys = missing_keys.join(", "); + // TODO: how to actually handle this? + diagnostics.push(DeserializationDiagnostic::new(format!( + "Missing {}.", + missing_keys + ))); + + None + } else { + Some(UtilityLayer { + layer: layer.expect("TODO: error message (this should never happen)"), + classes: classes.expect("TODO: error message (this should never happen)"), + }) + } + } +} + +#[derive(Debug, Clone)] +pub struct UseSortedClassesOptions { + pub attributes: Vec, + pub functions: Vec, + pub utilities: Vec, +} + +impl Default for UseSortedClassesOptions { + fn default() -> Self { + UseSortedClassesOptions { + attributes: CLASS_ATTRIBUTES.iter().map(|&s| s.to_string()).collect(), + functions: Vec::new(), + utilities: Vec::new(), + } + } +} + +const ALLOWED_OPTIONS: &[&str] = &["attributes", "functions", "preset", "utilities"]; + +impl Deserializable for UseSortedClassesOptions { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + value.deserialize(UseSortedClassesOptionsVisitor, name, diagnostics) + } +} + +struct UseSortedClassesOptionsVisitor; +impl DeserializationVisitor for UseSortedClassesOptionsVisitor { + type Output = UseSortedClassesOptions; + + const EXPECTED_TYPE: VisitableType = VisitableType::MAP; + + fn visit_map( + self, + members: impl Iterator>, + _range: TextRange, + _name: &str, + diagnostics: &mut Vec, + ) -> Option { + let mut result = UseSortedClassesOptions::default(); + let mut preset: UseSortedClassesPreset = UseSortedClassesPreset::TailwindCSS; + let mut utilities_option: Option> = None; + + for (key, value) in members.flatten() { + let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { + continue; + }; + match key_text.text() { + "attributes" => { + if let Some(attributes_option) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + let attributes_option: Vec = attributes_option; // TODO: is there a better way to do this? + result.attributes.extend(attributes_option); + } + } + "functions" => { + if let Some(functions) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + result.functions = functions; + } + } + "preset" => { + if let Some(preset_option) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + let preset_option: String = preset_option; // TODO: is there a better way to do this? + let preset_option = preset_option.as_str(); + match preset_option { + "tailwind-css" => { + preset = UseSortedClassesPreset::TailwindCSS; + } + "no-preset" => { + preset = UseSortedClassesPreset::None; + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_unknown_value( + preset_option, + value.range(), + ALLOWED_PRESETS, + )); + } + } + } + } + "utilities" => { + if let Some(utilities_opt) = + Deserializable::deserialize(&value, &key_text, diagnostics) + { + utilities_option = Some(utilities_opt); + } + } + unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key( + unknown_key, + key.range(), + ALLOWED_OPTIONS, + )), + } + } + + let resolved_utilities = match utilities_option { + Some(utilities) => utilities, + None => get_utilities_preset(&preset), + }; + result.utilities = resolved_utilities + .iter() + .flat_map(|layer| { + // TODO: extend layer here + layer.classes.clone() + }) + .collect(); + + Some(result) + } +} diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs new file mode 100644 index 000000000000..1ac08ede47f2 --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs @@ -0,0 +1,598 @@ +use super::options::UtilityLayer; + +#[derive(Debug, Default, Clone)] +pub enum UseSortedClassesPreset { + None, + #[default] + TailwindCSS, +} + +pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> Vec { + match preset { + UseSortedClassesPreset::None => { + vec![] + } + UseSortedClassesPreset::TailwindCSS => { + // TAILWIND-PRESET-START + vec![ + UtilityLayer { + layer: String::from("components"), + classes: vec![String::from("container$")], + }, + UtilityLayer { + layer: String::from("utilities"), + classes: vec![ + String::from("sr-only$"), + String::from("not-sr-only$"), + String::from("pointer-events-none$"), + String::from("pointer-events-auto$"), + String::from("visible$"), + String::from("invisible$"), + String::from("collapse$"), + String::from("static$"), + String::from("fixed$"), + String::from("absolute$"), + String::from("relative$"), + String::from("sticky$"), + String::from("inset-"), + String::from("inset-x-"), + String::from("inset-y-"), + String::from("start-"), + String::from("end-"), + String::from("top-"), + String::from("right-"), + String::from("bottom-"), + String::from("left-"), + String::from("isolate$"), + String::from("isolation-auto$"), + String::from("z-"), + String::from("order-"), + String::from("col-"), + String::from("col-start-"), + String::from("col-end-"), + String::from("row-"), + String::from("row-start-"), + String::from("row-end-"), + String::from("float-start$"), + String::from("float-end$"), + String::from("float-right$"), + String::from("float-left$"), + String::from("float-none$"), + String::from("clear-start$"), + String::from("clear-end$"), + String::from("clear-left$"), + String::from("clear-right$"), + String::from("clear-both$"), + String::from("clear-none$"), + String::from("m-"), + String::from("mx-"), + String::from("my-"), + String::from("ms-"), + String::from("me-"), + String::from("mt-"), + String::from("mr-"), + String::from("mb-"), + String::from("ml-"), + String::from("box-border$"), + String::from("box-content$"), + String::from("line-clamp-"), + String::from("line-clamp-none$"), + String::from("block$"), + String::from("inline-block$"), + String::from("inline$"), + String::from("flex$"), + String::from("inline-flex$"), + String::from("table$"), + String::from("inline-table$"), + String::from("table-caption$"), + String::from("table-cell$"), + String::from("table-column$"), + String::from("table-column-group$"), + String::from("table-footer-group$"), + String::from("table-header-group$"), + String::from("table-row-group$"), + String::from("table-row$"), + String::from("flow-root$"), + String::from("grid$"), + String::from("inline-grid$"), + String::from("contents$"), + String::from("list-item$"), + String::from("hidden$"), + String::from("aspect-"), + String::from("size-"), + String::from("h-"), + String::from("max-h-"), + String::from("min-h-"), + String::from("w-"), + String::from("min-w-"), + String::from("max-w-"), + String::from("flex-shrink$"), + String::from("flex-shrink-"), + String::from("shrink$"), + String::from("shrink-"), + String::from("flex-grow$"), + String::from("flex-grow-"), + String::from("grow$"), + String::from("grow-"), + String::from("basis-"), + String::from("table-auto$"), + String::from("table-fixed$"), + String::from("caption-top$"), + String::from("caption-bottom$"), + String::from("border-collapse$"), + String::from("border-separate$"), + String::from("border-spacing-"), + String::from("border-spacing-x-"), + String::from("border-spacing-y-"), + String::from("origin-"), + String::from("translate-x-"), + String::from("translate-y-"), + String::from("rotate-"), + String::from("skew-x-"), + String::from("skew-y-"), + String::from("scale-"), + String::from("scale-x-"), + String::from("scale-y-"), + String::from("transform$"), + String::from("transform-cpu$"), + String::from("transform-gpu$"), + String::from("transform-none$"), + String::from("animate-"), + String::from("cursor-"), + String::from("touch-auto$"), + String::from("touch-none$"), + String::from("touch-pan-x$"), + String::from("touch-pan-left$"), + String::from("touch-pan-right$"), + String::from("touch-pan-y$"), + String::from("touch-pan-up$"), + String::from("touch-pan-down$"), + String::from("touch-pinch-zoom$"), + String::from("touch-manipulation$"), + String::from("select-none$"), + String::from("select-text$"), + String::from("select-all$"), + String::from("select-auto$"), + String::from("resize-none$"), + String::from("resize-y$"), + String::from("resize-x$"), + String::from("resize$"), + String::from("snap-none$"), + String::from("snap-x$"), + String::from("snap-y$"), + String::from("snap-both$"), + String::from("snap-mandatory$"), + String::from("snap-proximity$"), + String::from("snap-start$"), + String::from("snap-end$"), + String::from("snap-center$"), + String::from("snap-align-none$"), + String::from("snap-normal$"), + String::from("snap-always$"), + String::from("scroll-m-"), + String::from("scroll-mx-"), + String::from("scroll-my-"), + String::from("scroll-ms-"), + String::from("scroll-me-"), + String::from("scroll-mt-"), + String::from("scroll-mr-"), + String::from("scroll-mb-"), + String::from("scroll-ml-"), + String::from("scroll-p-"), + String::from("scroll-px-"), + String::from("scroll-py-"), + String::from("scroll-ps-"), + String::from("scroll-pe-"), + String::from("scroll-pt-"), + String::from("scroll-pr-"), + String::from("scroll-pb-"), + String::from("scroll-pl-"), + String::from("list-inside$"), + String::from("list-outside$"), + String::from("list-"), + String::from("list-image-"), + String::from("appearance-none$"), + String::from("appearance-auto$"), + String::from("columns-"), + String::from("break-before-auto$"), + String::from("break-before-avoid$"), + String::from("break-before-all$"), + String::from("break-before-avoid-page$"), + String::from("break-before-page$"), + String::from("break-before-left$"), + String::from("break-before-right$"), + String::from("break-before-column$"), + String::from("break-inside-auto$"), + String::from("break-inside-avoid$"), + String::from("break-inside-avoid-page$"), + String::from("break-inside-avoid-column$"), + String::from("break-after-auto$"), + String::from("break-after-avoid$"), + String::from("break-after-all$"), + String::from("break-after-avoid-page$"), + String::from("break-after-page$"), + String::from("break-after-left$"), + String::from("break-after-right$"), + String::from("break-after-column$"), + String::from("auto-cols-"), + String::from("grid-flow-row$"), + String::from("grid-flow-col$"), + String::from("grid-flow-dense$"), + String::from("grid-flow-row-dense$"), + String::from("grid-flow-col-dense$"), + String::from("auto-rows-"), + String::from("grid-cols-"), + String::from("grid-rows-"), + String::from("flex-row$"), + String::from("flex-row-reverse$"), + String::from("flex-col$"), + String::from("flex-col-reverse$"), + String::from("flex-wrap$"), + String::from("flex-wrap-reverse$"), + String::from("flex-nowrap$"), + String::from("place-content-center$"), + String::from("place-content-start$"), + String::from("place-content-end$"), + String::from("place-content-between$"), + String::from("place-content-around$"), + String::from("place-content-evenly$"), + String::from("place-content-baseline$"), + String::from("place-content-stretch$"), + String::from("place-items-start$"), + String::from("place-items-end$"), + String::from("place-items-center$"), + String::from("place-items-baseline$"), + String::from("place-items-stretch$"), + String::from("content-normal$"), + String::from("content-center$"), + String::from("content-start$"), + String::from("content-end$"), + String::from("content-between$"), + String::from("content-around$"), + String::from("content-evenly$"), + String::from("content-baseline$"), + String::from("content-stretch$"), + String::from("items-start$"), + String::from("items-end$"), + String::from("items-center$"), + String::from("items-baseline$"), + String::from("items-stretch$"), + String::from("justify-normal$"), + String::from("justify-start$"), + String::from("justify-end$"), + String::from("justify-center$"), + String::from("justify-between$"), + String::from("justify-around$"), + String::from("justify-evenly$"), + String::from("justify-stretch$"), + String::from("justify-items-start$"), + String::from("justify-items-end$"), + String::from("justify-items-center$"), + String::from("justify-items-stretch$"), + String::from("gap-"), + String::from("gap-x-"), + String::from("gap-y-"), + String::from("space-x-"), + String::from("space-y-"), + String::from("space-y-reverse$"), + String::from("space-x-reverse$"), + String::from("divide-x$"), + String::from("divide-x-"), + String::from("divide-y$"), + String::from("divide-y-"), + String::from("divide-y-reverse$"), + String::from("divide-x-reverse$"), + String::from("divide-solid$"), + String::from("divide-dashed$"), + String::from("divide-dotted$"), + String::from("divide-double$"), + String::from("divide-none$"), + String::from("divide-"), + String::from("divide-opacity-"), + String::from("place-self-auto$"), + String::from("place-self-start$"), + String::from("place-self-end$"), + String::from("place-self-center$"), + String::from("place-self-stretch$"), + String::from("self-auto$"), + String::from("self-start$"), + String::from("self-end$"), + String::from("self-center$"), + String::from("self-stretch$"), + String::from("self-baseline$"), + String::from("justify-self-auto$"), + String::from("justify-self-start$"), + String::from("justify-self-end$"), + String::from("justify-self-center$"), + String::from("justify-self-stretch$"), + String::from("overflow-auto$"), + String::from("overflow-hidden$"), + String::from("overflow-clip$"), + String::from("overflow-visible$"), + String::from("overflow-scroll$"), + String::from("overflow-x-auto$"), + String::from("overflow-y-auto$"), + String::from("overflow-x-hidden$"), + String::from("overflow-y-hidden$"), + String::from("overflow-x-clip$"), + String::from("overflow-y-clip$"), + String::from("overflow-x-visible$"), + String::from("overflow-y-visible$"), + String::from("overflow-x-scroll$"), + String::from("overflow-y-scroll$"), + String::from("overscroll-auto$"), + String::from("overscroll-contain$"), + String::from("overscroll-none$"), + String::from("overscroll-y-auto$"), + String::from("overscroll-y-contain$"), + String::from("overscroll-y-none$"), + String::from("overscroll-x-auto$"), + String::from("overscroll-x-contain$"), + String::from("overscroll-x-none$"), + String::from("scroll-auto$"), + String::from("scroll-smooth$"), + String::from("truncate$"), + String::from("overflow-ellipsis$"), + String::from("text-ellipsis$"), + String::from("text-clip$"), + String::from("hyphens-none$"), + String::from("hyphens-manual$"), + String::from("hyphens-auto$"), + String::from("whitespace-normal$"), + String::from("whitespace-nowrap$"), + String::from("whitespace-pre$"), + String::from("whitespace-pre-line$"), + String::from("whitespace-pre-wrap$"), + String::from("whitespace-break-spaces$"), + String::from("text-wrap$"), + String::from("text-nowrap$"), + String::from("text-balance$"), + String::from("text-pretty$"), + String::from("break-normal$"), + String::from("break-words$"), + String::from("break-all$"), + String::from("break-keep$"), + String::from("rounded$"), + String::from("rounded-"), + String::from("rounded-s$"), + String::from("rounded-s-"), + String::from("rounded-e$"), + String::from("rounded-e-"), + String::from("rounded-t$"), + String::from("rounded-t-"), + String::from("rounded-r$"), + String::from("rounded-r-"), + String::from("rounded-b$"), + String::from("rounded-b-"), + String::from("rounded-l$"), + String::from("rounded-l-"), + String::from("rounded-ss$"), + String::from("rounded-ss-"), + String::from("rounded-se$"), + String::from("rounded-se-"), + String::from("rounded-ee$"), + String::from("rounded-ee-"), + String::from("rounded-es$"), + String::from("rounded-es-"), + String::from("rounded-tl$"), + String::from("rounded-tl-"), + String::from("rounded-tr$"), + String::from("rounded-tr-"), + String::from("rounded-br$"), + String::from("rounded-br-"), + String::from("rounded-bl$"), + String::from("rounded-bl-"), + String::from("border$"), + String::from("border-"), + String::from("border-x$"), + String::from("border-x-"), + String::from("border-y$"), + String::from("border-y-"), + String::from("border-s$"), + String::from("border-s-"), + String::from("border-e$"), + String::from("border-e-"), + String::from("border-t$"), + String::from("border-t-"), + String::from("border-r$"), + String::from("border-r-"), + String::from("border-b$"), + String::from("border-b-"), + String::from("border-l$"), + String::from("border-l-"), + String::from("border-solid$"), + String::from("border-dashed$"), + String::from("border-dotted$"), + String::from("border-double$"), + String::from("border-hidden$"), + String::from("border-none$"), + String::from("border-opacity-"), + String::from("bg-"), + String::from("bg-opacity-"), + String::from("from-"), + String::from("via-"), + String::from("to-"), + String::from("decoration-slice$"), + String::from("decoration-clone$"), + String::from("box-decoration-slice$"), + String::from("box-decoration-clone$"), + String::from("bg-fixed$"), + String::from("bg-local$"), + String::from("bg-scroll$"), + String::from("bg-clip-border$"), + String::from("bg-clip-padding$"), + String::from("bg-clip-content$"), + String::from("bg-clip-text$"), + String::from("bg-repeat$"), + String::from("bg-no-repeat$"), + String::from("bg-repeat-x$"), + String::from("bg-repeat-y$"), + String::from("bg-repeat-round$"), + String::from("bg-repeat-space$"), + String::from("bg-origin-border$"), + String::from("bg-origin-padding$"), + String::from("bg-origin-content$"), + String::from("fill-"), + String::from("stroke-"), + String::from("object-contain$"), + String::from("object-cover$"), + String::from("object-fill$"), + String::from("object-none$"), + String::from("object-scale-down$"), + String::from("object-"), + String::from("p-"), + String::from("px-"), + String::from("py-"), + String::from("ps-"), + String::from("pe-"), + String::from("pt-"), + String::from("pr-"), + String::from("pb-"), + String::from("pl-"), + String::from("text-left$"), + String::from("text-center$"), + String::from("text-right$"), + String::from("text-justify$"), + String::from("text-start$"), + String::from("text-end$"), + String::from("indent-"), + String::from("align-baseline$"), + String::from("align-top$"), + String::from("align-middle$"), + String::from("align-bottom$"), + String::from("align-text-top$"), + String::from("align-text-bottom$"), + String::from("align-sub$"), + String::from("align-super$"), + String::from("align-"), + String::from("font-"), + String::from("text-"), + String::from("uppercase$"), + String::from("lowercase$"), + String::from("capitalize$"), + String::from("normal-case$"), + String::from("italic$"), + String::from("not-italic$"), + String::from("normal-nums$"), + String::from("ordinal$"), + String::from("slashed-zero$"), + String::from("lining-nums$"), + String::from("oldstyle-nums$"), + String::from("proportional-nums$"), + String::from("tabular-nums$"), + String::from("diagonal-fractions$"), + String::from("stacked-fractions$"), + String::from("leading-"), + String::from("tracking-"), + String::from("text-opacity-"), + String::from("underline$"), + String::from("overline$"), + String::from("line-through$"), + String::from("no-underline$"), + String::from("decoration-"), + String::from("decoration-solid$"), + String::from("decoration-double$"), + String::from("decoration-dotted$"), + String::from("decoration-dashed$"), + String::from("decoration-wavy$"), + String::from("underline-offset-"), + String::from("antialiased$"), + String::from("subpixel-antialiased$"), + String::from("placeholder-"), + String::from("placeholder-opacity-"), + String::from("caret-"), + String::from("accent-"), + String::from("opacity-"), + String::from("bg-blend-normal$"), + String::from("bg-blend-multiply$"), + String::from("bg-blend-screen$"), + String::from("bg-blend-overlay$"), + String::from("bg-blend-darken$"), + String::from("bg-blend-lighten$"), + String::from("bg-blend-color-dodge$"), + String::from("bg-blend-color-burn$"), + String::from("bg-blend-hard-light$"), + String::from("bg-blend-soft-light$"), + String::from("bg-blend-difference$"), + String::from("bg-blend-exclusion$"), + String::from("bg-blend-hue$"), + String::from("bg-blend-saturation$"), + String::from("bg-blend-color$"), + String::from("bg-blend-luminosity$"), + String::from("mix-blend-normal$"), + String::from("mix-blend-multiply$"), + String::from("mix-blend-screen$"), + String::from("mix-blend-overlay$"), + String::from("mix-blend-darken$"), + String::from("mix-blend-lighten$"), + String::from("mix-blend-color-dodge$"), + String::from("mix-blend-color-burn$"), + String::from("mix-blend-hard-light$"), + String::from("mix-blend-soft-light$"), + String::from("mix-blend-difference$"), + String::from("mix-blend-exclusion$"), + String::from("mix-blend-hue$"), + String::from("mix-blend-saturation$"), + String::from("mix-blend-color$"), + String::from("mix-blend-luminosity$"), + String::from("mix-blend-plus-lighter$"), + String::from("shadow$"), + String::from("shadow-"), + String::from("outline-none$"), + String::from("outline$"), + String::from("outline-dashed$"), + String::from("outline-dotted$"), + String::from("outline-double$"), + String::from("outline-offset-"), + String::from("ring$"), + String::from("ring-"), + String::from("ring-inset$"), + String::from("ring-opacity-"), + String::from("ring-offset-"), + String::from("blur$"), + String::from("blur-"), + String::from("brightness-"), + String::from("contrast-"), + String::from("drop-shadow$"), + String::from("drop-shadow-"), + String::from("grayscale$"), + String::from("grayscale-"), + String::from("hue-rotate-"), + String::from("invert$"), + String::from("invert-"), + String::from("saturate-"), + String::from("sepia$"), + String::from("sepia-"), + String::from("filter$"), + String::from("filter-none$"), + String::from("backdrop-blur$"), + String::from("backdrop-blur-"), + String::from("backdrop-brightness-"), + String::from("backdrop-contrast-"), + String::from("backdrop-grayscale$"), + String::from("backdrop-grayscale-"), + String::from("backdrop-hue-rotate-"), + String::from("backdrop-invert$"), + String::from("backdrop-invert-"), + String::from("backdrop-opacity-"), + String::from("backdrop-saturate-"), + String::from("backdrop-sepia$"), + String::from("backdrop-sepia-"), + String::from("backdrop-filter$"), + String::from("backdrop-filter-none$"), + String::from("transition$"), + String::from("transition-"), + String::from("delay-"), + String::from("duration-"), + String::from("ease-"), + String::from("will-change-"), + String::from("content-"), + String::from("forced-color-adjust-auto$"), + String::from("forced-color-adjust-none$"), + ], + }, + ] + // TAILWIND-PRESET-END + } + } +} diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs new file mode 100644 index 000000000000..cd199cbc4d99 --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs @@ -0,0 +1,62 @@ +use rustc_hash::FxHashMap; + +fn get_utilities_match(spec: &String, class_name: &str) -> Option { + if spec.ends_with('$') && class_name == &spec[..spec.len() - 1] { + return Some(true); + } + if class_name.starts_with(spec) && class_name != spec.as_str() { + return Some(false); + } + None +} + +fn find_utilities_index(utilities: &[String], class_name: &str) -> Option { + let mut matched = false; + let mut match_index: usize = 0; + let mut last_size: usize = 0; + for (i, spec) in utilities.iter().enumerate() { + match get_utilities_match(spec, class_name) { + Some(true) => return Some(i), + Some(false) => { + let spec_size = spec.chars().count(); + if spec_size > last_size { + match_index = i; + last_size = spec_size; + matched = true; + } + } + _ => {} + } + } + if matched { + Some(match_index) + } else { + None + } +} + +// TODO: detect arbitrary css (e.g. [background:red]), put at the end +pub fn sort_class_name(class_name: &str, utilities: &Vec) -> String { + let classes = class_name.split_whitespace().collect::>(); + let mut unordered_classes: Vec<&str> = Vec::new(); + let mut utilities_map: FxHashMap> = FxHashMap::default(); + for class in classes { + match find_utilities_index(utilities, class) { + Some(index) => { + utilities_map.entry(index).or_default().push(class); + } + None => { + unordered_classes.push(class); + } + } + } + let mut sorted_classes: Vec<&str> = unordered_classes; + for i in 0..utilities.len() { + if let Some(classes) = utilities_map.get(&i) { + let mut abc_classes = classes.clone(); + abc_classes.sort_unstable(); + sorted_classes.extend(abc_classes); + } + } + sorted_classes.join(" ") +} From 67ccb2c4fb3fef9f9af71a8776697aae48a8d66f Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Sun, 31 Dec 2023 22:50:38 +0100 Subject: [PATCH 06/82] updated obsolete PoC code and cleaned up --- .../nursery/use_sorted_classes.rs | 20 ++- .../nursery/use_sorted_classes/class_info.rs | 126 +++++++++++++++ .../use_sorted_classes/class_parser.rs | 26 +-- .../nursery/use_sorted_classes/options.rs | 141 +--------------- .../nursery/use_sorted_classes/presets.rs | 12 +- .../nursery/use_sorted_classes/sort.rs | 152 ++++++++++++------ .../nursery/use_sorted_classes/sort_config.rs | 44 +++++ 7 files changed, 316 insertions(+), 205 deletions(-) create mode 100644 crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs create mode 100644 crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index abec2a497107..c0a7111b4841 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -1,8 +1,10 @@ +mod class_info; mod class_like_visitor; mod class_parser; mod options; mod presets; mod sort; +mod sort_config; use biome_analyze::{ context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, @@ -14,8 +16,12 @@ use biome_rowan::BatchMutationExt; use crate::JsRuleAction; +pub use self::options::UseSortedClassesOptions; use self::{ - class_like_visitor::ClassStringLike, options::UseSortedClassesOptions, sort::sort_class_name, + class_like_visitor::ClassStringLike, + presets::{get_utilities_preset, UseSortedClassesPreset}, + sort::sort_class_name, + sort_config::SortConfig, }; // rule metadata @@ -59,8 +65,16 @@ impl Rule for UseSortedClasses { fn run(ctx: &RuleContext) -> Option { let value = ctx.query().value()?; - let options = &ctx.options(); - let sorted_value = sort_class_name(value.as_str(), &options.utilities); + // TODO: unsure if options are needed here. The sort config should ideally be created once + // from the options and then reused for all queries. + // let options = &ctx.options(); + // TODO: the sort config should already exist at this point, and be generated from the options, + // including the preset and extended options as well. + let sort_config = SortConfig::new( + get_utilities_preset(&UseSortedClassesPreset::default()), + Vec::new(), + ); + let sorted_value = sort_class_name(value.as_str(), &sort_config); if value != sorted_value { Some(sorted_value) } else { diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs new file mode 100644 index 000000000000..6e17ea36f3ae --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs @@ -0,0 +1,126 @@ +use super::{ + class_parser::{parse_class, ClassSegmentStructure}, + sort_config::{SortConfig, UtilitiesConfig}, +}; + +// utilities +// --------- + +enum UtilityMatch { + Exact, + Partial, + None, +} + +impl UtilityMatch { + fn from(target: &String, utility_text: &str) -> UtilityMatch { + // If the target ends with `$`, then it's an exact target. + if target.ends_with('$') { + // Check if the utility matches the target (without the final `$`) exactly. + if utility_text == &target[..target.len() - 1] { + return UtilityMatch::Exact; + } + return UtilityMatch::None; + } + // Check the utility starts with the (partial) target. + if utility_text.starts_with(target) && utility_text != target { + return UtilityMatch::Partial; + } + UtilityMatch::None + } +} + +struct UtilityInfo { + layer: String, + index: usize, +} + +fn get_utility_info( + utility_config: &UtilitiesConfig, + utility_data: &ClassSegmentStructure, +) -> Option { + // Arbitrary CSS utilities always go in the "arbitrary" layer, at index 0. + // This layer is always at the end, and the order of the utilities in it is not + // determined at this point, so they all have the same index. + if utility_data.arbitrary { + return Some(UtilityInfo { + layer: "arbitrary".to_string(), + index: 0, + }); + } + + let utility_text = utility_data.text.as_str(); + let mut layer: &str = ""; + let mut match_index: usize = 0; + let mut last_size: usize = 0; + + // Iterate over each layer, looking for a match. + for layer_data in utility_config.iter() { + // Iterate over each target in the layer, looking for a match. + for (index, target) in layer_data.classes.iter().enumerate() { + match UtilityMatch::from(target, utility_text) { + UtilityMatch::Exact => { + // Exact matches can be returned immediately. + return Some(UtilityInfo { + layer: layer_data.name.clone(), + index: index + 1, + }); + } + UtilityMatch::Partial => { + // Multiple partial matches can occur, so we need to keep looking to find + // the longest target that matches. For example, if the utility text is + // `gap-x-4`, and there are targets like `gap-` and `gap-x-`, we want to + // make sure that the `gap-x-` target is matched as it is more specific, + // regardless of the order in which the targets are defined. + let target_size = target.chars().count(); + if target_size > last_size { + layer = &layer_data.name; + match_index = index; + last_size = target_size; + } + } + _ => {} + } + } + } + if layer != "" { + return Some(UtilityInfo { + layer: layer.to_string(), + index: match_index, + }); + } + None +} + +// classes +// ------- + +/// Information about a CSS class. +#[derive(Debug)] +pub struct ClassInfo { + pub text: String, + pub variant_weight: Option, // TODO: this will need to be Option + pub layer_index: usize, + pub utility_index: usize, +} + +/// Computes the information about a CSS class. +pub fn get_class_info(class_name: &str, sort_config: &SortConfig) -> Option { + let utility_data = parse_class(class_name); + let utility_info = get_utility_info(&sort_config.utilities, &utility_data.utility); + if let Some(utility_info) = utility_info { + return Some(ClassInfo { + text: class_name.to_string(), + variant_weight: if utility_data.variants.is_empty() { + None + } else { + Some(0) // TODO: actually compute variant weight + }, + layer_index: *sort_config.layer_index_map.get(&utility_info.layer)?, + utility_index: utility_info.index, + }); + } + // If there is no utility info, that means the class is not recognized as a utility, + // and it is a custom class instead. + None +} diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_parser.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_parser.rs index d36d3afa75a6..0ec7392de448 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_parser.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_parser.rs @@ -48,23 +48,23 @@ enum CharKind { Backslash, } -/// Information about a segment of a CSS class (variant or utility). +/// Information about the structure of a segment of a CSS class (variant or utility). #[derive(Debug)] -pub struct UtilitySegmentData { - arbitrary: bool, - text: String, +pub struct ClassSegmentStructure { + pub arbitrary: bool, + pub text: String, } -/// Information about a CSS class. +/// Information about the structure of a CSS class. #[derive(Debug)] -pub struct UtilityData { - variants: Vec, - utility: UtilitySegmentData, +pub struct ClassStructure { + pub variants: Vec, + pub utility: ClassSegmentStructure, } -/// Parses a CSS class into a utility data structure, containing a list of variants and the +/// Parses a CSS class into a class structure, containing a list of variants and the /// utility itself. -pub fn parse_class(class_name: &str) -> UtilityData { +pub fn parse_class(class_name: &str) -> ClassStructure { // state let mut arbitrary_block_depth = 0; let mut at_arbitrary_block_start = false; @@ -145,9 +145,9 @@ pub fn parse_class(class_name: &str) -> UtilityData { }; last_char = next_last_char; } - let mut variants: Vec = split_at_indexes(class_name, &delimiter_indexes) + let mut variants: Vec = split_at_indexes(class_name, &delimiter_indexes) .iter() - .map(|&s| UtilitySegmentData { + .map(|&s| ClassSegmentStructure { arbitrary: s.starts_with('['), text: s.to_string(), }) @@ -156,5 +156,5 @@ pub fn parse_class(class_name: &str) -> UtilityData { .pop() .expect("TODO: error message (this should never happen)"); - UtilityData { variants, utility } + ClassStructure { variants, utility } } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs index 2c571ded72ce..8a56702dca71 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs @@ -4,103 +4,12 @@ use biome_deserialize::{ }; use biome_rowan::TextRange; -use super::presets::{get_utilities_preset, UseSortedClassesPreset}; - const CLASS_ATTRIBUTES: [&str; 2] = ["class", "className"]; -const ALLOWED_PRESETS: &[&str] = &["no-preset", "tailwind-css"]; - -pub struct UtilityLayer { - pub layer: String, - pub classes: Vec, -} - -impl Deserializable for UtilityLayer { - fn deserialize( - value: &impl DeserializableValue, - name: &str, - diagnostics: &mut Vec, - ) -> Option { - value.deserialize(UtilityLayerVisitor, name, diagnostics) - } -} - -struct UtilityLayerVisitor; -impl DeserializationVisitor for UtilityLayerVisitor { - type Output = UtilityLayer; - - const EXPECTED_TYPE: VisitableType = VisitableType::MAP; - - fn visit_map( - self, - members: impl Iterator>, - _range: TextRange, - _name: &str, - diagnostics: &mut Vec, - ) -> Option { - let mut layer: Option = None; - let mut classes: Option> = None; - const ALLOWED_OPTIONS: &[&str] = &["layer", "classes"]; - - for (key, value) in members.flatten() { - let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { - continue; - }; - match key_text.text() { - "layer" => { - if let Some(layer_option) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - layer = Some(layer_option); - } - } - "classes" => { - if let Some(classes_option) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - classes = Some(classes_option); - } - } - unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key( - unknown_key, - key.range(), - ALLOWED_OPTIONS, - )), - } - } - - let missing_layer = layer.is_none(); - let missing_classes = classes.is_none(); - - if missing_layer || missing_classes { - let mut missing_keys: Vec<&str> = Vec::new(); - if missing_layer { - missing_keys.push("layer"); - } - if missing_classes { - missing_keys.push("classes"); - } - let missing_keys = missing_keys.join(", "); - // TODO: how to actually handle this? - diagnostics.push(DeserializationDiagnostic::new(format!( - "Missing {}.", - missing_keys - ))); - - None - } else { - Some(UtilityLayer { - layer: layer.expect("TODO: error message (this should never happen)"), - classes: classes.expect("TODO: error message (this should never happen)"), - }) - } - } -} #[derive(Debug, Clone)] pub struct UseSortedClassesOptions { pub attributes: Vec, pub functions: Vec, - pub utilities: Vec, } impl Default for UseSortedClassesOptions { @@ -108,12 +17,11 @@ impl Default for UseSortedClassesOptions { UseSortedClassesOptions { attributes: CLASS_ATTRIBUTES.iter().map(|&s| s.to_string()).collect(), functions: Vec::new(), - utilities: Vec::new(), } } } -const ALLOWED_OPTIONS: &[&str] = &["attributes", "functions", "preset", "utilities"]; +const ALLOWED_OPTIONS: &[&str] = &["attributes", "functions"]; impl Deserializable for UseSortedClassesOptions { fn deserialize( @@ -139,8 +47,9 @@ impl DeserializationVisitor for UseSortedClassesOptionsVisitor { diagnostics: &mut Vec, ) -> Option { let mut result = UseSortedClassesOptions::default(); - let mut preset: UseSortedClassesPreset = UseSortedClassesPreset::TailwindCSS; - let mut utilities_option: Option> = None; + result + .attributes + .extend(CLASS_ATTRIBUTES.iter().map(|&s| s.to_string())); for (key, value) in members.flatten() { let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { @@ -162,36 +71,6 @@ impl DeserializationVisitor for UseSortedClassesOptionsVisitor { result.functions = functions; } } - "preset" => { - if let Some(preset_option) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - let preset_option: String = preset_option; // TODO: is there a better way to do this? - let preset_option = preset_option.as_str(); - match preset_option { - "tailwind-css" => { - preset = UseSortedClassesPreset::TailwindCSS; - } - "no-preset" => { - preset = UseSortedClassesPreset::None; - } - _ => { - diagnostics.push(DeserializationDiagnostic::new_unknown_value( - preset_option, - value.range(), - ALLOWED_PRESETS, - )); - } - } - } - } - "utilities" => { - if let Some(utilities_opt) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - utilities_option = Some(utilities_opt); - } - } unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key( unknown_key, key.range(), @@ -200,18 +79,6 @@ impl DeserializationVisitor for UseSortedClassesOptionsVisitor { } } - let resolved_utilities = match utilities_option { - Some(utilities) => utilities, - None => get_utilities_preset(&preset), - }; - result.utilities = resolved_utilities - .iter() - .flat_map(|layer| { - // TODO: extend layer here - layer.classes.clone() - }) - .collect(); - Some(result) } } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs index 1ac08ede47f2..b5a187ae5394 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs @@ -1,13 +1,15 @@ -use super::options::UtilityLayer; +// Presets contain pre-defined sort configurations, notably from Tailwind CSS. -#[derive(Debug, Default, Clone)] +use super::sort_config::{UtilitiesConfig, UtilityLayer}; + +#[derive(Default)] pub enum UseSortedClassesPreset { None, #[default] TailwindCSS, } -pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> Vec { +pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> UtilitiesConfig { match preset { UseSortedClassesPreset::None => { vec![] @@ -16,11 +18,11 @@ pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> Vec Option { - if spec.ends_with('$') && class_name == &spec[..spec.len() - 1] { - return Some(true); - } - if class_name.starts_with(spec) && class_name != spec.as_str() { - return Some(false); +use super::{ + class_info::{get_class_info, ClassInfo}, + sort_config::SortConfig, +}; + +// sort +// ---- + +impl ClassInfo { + /// Compare based on the existence of variants. Classes with variants go last. + /// Returns `None` if both or none of the classes has variants. + fn cmp_has_variants(&self, other: &ClassInfo) -> Option { + if self.variant_weight.is_some() && other.variant_weight.is_some() { + return None; + } + if self.variant_weight.is_some() { + return Some(Ordering::Greater); + } + if other.variant_weight.is_some() { + return Some(Ordering::Less); + } + None } - None -} -fn find_utilities_index(utilities: &[String], class_name: &str) -> Option { - let mut matched = false; - let mut match_index: usize = 0; - let mut last_size: usize = 0; - for (i, spec) in utilities.iter().enumerate() { - match get_utilities_match(spec, class_name) { - Some(true) => return Some(i), - Some(false) => { - let spec_size = spec.chars().count(); - if spec_size > last_size { - match_index = i; - last_size = spec_size; - matched = true; - } - } - _ => {} + /// Compare based on layer indexes. Classes with lower indexes go first. + /// Returns `None` if the indexes are equal. + fn cmp_layers(&self, other: &ClassInfo) -> Option { + let result = self.layer_index.cmp(&other.layer_index); + if result != Ordering::Equal { + return Some(result); } + None } - if matched { - Some(match_index) - } else { + + /// Compare based on variants weight. Classes with higher weight go first. + /// Returns `None` if they have the same weight. + fn cmp_variants_weight(&self, other: &ClassInfo) -> Option { + // TODO: implement variant weight comparison. + None + } + + /// Compare based on utility index. Classes with lower indexes go first. + /// Returns `None` if the indexes are equal. + fn cmp_utilities(&self, other: &ClassInfo) -> Option { + let result = self.utility_index.cmp(&other.utility_index); + if result != Ordering::Equal { + return Some(result); + } None } } -// TODO: detect arbitrary css (e.g. [background:red]), put at the end -pub fn sort_class_name(class_name: &str, utilities: &Vec) -> String { +// See: https://github.com/tailwindlabs/tailwindcss/blob/970f2ca704dda95cf328addfe67b81d6679c8755/src/lib/offsets.js#L206 +// This comparison function follows a similar logic to the one in Tailwind CSS, with some +// simplifications and necessary differences. +fn compare_classes(a: &ClassInfo, b: &ClassInfo) -> Ordering { + // Classes with variants go last. + if let Some(has_variants_order) = a.cmp_has_variants(b) { + return has_variants_order; + } + + // Compare utility layers. + if let Some(layers_order) = a.cmp_layers(b) { + return layers_order; + } + + // TODO: sort screens at this point. + + // Compare variant weights. + if let Some(variants_weight_order) = a.cmp_variants_weight(b) { + return variants_weight_order; + } + + // Compare utility indexes. + if let Some(utilities_order) = a.cmp_utilities(b) { + return utilities_order; + } + + Ordering::Equal +} + +/// Sort the given class string according to the given sort config. +pub fn sort_class_name(class_name: &str, sort_config: &SortConfig) -> String { + // Obtain classes by splitting the class string by whitespace. let classes = class_name.split_whitespace().collect::>(); - let mut unordered_classes: Vec<&str> = Vec::new(); - let mut utilities_map: FxHashMap> = FxHashMap::default(); - for class in classes { - match find_utilities_index(utilities, class) { - Some(index) => { - utilities_map.entry(index).or_default().push(class); + + // Separate custom classes from recognized classes, and compute the recognized classes' info. + // Custom classes always go first, in the order that they appear in. + let mut sorted_classes: Vec<&str> = Vec::new(); + let mut classes_info: Vec = Vec::new(); + classes + .iter() + .for_each(|&class| match get_class_info(class, sort_config) { + Some(class_info) => { + classes_info.push(class_info); } None => { - unordered_classes.push(class); + sorted_classes.push(class); } - } - } - let mut sorted_classes: Vec<&str> = unordered_classes; - for i in 0..utilities.len() { - if let Some(classes) = utilities_map.get(&i) { - let mut abc_classes = classes.clone(); - abc_classes.sort_unstable(); - sorted_classes.extend(abc_classes); - } - } + }); + + // Pre-sort the recognized classes lexico-graphically. + classes_info.sort_unstable_by(|a, b| a.text.cmp(&b.text)); + + // Sort recognized classes using the compare function. Needs to be a stable sort to + // preserve the lexico-graphical order as a fallback. + classes_info.sort_by(compare_classes); + + // Add the sorted classes to the result. + sorted_classes.extend( + classes_info + .iter() + .map(|class_info| class_info.text.as_str()), + ); + + // Join the classes back into a string. sorted_classes.join(" ") } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs new file mode 100644 index 000000000000..1ffd47bdcc96 --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs @@ -0,0 +1,44 @@ +// The following structures define the data required to compute the information about +// CSS classes that is later used to compare them and sort them. + +use std::collections::HashMap; + +/// A utility layer, containing its name and an ordered list of classes. +pub struct UtilityLayer { + pub name: String, + pub classes: Vec, +} + +/// The utilities config, contains an ordered list of utility layers. +pub type UtilitiesConfig = Vec; + +/// The variants config, contains an ordered list of variants. +pub type VariantsConfig = Vec; + +/// The sort config, containing the utility config and the variant config. +pub struct SortConfig { + pub utilities: UtilitiesConfig, + pub variants: VariantsConfig, + pub layer_index_map: HashMap, +} + +impl SortConfig { + /// Creates a new sort config. + pub fn new(utilities_config: UtilitiesConfig, variants: VariantsConfig) -> Self { + // Compute the layer index map. + let mut layer_index_map: HashMap = HashMap::new(); + let mut last_index = 0; + layer_index_map.insert("parasite".to_string(), last_index); + for layer in utilities_config.iter() { + last_index += 1; + layer_index_map.insert(layer.name.clone(), last_index); + } + layer_index_map.insert("arbitrary".to_string(), last_index + 1); + + Self { + utilities: utilities_config, + variants, + layer_index_map, + } + } +} From e2672fe55ecae66f07e139a9b006901f3b8b663d Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Sun, 31 Dec 2023 23:01:53 +0100 Subject: [PATCH 07/82] use declare_node_union --- .../nursery/use_sorted_classes.rs | 12 ++--- .../use_sorted_classes/class_like_visitor.rs | 46 +++++++++---------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index c0a7111b4841..1c9dbe4c1baa 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -7,18 +7,18 @@ mod sort; mod sort_config; use biome_analyze::{ - context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, + context::RuleContext, declare_rule, ActionCategory, Ast, FixKind, Rule, RuleDiagnostic, }; use biome_console::markup; use biome_diagnostics::Applicability; use biome_js_factory::make::{js_string_literal, js_string_literal_expression, jsx_string}; -use biome_rowan::BatchMutationExt; +use biome_rowan::{AstNode, BatchMutationExt}; use crate::JsRuleAction; pub use self::options::UseSortedClassesOptions; use self::{ - class_like_visitor::ClassStringLike, + class_like_visitor::AnyClassStringLike, presets::{get_utilities_preset, UseSortedClassesPreset}, sort::sort_class_name, sort_config::SortConfig, @@ -58,7 +58,7 @@ declare_rule! { // ---- impl Rule for UseSortedClasses { - type Query = ClassStringLike; + type Query = Ast; type State = String; type Signals = Option; type Options = UseSortedClassesOptions; @@ -95,7 +95,7 @@ impl Rule for UseSortedClasses { fn action(ctx: &RuleContext, state: &Self::State) -> Option { let mut mutation = ctx.root().begin(); match ctx.query() { - ClassStringLike::StringLiteral(string_literal) => { + AnyClassStringLike::JsStringLiteralExpression(string_literal) => { let replacement = js_string_literal_expression(js_string_literal(state)); mutation.replace_node(string_literal.clone(), replacement); Some(JsRuleAction { @@ -108,7 +108,7 @@ impl Rule for UseSortedClasses { mutation, }) } - ClassStringLike::JsxString(jsx_string_node) => { + AnyClassStringLike::JsxString(jsx_string_node) => { let replacement = jsx_string(js_string_literal(state)); mutation.replace_node(jsx_string_node.clone(), replacement); Some(JsRuleAction { diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs index b6b447b68903..6066b05a9d97 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs @@ -6,10 +6,13 @@ use biome_js_syntax::{ JsCallArguments, JsCallExpression, JsLanguage, JsStringLiteralExpression, JsTemplateChunkElement, JsxAttribute, JsxString, }; -use biome_rowan::{AstNode, Language, SyntaxNode, TextRange, WalkEvent}; +use biome_rowan::{declare_node_union, AstNode, Language, SyntaxNode, TextRange, WalkEvent}; use super::UseSortedClassesOptions; +// utils +// ----- + fn get_options_from_analyzer(analyzer_options: &AnalyzerOptions) -> UseSortedClassesOptions { match analyzer_options .configuration @@ -83,14 +86,16 @@ impl Visitor for StringLiteralInAttributeVisitor { // When entering a JSX string node, and we are in a valid attribute, emit. if let Some(jsx_string) = JsxString::cast_ref(node) { if self.in_valid_attribute { - ctx.match_query(ClassStringLike::JsxString(jsx_string)); + ctx.match_query(AnyClassStringLike::JsxString(jsx_string)); } } // When entering a string literal node, and we are in a valid attribute, emit. if let Some(string_literal) = JsStringLiteralExpression::cast_ref(node) { if self.in_valid_attribute { - ctx.match_query(ClassStringLike::StringLiteral(string_literal)); + ctx.match_query(AnyClassStringLike::JsStringLiteralExpression( + string_literal, + )); } } } @@ -143,7 +148,9 @@ impl Visitor for StringLiteralInCallExpressionVisitor { // When entering a string literal node, and we are in a valid function and in arguments, emit. if let Some(string_literal) = JsStringLiteralExpression::cast_ref(node) { if self.in_valid_function && self.in_arguments { - ctx.match_query(ClassStringLike::StringLiteral(string_literal)); + ctx.match_query(AnyClassStringLike::JsStringLiteralExpression( + string_literal, + )); } } } @@ -165,45 +172,36 @@ impl Visitor for StringLiteralInCallExpressionVisitor { // query // ----- -#[derive(Clone)] -pub(crate) enum ClassStringLike { - StringLiteral(JsStringLiteralExpression), - JsxString(JsxString), - TemplateChunk(JsTemplateChunkElement), +declare_node_union! { + pub AnyClassStringLike = JsStringLiteralExpression | JsxString | JsTemplateChunkElement } -impl ClassStringLike { - pub fn range(&self) -> TextRange { - match self { - ClassStringLike::StringLiteral(string_literal) => string_literal.range(), - ClassStringLike::JsxString(jsx_string) => jsx_string.range(), - ClassStringLike::TemplateChunk(template_chunk) => template_chunk.range(), - } - } - +impl AnyClassStringLike { pub fn value(&self) -> Option { match self { - ClassStringLike::StringLiteral(string_literal) => { + AnyClassStringLike::JsStringLiteralExpression(string_literal) => { Some(string_literal.inner_string_text().ok()?.to_string()) } - ClassStringLike::JsxString(jsx_string) => { + AnyClassStringLike::JsxString(jsx_string) => { Some(jsx_string.inner_string_text().ok()?.to_string()) } - ClassStringLike::TemplateChunk(template_chunk) => Some(template_chunk.to_string()), + AnyClassStringLike::JsTemplateChunkElement(template_chunk) => { + Some(template_chunk.to_string()) + } } } } -impl QueryMatch for ClassStringLike { +impl QueryMatch for AnyClassStringLike { fn text_range(&self) -> TextRange { self.range() } } -impl Queryable for ClassStringLike { +impl Queryable for AnyClassStringLike { type Input = Self; type Language = JsLanguage; - type Output = ClassStringLike; + type Output = AnyClassStringLike; type Services = (); fn build_visitor( From 737005a3f9e9107c82e2c2a8d944d9409e65f77e Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Mon, 1 Jan 2024 21:55:10 +0100 Subject: [PATCH 08/82] cleanup, docs, unit test TODOs --- .../nursery/use_sorted_classes.rs | 15 ++---- ...ke_visitor.rs => any_class_string_like.rs} | 46 ++++++++-------- .../nursery/use_sorted_classes/class_info.rs | 52 +++++++++++++++---- .../{class_parser.rs => class_lexer.rs} | 27 ++++++---- .../nursery/use_sorted_classes/options.rs | 3 ++ .../nursery/use_sorted_classes/presets.rs | 9 ++-- .../nursery/use_sorted_classes/sort.rs | 7 ++- .../nursery/use_sorted_classes/sort_config.rs | 8 ++- 8 files changed, 108 insertions(+), 59 deletions(-) rename crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/{class_like_visitor.rs => any_class_string_like.rs} (79%) rename crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/{class_parser.rs => class_lexer.rs} (84%) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index 1c9dbe4c1baa..92a8d78cb0c9 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -1,6 +1,6 @@ +mod any_class_string_like; mod class_info; -mod class_like_visitor; -mod class_parser; +mod class_lexer; mod options; mod presets; mod sort; @@ -18,15 +18,12 @@ use crate::JsRuleAction; pub use self::options::UseSortedClassesOptions; use self::{ - class_like_visitor::AnyClassStringLike, + any_class_string_like::AnyClassStringLike, presets::{get_utilities_preset, UseSortedClassesPreset}, sort::sort_class_name, sort_config::SortConfig, }; -// rule metadata -// ------------- - declare_rule! { /// Enforce the sorting of CSS classes. /// @@ -54,9 +51,6 @@ declare_rule! { } } -// rule -// ---- - impl Rule for UseSortedClasses { type Query = Ast; type State = String; @@ -64,7 +58,6 @@ impl Rule for UseSortedClasses { type Options = UseSortedClassesOptions; fn run(ctx: &RuleContext) -> Option { - let value = ctx.query().value()?; // TODO: unsure if options are needed here. The sort config should ideally be created once // from the options and then reused for all queries. // let options = &ctx.options(); @@ -74,6 +67,8 @@ impl Rule for UseSortedClasses { get_utilities_preset(&UseSortedClassesPreset::default()), Vec::new(), ); + + let value = ctx.query().value()?; let sorted_value = sort_class_name(value.as_str(), &sort_config); if value != sorted_value { Some(sorted_value) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs similarity index 79% rename from crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs rename to crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs index 6066b05a9d97..6051b8431486 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_like_visitor.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs @@ -38,12 +38,12 @@ fn get_callee_name(call_expression: &JsCallExpression) -> Option { ) } -fn is_call_expression_of_valid_function( +fn is_call_expression_of_target_function( call_expression: &JsCallExpression, - functions: &[String], + target_functions: &[String], ) -> bool { match get_callee_name(call_expression) { - Some(name) => functions.contains(&name.to_string()), + Some(name) => target_functions.contains(&name.to_string()), None => false, } } @@ -52,9 +52,9 @@ fn get_attribute_name(attribute: &JsxAttribute) -> Option { Some(attribute.name().ok()?.as_jsx_name()?.to_string()) } -fn is_valid_attribute(attribute: &JsxAttribute, attributes: &[String]) -> bool { +fn is_target_attribute(attribute: &JsxAttribute, target_attributes: &[String]) -> bool { match get_attribute_name(attribute) { - Some(name) => attributes.contains(&name.to_string()), + Some(name) => target_attributes.contains(&name.to_string()), None => false, } } @@ -64,12 +64,12 @@ fn is_valid_attribute(attribute: &JsxAttribute, attributes: &[String]) -> bool { #[derive(Default)] struct StringLiteralInAttributeVisitor { - in_valid_attribute: bool, + in_target_attribute: bool, } +// Finds class-like strings in JSX attributes, including class, className, and others defined in the options. impl Visitor for StringLiteralInAttributeVisitor { type Language = JsLanguage; - fn visit( &mut self, event: &WalkEvent>, @@ -78,21 +78,21 @@ impl Visitor for StringLiteralInAttributeVisitor { let options = get_options_from_analyzer(ctx.options); match event { WalkEvent::Enter(node) => { - // When entering an attribute node, track if we are in a valid attribute. + // When entering an attribute node, track if we are in a target attribute. if let Some(attribute) = JsxAttribute::cast_ref(node) { - self.in_valid_attribute = is_valid_attribute(&attribute, &options.attributes); + self.in_target_attribute = is_target_attribute(&attribute, &options.attributes); } - // When entering a JSX string node, and we are in a valid attribute, emit. + // When entering a JSX string node, and we are in a target attribute, emit. if let Some(jsx_string) = JsxString::cast_ref(node) { - if self.in_valid_attribute { + if self.in_target_attribute { ctx.match_query(AnyClassStringLike::JsxString(jsx_string)); } } - // When entering a string literal node, and we are in a valid attribute, emit. + // When entering a string literal node, and we are in a target attribute, emit. if let Some(string_literal) = JsStringLiteralExpression::cast_ref(node) { - if self.in_valid_attribute { + if self.in_target_attribute { ctx.match_query(AnyClassStringLike::JsStringLiteralExpression( string_literal, )); @@ -100,9 +100,9 @@ impl Visitor for StringLiteralInAttributeVisitor { } } WalkEvent::Leave(node) => { - // When leaving an attribute node, reset in_valid_attribute. + // When leaving an attribute node, reset in_target_attribute. if JsxAttribute::cast_ref(node).is_some() { - self.in_valid_attribute = false; + self.in_target_attribute = false; } } } @@ -114,10 +114,11 @@ impl Visitor for StringLiteralInAttributeVisitor { #[derive(Default)] struct StringLiteralInCallExpressionVisitor { - in_valid_function: bool, + in_target_function: bool, in_arguments: bool, } +// Finds class-like strings inside function calls defined in the options, e.g. clsx(classes). impl Visitor for StringLiteralInCallExpressionVisitor { type Language = JsLanguage; @@ -132,11 +133,11 @@ impl Visitor for StringLiteralInCallExpressionVisitor { } match event { WalkEvent::Enter(node) => { - // When entering a call expression node, track if we are in a valid function and reset + // When entering a call expression node, track if we are in a target function and reset // in_arguments. if let Some(call_expression) = JsCallExpression::cast_ref(node) { - self.in_valid_function = - is_call_expression_of_valid_function(&call_expression, &options.functions); + self.in_target_function = + is_call_expression_of_target_function(&call_expression, &options.functions); self.in_arguments = false; } @@ -145,9 +146,9 @@ impl Visitor for StringLiteralInCallExpressionVisitor { self.in_arguments = true; } - // When entering a string literal node, and we are in a valid function and in arguments, emit. + // When entering a string literal node, and we are in a target function and in arguments, emit. if let Some(string_literal) = JsStringLiteralExpression::cast_ref(node) { - if self.in_valid_function && self.in_arguments { + if self.in_target_function && self.in_arguments { ctx.match_query(AnyClassStringLike::JsStringLiteralExpression( string_literal, )); @@ -167,16 +168,19 @@ impl Visitor for StringLiteralInCallExpressionVisitor { // functions (template chunk) visitor // ---------------------------------- +// Finds class-like template chunks in tagged template calls defined in the options, e.g. tw`classes`. // TODO: template chunk visitor // query // ----- declare_node_union! { + /// A string literal, JSX string, or template chunk representing a CSS class string. pub AnyClassStringLike = JsStringLiteralExpression | JsxString | JsTemplateChunkElement } impl AnyClassStringLike { + /// Returns the value of the string literal, JSX string, or template chunk. pub fn value(&self) -> Option { match self { AnyClassStringLike::JsStringLiteralExpression(string_literal) => { diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs index 6e17ea36f3ae..a6677ecb07bb 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs @@ -1,18 +1,35 @@ +// Each CSS class needs to be processed to determine the information that will be used to sort it. +// This information includes: +// - The layer it belongs to (e.g. `components` or `utilities`). +// - The index of the utility within the layer. +// - The total variants weight that results from the combination of all the variants. +// - The text of the class itself. +// It is generated according to the information contained in a `SortConfig`, which includes: +// - The list of layers, in order. +// - The list of utilities, in order, for each layer. +// - The list of variants, in order of importance (which is used to compute the variants weight). +// - Other options, such as prefix and separator. + use super::{ - class_parser::{parse_class, ClassSegmentStructure}, + class_lexer::{tokenize_class, ClassSegmentStructure}, sort_config::{SortConfig, UtilitiesConfig}, }; // utilities // --------- +/// The result of matching a utility against a target. enum UtilityMatch { + /// The utility matches an exact target. Exact, + /// The utility matches a partial target. Partial, + /// The utility does not match the target. None, } impl UtilityMatch { + /// Checks if a utility matches a target, and returns the result. fn from(target: &String, utility_text: &str) -> UtilityMatch { // If the target ends with `$`, then it's an exact target. if target.ends_with('$') { @@ -22,19 +39,27 @@ impl UtilityMatch { } return UtilityMatch::None; } - // Check the utility starts with the (partial) target. + // Check if the utility starts with the (partial) target. if utility_text.starts_with(target) && utility_text != target { return UtilityMatch::Partial; } + // If all of the above checks fail, there is no match. UtilityMatch::None } } +// TODO: unit tests. + +/// Sort-related information about a utility. struct UtilityInfo { + /// The layer the utility belongs to. layer: String, + /// The index of the utility within the layer. index: usize, } +/// Computes sort-related information about a CSS utility. If the utility is not recognized, +/// `None` is returned. fn get_utility_info( utility_config: &UtilitiesConfig, utility_data: &ClassSegmentStructure, @@ -63,7 +88,7 @@ fn get_utility_info( // Exact matches can be returned immediately. return Some(UtilityInfo { layer: layer_data.name.clone(), - index: index + 1, + index, }); } UtilityMatch::Partial => { @@ -92,21 +117,28 @@ fn get_utility_info( None } +// TODO: unit tests. + // classes // ------- -/// Information about a CSS class. +/// Sort-related information about a CSS class. #[derive(Debug)] pub struct ClassInfo { + /// The full text of the class itself. pub text: String, - pub variant_weight: Option, // TODO: this will need to be Option + /// The total variants weight that results from the combination of all the variants. + pub variant_weight: Option, // TODO: this will need to be Option + /// The layer the utility belongs to. pub layer_index: usize, + /// The index of the utility within the layer. pub utility_index: usize, } -/// Computes the information about a CSS class. +/// Computes sort-related information about a CSS class. If the class is not recognized as a utility, +/// it is considered a custom class instead and `None` is returned. pub fn get_class_info(class_name: &str, sort_config: &SortConfig) -> Option { - let utility_data = parse_class(class_name); + let utility_data = tokenize_class(class_name); let utility_info = get_utility_info(&sort_config.utilities, &utility_data.utility); if let Some(utility_info) = utility_info { return Some(ClassInfo { @@ -114,13 +146,15 @@ pub fn get_class_info(class_name: &str, sort_config: &SortConfig) -> Option(s: &'a str, indexes: &[usize]) -> Vec<&'a str> { @@ -23,6 +33,8 @@ fn split_at_indexes<'a>(s: &'a str, indexes: &[usize]) -> Vec<&'a str> { segments } +// TODO: unit tests. + #[derive(Debug, Clone, PartialEq)] enum Quote { Single, @@ -62,18 +74,17 @@ pub struct ClassStructure { pub utility: ClassSegmentStructure, } -/// Parses a CSS class into a class structure, containing a list of variants and the +/// Processes a CSS class into a class structure, containing a list of variants and the /// utility itself. -pub fn parse_class(class_name: &str) -> ClassStructure { - // state +pub fn tokenize_class(class_name: &str) -> ClassStructure { + // TODO: add custom separator argument (currently hardcoded to `:`). let mut arbitrary_block_depth = 0; let mut at_arbitrary_block_start = false; let mut quoted_arbitrary_block_type: Option = None; let mut last_char = CharKind::Other; let mut delimiter_indexes: Vec = Vec::new(); - // loop - for (index, c) in class_name.chars().enumerate() { + for (index, c) in class_name.char_indices() { let mut next_last_char = CharKind::Other; let mut is_start_of_arbitrary_block = false; match c { @@ -89,10 +100,6 @@ pub fn parse_class(class_name: &str) -> ClassStructure { '\'' | '"' | '`' => { if at_arbitrary_block_start { quoted_arbitrary_block_type = Quote::from_char(c); - if quoted_arbitrary_block_type.is_none() { - // Sanity check. - panic!("TODO: error message (this should never happen)"); - } } else if let CharKind::Backslash = last_char { // Escaped, ignore. } else { @@ -158,3 +165,5 @@ pub fn parse_class(class_name: &str) -> ClassStructure { ClassStructure { variants, utility } } + +// TODO: unit tests. diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs index 8a56702dca71..52a0a9e71b18 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs @@ -4,11 +4,14 @@ use biome_deserialize::{ }; use biome_rowan::TextRange; +/// Attributes that are always targets. const CLASS_ATTRIBUTES: [&str; 2] = ["class", "className"]; #[derive(Debug, Clone)] pub struct UseSortedClassesOptions { + /// Additional attribute targets specified by the user. pub attributes: Vec, + /// Function targets specified by the user. pub functions: Vec, } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs index b5a187ae5394..d46ba3f826e0 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs @@ -1,4 +1,5 @@ -// Presets contain pre-defined sort configurations, notably from Tailwind CSS. +// Presets contain pre-defined sort configurations, notably from Tailwind CSS. They are a +// starting point that can be extended (e.g. by adding custom utilities or variants). use super::sort_config::{UtilitiesConfig, UtilityLayer}; @@ -6,7 +7,7 @@ use super::sort_config::{UtilitiesConfig, UtilityLayer}; pub enum UseSortedClassesPreset { None, #[default] - TailwindCSS, + TailwindCSS, // TODO: should this be the default? } pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> UtilitiesConfig { @@ -15,7 +16,7 @@ pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> UtilitiesConfig vec![] } UseSortedClassesPreset::TailwindCSS => { - // TAILWIND-PRESET-START + // TAILWIND-UTILITIES-PRESET-START vec![ UtilityLayer { name: String::from("components"), @@ -594,7 +595,7 @@ pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> UtilitiesConfig ], }, ] - // TAILWIND-PRESET-END + // TAILWIND-UTILITIES-PRESET-END } } } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs index e04b1b4c07bd..7d3e20c8ed95 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs @@ -5,9 +5,6 @@ use super::{ sort_config::SortConfig, }; -// sort -// ---- - impl ClassInfo { /// Compare based on the existence of variants. Classes with variants go last. /// Returns `None` if both or none of the classes has variants. @@ -53,7 +50,7 @@ impl ClassInfo { } // See: https://github.com/tailwindlabs/tailwindcss/blob/970f2ca704dda95cf328addfe67b81d6679c8755/src/lib/offsets.js#L206 -// This comparison function follows a similar logic to the one in Tailwind CSS, with some +// This comparison function follows a very similar logic to the one in Tailwind CSS, with some // simplifications and necessary differences. fn compare_classes(a: &ClassInfo, b: &ClassInfo) -> Ordering { // Classes with variants go last. @@ -118,3 +115,5 @@ pub fn sort_class_name(class_name: &str, sort_config: &SortConfig) -> String { // Join the classes back into a string. sorted_classes.join(" ") } + +// TODO: unit tests. diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs index 1ffd47bdcc96..666bdade298e 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs @@ -1,5 +1,9 @@ -// The following structures define the data required to compute the information about -// CSS classes that is later used to compare them and sort them. +// The following structures define the config required to compute sort-related information about +// CSS classes (`ClassInfo`) that is later used to compare and sort them. A sort config includes: +// - The list of layers, in order. +// - The list of utilities, in order, for each layer. +// - The list of variants, in order of importance (which is used to compute the variants weight). +// - Other options, such as prefix and separator. use std::collections::HashMap; From 06cbfb34cf6adb934279aa929c162b79c4a73c1e Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 11 Jan 2024 13:11:35 +0000 Subject: [PATCH 09/82] resolve merge --- .../src/semantic_analyzers/nursery.rs | 2 ++ .../src/configuration/linter/rules.rs | 25 ++++++++++++++++--- .../src/configuration/parse/json/rules.rs | 8 ++++++ .../@biomejs/backend-jsonrpc/src/workspace.ts | 5 ++++ .../@biomejs/biome/configuration_schema.json | 7 ++++++ .../components/generated/NumberOfRules.astro | 2 +- .../src/content/docs/linter/rules/index.mdx | 1 + .../docs/linter/rules/use-sorted-classes.md | 6 ++++- 8 files changed, 51 insertions(+), 5 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs index 01e3f753974f..95bf2c4fbe3c 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs @@ -12,6 +12,7 @@ pub(crate) mod use_export_type; pub(crate) mod use_for_of; pub(crate) mod use_import_type; pub(crate) mod use_number_namespace; +pub(crate) mod use_sorted_classes; declare_group! { pub (crate) Nursery { @@ -27,6 +28,7 @@ declare_group! { self :: use_for_of :: UseForOf , self :: use_import_type :: UseImportType , self :: use_number_namespace :: UseNumberNamespace , + self :: use_sorted_classes :: UseSortedClasses , ] } } diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index e08747404bb2..8eedfb6cff0e 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2627,6 +2627,9 @@ pub struct Nursery { #[doc = "Enforce using function types instead of object type with call signatures."] #[serde(skip_serializing_if = "Option::is_none")] pub use_shorthand_function_type: Option, + #[doc = "Enforce the sorting of CSS classes."] + #[serde(skip_serializing_if = "Option::is_none")] + pub use_sorted_classes: Option, } impl MergeWith for Nursery { fn merge_with(&mut self, other: Nursery) { @@ -2702,6 +2705,9 @@ impl MergeWith for Nursery { if let Some(use_shorthand_function_type) = other.use_shorthand_function_type { self.use_shorthand_function_type = Some(use_shorthand_function_type); } + if let Some(use_sorted_classes) = other.use_sorted_classes { + self.use_sorted_classes = Some(use_sorted_classes); + } } fn merge_with_if_not_default(&mut self, other: Nursery) where @@ -2714,7 +2720,7 @@ impl MergeWith for Nursery { } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 24] = [ + pub(crate) const GROUP_RULES: [&'static str; 25] = [ "noDuplicateJsonKeys", "noEmptyBlockStatements", "noEmptyTypeParameters", @@ -2739,6 +2745,7 @@ impl Nursery { "useNodejsImportProtocol", "useNumberNamespace", "useShorthandFunctionType", + "useSortedClasses", ]; const RECOMMENDED_RULES: [&'static str; 11] = [ "noDuplicateJsonKeys", @@ -2766,7 +2773,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 24] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 25] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -2791,6 +2798,7 @@ impl Nursery { 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]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended(&self) -> bool { @@ -2927,6 +2935,11 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } + 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[24])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -3051,6 +3064,11 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } + 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[24])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3064,7 +3082,7 @@ impl Nursery { pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 11] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 24] { + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 25] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] @@ -3111,6 +3129,7 @@ impl Nursery { "useNodejsImportProtocol" => self.use_nodejs_import_protocol.as_ref(), "useNumberNamespace" => self.use_number_namespace.as_ref(), "useShorthandFunctionType" => self.use_shorthand_function_type.as_ref(), + "useSortedClasses" => self.use_sorted_classes.as_ref(), _ => None, } } diff --git a/crates/biome_service/src/configuration/parse/json/rules.rs b/crates/biome_service/src/configuration/parse/json/rules.rs index a299bd61b734..cd4aeb2c2ec8 100644 --- a/crates/biome_service/src/configuration/parse/json/rules.rs +++ b/crates/biome_service/src/configuration/parse/json/rules.rs @@ -1070,6 +1070,13 @@ impl Deserializable for Nursery { diagnostics, ); } + "useSortedClasses" => { + result.use_sorted_classes = Deserializable::deserialize( + &value, + "useSortedClasses", + diagnostics, + ); + } unknown_key => { diagnostics.push(DeserializationDiagnostic::new_unknown_key( unknown_key, @@ -1101,6 +1108,7 @@ impl Deserializable for Nursery { "useNodejsImportProtocol", "useNumberNamespace", "useShorthandFunctionType", + "useSortedClasses", ], )); } diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 7de3a6b5e6e4..fd645a87da36 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -954,6 +954,10 @@ export interface Nursery { * Enforce using function types instead of object type with call signatures. */ useShorthandFunctionType?: RuleConfiguration; + /** + * Enforce the sorting of CSS classes. + */ + useSortedClasses?: RuleConfiguration; } /** * A list of rules that belong to this group @@ -1690,6 +1694,7 @@ export type Category = | "lint/nursery/useNodejsImportProtocol" | "lint/nursery/useNumberNamespace" | "lint/nursery/useShorthandFunctionType" + | "lint/nursery/useSortedClasses" | "lint/performance/noAccumulatingSpread" | "lint/performance/noDelete" | "lint/security/noDangerouslySetInnerHtml" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index dfac87aea775..64f175357440 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1453,6 +1453,13 @@ { "$ref": "#/definitions/RuleConfiguration" }, { "type": "null" } ] + }, + "useSortedClasses": { + "description": "Enforce the sorting of CSS classes.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] } } }, diff --git a/website/src/components/generated/NumberOfRules.astro b/website/src/components/generated/NumberOfRules.astro index bcdbd5a4928e..e30ca4ab3133 100644 --- a/website/src/components/generated/NumberOfRules.astro +++ b/website/src/components/generated/NumberOfRules.astro @@ -1,2 +1,2 @@ -

Biome's linter has a total of 194 rules

\ No newline at end of file +

Biome's linter has a total of 195 rules

\ No newline at end of file diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index e341b025b765..7180f625fc75 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -256,3 +256,4 @@ Rules that belong to this group are not subject to semantic versionnode: protocol for Node.js builtin modules. | ⚠️ | | [useNumberNamespace](/linter/rules/use-number-namespace) | Use the Number properties instead of global ones. | ⚠️ | | [useShorthandFunctionType](/linter/rules/use-shorthand-function-type) | Enforce using function types instead of object type with call signatures. | 🔧 | +| [useSortedClasses](/linter/rules/use-sorted-classes) | Enforce the sorting of CSS classes. | 🔧 | diff --git a/website/src/content/docs/linter/rules/use-sorted-classes.md b/website/src/content/docs/linter/rules/use-sorted-classes.md index 18cf706fb636..f0549ab9aa1f 100644 --- a/website/src/content/docs/linter/rules/use-sorted-classes.md +++ b/website/src/content/docs/linter/rules/use-sorted-classes.md @@ -1,9 +1,13 @@ --- -title: useSortedClasses (since vnext) +title: useSortedClasses (not released) --- **Diagnostic Category: `lint/nursery/useSortedClasses`** +:::danger +This rule hasn't been released yet. +::: + :::caution This rule is part of the [nursery](/linter/rules/#nursery) group. ::: From e58fdb7855eba0a716261db3b05ac84e8320860d Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 11 Jan 2024 13:32:16 +0000 Subject: [PATCH 10/82] chore some code --- .../any_class_string_like.rs | 25 +++++++++---------- .../nursery/use_sorted_classes/class_info.rs | 22 ++++++++-------- .../nursery/use_sorted_classes/class_lexer.rs | 18 ++++++------- .../nursery/use_sorted_classes/options.rs | 3 +-- .../nursery/use_sorted_classes/presets.rs | 2 ++ .../nursery/use_sorted_classes/sort_config.rs | 12 ++++----- 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs index 6051b8431486..4f981e32f8b2 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs @@ -6,7 +6,9 @@ use biome_js_syntax::{ JsCallArguments, JsCallExpression, JsLanguage, JsStringLiteralExpression, JsTemplateChunkElement, JsxAttribute, JsxString, }; -use biome_rowan::{declare_node_union, AstNode, Language, SyntaxNode, TextRange, WalkEvent}; +use biome_rowan::{ + declare_node_union, AstNode, Language, SyntaxNode, TextRange, TokenText, WalkEvent, +}; use super::UseSortedClassesOptions; @@ -24,18 +26,15 @@ fn get_options_from_analyzer(analyzer_options: &AnalyzerOptions) -> UseSortedCla } } -fn get_callee_name(call_expression: &JsCallExpression) -> Option { - Some( - call_expression - .callee() - .ok()? - .as_js_identifier_expression()? - .name() - .ok()? - .name() - .ok()? - .to_string(), - ) +fn get_callee_name(call_expression: &JsCallExpression) -> Option { + call_expression + .callee() + .ok()? + .as_js_identifier_expression()? + .name() + .ok()? + .name() + .ok() } fn is_call_expression_of_target_function( diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs index a6677ecb07bb..16be9ba6870b 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs @@ -1,14 +1,14 @@ -// Each CSS class needs to be processed to determine the information that will be used to sort it. -// This information includes: -// - The layer it belongs to (e.g. `components` or `utilities`). -// - The index of the utility within the layer. -// - The total variants weight that results from the combination of all the variants. -// - The text of the class itself. -// It is generated according to the information contained in a `SortConfig`, which includes: -// - The list of layers, in order. -// - The list of utilities, in order, for each layer. -// - The list of variants, in order of importance (which is used to compute the variants weight). -// - Other options, such as prefix and separator. +//! Each CSS class needs to be processed to determine the information that will be used to sort it. +//! This information includes: +//! - The layer it belongs to (e.g. `components` or `utilities`). +//! - The index of the utility within the layer. +//! - The total variants weight that results from the combination of all the variants. +//! - The text of the class itself. +//! It is generated according to the information contained in a `SortConfig`, which includes: +//! - The list of layers, in order. +//! - The list of utilities, in order, for each layer. +//! - The list of variants, in order of importance (which is used to compute the variants weight). +//! - Other options, such as prefix and separator. use super::{ class_lexer::{tokenize_class, ClassSegmentStructure}, diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs index bd5bf29cf08f..ec9840a69a6f 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs @@ -1,12 +1,12 @@ -// CSS utility classes need to be lexed into segments, which represent the variants and the utility, -// and whether they are arbitrary or not. Some examples: -// - `px-2`: utility `px-2`. -// - `hover:px-2`: variant `hover`, utility `px-2`. -// - `sm:hover:px-2`: variant `sm`, variant `hover`, utility `px-2`. -// - `hover:[mask:circle]`: variant `hover`, utility `[mask:circle]` (arbitrary). -// - `[&:nth-child(3)]:px-2`: variant `[&:nth-child(3)]` (arbitrary), utility `px-2`. -// The results of the lexer are then used to process classes into `ClassInfo` structs, which are, in -// turn, used to sort the classes. +//! CSS utility classes need to be lexed into segments, which represent the variants and the utility, +//! and whether they are arbitrary or not. Some examples: +//! - `px-2`: utility `px-2`. +//! - `hover:px-2`: variant `hover`, utility `px-2`. +//! - `sm:hover:px-2`: variant `sm`, variant `hover`, utility `px-2`. +//! - `hover:[mask:circle]`: variant `hover`, utility `[mask:circle]` (arbitrary). +//! - `[&:nth-child(3)]:px-2`: variant `[&:nth-child(3)]` (arbitrary), utility `px-2`. +//! The results of the lexer are then used to process classes into `ClassInfo` structs, which are, in +//! turn, used to sort the classes. /// Splits a string into segments based on a list of indexes. The characters at the indexes are not /// included in the segments, as they are considered delimiters. diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs index 52a0a9e71b18..db93a5911d10 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs @@ -63,8 +63,7 @@ impl DeserializationVisitor for UseSortedClassesOptionsVisitor { if let Some(attributes_option) = Deserializable::deserialize(&value, &key_text, diagnostics) { - let attributes_option: Vec = attributes_option; // TODO: is there a better way to do this? - result.attributes.extend(attributes_option); + result.attributes.extend::>(attributes_option); } } "functions" => { diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs index d46ba3f826e0..d560d5251607 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs @@ -5,11 +5,13 @@ use super::sort_config::{UtilitiesConfig, UtilityLayer}; #[derive(Default)] pub enum UseSortedClassesPreset { + #[allow(unused)] None, #[default] TailwindCSS, // TODO: should this be the default? } +// TODO: make this static pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> UtilitiesConfig { match preset { UseSortedClassesPreset::None => { diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs index 666bdade298e..19aea269712e 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs @@ -1,9 +1,9 @@ -// The following structures define the config required to compute sort-related information about -// CSS classes (`ClassInfo`) that is later used to compare and sort them. A sort config includes: -// - The list of layers, in order. -// - The list of utilities, in order, for each layer. -// - The list of variants, in order of importance (which is used to compute the variants weight). -// - Other options, such as prefix and separator. +//! The following structures define the config required to compute sort-related information about +//! CSS classes (`ClassInfo`) that is later used to compare and sort them. A sort config includes: +//! - The list of layers, in order. +//! - The list of utilities, in order, for each layer. +//! - The list of variants, in order of importance (which is used to compute the variants weight). +//! - Other options, such as prefix and separator. use std::collections::HashMap; From 9ddba19091b59be07f9d98f84df56bca32675f8e Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 11 Jan 2024 17:13:10 +0000 Subject: [PATCH 11/82] shut down clippy --- .../src/semantic_analyzers/nursery/use_sorted_classes/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs index 7d3e20c8ed95..6b474b3da404 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs @@ -33,7 +33,7 @@ impl ClassInfo { /// Compare based on variants weight. Classes with higher weight go first. /// Returns `None` if they have the same weight. - fn cmp_variants_weight(&self, other: &ClassInfo) -> Option { + fn cmp_variants_weight(&self, _other: &ClassInfo) -> Option { // TODO: implement variant weight comparison. None } From e990013a4af2cdec9d8a9f7eb5e3cfe6ec529806 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Thu, 11 Jan 2024 22:35:20 +0100 Subject: [PATCH 12/82] public docs --- .../nursery/use_sorted_classes.rs | 82 ++++++++++++++++--- .../src/configuration/linter/rules.rs | 2 +- .../@biomejs/backend-jsonrpc/src/workspace.ts | 2 +- .../@biomejs/biome/configuration_schema.json | 2 +- .../src/content/docs/linter/rules/index.mdx | 2 +- .../docs/linter/rules/use-sorted-classes.md | 78 +++++++++++++++--- 6 files changed, 140 insertions(+), 28 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index 92a8d78cb0c9..14605941b515 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -25,24 +25,78 @@ use self::{ }; declare_rule! { - /// Enforce the sorting of CSS classes. + /// Enforce the sorting of CSS utility classes. /// - /// TODO: description + /// This rule implements the same sorting algorithm as [Tailwind CSS](https://tailwindcss.com/blog/automatic-class-sorting-with-prettier#how-classes-are-sorted), but supports any utility class framework including [UnoCSS](https://unocss.dev/). + /// + /// It is analogous to [`prettier-plugin-tailwindcss`](https://github.com/tailwindlabs/prettier-plugin-tailwindcss). + /// + /// NOTE: this rule is only partially implemented. Progress is being tracked in the following GitHub issue: https://github.com/biomejs/biome/issues/1274 /// /// ## Examples /// /// ### Invalid /// /// ```jsx,expect_diagnostic - ///

; + ///
; + /// ``` + /// + /// ## Options + /// + /// ### Code-related + /// + /// ```json + /// { + /// "options": { + /// "attributes": ["classList"], + /// "functions": ["clsx", "cva", "tw"] + /// } + /// } /// ``` /// - /// ## Valid + /// #### attributes + /// + /// Classes in the `class` and `className` JSX attributes are always sorted. Use this option to add more attributes that should be sorted. + /// + /// #### functions + /// + /// If specified, strings in the indicated functions will be sorted. This is useful when working with libraries like [`clsx`](https://github.com/lukeed/clsx) or [`cva`](https://cva.style/). + /// + /// Tagged template literals are also supported, for example: /// /// ```js - /// // TODO: examples + /// tw`px-2`; + /// tw.div`px-2`; /// ``` /// + /// NOTE: tagged template literal support has not been implemented yet. + /// + /// ### Sort-related + /// + /// NOTE: at the moment, this rule does not support customizing the sort options. Instead, the default Tailwind CSS configuration is hard-coded. + /// + /// ## Differences with [Prettier](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) + /// + /// The main key difference is that Tailwind CSS and its Prettier plugin read the `tailwind.config.js` file, which Biome can't access. Instead, Biome implements a simpler version of the configuration. The trade-offs are explained below. + /// + /// ### Values are not known + /// + /// The rule has no knowledge of values such as colors, font sizes, or spacing values, which are normally defined in a configuration file like `tailwind.config.js`. Instead, the rule matches utilities that support values in a simpler way: if they start with a known utility prefix, such as `px-` or `text-`, they're considered valid. + /// + /// This can result in false positives, i.e. classes that are wrongly recognized as utilities even though their values are incorrect. For example, if there's a `px-` utility defined in the configuration, it will match all of the following classes: `px-2`, `px-1337`, `px-[not-actually-valid]`, `px-literally-anything`. + /// + /// ### Custom additions must be specified + /// + /// The built-in Tailwind CSS preset (enabled by default) contains the set of utilities and variants that are available with the default configuration. More utilities and variants can be added through Tailwind CSS plugins. In Biome, these need to be manually specified in the Biome configuration file in order to "extend" the preset. + /// + /// ### Presets can't be modified + /// + /// In Tailwind CSS, core plugins (which provide the default utilities and variants) can be disabled. In Biome, however, there is no way to disable parts of a preset: it's all or nothing. A work-around is to, instead of using a preset, manually specify all utilities and variants in the Biome configuration file. + /// + /// ### Whitespace is collapsed + /// + /// The Tailwind CSS Prettier plugin preserves all original whitespace. This rule, however, collapses all whitespace (including newlines) into single spaces. + /// pub(crate) UseSortedClasses { version: "next", name: "useSortedClasses", @@ -79,17 +133,21 @@ impl Rule for UseSortedClasses { fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { Some( - RuleDiagnostic::new(rule_category!(), ctx.query().range(), "TODO: title").note( - markup! { - "TODO: description." - }, - ), + RuleDiagnostic::new( + rule_category!(), + ctx.query().range(), + "These classes should be sorted.", + ) + .note(markup! { + "The safe fix will automatically sort them." + }), ) } fn action(ctx: &RuleContext, state: &Self::State) -> Option { let mut mutation = ctx.root().begin(); match ctx.query() { + // TODO: make this DRYer AnyClassStringLike::JsStringLiteralExpression(string_literal) => { let replacement = js_string_literal_expression(js_string_literal(state)); mutation.replace_node(string_literal.clone(), replacement); @@ -97,7 +155,7 @@ impl Rule for UseSortedClasses { category: ActionCategory::QuickFix, applicability: Applicability::Always, message: markup! { - "TODO: message." + "Sort the classes." } .to_owned(), mutation, @@ -110,7 +168,7 @@ impl Rule for UseSortedClasses { category: ActionCategory::QuickFix, applicability: Applicability::Always, message: markup! { - "TODO: message." + "Sort the classes." } .to_owned(), mutation, diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index 8eedfb6cff0e..60d47216c6d2 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2627,7 +2627,7 @@ pub struct Nursery { #[doc = "Enforce using function types instead of object type with call signatures."] #[serde(skip_serializing_if = "Option::is_none")] pub use_shorthand_function_type: Option, - #[doc = "Enforce the sorting of CSS classes."] + #[doc = "Enforce the sorting of CSS utility classes."] #[serde(skip_serializing_if = "Option::is_none")] pub use_sorted_classes: Option, } diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index fd645a87da36..a6fc14d5f2e3 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -955,7 +955,7 @@ export interface Nursery { */ useShorthandFunctionType?: RuleConfiguration; /** - * Enforce the sorting of CSS classes. + * Enforce the sorting of CSS utility classes. */ useSortedClasses?: RuleConfiguration; } diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 64f175357440..1cd962cbf6c2 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1455,7 +1455,7 @@ ] }, "useSortedClasses": { - "description": "Enforce the sorting of CSS classes.", + "description": "Enforce the sorting of CSS utility classes.", "anyOf": [ { "$ref": "#/definitions/RuleConfiguration" }, { "type": "null" } diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index 7180f625fc75..498a6401d2bc 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -256,4 +256,4 @@ Rules that belong to this group are not subject to semantic versionnode: protocol for Node.js builtin modules. | ⚠️ | | [useNumberNamespace](/linter/rules/use-number-namespace) | Use the Number properties instead of global ones. | ⚠️ | | [useShorthandFunctionType](/linter/rules/use-shorthand-function-type) | Enforce using function types instead of object type with call signatures. | 🔧 | -| [useSortedClasses](/linter/rules/use-sorted-classes) | Enforce the sorting of CSS classes. | 🔧 | +| [useSortedClasses](/linter/rules/use-sorted-classes) | Enforce the sorting of CSS utility classes. | 🔧 | diff --git a/website/src/content/docs/linter/rules/use-sorted-classes.md b/website/src/content/docs/linter/rules/use-sorted-classes.md index f0549ab9aa1f..2432cef4e315 100644 --- a/website/src/content/docs/linter/rules/use-sorted-classes.md +++ b/website/src/content/docs/linter/rules/use-sorted-classes.md @@ -12,42 +12,96 @@ This rule hasn't been released yet. This rule is part of the [nursery](/linter/rules/#nursery) group. ::: -Enforce the sorting of CSS classes. +Enforce the sorting of CSS utility classes. -TODO: description +This rule implements the same sorting algorithm as [Tailwind CSS](https://tailwindcss.com/blog/automatic-class-sorting-with-prettier#how-classes-are-sorted), but supports any utility class framework including [UnoCSS](https://unocss.dev/). + +It is analogous to [`prettier-plugin-tailwindcss`](https://github.com/tailwindlabs/prettier-plugin-tailwindcss). + +NOTE: this rule is only partially implemented. Progress is being tracked in the following GitHub issue: https://github.com/biomejs/biome/issues/1274 ## Examples ### Invalid ```jsx -
; +
; ```
nursery/useSortedClasses.js:1:12 lint/nursery/useSortedClasses  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
-   TODO: title
+   These classes should be sorted.
   
-  > 1 │ <div class="px-2 foo px-4 bar" />;
-              ^^^^^^^^^^^^^^^^^^^
+  > 1 │ <div class="px-2 foo p-4 bar" />;
+              ^^^^^^^^^^^^^^^^^^
     2 │ 
   
-   TODO: description.
+   The safe fix will automatically sort them.
   
-   Safe fix: TODO: message.
+   Safe fix: Sort the classes.
   
-    1  - <div·class="px-2·foo·px-4·bar"·/>;
-      1+ <div·class="foo·bar·px-2·px-4"·/>;
+    1  - <div·class="px-2·foo·p-4·bar"·/>;
+      1+ <div·class="foo·bar·p-4·px-2"·/>;
     2 2  
   
 
-## Valid +## Options + +### Code-related + +```json +{ + "options": { + "attributes": ["classList"], + "functions": ["clsx", "cva", "tw"] + } +} +``` + +#### attributes + +Classes in the `class` and `className` JSX attributes are always sorted. Use this option to add more attributes that should be sorted. + +#### functions + +If specified, strings in the indicated functions will be sorted. This is useful when working with libraries like [`clsx`](https://github.com/lukeed/clsx) or [`cva`](https://cva.style/). + +Tagged template literals are also supported, for example: ```jsx -// TODO: examples +tw`px-2`; +tw.div`px-2`; ``` +NOTE: tagged template literal support has not been implemented yet. + +### Sort-related + +NOTE: at the moment, this rule does not support customizing the sort options. Instead, the default Tailwind CSS configuration is hard-coded. + +## Differences with [Prettier](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) + +The main key difference is that Tailwind CSS and its Prettier plugin read the `tailwind.config.js` file, which Biome can't access. Instead, Biome implements a simpler version of the configuration. The trade-offs are explained below. + +### Values are not known + +The rule has no knowledge of values such as colors, font sizes, or spacing values, which are normally defined in a configuration file like `tailwind.config.js`. Instead, the rule matches utilities that support values in a simpler way: if they start with a known utility prefix, such as `px-` or `text-`, they're considered valid. + +This can result in false positives, i.e. classes that are wrongly recognized as utilities even though their values are incorrect. For example, if there's a `px-` utility defined in the configuration, it will match all of the following classes: `px-2`, `px-1337`, `px-[not-actually-valid]`, `px-literally-anything`. + +### Custom additions must be specified + +The built-in Tailwind CSS preset (enabled by default) contains the set of utilities and variants that are available with the default configuration. More utilities and variants can be added through Tailwind CSS plugins. In Biome, these need to be manually specified in the Biome configuration file in order to "extend" the preset. + +### Presets can't be modified + +In Tailwind CSS, core plugins (which provide the default utilities and variants) can be disabled. In Biome, however, there is no way to disable parts of a preset: it's all or nothing. A work-around is to, instead of using a preset, manually specify all utilities and variants in the Biome configuration file. + +### Whitespace is collapsed + +The Tailwind CSS Prettier plugin preserves all original whitespace. This rule, however, collapses all whitespace (including newlines) into single spaces. + ## Related links - [Disable a rule](/linter/#disable-a-lint-rule) From cff3479d1297592bfef20ab872b48c25e9596ef1 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 12 Jan 2024 11:37:58 +0000 Subject: [PATCH 13/82] fix: conflict --- .../src/configuration/linter/rules.rs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index e889aab88256..e88f65a368ee 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2266,10 +2266,13 @@ pub struct Nursery { #[doc = "Enforce using function types instead of object type with call signatures."] #[serde(skip_serializing_if = "Option::is_none")] pub use_shorthand_function_type: Option, + #[doc = "Enforce the sorting of CSS utility classes."] + #[serde(skip_serializing_if = "Option::is_none")] + pub use_sorted_classes: Option, } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 24] = [ + pub(crate) const GROUP_RULES: [&'static str; 25] = [ "noDuplicateJsonKeys", "noEmptyBlockStatements", "noEmptyTypeParameters", @@ -2294,6 +2297,7 @@ impl Nursery { "useNodejsImportProtocol", "useNumberNamespace", "useShorthandFunctionType", + "useSortedClasses", ]; const RECOMMENDED_RULES: [&'static str; 11] = [ "noDuplicateJsonKeys", @@ -2321,7 +2325,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 24] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 25] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -2346,6 +2350,7 @@ impl Nursery { 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]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended(&self) -> bool { @@ -2482,6 +2487,11 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } + 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[24])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -2606,6 +2616,11 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } + 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[24])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -2619,7 +2634,7 @@ impl Nursery { pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 11] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 24] { + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 25] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] @@ -2666,6 +2681,7 @@ impl Nursery { "useNodejsImportProtocol" => self.use_nodejs_import_protocol.as_ref(), "useNumberNamespace" => self.use_number_namespace.as_ref(), "useShorthandFunctionType" => self.use_shorthand_function_type.as_ref(), + "useSortedClasses" => self.use_sorted_classes.as_ref(), _ => None, } } From 508a93fba43e3d21aa177ae2d8a5a1d3a032f217 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Fri, 12 Jan 2024 19:20:03 +0100 Subject: [PATCH 14/82] address review comments --- .../nursery/use_sorted_classes.rs | 19 ++++----- .../any_class_string_like.rs | 40 ++++++++++--------- .../nursery/use_sorted_classes/options.rs | 3 -- .../nursery/use_sorted_classes/sort.rs | 4 +- 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index 14605941b515..0bfdd84250b6 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -123,8 +123,8 @@ impl Rule for UseSortedClasses { ); let value = ctx.query().value()?; - let sorted_value = sort_class_name(value.as_str(), &sort_config); - if value != sorted_value { + let sorted_value = sort_class_name(&value, &sort_config); + if value.text() != sorted_value { Some(sorted_value) } else { None @@ -132,16 +132,11 @@ impl Rule for UseSortedClasses { } fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { - Some( - RuleDiagnostic::new( - rule_category!(), - ctx.query().range(), - "These classes should be sorted.", - ) - .note(markup! { - "The safe fix will automatically sort them." - }), - ) + Some(RuleDiagnostic::new( + rule_category!(), + ctx.query().range(), + "These CSS classes should be sorted.", + )) } fn action(ctx: &RuleContext, state: &Self::State) -> Option { diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs index 4f981e32f8b2..448eabe1d851 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs @@ -16,14 +16,12 @@ use super::UseSortedClassesOptions; // ----- fn get_options_from_analyzer(analyzer_options: &AnalyzerOptions) -> UseSortedClassesOptions { - match analyzer_options + analyzer_options .configuration .rules .get_rule_options::(&RuleKey::new("nursery", "useSortedClasses")) - { - Some(options) => options.clone(), - None => UseSortedClassesOptions::default(), - } + .cloned() + .unwrap_or_default() } fn get_callee_name(call_expression: &JsCallExpression) -> Option { @@ -41,14 +39,20 @@ fn is_call_expression_of_target_function( call_expression: &JsCallExpression, target_functions: &[String], ) -> bool { - match get_callee_name(call_expression) { - Some(name) => target_functions.contains(&name.to_string()), - None => false, - } + get_callee_name(call_expression) + .is_some_and(|name| target_functions.contains(&name.to_string())) } -fn get_attribute_name(attribute: &JsxAttribute) -> Option { - Some(attribute.name().ok()?.as_jsx_name()?.to_string()) +fn get_attribute_name(attribute: &JsxAttribute) -> Option { + Some( + attribute + .name() + .ok()? + .as_jsx_name()? + .value_token() + .ok()? + .token_text_trimmed(), + ) } fn is_target_attribute(attribute: &JsxAttribute, target_attributes: &[String]) -> bool { @@ -180,16 +184,14 @@ declare_node_union! { impl AnyClassStringLike { /// Returns the value of the string literal, JSX string, or template chunk. - pub fn value(&self) -> Option { + pub fn value(&self) -> Option { match self { - AnyClassStringLike::JsStringLiteralExpression(string_literal) => { - Some(string_literal.inner_string_text().ok()?.to_string()) - } - AnyClassStringLike::JsxString(jsx_string) => { - Some(jsx_string.inner_string_text().ok()?.to_string()) + Self::JsStringLiteralExpression(string_literal) => { + Some(string_literal.inner_string_text().ok()?) } - AnyClassStringLike::JsTemplateChunkElement(template_chunk) => { - Some(template_chunk.to_string()) + Self::JsxString(jsx_string) => Some(jsx_string.inner_string_text().ok()?), + Self::JsTemplateChunkElement(template_chunk) => { + Some(template_chunk.template_chunk_token().ok()?.token_text()) } } } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs index db93a5911d10..056b6e01ac01 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs @@ -50,9 +50,6 @@ impl DeserializationVisitor for UseSortedClassesOptionsVisitor { diagnostics: &mut Vec, ) -> Option { let mut result = UseSortedClassesOptions::default(); - result - .attributes - .extend(CLASS_ATTRIBUTES.iter().map(|&s| s.to_string())); for (key, value) in members.flatten() { let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs index 6b474b3da404..ab9e8ab2621f 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs @@ -1,5 +1,7 @@ use std::cmp::Ordering; +use biome_rowan::TokenText; + use super::{ class_info::{get_class_info, ClassInfo}, sort_config::SortConfig, @@ -79,7 +81,7 @@ fn compare_classes(a: &ClassInfo, b: &ClassInfo) -> Ordering { } /// Sort the given class string according to the given sort config. -pub fn sort_class_name(class_name: &str, sort_config: &SortConfig) -> String { +pub fn sort_class_name(class_name: &TokenText, sort_config: &SortConfig) -> String { // Obtain classes by splitting the class string by whitespace. let classes = class_name.split_whitespace().collect::>(); From debd8e6d27fe264f2abeeee75953add5cea27449 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Mon, 15 Jan 2024 20:59:07 +0100 Subject: [PATCH 15/82] fix query bug and lexer bug --- .../nursery/use_sorted_classes.rs | 2 +- .../nursery/use_sorted_classes/class_info.rs | 2 +- .../nursery/use_sorted_classes/class_lexer.rs | 8 ++--- .../specs/nursery/useSortedClasses/invalid.js | 1 - .../nursery/useSortedClasses/invalid.jsx | 1 + .../nursery/useSortedClasses/invalid.jsx.snap | 30 +++++++++++++++++++ .../specs/nursery/useSortedClasses/valid.js | 1 - .../specs/nursery/useSortedClasses/valid.jsx | 1 + .../nursery/useSortedClasses/valid.jsx.snap | 11 +++++++ 9 files changed, 48 insertions(+), 9 deletions(-) delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx.snap diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index 0bfdd84250b6..90e0a2163941 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -106,7 +106,7 @@ declare_rule! { } impl Rule for UseSortedClasses { - type Query = Ast; + type Query = AnyClassStringLike; type State = String; type Signals = Option; type Options = UseSortedClassesOptions; diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs index 16be9ba6870b..4fcd7e36b42f 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs @@ -138,7 +138,7 @@ pub struct ClassInfo { /// Computes sort-related information about a CSS class. If the class is not recognized as a utility, /// it is considered a custom class instead and `None` is returned. pub fn get_class_info(class_name: &str, sort_config: &SortConfig) -> Option { - let utility_data = tokenize_class(class_name); + let utility_data = tokenize_class(class_name)?; let utility_info = get_utility_info(&sort_config.utilities, &utility_data.utility); if let Some(utility_info) = utility_info { return Some(ClassInfo { diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs index ec9840a69a6f..6da2083f3f08 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs @@ -76,7 +76,7 @@ pub struct ClassStructure { /// Processes a CSS class into a class structure, containing a list of variants and the /// utility itself. -pub fn tokenize_class(class_name: &str) -> ClassStructure { +pub fn tokenize_class(class_name: &str) -> Option { // TODO: add custom separator argument (currently hardcoded to `:`). let mut arbitrary_block_depth = 0; let mut at_arbitrary_block_start = false; @@ -159,11 +159,9 @@ pub fn tokenize_class(class_name: &str) -> ClassStructure { text: s.to_string(), }) .collect(); - let utility = variants - .pop() - .expect("TODO: error message (this should never happen)"); + let utility = variants.pop()?; - ClassStructure { variants, utility } + Some(ClassStructure { variants, utility }) } // TODO: unit tests. diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.js deleted file mode 100644 index 5e9c281c65aa..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.js +++ /dev/null @@ -1 +0,0 @@ -// TODO: invalid test diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx new file mode 100644 index 000000000000..18493e704ec1 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx @@ -0,0 +1 @@ +
; diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap new file mode 100644 index 000000000000..6d5c4d4c70a0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap @@ -0,0 +1,30 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```jsx +
; + +``` + +# Diagnostics +``` +invalid.jsx:1:12 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + > 1 │
; + │ ^^^^^^^^^^ + 2 │ + + i Safe fix: Sort the classes. + + 1 │ - ; + 1 │ + ; + 2 2 │ + + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.js b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.js deleted file mode 100644 index 85130e78ecfd..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.js +++ /dev/null @@ -1 +0,0 @@ -// TODO: valid test diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx new file mode 100644 index 000000000000..51003b9333f0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx @@ -0,0 +1 @@ +
; diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx.snap new file mode 100644 index 000000000000..9597b5b16494 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx.snap @@ -0,0 +1,11 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.jsx +--- +# Input +```jsx +
; + +``` + + From 57d6cbfb6a7bcf4fd3c966d8487962f965b53eb2 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Tue, 16 Jan 2024 08:57:28 +0000 Subject: [PATCH 16/82] fix: test and clippy --- .../src/semantic_analyzers/nursery/use_sorted_classes.rs | 2 +- crates/biome_service/tests/invalid/hooks_missing_name.json.snap | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index 90e0a2163941..a11639f924a1 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -7,7 +7,7 @@ mod sort; mod sort_config; use biome_analyze::{ - context::RuleContext, declare_rule, ActionCategory, Ast, FixKind, Rule, RuleDiagnostic, + context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, }; use biome_console::markup; use biome_diagnostics::Applicability; diff --git a/crates/biome_service/tests/invalid/hooks_missing_name.json.snap b/crates/biome_service/tests/invalid/hooks_missing_name.json.snap index 87d24f439533..2a912f7c979b 100644 --- a/crates/biome_service/tests/invalid/hooks_missing_name.json.snap +++ b/crates/biome_service/tests/invalid/hooks_missing_name.json.snap @@ -41,6 +41,7 @@ hooks_missing_name.json:6:5 deserialize ━━━━━━━━━━━━━ - useNodejsImportProtocol - useNumberNamespace - useShorthandFunctionType + - useSortedClasses From 57043b2142f1a64b15b90b94b52d7e705e25a738 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Tue, 16 Jan 2024 09:07:14 +0000 Subject: [PATCH 17/82] chore: make fix unsafe, and make classes static --- .../nursery/use_sorted_classes.rs | 35 +- .../nursery/use_sorted_classes/class_info.rs | 2 +- .../nursery/use_sorted_classes/presets.rs | 1142 +++++++++-------- .../nursery/use_sorted_classes/sort_config.rs | 2 +- .../nursery/useSortedClasses/invalid.jsx | 2 +- .../nursery/useSortedClasses/invalid.jsx.snap | 12 +- .../src/content/docs/linter/rules/index.mdx | 2 +- .../docs/linter/rules/use-sorted-classes.md | 6 +- 8 files changed, 597 insertions(+), 606 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index a11639f924a1..13fbd2890bfd 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -101,7 +101,7 @@ declare_rule! { version: "next", name: "useSortedClasses", recommended: false, - fix_kind: FixKind::Safe, + fix_kind: FixKind::Unsafe, } } @@ -142,34 +142,25 @@ impl Rule for UseSortedClasses { fn action(ctx: &RuleContext, state: &Self::State) -> Option { let mut mutation = ctx.root().begin(); match ctx.query() { - // TODO: make this DRYer AnyClassStringLike::JsStringLiteralExpression(string_literal) => { let replacement = js_string_literal_expression(js_string_literal(state)); mutation.replace_node(string_literal.clone(), replacement); - Some(JsRuleAction { - category: ActionCategory::QuickFix, - applicability: Applicability::Always, - message: markup! { - "Sort the classes." - } - .to_owned(), - mutation, - }) } AnyClassStringLike::JsxString(jsx_string_node) => { let replacement = jsx_string(js_string_literal(state)); mutation.replace_node(jsx_string_node.clone(), replacement); - Some(JsRuleAction { - category: ActionCategory::QuickFix, - applicability: Applicability::Always, - message: markup! { - "Sort the classes." - } - .to_owned(), - mutation, - }) } - _ => None, - } + AnyClassStringLike::JsTemplateChunkElement(_) => return None, + }; + + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::MaybeIncorrect, + message: markup! { + "Sort the classes." + } + .to_owned(), + mutation, + }) } } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs index 4fcd7e36b42f..dccd5f4befc0 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs @@ -30,7 +30,7 @@ enum UtilityMatch { impl UtilityMatch { /// Checks if a utility matches a target, and returns the result. - fn from(target: &String, utility_text: &str) -> UtilityMatch { + fn from(target: &str, utility_text: &str) -> UtilityMatch { // If the target ends with `$`, then it's an exact target. if target.ends_with('$') { // Check if the utility matches the target (without the final `$`) exactly. diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs index d560d5251607..b07027c4a9e4 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs @@ -11,6 +11,576 @@ pub enum UseSortedClassesPreset { TailwindCSS, // TODO: should this be the default? } +const TAILWIND_PRESETS: [&str; 567] = [ + "sr-only$", + "not-sr-only$", + "pointer-events-none$", + "pointer-events-auto$", + "visible$", + "invisible$", + "collapse$", + "static$", + "fixed$", + "absolute$", + "relative$", + "sticky$", + "inset-", + "inset-x-", + "inset-y-", + "start-", + "end-", + "top-", + "right-", + "bottom-", + "left-", + "isolate$", + "isolation-auto$", + "z-", + "order-", + "col-", + "col-start-", + "col-end-", + "row-", + "row-start-", + "row-end-", + "float-start$", + "float-end$", + "float-right$", + "float-left$", + "float-none$", + "clear-start$", + "clear-end$", + "clear-left$", + "clear-right$", + "clear-both$", + "clear-none$", + "m-", + "mx-", + "my-", + "ms-", + "me-", + "mt-", + "mr-", + "mb-", + "ml-", + "box-border$", + "box-content$", + "line-clamp-", + "line-clamp-none$", + "block$", + "inline-block$", + "inline$", + "flex$", + "inline-flex$", + "table$", + "inline-table$", + "table-caption$", + "table-cell$", + "table-column$", + "table-column-group$", + "table-footer-group$", + "table-header-group$", + "table-row-group$", + "table-row$", + "flow-root$", + "grid$", + "inline-grid$", + "contents$", + "list-item$", + "hidden$", + "aspect-", + "size-", + "h-", + "max-h-", + "min-h-", + "w-", + "min-w-", + "max-w-", + "flex-shrink$", + "flex-shrink-", + "shrink$", + "shrink-", + "flex-grow$", + "flex-grow-", + "grow$", + "grow-", + "basis-", + "table-auto$", + "table-fixed$", + "caption-top$", + "caption-bottom$", + "border-collapse$", + "border-separate$", + "border-spacing-", + "border-spacing-x-", + "border-spacing-y-", + "origin-", + "translate-x-", + "translate-y-", + "rotate-", + "skew-x-", + "skew-y-", + "scale-", + "scale-x-", + "scale-y-", + "transform$", + "transform-cpu$", + "transform-gpu$", + "transform-none$", + "animate-", + "cursor-", + "touch-auto$", + "touch-none$", + "touch-pan-x$", + "touch-pan-left$", + "touch-pan-right$", + "touch-pan-y$", + "touch-pan-up$", + "touch-pan-down$", + "touch-pinch-zoom$", + "touch-manipulation$", + "select-none$", + "select-text$", + "select-all$", + "select-auto$", + "resize-none$", + "resize-y$", + "resize-x$", + "resize$", + "snap-none$", + "snap-x$", + "snap-y$", + "snap-both$", + "snap-mandatory$", + "snap-proximity$", + "snap-start$", + "snap-end$", + "snap-center$", + "snap-align-none$", + "snap-normal$", + "snap-always$", + "scroll-m-", + "scroll-mx-", + "scroll-my-", + "scroll-ms-", + "scroll-me-", + "scroll-mt-", + "scroll-mr-", + "scroll-mb-", + "scroll-ml-", + "scroll-p-", + "scroll-px-", + "scroll-py-", + "scroll-ps-", + "scroll-pe-", + "scroll-pt-", + "scroll-pr-", + "scroll-pb-", + "scroll-pl-", + "list-inside$", + "list-outside$", + "list-", + "list-image-", + "appearance-none$", + "appearance-auto$", + "columns-", + "break-before-auto$", + "break-before-avoid$", + "break-before-all$", + "break-before-avoid-page$", + "break-before-page$", + "break-before-left$", + "break-before-right$", + "break-before-column$", + "break-inside-auto$", + "break-inside-avoid$", + "break-inside-avoid-page$", + "break-inside-avoid-column$", + "break-after-auto$", + "break-after-avoid$", + "break-after-all$", + "break-after-avoid-page$", + "break-after-page$", + "break-after-left$", + "break-after-right$", + "break-after-column$", + "auto-cols-", + "grid-flow-row$", + "grid-flow-col$", + "grid-flow-dense$", + "grid-flow-row-dense$", + "grid-flow-col-dense$", + "auto-rows-", + "grid-cols-", + "grid-rows-", + "flex-row$", + "flex-row-reverse$", + "flex-col$", + "flex-col-reverse$", + "flex-wrap$", + "flex-wrap-reverse$", + "flex-nowrap$", + "place-content-center$", + "place-content-start$", + "place-content-end$", + "place-content-between$", + "place-content-around$", + "place-content-evenly$", + "place-content-baseline$", + "place-content-stretch$", + "place-items-start$", + "place-items-end$", + "place-items-center$", + "place-items-baseline$", + "place-items-stretch$", + "content-normal$", + "content-center$", + "content-start$", + "content-end$", + "content-between$", + "content-around$", + "content-evenly$", + "content-baseline$", + "content-stretch$", + "items-start$", + "items-end$", + "items-center$", + "items-baseline$", + "items-stretch$", + "justify-normal$", + "justify-start$", + "justify-end$", + "justify-center$", + "justify-between$", + "justify-around$", + "justify-evenly$", + "justify-stretch$", + "justify-items-start$", + "justify-items-end$", + "justify-items-center$", + "justify-items-stretch$", + "gap-", + "gap-x-", + "gap-y-", + "space-x-", + "space-y-", + "space-y-reverse$", + "space-x-reverse$", + "divide-x$", + "divide-x-", + "divide-y$", + "divide-y-", + "divide-y-reverse$", + "divide-x-reverse$", + "divide-solid$", + "divide-dashed$", + "divide-dotted$", + "divide-double$", + "divide-none$", + "divide-", + "divide-opacity-", + "place-self-auto$", + "place-self-start$", + "place-self-end$", + "place-self-center$", + "place-self-stretch$", + "self-auto$", + "self-start$", + "self-end$", + "self-center$", + "self-stretch$", + "self-baseline$", + "justify-self-auto$", + "justify-self-start$", + "justify-self-end$", + "justify-self-center$", + "justify-self-stretch$", + "overflow-auto$", + "overflow-hidden$", + "overflow-clip$", + "overflow-visible$", + "overflow-scroll$", + "overflow-x-auto$", + "overflow-y-auto$", + "overflow-x-hidden$", + "overflow-y-hidden$", + "overflow-x-clip$", + "overflow-y-clip$", + "overflow-x-visible$", + "overflow-y-visible$", + "overflow-x-scroll$", + "overflow-y-scroll$", + "overscroll-auto$", + "overscroll-contain$", + "overscroll-none$", + "overscroll-y-auto$", + "overscroll-y-contain$", + "overscroll-y-none$", + "overscroll-x-auto$", + "overscroll-x-contain$", + "overscroll-x-none$", + "scroll-auto$", + "scroll-smooth$", + "truncate$", + "overflow-ellipsis$", + "text-ellipsis$", + "text-clip$", + "hyphens-none$", + "hyphens-manual$", + "hyphens-auto$", + "whitespace-normal$", + "whitespace-nowrap$", + "whitespace-pre$", + "whitespace-pre-line$", + "whitespace-pre-wrap$", + "whitespace-break-spaces$", + "text-wrap$", + "text-nowrap$", + "text-balance$", + "text-pretty$", + "break-normal$", + "break-words$", + "break-all$", + "break-keep$", + "rounded$", + "rounded-", + "rounded-s$", + "rounded-s-", + "rounded-e$", + "rounded-e-", + "rounded-t$", + "rounded-t-", + "rounded-r$", + "rounded-r-", + "rounded-b$", + "rounded-b-", + "rounded-l$", + "rounded-l-", + "rounded-ss$", + "rounded-ss-", + "rounded-se$", + "rounded-se-", + "rounded-ee$", + "rounded-ee-", + "rounded-es$", + "rounded-es-", + "rounded-tl$", + "rounded-tl-", + "rounded-tr$", + "rounded-tr-", + "rounded-br$", + "rounded-br-", + "rounded-bl$", + "rounded-bl-", + "border$", + "border-", + "border-x$", + "border-x-", + "border-y$", + "border-y-", + "border-s$", + "border-s-", + "border-e$", + "border-e-", + "border-t$", + "border-t-", + "border-r$", + "border-r-", + "border-b$", + "border-b-", + "border-l$", + "border-l-", + "border-solid$", + "border-dashed$", + "border-dotted$", + "border-double$", + "border-hidden$", + "border-none$", + "border-opacity-", + "bg-", + "bg-opacity-", + "from-", + "via-", + "to-", + "decoration-slice$", + "decoration-clone$", + "box-decoration-slice$", + "box-decoration-clone$", + "bg-fixed$", + "bg-local$", + "bg-scroll$", + "bg-clip-border$", + "bg-clip-padding$", + "bg-clip-content$", + "bg-clip-text$", + "bg-repeat$", + "bg-no-repeat$", + "bg-repeat-x$", + "bg-repeat-y$", + "bg-repeat-round$", + "bg-repeat-space$", + "bg-origin-border$", + "bg-origin-padding$", + "bg-origin-content$", + "fill-", + "stroke-", + "object-contain$", + "object-cover$", + "object-fill$", + "object-none$", + "object-scale-down$", + "object-", + "p-", + "px-", + "py-", + "ps-", + "pe-", + "pt-", + "pr-", + "pb-", + "pl-", + "text-left$", + "text-center$", + "text-right$", + "text-justify$", + "text-start$", + "text-end$", + "indent-", + "align-baseline$", + "align-top$", + "align-middle$", + "align-bottom$", + "align-text-top$", + "align-text-bottom$", + "align-sub$", + "align-super$", + "align-", + "font-", + "text-", + "uppercase$", + "lowercase$", + "capitalize$", + "normal-case$", + "italic$", + "not-italic$", + "normal-nums$", + "ordinal$", + "slashed-zero$", + "lining-nums$", + "oldstyle-nums$", + "proportional-nums$", + "tabular-nums$", + "diagonal-fractions$", + "stacked-fractions$", + "leading-", + "tracking-", + "text-opacity-", + "underline$", + "overline$", + "line-through$", + "no-underline$", + "decoration-", + "decoration-solid$", + "decoration-double$", + "decoration-dotted$", + "decoration-dashed$", + "decoration-wavy$", + "underline-offset-", + "antialiased$", + "subpixel-antialiased$", + "placeholder-", + "placeholder-opacity-", + "caret-", + "accent-", + "opacity-", + "bg-blend-normal$", + "bg-blend-multiply$", + "bg-blend-screen$", + "bg-blend-overlay$", + "bg-blend-darken$", + "bg-blend-lighten$", + "bg-blend-color-dodge$", + "bg-blend-color-burn$", + "bg-blend-hard-light$", + "bg-blend-soft-light$", + "bg-blend-difference$", + "bg-blend-exclusion$", + "bg-blend-hue$", + "bg-blend-saturation$", + "bg-blend-color$", + "bg-blend-luminosity$", + "mix-blend-normal$", + "mix-blend-multiply$", + "mix-blend-screen$", + "mix-blend-overlay$", + "mix-blend-darken$", + "mix-blend-lighten$", + "mix-blend-color-dodge$", + "mix-blend-color-burn$", + "mix-blend-hard-light$", + "mix-blend-soft-light$", + "mix-blend-difference$", + "mix-blend-exclusion$", + "mix-blend-hue$", + "mix-blend-saturation$", + "mix-blend-color$", + "mix-blend-luminosity$", + "mix-blend-plus-lighter$", + "shadow$", + "shadow-", + "outline-none$", + "outline$", + "outline-dashed$", + "outline-dotted$", + "outline-double$", + "outline-offset-", + "ring$", + "ring-", + "ring-inset$", + "ring-opacity-", + "ring-offset-", + "blur$", + "blur-", + "brightness-", + "contrast-", + "drop-shadow$", + "drop-shadow-", + "grayscale$", + "grayscale-", + "hue-rotate-", + "invert$", + "invert-", + "saturate-", + "sepia$", + "sepia-", + "filter$", + "filter-none$", + "backdrop-blur$", + "backdrop-blur-", + "backdrop-brightness-", + "backdrop-contrast-", + "backdrop-grayscale$", + "backdrop-grayscale-", + "backdrop-hue-rotate-", + "backdrop-invert$", + "backdrop-invert-", + "backdrop-opacity-", + "backdrop-saturate-", + "backdrop-sepia$", + "backdrop-sepia-", + "backdrop-filter$", + "backdrop-filter-none$", + "transition$", + "transition-", + "delay-", + "duration-", + "ease-", + "will-change-", + "content-", + "forced-color-adjust-auto$", + "forced-color-adjust-none$", +]; + // TODO: make this static pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> UtilitiesConfig { match preset { @@ -22,579 +592,11 @@ pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> UtilitiesConfig vec![ UtilityLayer { name: String::from("components"), - classes: vec![String::from("container$")], + classes: ["container$"].as_slice(), }, UtilityLayer { name: String::from("utilities"), - classes: vec![ - String::from("sr-only$"), - String::from("not-sr-only$"), - String::from("pointer-events-none$"), - String::from("pointer-events-auto$"), - String::from("visible$"), - String::from("invisible$"), - String::from("collapse$"), - String::from("static$"), - String::from("fixed$"), - String::from("absolute$"), - String::from("relative$"), - String::from("sticky$"), - String::from("inset-"), - String::from("inset-x-"), - String::from("inset-y-"), - String::from("start-"), - String::from("end-"), - String::from("top-"), - String::from("right-"), - String::from("bottom-"), - String::from("left-"), - String::from("isolate$"), - String::from("isolation-auto$"), - String::from("z-"), - String::from("order-"), - String::from("col-"), - String::from("col-start-"), - String::from("col-end-"), - String::from("row-"), - String::from("row-start-"), - String::from("row-end-"), - String::from("float-start$"), - String::from("float-end$"), - String::from("float-right$"), - String::from("float-left$"), - String::from("float-none$"), - String::from("clear-start$"), - String::from("clear-end$"), - String::from("clear-left$"), - String::from("clear-right$"), - String::from("clear-both$"), - String::from("clear-none$"), - String::from("m-"), - String::from("mx-"), - String::from("my-"), - String::from("ms-"), - String::from("me-"), - String::from("mt-"), - String::from("mr-"), - String::from("mb-"), - String::from("ml-"), - String::from("box-border$"), - String::from("box-content$"), - String::from("line-clamp-"), - String::from("line-clamp-none$"), - String::from("block$"), - String::from("inline-block$"), - String::from("inline$"), - String::from("flex$"), - String::from("inline-flex$"), - String::from("table$"), - String::from("inline-table$"), - String::from("table-caption$"), - String::from("table-cell$"), - String::from("table-column$"), - String::from("table-column-group$"), - String::from("table-footer-group$"), - String::from("table-header-group$"), - String::from("table-row-group$"), - String::from("table-row$"), - String::from("flow-root$"), - String::from("grid$"), - String::from("inline-grid$"), - String::from("contents$"), - String::from("list-item$"), - String::from("hidden$"), - String::from("aspect-"), - String::from("size-"), - String::from("h-"), - String::from("max-h-"), - String::from("min-h-"), - String::from("w-"), - String::from("min-w-"), - String::from("max-w-"), - String::from("flex-shrink$"), - String::from("flex-shrink-"), - String::from("shrink$"), - String::from("shrink-"), - String::from("flex-grow$"), - String::from("flex-grow-"), - String::from("grow$"), - String::from("grow-"), - String::from("basis-"), - String::from("table-auto$"), - String::from("table-fixed$"), - String::from("caption-top$"), - String::from("caption-bottom$"), - String::from("border-collapse$"), - String::from("border-separate$"), - String::from("border-spacing-"), - String::from("border-spacing-x-"), - String::from("border-spacing-y-"), - String::from("origin-"), - String::from("translate-x-"), - String::from("translate-y-"), - String::from("rotate-"), - String::from("skew-x-"), - String::from("skew-y-"), - String::from("scale-"), - String::from("scale-x-"), - String::from("scale-y-"), - String::from("transform$"), - String::from("transform-cpu$"), - String::from("transform-gpu$"), - String::from("transform-none$"), - String::from("animate-"), - String::from("cursor-"), - String::from("touch-auto$"), - String::from("touch-none$"), - String::from("touch-pan-x$"), - String::from("touch-pan-left$"), - String::from("touch-pan-right$"), - String::from("touch-pan-y$"), - String::from("touch-pan-up$"), - String::from("touch-pan-down$"), - String::from("touch-pinch-zoom$"), - String::from("touch-manipulation$"), - String::from("select-none$"), - String::from("select-text$"), - String::from("select-all$"), - String::from("select-auto$"), - String::from("resize-none$"), - String::from("resize-y$"), - String::from("resize-x$"), - String::from("resize$"), - String::from("snap-none$"), - String::from("snap-x$"), - String::from("snap-y$"), - String::from("snap-both$"), - String::from("snap-mandatory$"), - String::from("snap-proximity$"), - String::from("snap-start$"), - String::from("snap-end$"), - String::from("snap-center$"), - String::from("snap-align-none$"), - String::from("snap-normal$"), - String::from("snap-always$"), - String::from("scroll-m-"), - String::from("scroll-mx-"), - String::from("scroll-my-"), - String::from("scroll-ms-"), - String::from("scroll-me-"), - String::from("scroll-mt-"), - String::from("scroll-mr-"), - String::from("scroll-mb-"), - String::from("scroll-ml-"), - String::from("scroll-p-"), - String::from("scroll-px-"), - String::from("scroll-py-"), - String::from("scroll-ps-"), - String::from("scroll-pe-"), - String::from("scroll-pt-"), - String::from("scroll-pr-"), - String::from("scroll-pb-"), - String::from("scroll-pl-"), - String::from("list-inside$"), - String::from("list-outside$"), - String::from("list-"), - String::from("list-image-"), - String::from("appearance-none$"), - String::from("appearance-auto$"), - String::from("columns-"), - String::from("break-before-auto$"), - String::from("break-before-avoid$"), - String::from("break-before-all$"), - String::from("break-before-avoid-page$"), - String::from("break-before-page$"), - String::from("break-before-left$"), - String::from("break-before-right$"), - String::from("break-before-column$"), - String::from("break-inside-auto$"), - String::from("break-inside-avoid$"), - String::from("break-inside-avoid-page$"), - String::from("break-inside-avoid-column$"), - String::from("break-after-auto$"), - String::from("break-after-avoid$"), - String::from("break-after-all$"), - String::from("break-after-avoid-page$"), - String::from("break-after-page$"), - String::from("break-after-left$"), - String::from("break-after-right$"), - String::from("break-after-column$"), - String::from("auto-cols-"), - String::from("grid-flow-row$"), - String::from("grid-flow-col$"), - String::from("grid-flow-dense$"), - String::from("grid-flow-row-dense$"), - String::from("grid-flow-col-dense$"), - String::from("auto-rows-"), - String::from("grid-cols-"), - String::from("grid-rows-"), - String::from("flex-row$"), - String::from("flex-row-reverse$"), - String::from("flex-col$"), - String::from("flex-col-reverse$"), - String::from("flex-wrap$"), - String::from("flex-wrap-reverse$"), - String::from("flex-nowrap$"), - String::from("place-content-center$"), - String::from("place-content-start$"), - String::from("place-content-end$"), - String::from("place-content-between$"), - String::from("place-content-around$"), - String::from("place-content-evenly$"), - String::from("place-content-baseline$"), - String::from("place-content-stretch$"), - String::from("place-items-start$"), - String::from("place-items-end$"), - String::from("place-items-center$"), - String::from("place-items-baseline$"), - String::from("place-items-stretch$"), - String::from("content-normal$"), - String::from("content-center$"), - String::from("content-start$"), - String::from("content-end$"), - String::from("content-between$"), - String::from("content-around$"), - String::from("content-evenly$"), - String::from("content-baseline$"), - String::from("content-stretch$"), - String::from("items-start$"), - String::from("items-end$"), - String::from("items-center$"), - String::from("items-baseline$"), - String::from("items-stretch$"), - String::from("justify-normal$"), - String::from("justify-start$"), - String::from("justify-end$"), - String::from("justify-center$"), - String::from("justify-between$"), - String::from("justify-around$"), - String::from("justify-evenly$"), - String::from("justify-stretch$"), - String::from("justify-items-start$"), - String::from("justify-items-end$"), - String::from("justify-items-center$"), - String::from("justify-items-stretch$"), - String::from("gap-"), - String::from("gap-x-"), - String::from("gap-y-"), - String::from("space-x-"), - String::from("space-y-"), - String::from("space-y-reverse$"), - String::from("space-x-reverse$"), - String::from("divide-x$"), - String::from("divide-x-"), - String::from("divide-y$"), - String::from("divide-y-"), - String::from("divide-y-reverse$"), - String::from("divide-x-reverse$"), - String::from("divide-solid$"), - String::from("divide-dashed$"), - String::from("divide-dotted$"), - String::from("divide-double$"), - String::from("divide-none$"), - String::from("divide-"), - String::from("divide-opacity-"), - String::from("place-self-auto$"), - String::from("place-self-start$"), - String::from("place-self-end$"), - String::from("place-self-center$"), - String::from("place-self-stretch$"), - String::from("self-auto$"), - String::from("self-start$"), - String::from("self-end$"), - String::from("self-center$"), - String::from("self-stretch$"), - String::from("self-baseline$"), - String::from("justify-self-auto$"), - String::from("justify-self-start$"), - String::from("justify-self-end$"), - String::from("justify-self-center$"), - String::from("justify-self-stretch$"), - String::from("overflow-auto$"), - String::from("overflow-hidden$"), - String::from("overflow-clip$"), - String::from("overflow-visible$"), - String::from("overflow-scroll$"), - String::from("overflow-x-auto$"), - String::from("overflow-y-auto$"), - String::from("overflow-x-hidden$"), - String::from("overflow-y-hidden$"), - String::from("overflow-x-clip$"), - String::from("overflow-y-clip$"), - String::from("overflow-x-visible$"), - String::from("overflow-y-visible$"), - String::from("overflow-x-scroll$"), - String::from("overflow-y-scroll$"), - String::from("overscroll-auto$"), - String::from("overscroll-contain$"), - String::from("overscroll-none$"), - String::from("overscroll-y-auto$"), - String::from("overscroll-y-contain$"), - String::from("overscroll-y-none$"), - String::from("overscroll-x-auto$"), - String::from("overscroll-x-contain$"), - String::from("overscroll-x-none$"), - String::from("scroll-auto$"), - String::from("scroll-smooth$"), - String::from("truncate$"), - String::from("overflow-ellipsis$"), - String::from("text-ellipsis$"), - String::from("text-clip$"), - String::from("hyphens-none$"), - String::from("hyphens-manual$"), - String::from("hyphens-auto$"), - String::from("whitespace-normal$"), - String::from("whitespace-nowrap$"), - String::from("whitespace-pre$"), - String::from("whitespace-pre-line$"), - String::from("whitespace-pre-wrap$"), - String::from("whitespace-break-spaces$"), - String::from("text-wrap$"), - String::from("text-nowrap$"), - String::from("text-balance$"), - String::from("text-pretty$"), - String::from("break-normal$"), - String::from("break-words$"), - String::from("break-all$"), - String::from("break-keep$"), - String::from("rounded$"), - String::from("rounded-"), - String::from("rounded-s$"), - String::from("rounded-s-"), - String::from("rounded-e$"), - String::from("rounded-e-"), - String::from("rounded-t$"), - String::from("rounded-t-"), - String::from("rounded-r$"), - String::from("rounded-r-"), - String::from("rounded-b$"), - String::from("rounded-b-"), - String::from("rounded-l$"), - String::from("rounded-l-"), - String::from("rounded-ss$"), - String::from("rounded-ss-"), - String::from("rounded-se$"), - String::from("rounded-se-"), - String::from("rounded-ee$"), - String::from("rounded-ee-"), - String::from("rounded-es$"), - String::from("rounded-es-"), - String::from("rounded-tl$"), - String::from("rounded-tl-"), - String::from("rounded-tr$"), - String::from("rounded-tr-"), - String::from("rounded-br$"), - String::from("rounded-br-"), - String::from("rounded-bl$"), - String::from("rounded-bl-"), - String::from("border$"), - String::from("border-"), - String::from("border-x$"), - String::from("border-x-"), - String::from("border-y$"), - String::from("border-y-"), - String::from("border-s$"), - String::from("border-s-"), - String::from("border-e$"), - String::from("border-e-"), - String::from("border-t$"), - String::from("border-t-"), - String::from("border-r$"), - String::from("border-r-"), - String::from("border-b$"), - String::from("border-b-"), - String::from("border-l$"), - String::from("border-l-"), - String::from("border-solid$"), - String::from("border-dashed$"), - String::from("border-dotted$"), - String::from("border-double$"), - String::from("border-hidden$"), - String::from("border-none$"), - String::from("border-opacity-"), - String::from("bg-"), - String::from("bg-opacity-"), - String::from("from-"), - String::from("via-"), - String::from("to-"), - String::from("decoration-slice$"), - String::from("decoration-clone$"), - String::from("box-decoration-slice$"), - String::from("box-decoration-clone$"), - String::from("bg-fixed$"), - String::from("bg-local$"), - String::from("bg-scroll$"), - String::from("bg-clip-border$"), - String::from("bg-clip-padding$"), - String::from("bg-clip-content$"), - String::from("bg-clip-text$"), - String::from("bg-repeat$"), - String::from("bg-no-repeat$"), - String::from("bg-repeat-x$"), - String::from("bg-repeat-y$"), - String::from("bg-repeat-round$"), - String::from("bg-repeat-space$"), - String::from("bg-origin-border$"), - String::from("bg-origin-padding$"), - String::from("bg-origin-content$"), - String::from("fill-"), - String::from("stroke-"), - String::from("object-contain$"), - String::from("object-cover$"), - String::from("object-fill$"), - String::from("object-none$"), - String::from("object-scale-down$"), - String::from("object-"), - String::from("p-"), - String::from("px-"), - String::from("py-"), - String::from("ps-"), - String::from("pe-"), - String::from("pt-"), - String::from("pr-"), - String::from("pb-"), - String::from("pl-"), - String::from("text-left$"), - String::from("text-center$"), - String::from("text-right$"), - String::from("text-justify$"), - String::from("text-start$"), - String::from("text-end$"), - String::from("indent-"), - String::from("align-baseline$"), - String::from("align-top$"), - String::from("align-middle$"), - String::from("align-bottom$"), - String::from("align-text-top$"), - String::from("align-text-bottom$"), - String::from("align-sub$"), - String::from("align-super$"), - String::from("align-"), - String::from("font-"), - String::from("text-"), - String::from("uppercase$"), - String::from("lowercase$"), - String::from("capitalize$"), - String::from("normal-case$"), - String::from("italic$"), - String::from("not-italic$"), - String::from("normal-nums$"), - String::from("ordinal$"), - String::from("slashed-zero$"), - String::from("lining-nums$"), - String::from("oldstyle-nums$"), - String::from("proportional-nums$"), - String::from("tabular-nums$"), - String::from("diagonal-fractions$"), - String::from("stacked-fractions$"), - String::from("leading-"), - String::from("tracking-"), - String::from("text-opacity-"), - String::from("underline$"), - String::from("overline$"), - String::from("line-through$"), - String::from("no-underline$"), - String::from("decoration-"), - String::from("decoration-solid$"), - String::from("decoration-double$"), - String::from("decoration-dotted$"), - String::from("decoration-dashed$"), - String::from("decoration-wavy$"), - String::from("underline-offset-"), - String::from("antialiased$"), - String::from("subpixel-antialiased$"), - String::from("placeholder-"), - String::from("placeholder-opacity-"), - String::from("caret-"), - String::from("accent-"), - String::from("opacity-"), - String::from("bg-blend-normal$"), - String::from("bg-blend-multiply$"), - String::from("bg-blend-screen$"), - String::from("bg-blend-overlay$"), - String::from("bg-blend-darken$"), - String::from("bg-blend-lighten$"), - String::from("bg-blend-color-dodge$"), - String::from("bg-blend-color-burn$"), - String::from("bg-blend-hard-light$"), - String::from("bg-blend-soft-light$"), - String::from("bg-blend-difference$"), - String::from("bg-blend-exclusion$"), - String::from("bg-blend-hue$"), - String::from("bg-blend-saturation$"), - String::from("bg-blend-color$"), - String::from("bg-blend-luminosity$"), - String::from("mix-blend-normal$"), - String::from("mix-blend-multiply$"), - String::from("mix-blend-screen$"), - String::from("mix-blend-overlay$"), - String::from("mix-blend-darken$"), - String::from("mix-blend-lighten$"), - String::from("mix-blend-color-dodge$"), - String::from("mix-blend-color-burn$"), - String::from("mix-blend-hard-light$"), - String::from("mix-blend-soft-light$"), - String::from("mix-blend-difference$"), - String::from("mix-blend-exclusion$"), - String::from("mix-blend-hue$"), - String::from("mix-blend-saturation$"), - String::from("mix-blend-color$"), - String::from("mix-blend-luminosity$"), - String::from("mix-blend-plus-lighter$"), - String::from("shadow$"), - String::from("shadow-"), - String::from("outline-none$"), - String::from("outline$"), - String::from("outline-dashed$"), - String::from("outline-dotted$"), - String::from("outline-double$"), - String::from("outline-offset-"), - String::from("ring$"), - String::from("ring-"), - String::from("ring-inset$"), - String::from("ring-opacity-"), - String::from("ring-offset-"), - String::from("blur$"), - String::from("blur-"), - String::from("brightness-"), - String::from("contrast-"), - String::from("drop-shadow$"), - String::from("drop-shadow-"), - String::from("grayscale$"), - String::from("grayscale-"), - String::from("hue-rotate-"), - String::from("invert$"), - String::from("invert-"), - String::from("saturate-"), - String::from("sepia$"), - String::from("sepia-"), - String::from("filter$"), - String::from("filter-none$"), - String::from("backdrop-blur$"), - String::from("backdrop-blur-"), - String::from("backdrop-brightness-"), - String::from("backdrop-contrast-"), - String::from("backdrop-grayscale$"), - String::from("backdrop-grayscale-"), - String::from("backdrop-hue-rotate-"), - String::from("backdrop-invert$"), - String::from("backdrop-invert-"), - String::from("backdrop-opacity-"), - String::from("backdrop-saturate-"), - String::from("backdrop-sepia$"), - String::from("backdrop-sepia-"), - String::from("backdrop-filter$"), - String::from("backdrop-filter-none$"), - String::from("transition$"), - String::from("transition-"), - String::from("delay-"), - String::from("duration-"), - String::from("ease-"), - String::from("will-change-"), - String::from("content-"), - String::from("forced-color-adjust-auto$"), - String::from("forced-color-adjust-none$"), - ], + classes: TAILWIND_PRESETS.as_slice(), }, ] // TAILWIND-UTILITIES-PRESET-END diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs index 19aea269712e..457c2f8561d1 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; /// A utility layer, containing its name and an ordered list of classes. pub struct UtilityLayer { pub name: String, - pub classes: Vec, + pub classes: &'static [&'static str], } /// The utilities config, contains an ordered list of utility layers. diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx index 18493e704ec1..56d3b3c22633 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx @@ -1 +1 @@ -
; +
diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap index 6d5c4d4c70a0..11daf1c58246 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap @@ -4,7 +4,7 @@ expression: invalid.jsx --- # Input ```jsx -
; +
``` @@ -14,14 +14,14 @@ invalid.jsx:1:12 lint/nursery/useSortedClasses FIXABLE ━━━━━━━ ! These CSS classes should be sorted. - > 1 │
; - │ ^^^^^^^^^^ + > 1 │
+ │ ^^^^^^^^^^^^^^^^^^ 2 │ - i Safe fix: Sort the classes. + i Unsafe fix: Sort the classes. - 1 │ - ; - 1 │ + ; + 1 │ - + 1 │ + 2 2 │ diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index 2b53db718603..a30adc9c8e7b 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -256,4 +256,4 @@ Rules that belong to this group are not subject to semantic versionnode: protocol for Node.js builtin modules. | ⚠️ | | [useNumberNamespace](/linter/rules/use-number-namespace) | Use the Number properties instead of global ones. | ⚠️ | | [useShorthandFunctionType](/linter/rules/use-shorthand-function-type) | Enforce using function types instead of object type with call signatures. | 🔧 | -| [useSortedClasses](/linter/rules/use-sorted-classes) | Enforce the sorting of CSS utility classes. | 🔧 | +| [useSortedClasses](/linter/rules/use-sorted-classes) | Enforce the sorting of CSS utility classes. | ⚠️ | diff --git a/website/src/content/docs/linter/rules/use-sorted-classes.md b/website/src/content/docs/linter/rules/use-sorted-classes.md index 2432cef4e315..02d952ce97ee 100644 --- a/website/src/content/docs/linter/rules/use-sorted-classes.md +++ b/website/src/content/docs/linter/rules/use-sorted-classes.md @@ -30,15 +30,13 @@ NOTE: this rule is only partially implemented. Progress is being tracked in the
nursery/useSortedClasses.js:1:12 lint/nursery/useSortedClasses  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
-   These classes should be sorted.
+   These CSS classes should be sorted.
   
   > 1 │ <div class="px-2 foo p-4 bar" />;
               ^^^^^^^^^^^^^^^^^^
     2 │ 
   
-   The safe fix will automatically sort them.
-  
-   Safe fix: Sort the classes.
+   Unsafe fix: Sort the classes.
   
     1  - <div·class="px-2·foo·p-4·bar"·/>;
       1+ <div·class="foo·bar·p-4·px-2"·/>;

From 89295d16ea18c3d01d94eea48a25b5f560091c09 Mon Sep 17 00:00:00 2001
From: Dani Guardiola 
Date: Sun, 21 Jan 2024 21:55:13 +0100
Subject: [PATCH 18/82] wip integration tests

---
 .../deprecatedConfig.options.json             |  16 +-
 .../useSortedClasses/codeOptionsSorted.jsx    |  46 ++
 .../codeOptionsSorted.jsx.snap                |  76 +++
 .../codeOptionsSorted.options.json            |  16 +
 .../useSortedClasses/codeOptionsUnsorted.jsx  |  46 ++
 .../codeOptionsUnsorted.jsx.snap              | 483 ++++++++++++++++++
 .../codeOptionsUnsorted.options.json          |  16 +
 .../nursery/useSortedClasses/invalid.jsx      |   1 -
 .../nursery/useSortedClasses/invalid.jsx.snap |  30 --
 .../specs/nursery/useSortedClasses/sorted.jsx |  46 ++
 .../nursery/useSortedClasses/sorted.jsx.snap  |  56 ++
 .../nursery/useSortedClasses/unsorted.jsx     |  48 ++
 .../useSortedClasses/unsorted.jsx.snap        | 466 +++++++++++++++++
 .../specs/nursery/useSortedClasses/valid.jsx  |   1 -
 .../nursery/useSortedClasses/valid.jsx.snap   |  11 -
 15 files changed, 1307 insertions(+), 51 deletions(-)
 create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx
 create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx.snap
 create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.options.json
 create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx
 create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap
 create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.options.json
 delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx
 delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap
 create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/sorted.jsx
 create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/sorted.jsx.snap
 create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx
 create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx.snap
 delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx
 delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx.snap

diff --git a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/deprecatedConfig.options.json b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/deprecatedConfig.options.json
index 68bf415d8699..778daef0f0eb 100644
--- a/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/deprecatedConfig.options.json
+++ b/crates/biome_js_analyze/tests/specs/correctness/useHookAtTopLevel/deprecatedConfig.options.json
@@ -1,14 +1,14 @@
 {
 	"$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json",
 	"linter": {
-	"rules": {
-		"correctness": {
-			"useHookAtTopLevel": {
-				"level": "error",
-				"options": {
-					"hooks": [
+		"rules": {
+			"correctness": {
+				"useHookAtTopLevel": {
+					"level": "error",
+					"options": {
+						"hooks": [
 							{
-                                "name": "useCustomHook"
+								"name": "useCustomHook"
 							}
 						]
 					}
@@ -16,4 +16,4 @@
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx
new file mode 100644
index 000000000000..bffc84ba9d8f
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx
@@ -0,0 +1,46 @@
+<>
+	{/* attributes */}
+	
+
+
+
+ {/* utility sorting */} +
+
+
+
+
+
+
+
+
+
+; + +// functions +clsx("foo bar p-4 px-2"); +// TODO: tagged template literals are not supported yet +tw`foo bar p-4 px-2`; +tw.div`foo bar p-4 px-2`; +notClassFunction("px-2 foo p-4 bar"); +notTemplateFunction`px-2 foo p-4 bar`; +notTemplateFunction.div`px-2 foo p-4 bar`; + +// nested values +
; +
; +
; +clsx(["foo bar p-4 px-2"]); +clsx({ + "foo bar p-4 px-2": [ + "foo bar p-4 px-2", + { "foo bar p-4 px-2": "foo bar p-4 px-2", custom: ["foo bar p-4 px-2"] }, + ], +}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx.snap new file mode 100644 index 000000000000..f769a12d982d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx.snap @@ -0,0 +1,76 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: codeOptionsSorted.jsx +--- +# Input +```jsx +<> + {/* attributes */} +
+
+
+
+ {/* utility sorting */} +
+
+
+
+
+
+
+
+
+
+; + +// functions +clsx("foo bar p-4 px-2"); +// TODO: tagged template literals are not supported yet +tw`foo bar p-4 px-2`; +tw.div`foo bar p-4 px-2`; +notClassFunction("px-2 foo p-4 bar"); +notTemplateFunction`px-2 foo p-4 bar`; +notTemplateFunction.div`px-2 foo p-4 bar`; + +// nested values +
; +
; +
; +clsx(["foo bar p-4 px-2"]); +clsx({ + "foo bar p-4 px-2": [ + "foo bar p-4 px-2", + { "foo bar p-4 px-2": "foo bar p-4 px-2", custom: ["foo bar p-4 px-2"] }, + ], +}); + +``` + +# Diagnostics +``` +codeOptionsSorted.options:8:17 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × The rule useSortedClasses doesn't accept any options. + + 6 │ "useSortedClasses": { + 7 │ "level": "error", + > 8 │ "options": { + │ ^ + > 9 │ "attributes": ["customClassAttribute"], + > 10 │ "functions": ["clsx", "tw"] + > 11 │ } + │ ^ + 12 │ } + 13 │ } + + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.options.json b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.options.json new file mode 100644 index 000000000000..b9e8a6b22d0c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.options.json @@ -0,0 +1,16 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "nursery": { + "useSortedClasses": { + "level": "error", + "options": { + "attributes": ["customClassAttribute"], + "functions": ["clsx", "tw"] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx new file mode 100644 index 000000000000..04406b27141c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx @@ -0,0 +1,46 @@ +<> + {/* attributes */} +
+
+
+
+ {/* utility sorting */} +
+
+
+
+
+
+
+
+
+
+; + +// functions +clsx("px-2 foo p-4 bar"); +// TODO: tagged template literals are not supported yet +tw`px-2 foo p-4 bar`; +tw.div`px-2 foo p-4 bar`; +notClassFunction("px-2 foo p-4 bar"); +notTemplateFunction`px-2 foo p-4 bar`; +notTemplateFunction.div`px-2 foo p-4 bar`; + +// nested values +
; +
; +
; +clsx(["px-2 foo p-4 bar"]); +clsx({ + "px-2 foo p-4 bar": [ + "px-2 foo p-4 bar", + { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + ], +}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap new file mode 100644 index 000000000000..e857f058311c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap @@ -0,0 +1,483 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: codeOptionsUnsorted.jsx +--- +# Input +```jsx +<> + {/* attributes */} +
+
+
+
+ {/* utility sorting */} +
+
+
+
+
+
+
+
+
+
+; + +// functions +clsx("px-2 foo p-4 bar"); +// TODO: tagged template literals are not supported yet +tw`px-2 foo p-4 bar`; +tw.div`px-2 foo p-4 bar`; +notClassFunction("px-2 foo p-4 bar"); +notTemplateFunction`px-2 foo p-4 bar`; +notTemplateFunction.div`px-2 foo p-4 bar`; + +// nested values +
; +
; +
; +clsx(["px-2 foo p-4 bar"]); +clsx({ + "px-2 foo p-4 bar": [ + "px-2 foo p-4 bar", + { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + ], +}); + +``` + +# Diagnostics +``` +codeOptionsUnsorted.options:8:17 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × The rule useSortedClasses doesn't accept any options. + + 6 │ "useSortedClasses": { + 7 │ "level": "error", + > 8 │ "options": { + │ ^ + > 9 │ "attributes": ["customClassAttribute"], + > 10 │ "functions": ["clsx", "tw"] + > 11 │ } + │ ^ + 12 │ } + 13 │ } + + +``` + +``` +codeOptionsUnsorted.jsx:3:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 1 │ <> + 2 │ {/* attributes */} + > 3 │
+ │ ^^^^^^^^^^^^^^^^^^ + 4 │
+ 5 │
+ + i Unsafe fix: Sort the classes. + + 1 1 │ <> + 2 2 │ {/* attributes */} + 3 │ - → + 3 │ + → + 4 4 │
+ 5 5 │
+ + +``` + +``` +codeOptionsUnsorted.jsx:4:17 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 2 │ {/* attributes */} + 3 │
+ > 4 │
+ │ ^^^^^^^^^^^^^^^^^^ + 5 │
+ 6 │
+ + i Unsafe fix: Sort the classes. + + 2 2 │ {/* attributes */} + 3 3 │
+ 4 │ - → + 4 │ + → + 5 5 │
+ 6 6 │
+ + +``` + +``` +codeOptionsUnsorted.jsx:8:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 6 │
+ 7 │ {/* utility sorting */} + > 8 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │
+ 10 │
+ + i Unsafe fix: Sort the classes. + + 6 6 │
+ 7 7 │ {/* utility sorting */} + 8 │ - → + 8 │ + → + 9 9 │
+ 10 10 │
+ + +``` + +``` +codeOptionsUnsorted.jsx:9:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 7 │ {/* utility sorting */} + 8 │
+ > 9 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │
+ 11 │
+ + i Unsafe fix: Sort the classes. + + 7 7 │ {/* utility sorting */} + 8 8 │
+ 9 │ - → + 9 │ + → + 10 10 │
+ 11 11 │
+ + +``` + +``` +codeOptionsUnsorted.jsx:10:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 8 │
+ 9 │
+ > 10 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 11 │
+ 12 │
+ + i Unsafe fix: Sort the classes. + + 8 8 │
+ 9 9 │
+ 10 │ - → + 10 │ + → + 11 11 │
+ 12 12 │
+ + +``` + +``` +codeOptionsUnsorted.jsx:11:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 9 │
+ 10 │
+ > 11 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │
+ 13 │
+ + i Unsafe fix: Sort the classes. + + 9 9 │
+ 10 10 │
+ 11 │ - → + 11 │ + → + 12 12 │
+ 13 13 │
+ + +``` + +``` +codeOptionsUnsorted.jsx:12:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 10 │
+ 11 │
+ > 12 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 13 │
+ 14 │
+ + i Unsafe fix: Sort the classes. + + 10 10 │
+ 11 11 │
+ 12 │ - → + 12 │ + → + 13 13 │
+ 14 14 │
+ + +``` + +``` +codeOptionsUnsorted.jsx:13:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 11 │
+ 12 │
+ > 13 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 14 │
+ 15 │
+ + i Unsafe fix: Sort the classes. + + 11 11 │
+ 12 12 │
+ 13 │ - → + 13 │ + → + 14 14 │
+ 15 15 │
+ + +``` + +``` +codeOptionsUnsorted.jsx:14:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 12 │
+ 13 │
+ > 14 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 15 │
+ 16 │
+ + i Unsafe fix: Sort the classes. + + 12 12 │
+ 13 13 │
+ 14 │ - → + 14 │ + → + 15 15 │
+ 16 16 │
+ + +``` + +``` +codeOptionsUnsorted.jsx:15:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 13 │
+ 14 │
+ > 15 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 16 │
+ 17 │
+ + i Unsafe fix: Sort the classes. + + 13 13 │
+ 14 14 │
+ 15 │ - → + 15 │ + → + 16 16 │
+ 17 17 │
+ + +``` + +``` +codeOptionsUnsorted.jsx:16:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 14 │
+ 15 │
+ > 16 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 17 │
+ 18 │ ; + + i Unsafe fix: Sort the classes. + + 14 14 │
+ 15 15 │
+ 16 │ - → + 16 │ + → + 17 17 │
+ 18 18 │ ; + + +``` + +``` +codeOptionsUnsorted.jsx:17:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 15 │
+ 16 │
+ > 17 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 18 │ ; + 19 │ + + i Unsafe fix: Sort the classes. + + 15 15 │
+ 16 16 │
+ 17 │ - → + 17 │ + → + 18 18 │ ; + 19 19 │ + + +``` + +``` +codeOptionsUnsorted.jsx:30:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 29 │ // nested values + > 30 │
; + │ ^^^^^^^^^^^^^^^^^^ + 31 │
; + 32 │
; + 30 │ + ; + 31 31 │
; + 32 32 │
; + > 31 │
; + │ ^^^^^^^^^^^^^^^^^^ + 32 │
; + 31 │ - ; + 31 │ + ; + 32 32 │
35 │ "px-2 foo p-4 bar", + │ ^^^^^^^^^^^^^^^^^^ + 36 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + 37 │ ], + + i Unsafe fix: Sort the classes. + + 33 33 │ class={{ + 34 34 │ "px-2 foo p-4 bar": [ + 35 │ - → → → "px-2·foo·p-4·bar", + 35 │ + → → → "foo·bar·p-4·px-2", + 36 36 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + 37 37 │ ], + + +``` + +``` +codeOptionsUnsorted.jsx:36:26 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 34 │ "px-2 foo p-4 bar": [ + 35 │ "px-2 foo p-4 bar", + > 36 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + │ ^^^^^^^^^^^^^^^^^^ + 37 │ ], + 38 │ }} + + i Unsafe fix: Sort the classes. + + 34 34 │ "px-2 foo p-4 bar": [ + 35 35 │ "px-2 foo p-4 bar", + 36 │ - → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["px-2·foo·p-4·bar"]·}, + 36 │ + → → → {·"px-2·foo·p-4·bar":·"foo·bar·p-4·px-2",·custom:·["px-2·foo·p-4·bar"]·}, + 37 37 │ ], + 38 38 │ }} + + +``` + +``` +codeOptionsUnsorted.jsx:36:55 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 34 │ "px-2 foo p-4 bar": [ + 35 │ "px-2 foo p-4 bar", + > 36 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + │ ^^^^^^^^^^^^^^^^^^ + 37 │ ], + 38 │ }} + + i Unsafe fix: Sort the classes. + + 34 34 │ "px-2 foo p-4 bar": [ + 35 35 │ "px-2 foo p-4 bar", + 36 │ - → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["px-2·foo·p-4·bar"]·}, + 36 │ + → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["foo·bar·p-4·px-2"]·}, + 37 37 │ ], + 38 38 │ }} + + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.options.json b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.options.json new file mode 100644 index 000000000000..b9e8a6b22d0c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.options.json @@ -0,0 +1,16 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "nursery": { + "useSortedClasses": { + "level": "error", + "options": { + "attributes": ["customClassAttribute"], + "functions": ["clsx", "tw"] + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx deleted file mode 100644 index 56d3b3c22633..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap deleted file mode 100644 index 11daf1c58246..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalid.jsx.snap +++ /dev/null @@ -1,30 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: invalid.jsx ---- -# Input -```jsx -
- -``` - -# Diagnostics -``` -invalid.jsx:1:12 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! These CSS classes should be sorted. - - > 1 │
- │ ^^^^^^^^^^^^^^^^^^ - 2 │ - - i Unsafe fix: Sort the classes. - - 1 │ - - 1 │ + - 2 2 │ - - -``` - - diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/sorted.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/sorted.jsx new file mode 100644 index 000000000000..c1605894f093 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/sorted.jsx @@ -0,0 +1,46 @@ +<> + {/* attributes */} +
+
+
+
+ {/* utility sorting */} +
+
+
+
+
+
+
+
+
+
+; + +// functions +clsx("px-2 foo p-4 bar"); +// TODO: tagged template literals are not supported yet +tw`px-2 foo p-4 bar`; +tw.div`px-2 foo p-4 bar`; +notClassFunction("px-2 foo p-4 bar"); +notTemplateFunction`px-2 foo p-4 bar`; +notTemplateFunction.div`px-2 foo p-4 bar`; + +// nested values +
; +
; +
; +clsx(["px-2 foo p-4 bar"]); +clsx({ + "px-2 foo p-4 bar": [ + "px-2 foo p-4 bar", + { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + ], +}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/sorted.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/sorted.jsx.snap new file mode 100644 index 000000000000..f73ffe8db4b6 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/sorted.jsx.snap @@ -0,0 +1,56 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: sorted.jsx +--- +# Input +```jsx +<> + {/* attributes */} +
+
+
+
+ {/* utility sorting */} +
+
+
+
+
+
+
+
+
+
+; + +// functions +clsx("px-2 foo p-4 bar"); +// TODO: tagged template literals are not supported yet +tw`px-2 foo p-4 bar`; +tw.div`px-2 foo p-4 bar`; +notClassFunction("px-2 foo p-4 bar"); +notTemplateFunction`px-2 foo p-4 bar`; +notTemplateFunction.div`px-2 foo p-4 bar`; + +// nested values +
; +
; +
; +clsx(["px-2 foo p-4 bar"]); +clsx({ + "px-2 foo p-4 bar": [ + "px-2 foo p-4 bar", + { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + ], +}); + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx new file mode 100644 index 000000000000..39a3da3da266 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx @@ -0,0 +1,48 @@ +<> + {/* attributes */} +
+
+
+
+ {/* with variants */} +
+
+
+
+
+
+
+
+
+
+; + +// functions +clsx("px-2 foo p-4 bar"); +// TODO: tagged template literals are not supported yet +tw`px-2 foo p-4 bar`; +tw.div`px-2 foo p-4 bar`; +notClassFunction("px-2 foo p-4 bar"); +notTemplateFunction`px-2 foo p-4 bar`; +notTemplateFunction.div`px-2 foo p-4 bar`; + +// nested values +
; +
; +
; +clsx(["px-2 foo p-4 bar"]); +clsx({ + "px-2 foo p-4 bar": [ + "px-2 foo p-4 bar", + { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + ], +}); diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx.snap new file mode 100644 index 000000000000..3237d5bb8b1d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx.snap @@ -0,0 +1,466 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: unsorted.jsx +--- +# Input +```jsx +<> + {/* attributes */} +
+
+
+
+ {/* with variants */} +
+
+
+
+
+
+
+
+
+
+; + +// functions +clsx("px-2 foo p-4 bar"); +// TODO: tagged template literals are not supported yet +tw`px-2 foo p-4 bar`; +tw.div`px-2 foo p-4 bar`; +notClassFunction("px-2 foo p-4 bar"); +notTemplateFunction`px-2 foo p-4 bar`; +notTemplateFunction.div`px-2 foo p-4 bar`; + +// nested values +
; +
; +
; +clsx(["px-2 foo p-4 bar"]); +clsx({ + "px-2 foo p-4 bar": [ + "px-2 foo p-4 bar", + { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + ], +}); + +``` + +# Diagnostics +``` +unsorted.jsx:3:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 1 │ <> + 2 │ {/* attributes */} + > 3 │
+ │ ^^^^^^^^^^^^^^^^^^ + 4 │
+ 5 │
+ + i Unsafe fix: Sort the classes. + + 1 1 │ <> + 2 2 │ {/* attributes */} + 3 │ - → + 3 │ + → + 4 4 │
+ 5 5 │
+ + +``` + +``` +unsorted.jsx:4:17 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 2 │ {/* attributes */} + 3 │
+ > 4 │
+ │ ^^^^^^^^^^^^^^^^^^ + 5 │
+ 6 │
+ + i Unsafe fix: Sort the classes. + + 2 2 │ {/* attributes */} + 3 3 │
+ 4 │ - → + 4 │ + → + 5 5 │
+ 6 6 │
+ + +``` + +``` +unsorted.jsx:8:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 6 │
+ 7 │ {/* with variants */} + > 8 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │
+ 10 │
+ + i Unsafe fix: Sort the classes. + + 6 6 │
+ 7 7 │ {/* with variants */} + 8 │ - → + 8 │ + → + 9 9 │
+ 10 10 │
+ + +``` + +``` +unsorted.jsx:9:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 7 │ {/* with variants */} + 8 │
+ > 9 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │
+ 11 │
+ + i Unsafe fix: Sort the classes. + + 7 7 │ {/* with variants */} + 8 8 │
+ 9 │ - → + 9 │ + → + 10 10 │
+ 11 11 │
+ + +``` + +``` +unsorted.jsx:10:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 8 │
+ 9 │
+ > 10 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 11 │
+ 12 │
+ + i Unsafe fix: Sort the classes. + + 8 8 │
+ 9 9 │
+ 10 │ - → + 10 │ + → + 11 11 │
+ 12 12 │
+ + +``` + +``` +unsorted.jsx:11:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 9 │
+ 10 │
+ > 11 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │
+ 13 │
+ + i Unsafe fix: Sort the classes. + + 9 9 │
+ 10 10 │
+ 11 │ - → + 11 │ + → + 12 12 │
+ 13 13 │
+ + +``` + +``` +unsorted.jsx:12:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 10 │
+ 11 │
+ > 12 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 13 │
+ 14 │
+ + i Unsafe fix: Sort the classes. + + 10 10 │
+ 11 11 │
+ 12 │ - → + 12 │ + → + 13 13 │
+ 14 14 │
+ + +``` + +``` +unsorted.jsx:13:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 11 │
+ 12 │
+ > 13 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 14 │
+ 15 │
+ + i Unsafe fix: Sort the classes. + + 11 11 │
+ 12 12 │
+ 13 │ - → + 13 │ + → + 14 14 │
+ 15 15 │
+ + +``` + +``` +unsorted.jsx:14:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 12 │
+ 13 │
+ > 14 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 15 │
+ 16 │
+ + i Unsafe fix: Sort the classes. + + 12 12 │
+ 13 13 │
+ 14 │ - → + 14 │ + → + 15 15 │
+ 16 16 │
+ + +``` + +``` +unsorted.jsx:15:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 13 │
+ 14 │
+ > 15 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 16 │
+ 17 │
+ + i Unsafe fix: Sort the classes. + + 13 13 │
+ 14 14 │
+ 15 │ - → + 15 │ + → + 16 16 │
+ 17 17 │
+ + +``` + +``` +unsorted.jsx:16:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 14 │
+ 15 │
+ > 16 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 17 │
+ 18 │ ; + + i Unsafe fix: Sort the classes. + + 14 14 │
+ 15 15 │
+ 16 │ - → + 16 │ + → + 17 17 │
+ 18 18 │ ; + + +``` + +``` +unsorted.jsx:17:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 15 │
+ 16 │
+ > 17 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 18 │ ; + 19 │ + + i Unsafe fix: Sort the classes. + + 15 15 │
+ 16 16 │
+ 17 │ - → + 17 │ + → + 18 18 │ ; + 19 19 │ + + +``` + +``` +unsorted.jsx:30:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 29 │ // nested values + > 30 │
; + │ ^^^^^^^^^^^^^^^^^^ + 31 │
; + 32 │
; + 30 │ + ; + 31 31 │
; + 32 32 │
; + > 31 │
; + │ ^^^^^^^^^^^^^^^^^^ + 32 │
; + 31 │ - ; + 31 │ + ; + 32 32 │
36 │ "px-2 foo p-4 bar", + │ ^^^^^^^^^^^^^^^^^^ + 37 │ // TODO: property should be sorted + 38 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + + i Unsafe fix: Sort the classes. + + 34 34 │ // TODO: property should be sorted + 35 35 │ "px-2 foo p-4 bar": [ + 36 │ - → → → "px-2·foo·p-4·bar", + 36 │ + → → → "foo·bar·p-4·px-2", + 37 37 │ // TODO: property should be sorted + 38 38 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + + +``` + +``` +unsorted.jsx:38:26 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 36 │ "px-2 foo p-4 bar", + 37 │ // TODO: property should be sorted + > 38 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + │ ^^^^^^^^^^^^^^^^^^ + 39 │ ], + 40 │ }} + + i Unsafe fix: Sort the classes. + + 36 36 │ "px-2 foo p-4 bar", + 37 37 │ // TODO: property should be sorted + 38 │ - → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["px-2·foo·p-4·bar"]·}, + 38 │ + → → → {·"px-2·foo·p-4·bar":·"foo·bar·p-4·px-2",·custom:·["px-2·foo·p-4·bar"]·}, + 39 39 │ ], + 40 40 │ }} + + +``` + +``` +unsorted.jsx:38:55 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 36 │ "px-2 foo p-4 bar", + 37 │ // TODO: property should be sorted + > 38 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + │ ^^^^^^^^^^^^^^^^^^ + 39 │ ], + 40 │ }} + + i Unsafe fix: Sort the classes. + + 36 36 │ "px-2 foo p-4 bar", + 37 37 │ // TODO: property should be sorted + 38 │ - → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["px-2·foo·p-4·bar"]·}, + 38 │ + → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["foo·bar·p-4·px-2"]·}, + 39 39 │ ], + 40 40 │ }} + + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx deleted file mode 100644 index 51003b9333f0..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx +++ /dev/null @@ -1 +0,0 @@ -
; diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx.snap deleted file mode 100644 index 9597b5b16494..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/valid.jsx.snap +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: valid.jsx ---- -# Input -```jsx -
; - -``` - - From 3f7904dd5efd0d358656ffff739d74f579bc6c72 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Mon, 22 Jan 2024 01:33:23 +0100 Subject: [PATCH 19/82] fix options and tests --- crates/biome_js_analyze/src/options.rs | 12 ++ .../nursery/use_sorted_classes.rs | 4 +- .../any_class_string_like.rs | 24 ++- .../nursery/use_sorted_classes/options.rs | 50 +++--- .../codeOptionsSorted.jsx.snap | 20 --- .../codeOptionsUnsorted.jsx.snap | 162 ++++++++++++++++-- .../@biomejs/backend-jsonrpc/src/workspace.ts | 13 +- .../@biomejs/biome/configuration_schema.json | 20 +++ 8 files changed, 233 insertions(+), 72 deletions(-) diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs index 4deab070fcdd..5b6af44b806e 100644 --- a/crates/biome_js_analyze/src/options.rs +++ b/crates/biome_js_analyze/src/options.rs @@ -4,6 +4,7 @@ use crate::analyzers::nursery::use_consistent_array_type::ConsistentArrayTypeOpt use crate::analyzers::nursery::use_filenaming_convention::FilenamingConventionOptions; use crate::semantic_analyzers::correctness::use_exhaustive_dependencies::HooksOptions; use crate::semantic_analyzers::correctness::use_hook_at_top_level::DeprecatedHooksOptions; +use crate::semantic_analyzers::nursery::use_sorted_classes::UtilityClassSortingOptions; use crate::semantic_analyzers::style::no_restricted_globals::RestrictedGlobalsOptions; use crate::semantic_analyzers::style::use_naming_convention::NamingConventionOptions; use crate::{ @@ -38,6 +39,8 @@ pub enum PossibleOptions { RestrictedGlobals(RestrictedGlobalsOptions), /// Options for `useValidAriaRole` rule ValidAriaRole(ValidAriaRoleOptions), + /// Options for `useSortedClasses` rule + UtilityClassSorting(UtilityClassSortingOptions), } impl Default for PossibleOptions { @@ -105,6 +108,13 @@ impl PossibleOptions { }; RuleOptions::new(options) } + "useSortedClasses" => { + let options = match self { + PossibleOptions::UtilityClassSorting(options) => options.clone(), + _ => UtilityClassSortingOptions::default(), + }; + RuleOptions::new(options) + } // TODO: review error _ => panic!("This rule {:?} doesn't have options", rule_key), } @@ -137,6 +147,8 @@ impl Deserializable for PossibleOptions { "useValidAriaRole" => { Deserializable::deserialize(value, "options", diagnostics).map(Self::ValidAriaRole) } + "useSortedClasses" => Deserializable::deserialize(value, "options", diagnostics) + .map(Self::UtilityClassSorting), _ => { diagnostics.push( DeserializationDiagnostic::new(markup! { diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index 13fbd2890bfd..79b909e64645 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -16,7 +16,7 @@ use biome_rowan::{AstNode, BatchMutationExt}; use crate::JsRuleAction; -pub use self::options::UseSortedClassesOptions; +pub use self::options::UtilityClassSortingOptions; use self::{ any_class_string_like::AnyClassStringLike, presets::{get_utilities_preset, UseSortedClassesPreset}, @@ -109,7 +109,7 @@ impl Rule for UseSortedClasses { type Query = AnyClassStringLike; type State = String; type Signals = Option; - type Options = UseSortedClassesOptions; + type Options = UtilityClassSortingOptions; fn run(ctx: &RuleContext) -> Option { // TODO: unsure if options are needed here. The sort config should ideally be created once diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs index 448eabe1d851..dca5d14ef8cc 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs @@ -10,16 +10,19 @@ use biome_rowan::{ declare_node_union, AstNode, Language, SyntaxNode, TextRange, TokenText, WalkEvent, }; -use super::UseSortedClassesOptions; +use super::UtilityClassSortingOptions; // utils // ----- -fn get_options_from_analyzer(analyzer_options: &AnalyzerOptions) -> UseSortedClassesOptions { +fn get_options_from_analyzer(analyzer_options: &AnalyzerOptions) -> UtilityClassSortingOptions { analyzer_options .configuration .rules - .get_rule_options::(&RuleKey::new("nursery", "useSortedClasses")) + .get_rule_options::(&RuleKey::new( + "nursery", + "useSortedClasses", + )) .cloned() .unwrap_or_default() } @@ -79,11 +82,15 @@ impl Visitor for StringLiteralInAttributeVisitor { mut ctx: VisitorContext, ) { let options = get_options_from_analyzer(ctx.options); + let attributes = match &options.attributes { + Some(attributes) => attributes, + None => return, + }; match event { WalkEvent::Enter(node) => { // When entering an attribute node, track if we are in a target attribute. if let Some(attribute) = JsxAttribute::cast_ref(node) { - self.in_target_attribute = is_target_attribute(&attribute, &options.attributes); + self.in_target_attribute = is_target_attribute(&attribute, attributes); } // When entering a JSX string node, and we are in a target attribute, emit. @@ -131,16 +138,17 @@ impl Visitor for StringLiteralInCallExpressionVisitor { mut ctx: VisitorContext, ) { let options = get_options_from_analyzer(ctx.options); - if options.functions.is_empty() { - return; - } + let functions = match &options.functions { + Some(functions) => functions, + None => return, + }; match event { WalkEvent::Enter(node) => { // When entering a call expression node, track if we are in a target function and reset // in_arguments. if let Some(call_expression) = JsCallExpression::cast_ref(node) { self.in_target_function = - is_call_expression_of_target_function(&call_expression, &options.functions); + is_call_expression_of_target_function(&call_expression, functions); self.in_arguments = false; } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs index 056b6e01ac01..dfb0ff3adf0d 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/options.rs @@ -3,42 +3,49 @@ use biome_deserialize::{ VisitableType, }; use biome_rowan::TextRange; +#[cfg(feature = "schemars")] +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; /// Attributes that are always targets. const CLASS_ATTRIBUTES: [&str; 2] = ["class", "className"]; -#[derive(Debug, Clone)] -pub struct UseSortedClassesOptions { - /// Additional attribute targets specified by the user. - pub attributes: Vec, - /// Function targets specified by the user. - pub functions: Vec, +#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct UtilityClassSortingOptions { + /// Additional attributes that will be sorted. + #[serde(skip_serializing_if = "Option::is_none")] + pub attributes: Option>, + /// Names of the functions or tagged templates that will be sorted. + #[serde(skip_serializing_if = "Option::is_none")] + pub functions: Option>, } -impl Default for UseSortedClassesOptions { +impl Default for UtilityClassSortingOptions { fn default() -> Self { - UseSortedClassesOptions { - attributes: CLASS_ATTRIBUTES.iter().map(|&s| s.to_string()).collect(), - functions: Vec::new(), + UtilityClassSortingOptions { + attributes: Some(CLASS_ATTRIBUTES.iter().map(|&s| s.to_string()).collect()), + functions: None, } } } const ALLOWED_OPTIONS: &[&str] = &["attributes", "functions"]; -impl Deserializable for UseSortedClassesOptions { +impl Deserializable for UtilityClassSortingOptions { fn deserialize( value: &impl DeserializableValue, name: &str, diagnostics: &mut Vec, ) -> Option { - value.deserialize(UseSortedClassesOptionsVisitor, name, diagnostics) + value.deserialize(UtilityClassSortingOptionsVisitor, name, diagnostics) } } -struct UseSortedClassesOptionsVisitor; -impl DeserializationVisitor for UseSortedClassesOptionsVisitor { - type Output = UseSortedClassesOptions; +struct UtilityClassSortingOptionsVisitor; +impl DeserializationVisitor for UtilityClassSortingOptionsVisitor { + type Output = UtilityClassSortingOptions; const EXPECTED_TYPE: VisitableType = VisitableType::MAP; @@ -49,7 +56,7 @@ impl DeserializationVisitor for UseSortedClassesOptionsVisitor { _name: &str, diagnostics: &mut Vec, ) -> Option { - let mut result = UseSortedClassesOptions::default(); + let mut result = UtilityClassSortingOptions::default(); for (key, value) in members.flatten() { let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { @@ -60,15 +67,14 @@ impl DeserializationVisitor for UseSortedClassesOptionsVisitor { if let Some(attributes_option) = Deserializable::deserialize(&value, &key_text, diagnostics) { - result.attributes.extend::>(attributes_option); + result + .attributes + .get_or_insert_with(Vec::new) + .extend::>(attributes_option); } } "functions" => { - if let Some(functions) = - Deserializable::deserialize(&value, &key_text, diagnostics) - { - result.functions = functions; - } + result.functions = Deserializable::deserialize(&value, &key_text, diagnostics) } unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key( unknown_key, diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx.snap index f769a12d982d..7c765a3d6d47 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsSorted.jsx.snap @@ -53,24 +53,4 @@ clsx({ ``` -# Diagnostics -``` -codeOptionsSorted.options:8:17 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × The rule useSortedClasses doesn't accept any options. - - 6 │ "useSortedClasses": { - 7 │ "level": "error", - > 8 │ "options": { - │ ^ - > 9 │ "attributes": ["customClassAttribute"], - > 10 │ "functions": ["clsx", "tw"] - > 11 │ } - │ ^ - 12 │ } - 13 │ } - - -``` - diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap index e857f058311c..eec24aa6ec29 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap @@ -54,25 +54,6 @@ clsx({ ``` # Diagnostics -``` -codeOptionsUnsorted.options:8:17 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × The rule useSortedClasses doesn't accept any options. - - 6 │ "useSortedClasses": { - 7 │ "level": "error", - > 8 │ "options": { - │ ^ - > 9 │ "attributes": ["customClassAttribute"], - > 10 │ "functions": ["clsx", "tw"] - > 11 │ } - │ ^ - 12 │ } - 13 │ } - - -``` - ``` codeOptionsUnsorted.jsx:3:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -119,6 +100,30 @@ codeOptionsUnsorted.jsx:4:17 lint/nursery/useSortedClasses FIXABLE ━━━ 6 6 │
+``` + +``` +codeOptionsUnsorted.jsx:5:28 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 3 │
+ 4 │
+ > 5 │
+ │ ^^^^^^^^^^^^^^^^^^ + 6 │
+ 7 │ {/* utility sorting */} + + i Unsafe fix: Sort the classes. + + 3 3 │
+ 4 4 │
+ 5 │ - → + 5 │ + → + 6 6 │
+ 7 7 │ {/* utility sorting */} + + ``` ``` @@ -359,6 +364,29 @@ codeOptionsUnsorted.jsx:17:13 lint/nursery/useSortedClasses FIXABLE ━━━ 19 19 │ +``` + +``` +codeOptionsUnsorted.jsx:21:6 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 20 │ // functions + > 21 │ clsx("px-2 foo p-4 bar"); + │ ^^^^^^^^^^^^^^^^^^ + 22 │ // TODO: tagged template literals are not supported yet + 23 │ tw`px-2 foo p-4 bar`; + + i Unsafe fix: Sort the classes. + + 19 19 │ + 20 20 │ // functions + 21 │ - clsx("px-2·foo·p-4·bar"); + 21 │ + clsx("foo·bar·p-4·px-2"); + 22 22 │ // TODO: tagged template literals are not supported yet + 23 23 │ tw`px-2 foo p-4 bar`; + + ``` ``` @@ -480,4 +508,100 @@ codeOptionsUnsorted.jsx:36:55 lint/nursery/useSortedClasses FIXABLE ━━━ ``` +``` +codeOptionsUnsorted.jsx:40:7 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 38 │ }} + 39 │ />; + > 40 │ clsx(["px-2 foo p-4 bar"]); + │ ^^^^^^^^^^^^^^^^^^ + 41 │ clsx({ + 42 │ "px-2 foo p-4 bar": [ + + i Unsafe fix: Sort the classes. + + 38 38 │ }} + 39 39 │ />; + 40 │ - clsx(["px-2·foo·p-4·bar"]); + 40 │ + clsx(["foo·bar·p-4·px-2"]); + 41 41 │ clsx({ + 42 42 │ "px-2 foo p-4 bar": [ + + +``` + +``` +codeOptionsUnsorted.jsx:43:3 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 41 │ clsx({ + 42 │ "px-2 foo p-4 bar": [ + > 43 │ "px-2 foo p-4 bar", + │ ^^^^^^^^^^^^^^^^^^ + 44 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + 45 │ ], + + i Unsafe fix: Sort the classes. + + 41 41 │ clsx({ + 42 42 │ "px-2 foo p-4 bar": [ + 43 │ - → → "px-2·foo·p-4·bar", + 43 │ + → → "foo·bar·p-4·px-2", + 44 44 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + 45 45 │ ], + + +``` + +``` +codeOptionsUnsorted.jsx:44:25 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 42 │ "px-2 foo p-4 bar": [ + 43 │ "px-2 foo p-4 bar", + > 44 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + │ ^^^^^^^^^^^^^^^^^^ + 45 │ ], + 46 │ }); + + i Unsafe fix: Sort the classes. + + 42 42 │ "px-2 foo p-4 bar": [ + 43 43 │ "px-2 foo p-4 bar", + 44 │ - → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["px-2·foo·p-4·bar"]·}, + 44 │ + → → {·"px-2·foo·p-4·bar":·"foo·bar·p-4·px-2",·custom:·["px-2·foo·p-4·bar"]·}, + 45 45 │ ], + 46 46 │ }); + + +``` + +``` +codeOptionsUnsorted.jsx:44:54 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! These CSS classes should be sorted. + + 42 │ "px-2 foo p-4 bar": [ + 43 │ "px-2 foo p-4 bar", + > 44 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + │ ^^^^^^^^^^^^^^^^^^ + 45 │ ], + 46 │ }); + + i Unsafe fix: Sort the classes. + + 42 42 │ "px-2 foo p-4 bar": [ + 43 43 │ "px-2 foo p-4 bar", + 44 │ - → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["px-2·foo·p-4·bar"]·}, + 44 │ + → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["foo·bar·p-4·px-2"]·}, + 45 45 │ ], + 46 46 │ }); + + +``` + diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index a3e687ff8899..d1fcf65d4b29 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1396,7 +1396,8 @@ export type PossibleOptions = | DeprecatedHooksOptions | NamingConventionOptions | RestrictedGlobalsOptions - | ValidAriaRoleOptions; + | ValidAriaRoleOptions + | UtilityClassSortingOptions; /** * Options for the rule `noExcessiveCognitiveComplexity`. */ @@ -1461,6 +1462,16 @@ export interface ValidAriaRoleOptions { allowedInvalidRoles: string[]; ignoreNonDom: boolean; } +export interface UtilityClassSortingOptions { + /** + * Additional attributes that will be sorted. + */ + attributes?: string[]; + /** + * Names of the functions or tagged templates that will be sorted. + */ + functions?: string[]; +} export type ConsistentArrayType = "shorthand" | "generic"; export type FilenameCases = FilenameCase[]; export interface Hooks { diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 8b4f0bae182c..53116760e67a 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1673,6 +1673,10 @@ { "description": "Options for `useValidAriaRole` rule", "allOf": [{ "$ref": "#/definitions/ValidAriaRoleOptions" }] + }, + { + "description": "Options for `useSortedClasses` rule", + "allOf": [{ "$ref": "#/definitions/UtilityClassSortingOptions" }] } ] }, @@ -2380,6 +2384,22 @@ } ] }, + "UtilityClassSortingOptions": { + "type": "object", + "properties": { + "attributes": { + "description": "Additional attributes that will be sorted.", + "type": ["array", "null"], + "items": { "type": "string" } + }, + "functions": { + "description": "Names of the functions or tagged templates that will be sorted.", + "type": ["array", "null"], + "items": { "type": "string" } + } + }, + "additionalProperties": false + }, "ValidAriaRoleOptions": { "type": "object", "required": ["allowedInvalidRoles", "ignoreNonDom"], From 71039f521de74b632f73ff102e685ec019ebe776 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Mon, 22 Jan 2024 01:47:55 +0100 Subject: [PATCH 20/82] class info unit tests --- .../nursery/use_sorted_classes/class_info.rs | 155 +++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs index dccd5f4befc0..833ea522533c 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs @@ -19,6 +19,7 @@ use super::{ // --------- /// The result of matching a utility against a target. +#[derive(Debug, Eq, PartialEq)] enum UtilityMatch { /// The utility matches an exact target. Exact, @@ -48,9 +49,49 @@ impl UtilityMatch { } } -// TODO: unit tests. +// unit test +#[cfg(test)] +mod utility_match_tests { + use super::*; + + #[test] + fn test_exact_match() { + assert_eq!(UtilityMatch::from("px-2$", "px-2"), UtilityMatch::Exact); + // TODO: support negative values + // assert_eq!(UtilityMatch::from("px-2$", "-px-2"), UtilityMatch::Exact); + assert_eq!(UtilityMatch::from("px-2$", "not-px-2"), UtilityMatch::None); + assert_eq!(UtilityMatch::from("px-2$", "px-2-"), UtilityMatch::None); + assert_eq!(UtilityMatch::from("px-2$", "px-4"), UtilityMatch::None); + assert_eq!(UtilityMatch::from("px-2$", "px-2$"), UtilityMatch::None); + assert_eq!(UtilityMatch::from("px-2$", "px-2-"), UtilityMatch::None); + assert_eq!(UtilityMatch::from("px-2$", "px-2.5"), UtilityMatch::None); + assert_eq!(UtilityMatch::from("px-2$", "px-2.5$"), UtilityMatch::None); + assert_eq!(UtilityMatch::from("px-2$", "px-2.5-"), UtilityMatch::None); + } + + #[test] + fn test_partial_match() { + assert_eq!(UtilityMatch::from("px-", "px-2"), UtilityMatch::Partial); + // TODO: support negative values + // assert_eq!(UtilityMatch::from("px-", "-px-2"), UtilityMatch::Partial); + assert_eq!(UtilityMatch::from("px-", "px-2.5"), UtilityMatch::Partial); + assert_eq!( + UtilityMatch::from("px-", "px-anything"), + UtilityMatch::Partial + ); + assert_eq!( + UtilityMatch::from("px-", "px-%$>?+=-"), + UtilityMatch::Partial + ); + assert_eq!(UtilityMatch::from("px-", "px-"), UtilityMatch::None); + // TODO: support negative values + // assert_eq!(UtilityMatch::from("px-", "-px-"), UtilityMatch::None); + assert_eq!(UtilityMatch::from("px-", "not-px-2"), UtilityMatch::None); + } +} /// Sort-related information about a utility. +#[derive(Debug, Eq, PartialEq)] struct UtilityInfo { /// The layer the utility belongs to. layer: String, @@ -117,7 +158,117 @@ fn get_utility_info( None } -// TODO: unit tests. +// unit test +#[cfg(test)] +mod get_utility_info_tests { + use super::*; + use crate::semantic_analyzers::nursery::use_sorted_classes::sort_config::UtilityLayer; + + #[test] + fn test_exact_match() { + let utility_config = vec![UtilityLayer { + name: "layer".to_string(), + classes: &["px-2$"], + }]; + let utility_data = ClassSegmentStructure { + text: "px-2".to_string(), + arbitrary: false, + }; + assert_eq!( + get_utility_info(&utility_config, &utility_data), + Some(UtilityInfo { + layer: "layer".to_string(), + index: 0, + }) + ); + let utility_data = ClassSegmentStructure { + text: "px-4".to_string(), + arbitrary: false, + }; + assert_eq!(get_utility_info(&utility_config, &utility_data), None); + } + + #[test] + fn test_partial_match() { + let utility_config = vec![UtilityLayer { + name: "layer".to_string(), + classes: &["px-"], + }]; + let utility_data = ClassSegmentStructure { + text: "px-2".to_string(), + arbitrary: false, + }; + assert_eq!( + get_utility_info(&utility_config, &utility_data), + Some(UtilityInfo { + layer: "layer".to_string(), + index: 0, + }) + ); + let utility_data = ClassSegmentStructure { + text: "not-px-2".to_string(), + arbitrary: false, + }; + assert_eq!(get_utility_info(&utility_config, &utility_data), None); + } + + #[test] + fn test_partial_match_longest() { + let utility_config = vec![UtilityLayer { + name: "layer".to_string(), + classes: &["border-", "border-t-"], + }]; + let utility_data = ClassSegmentStructure { + text: "border-t-2".to_string(), + arbitrary: false, + }; + assert_eq!( + get_utility_info(&utility_config, &utility_data), + Some(UtilityInfo { + layer: "layer".to_string(), + index: 1, + }) + ); + } + + #[test] + fn test_partial_match_longest_first() { + let utility_config = vec![UtilityLayer { + name: "layer".to_string(), + classes: &["border-t-", "border-"], + }]; + let utility_data = ClassSegmentStructure { + text: "border-t-2".to_string(), + arbitrary: false, + }; + assert_eq!( + get_utility_info(&utility_config, &utility_data), + Some(UtilityInfo { + layer: "layer".to_string(), + index: 0, + }) + ); + } + + #[test] + fn test_arbitrary_layer() { + let utility_config = vec![UtilityLayer { + name: "layer".to_string(), + classes: &["border-t-", "border-"], + }]; + let utility_data = ClassSegmentStructure { + text: "[arbitrary:css]".to_string(), + arbitrary: true, + }; + assert_eq!( + get_utility_info(&utility_config, &utility_data), + Some(UtilityInfo { + layer: "arbitrary".to_string(), + index: 0, + }) + ); + } +} // classes // ------- From 9144a6528aafc2aab57857eae042cfdf2695cec5 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Mon, 22 Jan 2024 01:48:54 +0100 Subject: [PATCH 21/82] TODOs --- .../nursery/use_sorted_classes/any_class_string_like.rs | 2 ++ .../src/semantic_analyzers/nursery/use_sorted_classes/sort.rs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs index dca5d14ef8cc..d2e5e8d3258e 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/any_class_string_like.rs @@ -74,6 +74,7 @@ struct StringLiteralInAttributeVisitor { } // Finds class-like strings in JSX attributes, including class, className, and others defined in the options. +// TODO: match object properties too impl Visitor for StringLiteralInAttributeVisitor { type Language = JsLanguage; fn visit( @@ -129,6 +130,7 @@ struct StringLiteralInCallExpressionVisitor { } // Finds class-like strings inside function calls defined in the options, e.g. clsx(classes). +// TODO: match object properties too impl Visitor for StringLiteralInCallExpressionVisitor { type Language = JsLanguage; diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs index ab9e8ab2621f..51bca5a24bf1 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs @@ -51,6 +51,8 @@ impl ClassInfo { } } +// TODO: implement through Ord/PartialOrd trait. + // See: https://github.com/tailwindlabs/tailwindcss/blob/970f2ca704dda95cf328addfe67b81d6679c8755/src/lib/offsets.js#L206 // This comparison function follows a very similar logic to the one in Tailwind CSS, with some // simplifications and necessary differences. @@ -100,6 +102,8 @@ pub fn sort_class_name(class_name: &TokenText, sort_config: &SortConfig) -> Stri } }); + // TODO: make this the last step of compare instead? + // Pre-sort the recognized classes lexico-graphically. classes_info.sort_unstable_by(|a, b| a.text.cmp(&b.text)); From 56c4478806e6089ab6355f2cf6c35534d0cc76c8 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Mon, 22 Jan 2024 01:49:06 +0100 Subject: [PATCH 22/82] improve preset structure --- .../nursery/use_sorted_classes/presets.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs index b07027c4a9e4..3006146bac29 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs @@ -8,10 +8,19 @@ pub enum UseSortedClassesPreset { #[allow(unused)] None, #[default] - TailwindCSS, // TODO: should this be the default? + TailwindCSS, } -const TAILWIND_PRESETS: [&str; 567] = [ +// TAILWIND-COMPONENTS-LAYER-CLASSES-START +const UTILITIES_COMPONENTS_CLASSES: [&str; 1] = [ + // TODO: auto-generated message + "container$", +]; +// TAILWIND-COMPONENTS-LAYER-CLASSES-END + +// TAILWIND-UTILITIES-LAYER-CLASSES-START +const UTILITIES_LAYER_CLASSES: [&str; 567] = [ + // TODO: auto-generated message "sr-only$", "not-sr-only$", "pointer-events-none$", @@ -580,8 +589,8 @@ const TAILWIND_PRESETS: [&str; 567] = [ "forced-color-adjust-auto$", "forced-color-adjust-none$", ]; +// TAILWIND-UTILITIES-LAYER-CLASSES-END -// TODO: make this static pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> UtilitiesConfig { match preset { UseSortedClassesPreset::None => { @@ -592,11 +601,11 @@ pub fn get_utilities_preset(preset: &UseSortedClassesPreset) -> UtilitiesConfig vec![ UtilityLayer { name: String::from("components"), - classes: ["container$"].as_slice(), + classes: UTILITIES_COMPONENTS_CLASSES.as_slice(), }, UtilityLayer { name: String::from("utilities"), - classes: TAILWIND_PRESETS.as_slice(), + classes: UTILITIES_LAYER_CLASSES.as_slice(), }, ] // TAILWIND-UTILITIES-PRESET-END From 2195b0b8f8ebecee27c8e623cd820f570c0123f6 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Mon, 22 Jan 2024 04:43:47 +0100 Subject: [PATCH 23/82] one more class info unit test --- .../nursery/use_sorted_classes/class_info.rs | 89 ++++++++++++++++++- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs index 833ea522533c..7bd2b438b65e 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_info.rs @@ -49,7 +49,6 @@ impl UtilityMatch { } } -// unit test #[cfg(test)] mod utility_match_tests { use super::*; @@ -158,7 +157,6 @@ fn get_utility_info( None } -// unit test #[cfg(test)] mod get_utility_info_tests { use super::*; @@ -274,7 +272,7 @@ mod get_utility_info_tests { // ------- /// Sort-related information about a CSS class. -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct ClassInfo { /// The full text of the class itself. pub text: String, @@ -308,4 +306,87 @@ pub fn get_class_info(class_name: &str, sort_config: &SortConfig) -> Option Date: Mon, 22 Jan 2024 04:44:11 +0100 Subject: [PATCH 24/82] fixes --- .../nursery/use_sorted_classes/presets.rs | 4 ++-- .../nursery/use_sorted_classes/sort_config.rs | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs index 3006146bac29..6693a0a72e93 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/presets.rs @@ -1,5 +1,5 @@ -// Presets contain pre-defined sort configurations, notably from Tailwind CSS. They are a -// starting point that can be extended (e.g. by adding custom utilities or variants). +//! Presets contain pre-defined sort configurations, notably from Tailwind CSS. They are a +//! starting point that can be extended (e.g. by adding custom utilities or variants). use super::sort_config::{UtilitiesConfig, UtilityLayer}; diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs index 457c2f8561d1..c333818a9b6a 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort_config.rs @@ -31,13 +31,12 @@ impl SortConfig { pub fn new(utilities_config: UtilitiesConfig, variants: VariantsConfig) -> Self { // Compute the layer index map. let mut layer_index_map: HashMap = HashMap::new(); - let mut last_index = 0; - layer_index_map.insert("parasite".to_string(), last_index); + let mut index = 0; for layer in utilities_config.iter() { - last_index += 1; - layer_index_map.insert(layer.name.clone(), last_index); + layer_index_map.insert(layer.name.clone(), index); + index += 1; } - layer_index_map.insert("arbitrary".to_string(), last_index + 1); + layer_index_map.insert("arbitrary".to_string(), index); Self { utilities: utilities_config, From 0afcef5231ce099160490f61ab08b80c0f1b7c44 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Tue, 23 Jan 2024 03:02:40 +0100 Subject: [PATCH 25/82] lexer unit tests --- .../nursery/use_sorted_classes/class_lexer.rs | 238 +++++++++++++++++- 1 file changed, 234 insertions(+), 4 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs index 6da2083f3f08..b96a5070adb5 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs @@ -33,7 +33,27 @@ fn split_at_indexes<'a>(s: &'a str, indexes: &[usize]) -> Vec<&'a str> { segments } -// TODO: unit tests. +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_split_at_indexes() { + assert_eq!( + split_at_indexes("foo:bar:baz", &[3, 7]), + vec!["foo", "bar", "baz"] + ); + assert_eq!(split_at_indexes("foobar:baz", &[6]), vec!["foobar", "baz"]); + assert_eq!(split_at_indexes("foobarbaz", &[]), vec!["foobarbaz"]); + assert_eq!( + split_at_indexes("foo_bar_baz", &[3, 7]), + vec!["foo", "bar", "baz"] + ); + assert_eq!(split_at_indexes(":", &[0]), Vec::<&str>::new()); + assert_eq!(split_at_indexes(":::", &[0]), vec!["::"]); + assert_eq!(split_at_indexes(":::", &[1]), vec![":", ":"]); + } +} #[derive(Debug, Clone, PartialEq)] enum Quote { @@ -61,14 +81,14 @@ enum CharKind { } /// Information about the structure of a segment of a CSS class (variant or utility). -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct ClassSegmentStructure { pub arbitrary: bool, pub text: String, } /// Information about the structure of a CSS class. -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct ClassStructure { pub variants: Vec, pub utility: ClassSegmentStructure, @@ -87,6 +107,7 @@ pub fn tokenize_class(class_name: &str) -> Option { for (index, c) in class_name.char_indices() { let mut next_last_char = CharKind::Other; let mut is_start_of_arbitrary_block = false; + match c { '[' => { if arbitrary_block_depth == 0 { @@ -135,6 +156,8 @@ pub fn tokenize_class(class_name: &str) -> Option { quoted_arbitrary_block_type = None; } } + } else { + return None; } } ':' => { @@ -164,4 +187,211 @@ pub fn tokenize_class(class_name: &str) -> Option { Some(ClassStructure { variants, utility }) } -// TODO: unit tests. +#[cfg(test)] +mod tests_tokenize_class { + use super::*; + + #[test] + fn test_tokenize_class() { + assert_eq!( + tokenize_class("px-2"), + Some(ClassStructure { + variants: Vec::new(), + utility: ClassSegmentStructure { + arbitrary: false, + text: "px-2".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("hover:px-2"), + Some(ClassStructure { + variants: vec![ClassSegmentStructure { + arbitrary: false, + text: "hover".to_string(), + }], + utility: ClassSegmentStructure { + arbitrary: false, + text: "px-2".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("sm:hover:px-2"), + Some(ClassStructure { + variants: vec![ + ClassSegmentStructure { + arbitrary: false, + text: "sm".to_string(), + }, + ClassSegmentStructure { + arbitrary: false, + text: "hover".to_string(), + }, + ], + utility: ClassSegmentStructure { + arbitrary: false, + text: "px-2".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("hover:[mask:circle]"), + Some(ClassStructure { + variants: vec![ClassSegmentStructure { + arbitrary: false, + text: "hover".to_string(), + }], + utility: ClassSegmentStructure { + arbitrary: true, + text: "[mask:circle]".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("[&:nth-child(3)]:px-2"), + Some(ClassStructure { + variants: vec![ClassSegmentStructure { + arbitrary: true, + text: "[&:nth-child(3)]".to_string(), + }], + utility: ClassSegmentStructure { + arbitrary: false, + text: "px-2".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("hover:[mask:circle]"), + Some(ClassStructure { + variants: vec![ClassSegmentStructure { + arbitrary: false, + text: "hover".to_string(), + },], + utility: ClassSegmentStructure { + arbitrary: true, + text: "[mask:circle]".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("[&:nth-child(3)]:[mask:circle]"), + Some(ClassStructure { + variants: vec![ClassSegmentStructure { + arbitrary: true, + text: "[&:nth-child(3)]".to_string(), + },], + utility: ClassSegmentStructure { + arbitrary: true, + text: "[mask:circle]".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("font-[Roboto]:[mask:circle]"), + Some(ClassStructure { + variants: vec![ClassSegmentStructure { + arbitrary: false, + text: "font-[Roboto]".to_string(), + },], + utility: ClassSegmentStructure { + arbitrary: true, + text: "[mask:circle]".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("font-['Roboto']:[mask:circle]"), + Some(ClassStructure { + variants: vec![ClassSegmentStructure { + arbitrary: false, + text: "font-['Roboto']".to_string(), + },], + utility: ClassSegmentStructure { + arbitrary: true, + text: "[mask:circle]".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("quotes-['Ro'b\"`oto']:block"), + Some(ClassStructure { + variants: vec![ClassSegmentStructure { + arbitrary: false, + text: "quotes-['Ro'b\"`oto']".to_string(), + },], + utility: ClassSegmentStructure { + arbitrary: false, + text: "block".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("quotes-[']']:block"), + Some(ClassStructure { + variants: vec![ClassSegmentStructure { + arbitrary: false, + text: "quotes-[']']".to_string(), + },], + utility: ClassSegmentStructure { + arbitrary: false, + text: "block".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("quotes-[\"]\"]"), + Some(ClassStructure { + variants: Vec::new(), + utility: ClassSegmentStructure { + arbitrary: false, + text: "quotes-[\"]\"]".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("quotes-[`]`]"), + Some(ClassStructure { + variants: Vec::new(), + utility: ClassSegmentStructure { + arbitrary: false, + text: "quotes-[`]`]".to_string(), + }, + }) + ); + assert_eq!(tokenize_class("no-quotes-[]]:block"), None); + assert_eq!( + tokenize_class("escaped-quotes-[']\\']:block"), + Some(ClassStructure { + variants: Vec::new(), + utility: ClassSegmentStructure { + arbitrary: false, + text: "escaped-quotes-[']\\']:block".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("double-escaped-quotes-[']\\\\']:block"), + Some(ClassStructure { + variants: vec![ClassSegmentStructure { + arbitrary: false, + text: "double-escaped-quotes-[']\\\\']".to_string(), + },], + utility: ClassSegmentStructure { + arbitrary: false, + text: "block".to_string(), + }, + }) + ); + assert_eq!( + tokenize_class("triple-escaped-quotes-[']\\\\\\']:block"), + Some(ClassStructure { + variants: Vec::new(), + utility: ClassSegmentStructure { + arbitrary: false, + text: "triple-escaped-quotes-[']\\\\\\']:block".to_string(), + }, + }) + ); + } +} From 84720be1dcdf9f49d24d8410d6466de569175702 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Tue, 23 Jan 2024 03:04:15 +0100 Subject: [PATCH 26/82] remove TODO (already covered by integration tests) --- .../src/semantic_analyzers/nursery/use_sorted_classes/sort.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs index 51bca5a24bf1..9efa87347ea6 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/sort.rs @@ -121,5 +121,3 @@ pub fn sort_class_name(class_name: &TokenText, sort_config: &SortConfig) -> Stri // Join the classes back into a string. sorted_classes.join(" ") } - -// TODO: unit tests. From 0d0b4ed721c79329503772556837cb826833f1ff Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Tue, 23 Jan 2024 04:05:19 +0100 Subject: [PATCH 27/82] remove panics --- .../nursery/use_sorted_classes/class_lexer.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs index b96a5070adb5..b3ff66197a48 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes/class_lexer.rs @@ -124,8 +124,7 @@ pub fn tokenize_class(class_name: &str) -> Option { } else if let CharKind::Backslash = last_char { // Escaped, ignore. } else { - let quote = Quote::from_char(c) - .expect("TODO: error message (this should never happen)"); + let quote = Quote::from_char(c)?; next_last_char = CharKind::Quote(quote); } } @@ -167,9 +166,6 @@ pub fn tokenize_class(class_name: &str) -> Option { } _ => {} }; - if arbitrary_block_depth < 0 { - panic!("TODO: error message (this should never happen)"); - }; if at_arbitrary_block_start && !is_start_of_arbitrary_block { at_arbitrary_block_start = false; }; From 72ee1f117ede86bc4b4f06c4fc827ac264d0d67e Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Tue, 23 Jan 2024 04:05:37 +0100 Subject: [PATCH 28/82] improve docs --- .../nursery/use_sorted_classes.rs | 46 +++++++++++++++++-- .../docs/linter/rules/use-sorted-classes.md | 46 +++++++++++++++++-- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index 79b909e64645..cb091d08e374 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -31,7 +31,28 @@ declare_rule! { /// /// It is analogous to [`prettier-plugin-tailwindcss`](https://github.com/tailwindlabs/prettier-plugin-tailwindcss). /// - /// NOTE: this rule is only partially implemented. Progress is being tracked in the following GitHub issue: https://github.com/biomejs/biome/issues/1274 + /// + /// :::caution + /// ## Important notes + /// + /// This rule is a work in progress, and is only partially implemented. Progress is being tracked in the following GitHub issue: https://github.com/biomejs/biome/issues/1274 + /// + /// Currently, utility class sorting is **not part of the formatter**, and is implemented as a linter rule instead, with an automatic fix. The fix is, at this stage, classified as unsafe. This means that **it won't be applied automatically** as part of IDE actions such as "fix on save". + /// + /// We appreciate any feedback on this rule, and encourage you to try it out and report any issues you find. + /// + /// **Please read this entire documentation page before reporting an issue.** + /// + /// Notably, keep in mind that the following features are not supported yet: + /// + /// - Variant sorting. + /// - Custom utilitites and variants (such as ones introduced by Tailwind CSS plugins). Only the default Tailwind CSS configuration is supported. + /// - Options such as `prefix` and `separator`. + /// - Tagged template literals. + /// - Object properties (e.g. in `clsx` calls). + /// + /// Please don't report issues about these features. + /// ::: /// /// ## Examples /// @@ -62,6 +83,12 @@ declare_rule! { /// /// If specified, strings in the indicated functions will be sorted. This is useful when working with libraries like [`clsx`](https://github.com/lukeed/clsx) or [`cva`](https://cva.style/). /// + /// ```js + /// clsx("px-2 foo p-4 bar", { + /// "block mx-4": condition, + /// }); + /// ``` + /// /// Tagged template literals are also supported, for example: /// /// ```js @@ -69,21 +96,28 @@ declare_rule! { /// tw.div`px-2`; /// ``` /// - /// NOTE: tagged template literal support has not been implemented yet. + /// :::caution + /// Tagged template literal support has not been implemented yet. + /// ::: /// /// ### Sort-related /// - /// NOTE: at the moment, this rule does not support customizing the sort options. Instead, the default Tailwind CSS configuration is hard-coded. + /// :::caution + /// At the moment, this rule does not support customizing the sort options. Instead, the default Tailwind CSS configuration is hard-coded. + /// ::: /// /// ## Differences with [Prettier](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) /// - /// The main key difference is that Tailwind CSS and its Prettier plugin read the `tailwind.config.js` file, which Biome can't access. Instead, Biome implements a simpler version of the configuration. The trade-offs are explained below. + /// The main key difference is that Tailwind CSS and its Prettier plugin read and execute the `tailwind.config.js` JavaScript file, which Biome can't do. Instead, Biome implements a simpler version of the configuration. The trade-offs are explained below. /// /// ### Values are not known /// /// The rule has no knowledge of values such as colors, font sizes, or spacing values, which are normally defined in a configuration file like `tailwind.config.js`. Instead, the rule matches utilities that support values in a simpler way: if they start with a known utility prefix, such as `px-` or `text-`, they're considered valid. /// - /// This can result in false positives, i.e. classes that are wrongly recognized as utilities even though their values are incorrect. For example, if there's a `px-` utility defined in the configuration, it will match all of the following classes: `px-2`, `px-1337`, `px-[not-actually-valid]`, `px-literally-anything`. + /// This has two implications: + /// + /// - False positives: classes can be wrongly recognized as utilities even though their values are incorrect. For example, if there's a `px-` utility defined in the configuration, it will match all of the following classes: `px-2`, `px-1337`, `px-[not-actually-valid]`, `px-literally-anything`. + /// - No distinction between different utilities that share the same prefix: for example, `text-red-500` and `text-lg` are both interpreted as the same type of utility by this rule, even though the former refers to a color and the latter to a font size. This results in all utilities that share the same prefix being sorted together, regardless of their actual values. /// /// ### Custom additions must be specified /// @@ -97,6 +131,8 @@ declare_rule! { /// /// The Tailwind CSS Prettier plugin preserves all original whitespace. This rule, however, collapses all whitespace (including newlines) into single spaces. /// + /// This is a deliberate decision. We're unsure about this behavior, and would appreciate feedback on it. If this is a problem for you, please share a detailed explanation of your use case in [the GitHub issue](https://github.com/biomejs/biome/issues/1274). + /// pub(crate) UseSortedClasses { version: "next", name: "useSortedClasses", diff --git a/website/src/content/docs/linter/rules/use-sorted-classes.md b/website/src/content/docs/linter/rules/use-sorted-classes.md index 02d952ce97ee..612f7f3ca484 100644 --- a/website/src/content/docs/linter/rules/use-sorted-classes.md +++ b/website/src/content/docs/linter/rules/use-sorted-classes.md @@ -18,7 +18,28 @@ This rule implements the same sorting algorithm as [Tailwind CSS](https://tailwi It is analogous to [`prettier-plugin-tailwindcss`](https://github.com/tailwindlabs/prettier-plugin-tailwindcss). -NOTE: this rule is only partially implemented. Progress is being tracked in the following GitHub issue: https://github.com/biomejs/biome/issues/1274 +:::caution + +## Important notes + +This rule is a work in progress, and is only partially implemented. Progress is being tracked in the following GitHub issue: https://github.com/biomejs/biome/issues/1274 + +Currently, utility class sorting is **not part of the formatter**, and is implemented as a linter rule instead, with an automatic fix. The fix is, at this stage, classified as unsafe. This means that **it won't be applied automatically** as part of IDE actions such as "fix on save". + +We appreciate any feedback on this rule, and encourage you to try it out and report any issues you find. + +**Please read this entire documentation page before reporting an issue.** + +Notably, keep in mind that the following features are not supported yet: + +- Variant sorting. +- Custom utilitites and variants (such as ones introduced by Tailwind CSS plugins). Only the default Tailwind CSS configuration is supported. +- Options such as `prefix` and `separator`. +- Tagged template literals. +- Object properties (e.g. in `clsx` calls). + +Please don't report issues about these features. +::: ## Examples @@ -65,6 +86,12 @@ Classes in the `class` and `className` JSX attributes are always sorted. Use thi If specified, strings in the indicated functions will be sorted. This is useful when working with libraries like [`clsx`](https://github.com/lukeed/clsx) or [`cva`](https://cva.style/). +```jsx +clsx("px-2 foo p-4 bar", { + "block mx-4": condition, +}); +``` + Tagged template literals are also supported, for example: ```jsx @@ -72,21 +99,28 @@ tw`px-2`; tw.div`px-2`; ``` -NOTE: tagged template literal support has not been implemented yet. +:::caution +Tagged template literal support has not been implemented yet. +::: ### Sort-related -NOTE: at the moment, this rule does not support customizing the sort options. Instead, the default Tailwind CSS configuration is hard-coded. +:::caution +At the moment, this rule does not support customizing the sort options. Instead, the default Tailwind CSS configuration is hard-coded. +::: ## Differences with [Prettier](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) -The main key difference is that Tailwind CSS and its Prettier plugin read the `tailwind.config.js` file, which Biome can't access. Instead, Biome implements a simpler version of the configuration. The trade-offs are explained below. +The main key difference is that Tailwind CSS and its Prettier plugin read and execute the `tailwind.config.js` JavaScript file, which Biome can't do. Instead, Biome implements a simpler version of the configuration. The trade-offs are explained below. ### Values are not known The rule has no knowledge of values such as colors, font sizes, or spacing values, which are normally defined in a configuration file like `tailwind.config.js`. Instead, the rule matches utilities that support values in a simpler way: if they start with a known utility prefix, such as `px-` or `text-`, they're considered valid. -This can result in false positives, i.e. classes that are wrongly recognized as utilities even though their values are incorrect. For example, if there's a `px-` utility defined in the configuration, it will match all of the following classes: `px-2`, `px-1337`, `px-[not-actually-valid]`, `px-literally-anything`. +This has two implications: + +- False positives: classes can be wrongly recognized as utilities even though their values are incorrect. For example, if there's a `px-` utility defined in the configuration, it will match all of the following classes: `px-2`, `px-1337`, `px-[not-actually-valid]`, `px-literally-anything`. +- No distinction between different utilities that share the same prefix: for example, `text-red-500` and `text-lg` are both interpreted as the same type of utility by this rule, even though the former refers to a color and the latter to a font size. This results in all utilities that share the same prefix being sorted together, regardless of their actual values. ### Custom additions must be specified @@ -100,6 +134,8 @@ In Tailwind CSS, core plugins (which provide the default utilities and variants) The Tailwind CSS Prettier plugin preserves all original whitespace. This rule, however, collapses all whitespace (including newlines) into single spaces. +This is a deliberate decision. We're unsure about this behavior, and would appreciate feedback on it. If this is a problem for you, please share a detailed explanation of your use case in [the GitHub issue](https://github.com/biomejs/biome/issues/1274). + ## Related links - [Disable a rule](/linter/#disable-a-lint-rule) From e078cbf8ba0914a526392ad7cebd0f4c8ee1d913 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Wed, 24 Jan 2024 10:12:55 +0000 Subject: [PATCH 29/82] tweak docs --- .../nursery/use_sorted_classes.rs | 19 ++- .../nursery/useSortedClasses/unsorted.jsx | 9 -- .../useSortedClasses/unsorted.jsx.snap | 127 ++++++++---------- .../docs/linter/rules/use-sorted-classes.md | 33 +++++ 4 files changed, 110 insertions(+), 78 deletions(-) diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs index 79b909e64645..f00b4192379e 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_sorted_classes.rs @@ -41,6 +41,23 @@ declare_rule! { ///
; /// ``` /// + /// ```jsx,expect_diagnostic + ///
+ /// ``` + /// + /// ### Valid + /// + /// ```jsx + ///
+ /// clsx("px-2 foo p-4 bar"); + /// clsx({ + /// "px-2 foo p-4 bar": [ + /// "px-2 foo p-4 bar", + /// { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + /// ], + /// }); + /// ``` + /// /// ## Options /// /// ### Code-related @@ -64,7 +81,7 @@ declare_rule! { /// /// Tagged template literals are also supported, for example: /// - /// ```js + /// ```js,ignore /// tw`px-2`; /// tw.div`px-2`; /// ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx index 39a3da3da266..dfd3c7a7acdb 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx @@ -17,15 +17,6 @@
; -// functions -clsx("px-2 foo p-4 bar"); -// TODO: tagged template literals are not supported yet -tw`px-2 foo p-4 bar`; -tw.div`px-2 foo p-4 bar`; -notClassFunction("px-2 foo p-4 bar"); -notTemplateFunction`px-2 foo p-4 bar`; -notTemplateFunction.div`px-2 foo p-4 bar`; - // nested values
;
; diff --git a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx.snap index 3237d5bb8b1d..f0507712413e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx.snap @@ -23,15 +23,6 @@ expression: unsorted.jsx
; -// functions -clsx("px-2 foo p-4 bar"); -// TODO: tagged template literals are not supported yet -tw`px-2 foo p-4 bar`; -tw.div`px-2 foo p-4 bar`; -notClassFunction("px-2 foo p-4 bar"); -notTemplateFunction`px-2 foo p-4 bar`; -notTemplateFunction.div`px-2 foo p-4 bar`; - // nested values
;
; @@ -345,120 +336,120 @@ unsorted.jsx:17:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━ ``` ``` -unsorted.jsx:30:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +unsorted.jsx:21:13 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! These CSS classes should be sorted. - 29 │ // nested values - > 30 │
; + 20 │ // nested values + > 21 │
; │ ^^^^^^^^^^^^^^^^^^ - 31 │
; - 32 │
; + 23 │
; - 30 │ + ; - 31 31 │
; - 32 32 │
; + 21 │ + ; + 22 22 │
; + 23 23 │
; - > 31 │
; + 20 │ // nested values + 21 │
; + > 22 │
; │ ^^^^^^^^^^^^^^^^^^ - 32 │
; - 31 │ - ; - 31 │ + ; - 32 32 │
; + 22 │ - ; + 22 │ + ; + 23 23 │
36 │ "px-2 foo p-4 bar", + 25 │ // TODO: property should be sorted + 26 │ "px-2 foo p-4 bar": [ + > 27 │ "px-2 foo p-4 bar", │ ^^^^^^^^^^^^^^^^^^ - 37 │ // TODO: property should be sorted - 38 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + 28 │ // TODO: property should be sorted + 29 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, i Unsafe fix: Sort the classes. - 34 34 │ // TODO: property should be sorted - 35 35 │ "px-2 foo p-4 bar": [ - 36 │ - → → → "px-2·foo·p-4·bar", - 36 │ + → → → "foo·bar·p-4·px-2", - 37 37 │ // TODO: property should be sorted - 38 38 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + 25 25 │ // TODO: property should be sorted + 26 26 │ "px-2 foo p-4 bar": [ + 27 │ - → → → "px-2·foo·p-4·bar", + 27 │ + → → → "foo·bar·p-4·px-2", + 28 28 │ // TODO: property should be sorted + 29 29 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, ``` ``` -unsorted.jsx:38:26 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +unsorted.jsx:29:26 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! These CSS classes should be sorted. - 36 │ "px-2 foo p-4 bar", - 37 │ // TODO: property should be sorted - > 38 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + 27 │ "px-2 foo p-4 bar", + 28 │ // TODO: property should be sorted + > 29 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, │ ^^^^^^^^^^^^^^^^^^ - 39 │ ], - 40 │ }} + 30 │ ], + 31 │ }} i Unsafe fix: Sort the classes. - 36 36 │ "px-2 foo p-4 bar", - 37 37 │ // TODO: property should be sorted - 38 │ - → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["px-2·foo·p-4·bar"]·}, - 38 │ + → → → {·"px-2·foo·p-4·bar":·"foo·bar·p-4·px-2",·custom:·["px-2·foo·p-4·bar"]·}, - 39 39 │ ], - 40 40 │ }} + 27 27 │ "px-2 foo p-4 bar", + 28 28 │ // TODO: property should be sorted + 29 │ - → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["px-2·foo·p-4·bar"]·}, + 29 │ + → → → {·"px-2·foo·p-4·bar":·"foo·bar·p-4·px-2",·custom:·["px-2·foo·p-4·bar"]·}, + 30 30 │ ], + 31 31 │ }} ``` ``` -unsorted.jsx:38:55 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +unsorted.jsx:29:55 lint/nursery/useSortedClasses FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! These CSS classes should be sorted. - 36 │ "px-2 foo p-4 bar", - 37 │ // TODO: property should be sorted - > 38 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + 27 │ "px-2 foo p-4 bar", + 28 │ // TODO: property should be sorted + > 29 │ { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, │ ^^^^^^^^^^^^^^^^^^ - 39 │ ], - 40 │ }} + 30 │ ], + 31 │ }} i Unsafe fix: Sort the classes. - 36 36 │ "px-2 foo p-4 bar", - 37 37 │ // TODO: property should be sorted - 38 │ - → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["px-2·foo·p-4·bar"]·}, - 38 │ + → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["foo·bar·p-4·px-2"]·}, - 39 39 │ ], - 40 40 │ }} + 27 27 │ "px-2 foo p-4 bar", + 28 28 │ // TODO: property should be sorted + 29 │ - → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["px-2·foo·p-4·bar"]·}, + 29 │ + → → → {·"px-2·foo·p-4·bar":·"px-2·foo·p-4·bar",·custom:·["foo·bar·p-4·px-2"]·}, + 30 30 │ ], + 31 31 │ }} ``` diff --git a/website/src/content/docs/linter/rules/use-sorted-classes.md b/website/src/content/docs/linter/rules/use-sorted-classes.md index 02d952ce97ee..9a27563aaf82 100644 --- a/website/src/content/docs/linter/rules/use-sorted-classes.md +++ b/website/src/content/docs/linter/rules/use-sorted-classes.md @@ -44,6 +44,39 @@ NOTE: this rule is only partially implemented. Progress is being tracked in the
+```jsx +
+``` + +
nursery/useSortedClasses.js:1:16 lint/nursery/useSortedClasses  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   These CSS classes should be sorted.
+  
+  > 1 │ <div className="px-2 foo p-4 bar" />
+                  ^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Unsafe fix: Sort the classes.
+  
+    1  - <div·className="px-2·foo·p-4·bar"·/>
+      1+ <div·className="foo·bar·p-4·px-2"·/>
+    2 2  
+  
+
+ +### Valid + +```jsx +
+clsx("px-2 foo p-4 bar"); +clsx({ + "px-2 foo p-4 bar": [ + "px-2 foo p-4 bar", + { "px-2 foo p-4 bar": "px-2 foo p-4 bar", custom: ["px-2 foo p-4 bar"] }, + ], +}); +``` + ## Options ### Code-related From da104cd7b1514977579d26b0990fbca936401d6e Mon Sep 17 00:00:00 2001 From: Cookie <34422996+CookieDasora@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:06:13 +0000 Subject: [PATCH 30/82] docs(pt-br): translated git hooks page (#1498) Co-authored-by: Victor <78874691+victor-teles@users.noreply.github.com> --- website/astro.config.ts | 3 + .../docs/pt-br/guides/integrate-in-vcs.mdx | 68 +++++ website/src/content/docs/pt-br/index.mdx | 211 ++++++++++++++-- .../content/docs/pt-br/recipes/git-hooks.mdx | 234 ++++++++++++++++++ 4 files changed, 489 insertions(+), 27 deletions(-) create mode 100644 website/src/content/docs/pt-br/guides/integrate-in-vcs.mdx create mode 100644 website/src/content/docs/pt-br/recipes/git-hooks.mdx diff --git a/website/astro.config.ts b/website/astro.config.ts index b1247651fdd6..ef4c14eefe4a 100644 --- a/website/astro.config.ts +++ b/website/astro.config.ts @@ -138,6 +138,9 @@ export default defineConfig({ { label: "Integrate Biome with your VCS", link: "/guides/integrate-in-vcs", + translations: { + "pt-BR": "Integrando o Biome com o seu VCS", + }, }, ], }, diff --git a/website/src/content/docs/pt-br/guides/integrate-in-vcs.mdx b/website/src/content/docs/pt-br/guides/integrate-in-vcs.mdx new file mode 100644 index 000000000000..4424bbd8305f --- /dev/null +++ b/website/src/content/docs/pt-br/guides/integrate-in-vcs.mdx @@ -0,0 +1,68 @@ +--- +title: Integrando o Biome com o seu VCS +description: Aprenda como o Biome funciona, incluindo suposições, configuração e mais. +--- + + +A integração com o VCS (Sistema de controle de versão) é projetada para aproveitar recursos **adicionais** que somente um VCS pode fornecer, permitindo customizar +sua experiência ainda mais. + +Essa integração é **opcional** e possui dois campos obrigatórios. O campo `vcs.enabled` e o campo `vcs.clientKind`, ambos no arquivo de configuração: + +```json title="biome.json" +{ + "vcs": { + "enabled": true, + "clientKind": "git" + } +} +``` + +Essa configuração não faz **nada por si só**. Você precisa adicionar novos recursos. + +### Ignorando arquivos + +Esse é um recurso que permite que o Biome leia os arquivos ignorados do VCS e ignora todos os arquivos e pastas especificados nele. Esse é um recurso opcional, e você precisa ativar o campo `vcs.useIgnoreFile`: + +```json title="biome.json" ins={5} +{ + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + } +} +``` + +### Processando apenas arquivos modificados + +Esse é um recurso que só está disponível via CLI e permite processar **apenas** os arquivos que foram **alterados** de uma revisão para outra. + +Primeiro, você tem que atualizar seu arquivo de configuração e dizer para o Biome qual é a branch padrão com o campo `vcs.defaultBranch`: + +```json title="biome.json" ins={6} +{ + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true, + "defaultBranch": "main" + } +} +``` + +Em seguida, adicione a opção `--changed` no seu comando para processar apenas os arquivos que o seu VCS marcar como "modificado". O Biome, com ajuda do VCS, vai determinar o arquivo alterado da branch `main` e sua revisão atual: + +```shell +biome format --changed +``` + +:::caution +O Biome não verifica o que foi alterado, isso significa que adicionar espaços e novas linhas em um arquivo vai marcar-lo como "modificado" +::: + +Alternativamente, você pode usar a opção `--since` para especificar uma branch. Essa opção **tem precedência** sobre a opção `vcs.defaultBranch`. Por exemplo, você pode querer verificar suas alterações em relação a branch `next`: + +```shell +biome format --changed --since=next +``` diff --git a/website/src/content/docs/pt-br/index.mdx b/website/src/content/docs/pt-br/index.mdx index c3b137b31011..fb4a31e57142 100644 --- a/website/src/content/docs/pt-br/index.mdx +++ b/website/src/content/docs/pt-br/index.mdx @@ -2,9 +2,11 @@ title: Biome head: - tag: title - content: Biome, toolchain of the web + content: Biome, conjunto de ferramentas da web template: splash description: Formate, verifique erros e muito mais em uma fração de segundo. +editUrl: false +next: false hero: title: Um conjunto de ferramentas para seu projeto web tagline: Formate, verifique erros e muito mais em uma fração de segundo. @@ -24,35 +26,190 @@ hero: --- import { Card, CardGrid } from "@astrojs/starlight/components"; +import Inputf from "@src/components/formatter/input.md"; +import Outputf from "@src/components/formatter/output.md"; +import ProgressBar from "../../../playground/components/Progress.tsx"; +import Community from "@src/components/Community.astro"; +import { Icon } from "@astrojs/starlight/components"; +import arrow from "../../../assets/svg/arrow-right.svg"; +import { Image } from "astro:assets"; +import "../../../styles/_performance.scss"; +import "../../../styles/_installation.scss"; +import "../../../styles/_community.scss"; import Netlify from "@src/components/Netlify.astro"; import Sponsors from "@src/components/Sponsors.astro"; +import LinterExample from "@src/components/linter/example.md"; - - - Construído com Rust e uma arquitetura inovadora inspirado pelo - rust-analyzer. - - - Nenhuma configuração é necessária para começar a usar! Caso precise, temos - diversas opções disponíveis! - - - Projetado para suportar codebases de qualquer tamanho. Focado no crescimento - do projeto, em vez de suas ferramentas. - - - Com uma integração interna eficiente, conseguimos reutilizar trabalhos - anteriores, e qualquer melhoria em uma ferramenta beneficia todas elas. - - - Esqueça aquelas mensagens de erro obscuras! Quando alguma coisa está errada, - nós falamos exatamente o que está errado e como consertar! - - - Suporte pronto para todas as funcionalidades de linguagem que você utiliza - atualmente. Suporte de primeira classe para TypeScript e JSX. - - + +
+
+ + +
+

Formata código como o Prettier e economiza tempo

+ + **Biome é um [formatador rápido](https://github.com/biomejs/biome/tree/main/benchmark#formatting)** para _JavaScript_, _TypeScript_, _JSX_, e _JSON_ que atinge **[97% de compatibilidade com o _Prettier_](https://console.algora.io/challenges/prettier)**, **economizando tempo para o desenvolvedor e para o CI**. + + O Biome pode até **formatar código malformado** enquanto você programa no seu [editor favorito](/pt-br/guides/integrate-in-editor/). + +
+
+ CÓDIGO + +
+
+ SAÍDA + +
+
+ PERFORMANCE +
+
+
+ + +
+
+
+
~35x
+
+ Mais rápido que o Prettier quando formata 171,217 linhas de código em 2,104 arquivos com um Intel Core i7 1270P. +
+
+
+
+
+ + + Teste o formatador do Biome no [playground](/playground/) ou diretamente no seu projeto: + + ```shell + npm i -D --save-exact @biomejs/biome + npx @biomejs/biome format --write ./src + ``` +
+ + +
+

Corrija problemas e aprenda boas práticas

+ + **O Biome é um [linter performático](https://github.com/biomejs/biome/tree/main/benchmark#linting)** para _JavaScript_, _TypeScript_, e _JSX_ que possui **[mais de 190 regras](https://biomejs.dev/pt-br/linter/rules/)** do ESLint, TypeScript ESLint, e [de outras fontes](https://github.com/biomejs/biome/discussions/3). + + **O Biome retorna diagnósticos detalhados e contextualizados** que ajuda a melhorar seu código e ajuda você a se tornar um programador melhor! + + + + Teste o linter do Biome no [playground](/playground/) ou diretamente no seu projeto: + + ```shell + npm i -D --save-exact @biomejs/biome + npx @biomejs/biome lint --apply ./src + ``` + +
+ + +
+

Tudo de uma vez

+ + Não só como você pode formatar e analisar erros no seu código separadamente, você pode fazer **tudo isso com um comando**! + + Cada ferramenta integra suavemente com as outras para criar um **conjunto de ferramentas coesivo** para projetos web. + + Rode todas as ferramentas com o comando `check` + + ```shell + npm i -D --save-exact @biomejs/biome + npx @biomejs/biome check --apply ./src + ``` + +
+ + + +
+ + + + Construído com Rust e uma arquitetura inovadora inspirado pelo rust-analyzer. + + + Nenhuma configuração é necessária para começar a usar! Caso precise, temos + diversas opções disponíveis! + + + Projetado para suportar codebases de qualquer tamanho. Focado no crescimento + do projeto, em vez de suas ferramentas. + + + Com uma integração interna eficiente, conseguimos reutilizar trabalhos + anteriores, e qualquer melhoria em uma ferramenta beneficia todas elas. + + + Esqueça aquelas mensagens de erro obscuras! Quando alguma coisa está errada, + nós falamos exatamente o que está errado e como consertar! + + + Suporte pronto para todas as funcionalidades de linguagem que você utiliza + atualmente. Suporte de primeira classe para TypeScript e JSX. + + +
+
+
+
+

Experimente o Biome

+
+ Instale o Biome utilizando o seu gerenciador de pacotes favorito e integre ele no seu editor. +
+ + Instale com um gerenciador de pacotes + arrow + + + Integre o Biome no seu editor + arrow + +
+ +
+ +
+
+ +
+
+

Comunidade

+
+
+ Desenvolvido pela nossa incrível comunidade +
+ +
+
+ +
+
diff --git a/website/src/content/docs/pt-br/recipes/git-hooks.mdx b/website/src/content/docs/pt-br/recipes/git-hooks.mdx new file mode 100644 index 000000000000..e9b6b98d7568 --- /dev/null +++ b/website/src/content/docs/pt-br/recipes/git-hooks.mdx @@ -0,0 +1,234 @@ +--- +title: Git Hooks +description: Usando o Biome com Git Hooks +--- + +O Git permite executar scripts durante a execução de comandos git utilizando o [Git Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks). +Você pode, por exemplo, formatar e lintar arquivos em stage antes de efetuar o commit ou push. +Existem diversas ferramentas para simplificar o gerenciamento do Git Hooks. +Nas próximas seções iremos introduzir algumas delas e como elas podem ser utilizadas com o Biome. + + +## Lefthook + +[Lefthook](https://github.com/evilmartians/lefthook) é um hook manager rápido, multiplataforma e livre de dependências. +Ele pode ser [instalado via NPM](https://github.com/evilmartians/lefthook#install). + + +Crie um arquivo chamado `lefthook.yml` na raíz do seu repositório Git. +Alguns exemplos de configurações: + +- Formatar e verificar erros antes de efetuar o commit. + + ```yaml title="lefthook.yml" + pre-commit: + commands: + check: + glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}" + run: npx biome check --no-errors-on-unmatched --files-ignore-unknown=true {staged_files} + ``` + +- Formatar, verificar erros e aplicar correções seguras antes de efetuar o commit. + + ```yaml title="lefthook.yml" + pre-commit: + commands: + check: + glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}" + run: npx biome check --apply --no-errors-on-unmatched --files-ignore-unknown=true {staged_files} && git update-index --again + ``` + + `git update-index --again` adiciona novamente os arquivos em stage. + +- Formatar e verificar erros antes de fazer o push. + + ```yaml title="lefthook.yml" + pre-push: + commands: + check: + glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}" + run: npx biome check --no-errors-on-unmatched --files-ignore-unknown=true {pushed_files} + ``` + +Não é necessário utilizar tanto `glob` quanto `--files-ignore-unknown=true`. +Utilizando apenas `--files-ignore-unknown=true` permite que os arquivos suportados sejam lidados pelo Biome. +Se você deseja mais controle sobre os arquivos que são lidados, você deve usar `glob` + +`--no-errors-on-unmatched` silencia possíveis erros no caso de *nenhum arquivo ser processado*. + +Uma vez configurado, execute `lefthook install` para inicializar os hooks. + +Se o seu projeto possuí um `package.json`, +você pode configurar automaticamente os hooks do _Lefthook_ após a instalação do pacote usando `scripts.postinstall`: + + +```json title="package.json" +{ + "scripts": { + "postinstall": "lefthook install" + } +} +``` + + +## Husky + +[Husky](https://github.com/typicode/husky) é um hook manager amplamente utilizado no ecossistema JavaScript. +O Husky não esconde alterações unstaged e não é capaz de fornecer uma lista de arquivos em stage. +Por isso que ele é normalmente utilizado em conjunto com outra ferramenta como _lint-staged_ ou _git-format-staged_. + +Se o seu projeto contém um `package.json`, +você pode configurar automaticamente os hooks do _husky_ após a instalação do pacote usando `scripts.postinstall`: + +```json title="package.json" +{ + "scripts": { + "postinstall": "husky install" + } +} +``` + +### lint-staged + +[lint-staged](https://github.com/lint-staged/lint-staged) é uma das ferramentas mais utilizadas no ecossistema JavaScript. + +Adicione o seguinte no arquivo de configuração do husky: + +```shell title=".husky/pre-commit" +lint-staged +``` + +O arquivo de configuração do lint-staged está ligada diretamente no `package.json`. +Aqui alguns exemplos de comandos que podem ser úteis ao executar os Git hooks: + +```jsonc title="package.json" +{ + "lint-staged": { + // Executa o Biome em arquivos em stage que possui as seguintes extensões: js, ts, jsx, tsx, json e jsonc. + "**.{js|ts|cjs|mjs|d.cts|d.mts|jsx|tsx|json|jsonc}": [ + "biome check --files-ignore-unknown=true", // Formatar e verificar erros. + "biome check --apply --no-errors-on-unmatched", // Formatar, organizar importações, verificar erros e aplicar correções seguras. + "biome check --apply --organize-imports-enabled=false --no-errors-on-unmatched", // Formatar e aplicar correções seguras. + "biome check --apply-unsafe --no-errors-on-unmatched", // Formatar, organizar importações, verificar erros e aplicar correções seguras e não seguras. + "biome format --write --no-errors-on-unmatched", // Formatação + "biome lint --apply --no-errors-on-unmatched", // Verificar erros e aplicar correções seguras. + ], + // Você pode também passar todos os arquivos e ignorar extensões desconhecidas. + "*": [ + "biome check --no-errors-on-unmatched --files-ignore-unknown=true", // Formatar e verificar erros. + ] + } +} +``` + +Lembre-se de usar a opção do CLI `--no-errors-on-unmatched` no seu comando para silenciar possíveis erros no caso de *nenhum arquivo ser processado*. + + +### git-format-staged + +Em constrate a outras ferramentas como _lefthook_, _pre-commit_ e _lint-staged_, +[git-format-staged](https://github.com/hallettj/git-format-staged) não utiliza `git stash` internamente. +Isso evita intervenção manual quando acontece um conflito entre alterações unstaged e alterações em stage. +Veja a [comparação do _git-format-staged_ com outras ferramentas](https://github.com/hallettj/git-format-staged#comparisons-to-similar-utilities). + +Veja alguns exemplos de configuração: + +- Formatar e verificar erros antes de efetuar o commit + + ```shell title=".husky/pre-commit" + git-format-staged --formatter 'biome check --files-ignore-unknown=true --no-errors-on-unmatched \"{}\"' . + ``` + +- Formatar, verificar erros e aplicar correções seguras antes de efetuar o commit + + ```shell title=".husky/pre-commit" + git-format-staged --formatter 'biome check --apply --files-ignore-unknown=true --no-errors-on-unmatched \"{}\"' . + ``` + + +## pre-commit + +[pre-commit](https://pre-commit.com/) é um hook manager multilíngue. +O Biome fornece quatro [pre-commit](https://pre-commit.com/) hooks por meio do repositório [biomejs/pre-commit](https://github.com/biomejs/pre-commit). + +| hook `id` | Descrição | +| --------------- | -------------------------------------------------------------------------------------------------- | +| `biome-ci` | Formata, verifica se as importações estão organizadas e verifica erros | +| `biome-check` | Formata, organiza as importações, verifica erros e aplica correções seguras nos arquivos do commit | +| `biome-format` | Formata os arquivos do commit | +| `biome-lint` | Verifica erros e aplica correções seguras nos arquivos do commit | + +No exemplo a seguir, assumimos que você [instalou o pre-commit](https://pre-commit.com/index.html#install) e executou o comando `pre-commit install` no seu repositório. +Se você quiser usar o hook `biome-check`, adicione a seguinte configuração na raíz do seu projeto em um arquivo chamado `.pre-commit-config.yaml`: + +```yaml title=".pre-commit-config.yaml" +repos: +- repo: https://github.com/biomejs/pre-commit + rev: "0.1.0" # Use the sha / tag you want to point at + hooks: + - id: biome-check + additional_dependencies: ["@biomejs/biome@1.4.1"] +``` + +Isso vai rodar o comando `biome check --apply` quando você executar `git commit`. + +Observe que você deve especificar a versão do Biome para usar devido a opção `additional_dependencies`. +O [pre-commit](https://pre-commit.com/) instala separadamente as ferramentas e sabe qual instalar. + +Se o Biome já está instalado como um pacote `npm` no seu repositório local, +então pode ser um fardo atualizar tanto o `package.json` e o `.pre-commit-config.yaml` quando você atualiza o Biome. +Em vez de utilizar os hooks que o Biome fornece, você pode especificar o seu próprio [hook local](https://pre-commit.com/index.html#repository-local-hooks). + +Por exemplo, se você usa o `npm`, você pode escrever o seguinte hook em `.pre-commit-config.yaml`: + +```yaml title=".pre-commit-config.yaml" +repos: + - repo: local + hooks: + - id: local-biome-check + name: biome check + entry: npx biome check --apply --files-ignore-unknown=true --no-errors-on-unmatched + language: system + types: [text] + files: "\\.(jsx?|tsx?|c(js|ts)|m(js|ts)|d\\.(ts|cts|mts)|jsonc?)$" +``` + +A opção `files` é opcional, +porque o Biome é capaz de ignorar arquivos desconhecidos (usando a opção `--files-ignore-unknown=true`). + +## Shell script + +Você pode usar também um shell script customizado. +Vale ressaltar que você pode encontrar incompatibilidades entre plataformas. +Recomendammos o uso das ferramentas apresentadas anteriormente. + +Alguns exemplos: + +- Formatar e verificar erros antes do commit + + ```shell title=".git/hooks/pre-commit" + #!/bin/sh + set -eu + + git diff -z --staged --name-only --diff-filter=ACMR | \ + xargs -0 npx biome check --files-ignore-unknown=true --no-errors-on-unmatched $STAGED_FILES + ``` + +- Formatar, verificar erros e aplicar correções seguras antes do commit. + + ```shell title=".git/hooks/pre-commit" + #!/bin/sh + set -eu + + if ! git status --short | grep --quiet '^MM'; then + printf '%s\n' "ERROR: Some staged files have unstaged changes" >&2 + exit 1; + fi + + git diff -z --staged --name-only --diff-filter=ACMR | \ + xargs -0 npx biome check --files-ignore-unknown=true --no-errors-on-unmatched + + git update-index --again + ``` + + Note que fazemos o hook falhar caso os arquivos em stage tenha alterações unstaged. From 841f8cb9f932ccbf786050683aea612dc5c042bf Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 12 Jan 2024 20:50:50 +0000 Subject: [PATCH 31/82] feat: HTML grammar (#1544) --- Cargo.lock | 15 + Cargo.toml | 17 +- crates/biome_html_factory/Cargo.toml | 18 + crates/biome_html_factory/src/generated.rs | 6 + .../src/generated/node_factory.rs | 205 +++ .../src/generated/syntax_factory.rs | 357 +++++ crates/biome_html_factory/src/lib.rs | 12 + crates/biome_html_factory/src/make.rs | 7 + crates/biome_html_syntax/Cargo.toml | 20 + crates/biome_html_syntax/src/file_source.rs | 68 + crates/biome_html_syntax/src/generated.rs | 9 + .../biome_html_syntax/src/generated/kind.rs | 95 ++ .../biome_html_syntax/src/generated/macros.rs | 77 + .../biome_html_syntax/src/generated/nodes.rs | 1313 +++++++++++++++++ .../src/generated/nodes_mut.rs | 205 +++ crates/biome_html_syntax/src/lib.rs | 107 ++ crates/biome_html_syntax/src/syntax_node.rs | 24 + xtask/codegen/html.ungram | 115 ++ xtask/codegen/src/ast.rs | 16 +- xtask/codegen/src/css_kinds_src.rs | 2 +- xtask/codegen/src/formatter.rs | 7 + xtask/codegen/src/generate_macros.rs | 2 +- xtask/codegen/src/generate_node_factory.rs | 4 +- xtask/codegen/src/generate_nodes.rs | 4 +- xtask/codegen/src/generate_nodes_mut.rs | 2 +- xtask/codegen/src/generate_syntax_factory.rs | 7 +- xtask/codegen/src/generate_syntax_kinds.rs | 16 +- xtask/codegen/src/html_kinds_src.rs | 38 + .../src/{kinds_src.rs => js_kinds_src.rs} | 13 +- xtask/codegen/src/json_kinds_src.rs | 2 +- xtask/codegen/src/kind_src.rs | 9 + xtask/codegen/src/lib.rs | 26 +- 32 files changed, 2785 insertions(+), 33 deletions(-) create mode 100644 crates/biome_html_factory/Cargo.toml create mode 100644 crates/biome_html_factory/src/generated.rs create mode 100644 crates/biome_html_factory/src/generated/node_factory.rs create mode 100644 crates/biome_html_factory/src/generated/syntax_factory.rs create mode 100644 crates/biome_html_factory/src/lib.rs create mode 100644 crates/biome_html_factory/src/make.rs create mode 100644 crates/biome_html_syntax/Cargo.toml create mode 100644 crates/biome_html_syntax/src/file_source.rs create mode 100644 crates/biome_html_syntax/src/generated.rs create mode 100644 crates/biome_html_syntax/src/generated/kind.rs create mode 100644 crates/biome_html_syntax/src/generated/macros.rs create mode 100644 crates/biome_html_syntax/src/generated/nodes.rs create mode 100644 crates/biome_html_syntax/src/generated/nodes_mut.rs create mode 100644 crates/biome_html_syntax/src/lib.rs create mode 100644 crates/biome_html_syntax/src/syntax_node.rs create mode 100644 xtask/codegen/html.ungram create mode 100644 xtask/codegen/src/html_kinds_src.rs rename xtask/codegen/src/{kinds_src.rs => js_kinds_src.rs} (98%) create mode 100644 xtask/codegen/src/kind_src.rs diff --git a/Cargo.lock b/Cargo.lock index 225cf53fb739..d0d2f43393c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -430,6 +430,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "biome_html_factory" +version = "0.4.0" +dependencies = [ + "biome_html_syntax", + "biome_rowan", +] + +[[package]] +name = "biome_html_syntax" +version = "0.4.0" +dependencies = [ + "biome_rowan", +] + [[package]] name = "biome_js_analyze" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index bfaa699576d7..4ac687785395 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,8 @@ biome_diagnostics_categories = { version = "0.4.0", path = "./crates/biome_diagn biome_diagnostics_macros = { version = "0.4.0", path = "./crates/biome_diagnostics_macros" } biome_formatter = { version = "0.4.0", path = "./crates/biome_formatter" } biome_fs = { version = "0.4.0", path = "./crates/biome_fs" } +biome_html_factory = { version = "0.4.0", path = "./crates/biome_html_factory" } +biome_html_syntax = { version = "0.4.0", path = "./crates/biome_html_syntax" } biome_js_analyze = { version = "0.4.0", path = "./crates/biome_js_analyze" } biome_js_factory = { version = "0.4.0", path = "./crates/biome_js_factory" } biome_js_formatter = { version = "0.4.0", path = "./crates/biome_js_formatter" } @@ -99,13 +101,14 @@ biome_json_factory = { version = "0.4.0", path = "./crates/biome_json_ biome_json_formatter = { version = "0.4.0", path = "./crates/biome_json_formatter" } biome_json_parser = { version = "0.4.0", path = "./crates/biome_json_parser" } biome_json_syntax = { version = "0.4.0", path = "./crates/biome_json_syntax" } -biome_markup = { version = "0.4.0", path = "./crates/biome_markup" } -biome_parser = { version = "0.4.0", path = "./crates/biome_parser" } -biome_rowan = { version = "0.4.0", path = "./crates/biome_rowan" } -biome_suppression = { version = "0.4.0", path = "./crates/biome_suppression" } -biome_text_edit = { version = "0.4.0", path = "./crates/biome_text_edit" } -biome_text_size = { version = "0.4.0", path = "./crates/biome_text_size" } -biome_unicode_table = { version = "0.4.0", path = "./crates/biome_unicode_table" } + +biome_markup = { version = "0.4.0", path = "./crates/biome_markup" } +biome_parser = { version = "0.4.0", path = "./crates/biome_parser" } +biome_rowan = { version = "0.4.0", path = "./crates/biome_rowan" } +biome_suppression = { version = "0.4.0", path = "./crates/biome_suppression" } +biome_text_edit = { version = "0.4.0", path = "./crates/biome_text_edit" } +biome_text_size = { version = "0.4.0", path = "./crates/biome_text_size" } +biome_unicode_table = { version = "0.4.0", path = "./crates/biome_unicode_table" } # not publish biome_cli = { path = "./crates/biome_cli" } biome_flags = { path = "./crates/biome_flags" } diff --git a/crates/biome_html_factory/Cargo.toml b/crates/biome_html_factory/Cargo.toml new file mode 100644 index 000000000000..55ad88a2caa5 --- /dev/null +++ b/crates/biome_html_factory/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors.workspace = true +description = "Utilities to create HTML AST for biome_html_parser" +documentation = "https://docs.rs/biome_html_factory" +edition.workspace = true +license.workspace = true +name = "biome_html_factory" +repository.workspace = true +version = "0.4.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +biome_html_syntax = { workspace = true } +biome_rowan = { workspace = true } + +[lints] +workspace = true diff --git a/crates/biome_html_factory/src/generated.rs b/crates/biome_html_factory/src/generated.rs new file mode 100644 index 000000000000..d943c24df8f8 --- /dev/null +++ b/crates/biome_html_factory/src/generated.rs @@ -0,0 +1,6 @@ +#[rustfmt::skip] +pub(super) mod syntax_factory; +#[rustfmt::skip] +pub mod node_factory; + +pub use syntax_factory::HtmlSyntaxFactory; diff --git a/crates/biome_html_factory/src/generated/node_factory.rs b/crates/biome_html_factory/src/generated/node_factory.rs new file mode 100644 index 000000000000..ebb5167507cf --- /dev/null +++ b/crates/biome_html_factory/src/generated/node_factory.rs @@ -0,0 +1,205 @@ +//! Generated file, do not edit by hand, see `xtask/codegen` + +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +use biome_html_syntax::{ + HtmlSyntaxElement as SyntaxElement, HtmlSyntaxNode as SyntaxNode, + HtmlSyntaxToken as SyntaxToken, *, +}; +use biome_rowan::AstNode; +pub fn html_attribute(name: HtmlName) -> HtmlAttributeBuilder { + HtmlAttributeBuilder { + name, + initializer: None, + } +} +pub struct HtmlAttributeBuilder { + name: HtmlName, + initializer: Option, +} +impl HtmlAttributeBuilder { + pub fn with_initializer(mut self, initializer: HtmlAttributeInitializerClause) -> Self { + self.initializer = Some(initializer); + self + } + pub fn build(self) -> HtmlAttribute { + HtmlAttribute::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_ATTRIBUTE, + [ + Some(SyntaxElement::Node(self.name.into_syntax())), + self.initializer + .map(|token| SyntaxElement::Node(token.into_syntax())), + ], + )) + } +} +pub fn html_attribute_initializer_clause( + eq_token: SyntaxToken, + value: HtmlString, +) -> HtmlAttributeInitializerClause { + HtmlAttributeInitializerClause::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_ATTRIBUTE_INITIALIZER_CLAUSE, + [ + Some(SyntaxElement::Token(eq_token)), + Some(SyntaxElement::Node(value.into_syntax())), + ], + )) +} +pub fn html_closing_element( + l_angle_token: SyntaxToken, + slash_token: SyntaxToken, + name: HtmlName, + r_angle_token: SyntaxToken, +) -> HtmlClosingElement { + HtmlClosingElement::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_CLOSING_ELEMENT, + [ + Some(SyntaxElement::Token(l_angle_token)), + Some(SyntaxElement::Token(slash_token)), + Some(SyntaxElement::Node(name.into_syntax())), + Some(SyntaxElement::Token(r_angle_token)), + ], + )) +} +pub fn html_directive( + l_angle_token: SyntaxToken, + excl_token: SyntaxToken, + content: HtmlString, + r_angle_token: SyntaxToken, +) -> HtmlDirective { + HtmlDirective::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_DIRECTIVE, + [ + Some(SyntaxElement::Token(l_angle_token)), + Some(SyntaxElement::Token(excl_token)), + Some(SyntaxElement::Node(content.into_syntax())), + Some(SyntaxElement::Token(r_angle_token)), + ], + )) +} +pub fn html_element( + opening_element: HtmlOpeningElement, + children: HtmlElementList, + closing_element: HtmlClosingElement, +) -> HtmlElement { + HtmlElement::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_ELEMENT, + [ + Some(SyntaxElement::Node(opening_element.into_syntax())), + Some(SyntaxElement::Node(children.into_syntax())), + Some(SyntaxElement::Node(closing_element.into_syntax())), + ], + )) +} +pub fn html_name(value_token: SyntaxToken) -> HtmlName { + HtmlName::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_NAME, + [Some(SyntaxElement::Token(value_token))], + )) +} +pub fn html_opening_element( + l_angle_token: SyntaxToken, + name: HtmlName, + attributes: HtmlAttributeList, + r_angle_token: SyntaxToken, +) -> HtmlOpeningElement { + HtmlOpeningElement::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_OPENING_ELEMENT, + [ + Some(SyntaxElement::Token(l_angle_token)), + Some(SyntaxElement::Node(name.into_syntax())), + Some(SyntaxElement::Node(attributes.into_syntax())), + Some(SyntaxElement::Token(r_angle_token)), + ], + )) +} +pub fn html_root( + directive: HtmlDirective, + tags: HtmlElementList, + eof_token: SyntaxToken, +) -> HtmlRootBuilder { + HtmlRootBuilder { + directive, + tags, + eof_token, + bom_token: None, + } +} +pub struct HtmlRootBuilder { + directive: HtmlDirective, + tags: HtmlElementList, + eof_token: SyntaxToken, + bom_token: Option, +} +impl HtmlRootBuilder { + pub fn with_bom_token(mut self, bom_token: SyntaxToken) -> Self { + self.bom_token = Some(bom_token); + self + } + pub fn build(self) -> HtmlRoot { + HtmlRoot::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_ROOT, + [ + self.bom_token.map(|token| SyntaxElement::Token(token)), + Some(SyntaxElement::Node(self.directive.into_syntax())), + Some(SyntaxElement::Node(self.tags.into_syntax())), + Some(SyntaxElement::Token(self.eof_token)), + ], + )) + } +} +pub fn html_self_closing_element( + l_angle_token: SyntaxToken, + name: HtmlName, + attributes: HtmlAttributeList, + slash_token: SyntaxToken, + r_angle_token: SyntaxToken, +) -> HtmlSelfClosingElement { + HtmlSelfClosingElement::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_SELF_CLOSING_ELEMENT, + [ + Some(SyntaxElement::Token(l_angle_token)), + Some(SyntaxElement::Node(name.into_syntax())), + Some(SyntaxElement::Node(attributes.into_syntax())), + Some(SyntaxElement::Token(slash_token)), + Some(SyntaxElement::Token(r_angle_token)), + ], + )) +} +pub fn html_string(value_token: SyntaxToken) -> HtmlString { + HtmlString::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_STRING, + [Some(SyntaxElement::Token(value_token))], + )) +} +pub fn html_attribute_list(items: I) -> HtmlAttributeList +where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, +{ + HtmlAttributeList::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_ATTRIBUTE_LIST, + items + .into_iter() + .map(|item| Some(item.into_syntax().into())), + )) +} +pub fn html_element_list(items: I) -> HtmlElementList +where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, +{ + HtmlElementList::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::HTML_ELEMENT_LIST, + items + .into_iter() + .map(|item| Some(item.into_syntax().into())), + )) +} +pub fn html_bogus(slots: I) -> HtmlBogus +where + I: IntoIterator>, + I::IntoIter: ExactSizeIterator, +{ + HtmlBogus::unwrap_cast(SyntaxNode::new_detached(HtmlSyntaxKind::HTML_BOGUS, slots)) +} diff --git a/crates/biome_html_factory/src/generated/syntax_factory.rs b/crates/biome_html_factory/src/generated/syntax_factory.rs new file mode 100644 index 000000000000..beab1db6a5ba --- /dev/null +++ b/crates/biome_html_factory/src/generated/syntax_factory.rs @@ -0,0 +1,357 @@ +//! Generated file, do not edit by hand, see `xtask/codegen` + +use biome_html_syntax::{HtmlSyntaxKind, HtmlSyntaxKind::*, T, *}; +use biome_rowan::{ + AstNode, ParsedChildren, RawNodeSlots, RawSyntaxNode, SyntaxFactory, SyntaxKind, +}; +#[derive(Debug)] +pub struct HtmlSyntaxFactory; +impl SyntaxFactory for HtmlSyntaxFactory { + type Kind = HtmlSyntaxKind; + #[allow(unused_mut)] + fn make_syntax( + kind: Self::Kind, + children: ParsedChildren, + ) -> RawSyntaxNode { + match kind { + HTML_BOGUS => RawSyntaxNode::new(kind, children.into_iter().map(Some)), + HTML_ATTRIBUTE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if HtmlName::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlAttributeInitializerClause::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + HTML_ATTRIBUTE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(HTML_ATTRIBUTE, children) + } + HTML_ATTRIBUTE_INITIALIZER_CLAUSE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [=] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlString::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + HTML_ATTRIBUTE_INITIALIZER_CLAUSE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(HTML_ATTRIBUTE_INITIALIZER_CLAUSE, children) + } + HTML_CLOSING_ELEMENT => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [<] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [/] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlName::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [>] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + HTML_CLOSING_ELEMENT.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(HTML_CLOSING_ELEMENT, children) + } + HTML_DIRECTIVE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [<] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T![!] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlString::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [>] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + HTML_DIRECTIVE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(HTML_DIRECTIVE, children) + } + HTML_ELEMENT => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if HtmlOpeningElement::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlElementList::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlClosingElement::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + HTML_ELEMENT.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(HTML_ELEMENT, children) + } + HTML_NAME => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<1usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == HTML_IDENT { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + HTML_NAME.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(HTML_NAME, children) + } + HTML_OPENING_ELEMENT => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [<] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlName::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlAttributeList::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [>] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + HTML_OPENING_ELEMENT.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(HTML_OPENING_ELEMENT, children) + } + HTML_ROOT => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == T![UNICODE_BOM] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlDirective::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlElementList::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T![EOF] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + HTML_ROOT.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(HTML_ROOT, children) + } + HTML_SELF_CLOSING_ELEMENT => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<5usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [<] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlName::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if HtmlAttributeList::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [/] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [>] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + HTML_SELF_CLOSING_ELEMENT.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(HTML_SELF_CLOSING_ELEMENT, children) + } + HTML_STRING => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<1usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == HTML_STRING_LITERAL { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + HTML_STRING.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(HTML_STRING, children) + } + HTML_ATTRIBUTE_LIST => { + Self::make_node_list_syntax(kind, children, HtmlAttribute::can_cast) + } + HTML_ELEMENT_LIST => { + Self::make_node_list_syntax(kind, children, AnyHtmlElement::can_cast) + } + _ => unreachable!("Is {:?} a token?", kind), + } + } +} diff --git a/crates/biome_html_factory/src/lib.rs b/crates/biome_html_factory/src/lib.rs new file mode 100644 index 000000000000..fc1459eab3d9 --- /dev/null +++ b/crates/biome_html_factory/src/lib.rs @@ -0,0 +1,12 @@ +use biome_html_syntax::HtmlLanguage; +use biome_rowan::TreeBuilder; + +mod generated; +pub use crate::generated::HtmlSyntaxFactory; +pub mod make; + +// Re-exported for tests +#[doc(hidden)] +pub use biome_html_syntax as syntax; + +pub type JsonSyntaxTreeBuilder = TreeBuilder<'static, HtmlLanguage, HtmlSyntaxFactory>; diff --git a/crates/biome_html_factory/src/make.rs b/crates/biome_html_factory/src/make.rs new file mode 100644 index 000000000000..df4cffff5316 --- /dev/null +++ b/crates/biome_html_factory/src/make.rs @@ -0,0 +1,7 @@ +use biome_html_syntax::{HtmlSyntaxKind, HtmlSyntaxToken}; + +pub use crate::generated::node_factory::*; + +pub fn ident(text: &str) -> HtmlSyntaxToken { + HtmlSyntaxToken::new_detached(HtmlSyntaxKind::IDENT, text, [], []) +} diff --git a/crates/biome_html_syntax/Cargo.toml b/crates/biome_html_syntax/Cargo.toml new file mode 100644 index 000000000000..75fc6aff005d --- /dev/null +++ b/crates/biome_html_syntax/Cargo.toml @@ -0,0 +1,20 @@ +[package] +authors.workspace = true +categories.workspace = true +description = "SyntaxKind and common rowan definitions for biome_html_parser" +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "biome_html_syntax" +repository.workspace = true +version = "0.4.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +biome_rowan = { workspace = true } + + +[lints] +workspace = true diff --git a/crates/biome_html_syntax/src/file_source.rs b/crates/biome_html_syntax/src/file_source.rs new file mode 100644 index 000000000000..fce0556f97c4 --- /dev/null +++ b/crates/biome_html_syntax/src/file_source.rs @@ -0,0 +1,68 @@ +use crate::HtmlLanguage; +use biome_rowan::{FileSource, FileSourceError}; +use std::path::Path; + +#[derive(Debug, Default, Clone)] +pub struct HtmlFileSource { + #[allow(unused)] + variant: HtmlVariant, +} + +#[derive(Debug, Default, Clone)] +enum HtmlVariant { + #[default] + Standard, + Astro, +} + +impl HtmlFileSource { + pub fn html() -> Self { + Self { + variant: HtmlVariant::Standard, + } + } + pub fn astro() -> Self { + Self { + variant: HtmlVariant::Astro, + } + } +} + +impl<'a> FileSource<'a, HtmlLanguage> for HtmlFileSource {} + +impl TryFrom<&Path> for HtmlFileSource { + type Error = FileSourceError; + + fn try_from(path: &Path) -> Result { + let file_name = path + .file_name() + .ok_or_else(|| FileSourceError::MissingFileName(path.into()))? + .to_str() + .ok_or_else(|| FileSourceError::MissingFileName(path.into()))?; + + let extension = path + .extension() + .ok_or_else(|| FileSourceError::MissingFileExtension(path.into()))? + .to_str() + .ok_or_else(|| FileSourceError::MissingFileExtension(path.into()))?; + + compute_source_type_from_path_or_extension(file_name, extension) + } +} + +/// It deduce the [HtmlFileSource] from the file name and its extension +fn compute_source_type_from_path_or_extension( + file_name: &str, + extension: &str, +) -> Result { + Ok(match extension { + "html" => HtmlFileSource::html(), + "astro" => HtmlFileSource::astro(), + _ => { + return Err(FileSourceError::UnknownExtension( + file_name.into(), + extension.into(), + )); + } + }) +} diff --git a/crates/biome_html_syntax/src/generated.rs b/crates/biome_html_syntax/src/generated.rs new file mode 100644 index 000000000000..e62bfed1de4e --- /dev/null +++ b/crates/biome_html_syntax/src/generated.rs @@ -0,0 +1,9 @@ +#[rustfmt::skip] +pub(super) mod nodes; +#[rustfmt::skip] +pub mod macros; +#[macro_use] +pub mod kind; + +pub use kind::*; +pub use nodes::*; diff --git a/crates/biome_html_syntax/src/generated/kind.rs b/crates/biome_html_syntax/src/generated/kind.rs new file mode 100644 index 000000000000..3e25d00222e1 --- /dev/null +++ b/crates/biome_html_syntax/src/generated/kind.rs @@ -0,0 +1,95 @@ +//! Generated file, do not edit by hand, see `xtask/codegen` + +#![allow(clippy::all)] +#![allow(bad_style, missing_docs, unreachable_pub)] +#[doc = r" The kind of syntax node, e.g. `IDENT`, `FUNCTION_KW`, or `FOR_STMT`."] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[repr(u16)] +pub enum HtmlSyntaxKind { + #[doc(hidden)] + TOMBSTONE, + #[doc = r" Marks the end of the file. May have trivia attached"] + EOF, + #[doc = r" Any Unicode BOM character that may be present at the start of"] + #[doc = r" a file."] + UNICODE_BOM, + L_ANGLE, + R_ANGLE, + SLASH, + EQ, + BANG, + NULL_KW, + TRUE_KW, + FALSE_KW, + HTML_STRING_LITERAL, + ERROR_TOKEN, + NEWLINE, + WHITESPACE, + IDENT, + COMMENT, + HTML_IDENT, + HTML_ROOT, + HTML_DIRECTIVE, + HTML_SELF_CLOSING_TAG, + HTML_ELEMENT, + HTML_OPENING_ELEMENT, + HTML_CLOSING_ELEMENT, + HTML_SELF_CLOSING_ELEMENT, + HTML_ATTRIBUTE, + HTML_ATTRIBUTE_INITIALIZER_CLAUSE, + HTML_STRING, + HTML_NAME, + HTML_ELEMENT_LIST, + HTML_ATTRIBUTE_LIST, + HTML_BOGUS, + #[doc(hidden)] + __LAST, +} +use self::HtmlSyntaxKind::*; +impl HtmlSyntaxKind { + pub const fn is_punct(self) -> bool { + match self { + L_ANGLE | R_ANGLE | SLASH | EQ | BANG => true, + _ => false, + } + } + pub const fn is_literal(self) -> bool { + match self { + HTML_STRING_LITERAL => true, + _ => false, + } + } + pub const fn is_list(self) -> bool { + match self { + HTML_ELEMENT_LIST | HTML_ATTRIBUTE_LIST => true, + _ => false, + } + } + pub fn from_keyword(ident: &str) -> Option { + let kw = match ident { + "null" => NULL_KW, + "true" => TRUE_KW, + "false" => FALSE_KW, + _ => return None, + }; + Some(kw) + } + pub const fn to_string(&self) -> Option<&'static str> { + let tok = match self { + L_ANGLE => "<", + R_ANGLE => ">", + SLASH => "/", + EQ => "=", + BANG => "!", + NULL_KW => "null", + TRUE_KW => "true", + FALSE_KW => "false", + HTML_STRING_LITERAL => "string literal", + _ => return None, + }; + Some(tok) + } +} +#[doc = r" Utility macro for creating a SyntaxKind through simple macro syntax"] +#[macro_export] +macro_rules ! T { [<] => { $ crate :: HtmlSyntaxKind :: L_ANGLE } ; [>] => { $ crate :: HtmlSyntaxKind :: R_ANGLE } ; [/] => { $ crate :: HtmlSyntaxKind :: SLASH } ; [=] => { $ crate :: HtmlSyntaxKind :: EQ } ; [!] => { $ crate :: HtmlSyntaxKind :: BANG } ; [null] => { $ crate :: HtmlSyntaxKind :: NULL_KW } ; [true] => { $ crate :: HtmlSyntaxKind :: TRUE_KW } ; [false] => { $ crate :: HtmlSyntaxKind :: FALSE_KW } ; [ident] => { $ crate :: HtmlSyntaxKind :: IDENT } ; [EOF] => { $ crate :: HtmlSyntaxKind :: EOF } ; [UNICODE_BOM] => { $ crate :: HtmlSyntaxKind :: UNICODE_BOM } ; [#] => { $ crate :: HtmlSyntaxKind :: HASH } ; } diff --git a/crates/biome_html_syntax/src/generated/macros.rs b/crates/biome_html_syntax/src/generated/macros.rs new file mode 100644 index 000000000000..a96b93b53a34 --- /dev/null +++ b/crates/biome_html_syntax/src/generated/macros.rs @@ -0,0 +1,77 @@ +//! Generated file, do not edit by hand, see `xtask/codegen` + +#[doc = r" Reconstruct an AstNode from a SyntaxNode"] +#[doc = r""] +#[doc = r" This macros performs a match over the [kind](biome_rowan::SyntaxNode::kind)"] +#[doc = r" of the provided [biome_rowan::SyntaxNode] and constructs the appropriate"] +#[doc = r" AstNode type for it, then execute the provided expression over it."] +#[doc = r""] +#[doc = r" # Examples"] +#[doc = r""] +#[doc = r" ```ignore"] +#[doc = r" map_syntax_node!(syntax_node, node => node.format())"] +#[doc = r" ```"] +#[macro_export] +macro_rules! map_syntax_node { + ($ node : expr , $ pattern : pat => $ body : expr) => { + match $node { + node => match $crate::HtmlSyntaxNode::kind(&node) { + $crate::HtmlSyntaxKind::HTML_ATTRIBUTE => { + let $pattern = unsafe { $crate::HtmlAttribute::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_ATTRIBUTE_INITIALIZER_CLAUSE => { + let $pattern = + unsafe { $crate::HtmlAttributeInitializerClause::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_CLOSING_ELEMENT => { + let $pattern = unsafe { $crate::HtmlClosingElement::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_DIRECTIVE => { + let $pattern = unsafe { $crate::HtmlDirective::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_ELEMENT => { + let $pattern = unsafe { $crate::HtmlElement::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_NAME => { + let $pattern = unsafe { $crate::HtmlName::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_OPENING_ELEMENT => { + let $pattern = unsafe { $crate::HtmlOpeningElement::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_ROOT => { + let $pattern = unsafe { $crate::HtmlRoot::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_SELF_CLOSING_ELEMENT => { + let $pattern = unsafe { $crate::HtmlSelfClosingElement::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_STRING => { + let $pattern = unsafe { $crate::HtmlString::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_BOGUS => { + let $pattern = unsafe { $crate::HtmlBogus::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_ATTRIBUTE_LIST => { + let $pattern = unsafe { $crate::HtmlAttributeList::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::HTML_ELEMENT_LIST => { + let $pattern = unsafe { $crate::HtmlElementList::new_unchecked(node) }; + $body + } + _ => unreachable!(), + }, + } + }; +} +pub(crate) use map_syntax_node; diff --git a/crates/biome_html_syntax/src/generated/nodes.rs b/crates/biome_html_syntax/src/generated/nodes.rs new file mode 100644 index 000000000000..31da15ef7872 --- /dev/null +++ b/crates/biome_html_syntax/src/generated/nodes.rs @@ -0,0 +1,1313 @@ +//! Generated file, do not edit by hand, see `xtask/codegen` + +#![allow(clippy::enum_variant_names)] +#![allow(clippy::match_like_matches_macro)] +use crate::{ + macros::map_syntax_node, + HtmlLanguage as Language, HtmlSyntaxElement as SyntaxElement, + HtmlSyntaxElementChildren as SyntaxElementChildren, + HtmlSyntaxKind::{self as SyntaxKind, *}, + HtmlSyntaxList as SyntaxList, HtmlSyntaxNode as SyntaxNode, HtmlSyntaxToken as SyntaxToken, +}; +use biome_rowan::{support, AstNode, RawSyntaxKind, SyntaxKindSet, SyntaxResult}; +#[allow(unused)] +use biome_rowan::{ + AstNodeList, AstNodeListIterator, AstSeparatedList, AstSeparatedListNodesIterator, +}; +#[cfg(feature = "serde")] +use serde::ser::SerializeSeq; +#[cfg(feature = "serde")] +use serde::{Serialize, Serializer}; +use std::fmt::{Debug, Formatter}; +#[doc = r" Sentinel value indicating a missing element in a dynamic node, where"] +#[doc = r" the slots are not statically known."] +#[allow(dead_code)] +pub(crate) const SLOT_MAP_EMPTY_VALUE: u8 = u8::MAX; +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HtmlAttribute { + pub(crate) syntax: SyntaxNode, +} +impl HtmlAttribute { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> HtmlAttributeFields { + HtmlAttributeFields { + name: self.name(), + initializer: self.initializer(), + } + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 0usize) + } + pub fn initializer(&self) -> Option { + support::node(&self.syntax, 1usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlAttribute { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlAttributeFields { + pub name: SyntaxResult, + pub initializer: Option, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HtmlAttributeInitializerClause { + pub(crate) syntax: SyntaxNode, +} +impl HtmlAttributeInitializerClause { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> HtmlAttributeInitializerClauseFields { + HtmlAttributeInitializerClauseFields { + eq_token: self.eq_token(), + value: self.value(), + } + } + pub fn eq_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn value(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlAttributeInitializerClause { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlAttributeInitializerClauseFields { + pub eq_token: SyntaxResult, + pub value: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HtmlClosingElement { + pub(crate) syntax: SyntaxNode, +} +impl HtmlClosingElement { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> HtmlClosingElementFields { + HtmlClosingElementFields { + l_angle_token: self.l_angle_token(), + slash_token: self.slash_token(), + name: self.name(), + r_angle_token: self.r_angle_token(), + } + } + pub fn l_angle_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn slash_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } + pub fn r_angle_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlClosingElement { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlClosingElementFields { + pub l_angle_token: SyntaxResult, + pub slash_token: SyntaxResult, + pub name: SyntaxResult, + pub r_angle_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HtmlDirective { + pub(crate) syntax: SyntaxNode, +} +impl HtmlDirective { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> HtmlDirectiveFields { + HtmlDirectiveFields { + l_angle_token: self.l_angle_token(), + excl_token: self.excl_token(), + content: self.content(), + r_angle_token: self.r_angle_token(), + } + } + pub fn l_angle_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn excl_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } + pub fn content(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } + pub fn r_angle_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlDirective { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlDirectiveFields { + pub l_angle_token: SyntaxResult, + pub excl_token: SyntaxResult, + pub content: SyntaxResult, + pub r_angle_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HtmlElement { + pub(crate) syntax: SyntaxNode, +} +impl HtmlElement { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> HtmlElementFields { + HtmlElementFields { + opening_element: self.opening_element(), + children: self.children(), + closing_element: self.closing_element(), + } + } + pub fn opening_element(&self) -> SyntaxResult { + support::required_node(&self.syntax, 0usize) + } + pub fn children(&self) -> HtmlElementList { + support::list(&self.syntax, 1usize) + } + pub fn closing_element(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlElement { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlElementFields { + pub opening_element: SyntaxResult, + pub children: HtmlElementList, + pub closing_element: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HtmlName { + pub(crate) syntax: SyntaxNode, +} +impl HtmlName { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> HtmlNameFields { + HtmlNameFields { + value_token: self.value_token(), + } + } + pub fn value_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlName { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlNameFields { + pub value_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HtmlOpeningElement { + pub(crate) syntax: SyntaxNode, +} +impl HtmlOpeningElement { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> HtmlOpeningElementFields { + HtmlOpeningElementFields { + l_angle_token: self.l_angle_token(), + name: self.name(), + attributes: self.attributes(), + r_angle_token: self.r_angle_token(), + } + } + pub fn l_angle_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } + pub fn attributes(&self) -> HtmlAttributeList { + support::list(&self.syntax, 2usize) + } + pub fn r_angle_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlOpeningElement { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlOpeningElementFields { + pub l_angle_token: SyntaxResult, + pub name: SyntaxResult, + pub attributes: HtmlAttributeList, + pub r_angle_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HtmlRoot { + pub(crate) syntax: SyntaxNode, +} +impl HtmlRoot { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> HtmlRootFields { + HtmlRootFields { + bom_token: self.bom_token(), + directive: self.directive(), + tags: self.tags(), + eof_token: self.eof_token(), + } + } + pub fn bom_token(&self) -> Option { + support::token(&self.syntax, 0usize) + } + pub fn directive(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } + pub fn tags(&self) -> HtmlElementList { + support::list(&self.syntax, 2usize) + } + pub fn eof_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlRoot { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlRootFields { + pub bom_token: Option, + pub directive: SyntaxResult, + pub tags: HtmlElementList, + pub eof_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HtmlSelfClosingElement { + pub(crate) syntax: SyntaxNode, +} +impl HtmlSelfClosingElement { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> HtmlSelfClosingElementFields { + HtmlSelfClosingElementFields { + l_angle_token: self.l_angle_token(), + name: self.name(), + attributes: self.attributes(), + slash_token: self.slash_token(), + r_angle_token: self.r_angle_token(), + } + } + pub fn l_angle_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } + pub fn attributes(&self) -> HtmlAttributeList { + support::list(&self.syntax, 2usize) + } + pub fn slash_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } + pub fn r_angle_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 4usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlSelfClosingElement { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlSelfClosingElementFields { + pub l_angle_token: SyntaxResult, + pub name: SyntaxResult, + pub attributes: HtmlAttributeList, + pub slash_token: SyntaxResult, + pub r_angle_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HtmlString { + pub(crate) syntax: SyntaxNode, +} +impl HtmlString { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> HtmlStringFields { + HtmlStringFields { + value_token: self.value_token(), + } + } + pub fn value_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlString { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlStringFields { + pub value_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize))] +pub enum AnyHtmlElement { + HtmlElement(HtmlElement), + HtmlSelfClosingElement(HtmlSelfClosingElement), +} +impl AnyHtmlElement { + pub fn as_html_element(&self) -> Option<&HtmlElement> { + match &self { + AnyHtmlElement::HtmlElement(item) => Some(item), + _ => None, + } + } + pub fn as_html_self_closing_element(&self) -> Option<&HtmlSelfClosingElement> { + match &self { + AnyHtmlElement::HtmlSelfClosingElement(item) => Some(item), + _ => None, + } + } +} +impl AstNode for HtmlAttribute { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_ATTRIBUTE as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_ATTRIBUTE + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlAttribute { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlAttribute") + .field("name", &support::DebugSyntaxResult(self.name())) + .field( + "initializer", + &support::DebugOptionalElement(self.initializer()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlAttribute) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlAttribute) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for HtmlAttributeInitializerClause { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_ATTRIBUTE_INITIALIZER_CLAUSE as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_ATTRIBUTE_INITIALIZER_CLAUSE + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlAttributeInitializerClause { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlAttributeInitializerClause") + .field("eq_token", &support::DebugSyntaxResult(self.eq_token())) + .field("value", &support::DebugSyntaxResult(self.value())) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlAttributeInitializerClause) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlAttributeInitializerClause) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for HtmlClosingElement { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_CLOSING_ELEMENT as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_CLOSING_ELEMENT + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlClosingElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlClosingElement") + .field( + "l_angle_token", + &support::DebugSyntaxResult(self.l_angle_token()), + ) + .field( + "slash_token", + &support::DebugSyntaxResult(self.slash_token()), + ) + .field("name", &support::DebugSyntaxResult(self.name())) + .field( + "r_angle_token", + &support::DebugSyntaxResult(self.r_angle_token()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlClosingElement) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlClosingElement) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for HtmlDirective { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_DIRECTIVE as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_DIRECTIVE + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlDirective { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlDirective") + .field( + "l_angle_token", + &support::DebugSyntaxResult(self.l_angle_token()), + ) + .field("excl_token", &support::DebugSyntaxResult(self.excl_token())) + .field("content", &support::DebugSyntaxResult(self.content())) + .field( + "r_angle_token", + &support::DebugSyntaxResult(self.r_angle_token()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlDirective) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlDirective) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for HtmlElement { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_ELEMENT as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_ELEMENT + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlElement") + .field( + "opening_element", + &support::DebugSyntaxResult(self.opening_element()), + ) + .field("children", &self.children()) + .field( + "closing_element", + &support::DebugSyntaxResult(self.closing_element()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlElement) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlElement) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for HtmlName { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_NAME as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_NAME + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlName") + .field( + "value_token", + &support::DebugSyntaxResult(self.value_token()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlName) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlName) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for HtmlOpeningElement { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_OPENING_ELEMENT as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_OPENING_ELEMENT + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlOpeningElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlOpeningElement") + .field( + "l_angle_token", + &support::DebugSyntaxResult(self.l_angle_token()), + ) + .field("name", &support::DebugSyntaxResult(self.name())) + .field("attributes", &self.attributes()) + .field( + "r_angle_token", + &support::DebugSyntaxResult(self.r_angle_token()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlOpeningElement) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlOpeningElement) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for HtmlRoot { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_ROOT as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_ROOT + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlRoot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlRoot") + .field( + "bom_token", + &support::DebugOptionalElement(self.bom_token()), + ) + .field("directive", &support::DebugSyntaxResult(self.directive())) + .field("tags", &self.tags()) + .field("eof_token", &support::DebugSyntaxResult(self.eof_token())) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlRoot) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlRoot) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for HtmlSelfClosingElement { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_SELF_CLOSING_ELEMENT as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_SELF_CLOSING_ELEMENT + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlSelfClosingElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlSelfClosingElement") + .field( + "l_angle_token", + &support::DebugSyntaxResult(self.l_angle_token()), + ) + .field("name", &support::DebugSyntaxResult(self.name())) + .field("attributes", &self.attributes()) + .field( + "slash_token", + &support::DebugSyntaxResult(self.slash_token()), + ) + .field( + "r_angle_token", + &support::DebugSyntaxResult(self.r_angle_token()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlSelfClosingElement) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlSelfClosingElement) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for HtmlString { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_STRING as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_STRING + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlString") + .field( + "value_token", + &support::DebugSyntaxResult(self.value_token()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlString) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlString) -> SyntaxElement { + n.syntax.into() + } +} +impl From for AnyHtmlElement { + fn from(node: HtmlElement) -> AnyHtmlElement { + AnyHtmlElement::HtmlElement(node) + } +} +impl From for AnyHtmlElement { + fn from(node: HtmlSelfClosingElement) -> AnyHtmlElement { + AnyHtmlElement::HtmlSelfClosingElement(node) + } +} +impl AstNode for AnyHtmlElement { + type Language = Language; + const KIND_SET: SyntaxKindSet = + HtmlElement::KIND_SET.union(HtmlSelfClosingElement::KIND_SET); + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, HTML_ELEMENT | HTML_SELF_CLOSING_ELEMENT) + } + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + HTML_ELEMENT => AnyHtmlElement::HtmlElement(HtmlElement { syntax }), + HTML_SELF_CLOSING_ELEMENT => { + AnyHtmlElement::HtmlSelfClosingElement(HtmlSelfClosingElement { syntax }) + } + _ => return None, + }; + Some(res) + } + fn syntax(&self) -> &SyntaxNode { + match self { + AnyHtmlElement::HtmlElement(it) => &it.syntax, + AnyHtmlElement::HtmlSelfClosingElement(it) => &it.syntax, + } + } + fn into_syntax(self) -> SyntaxNode { + match self { + AnyHtmlElement::HtmlElement(it) => it.syntax, + AnyHtmlElement::HtmlSelfClosingElement(it) => it.syntax, + } + } +} +impl std::fmt::Debug for AnyHtmlElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AnyHtmlElement::HtmlElement(it) => std::fmt::Debug::fmt(it, f), + AnyHtmlElement::HtmlSelfClosingElement(it) => std::fmt::Debug::fmt(it, f), + } + } +} +impl From for SyntaxNode { + fn from(n: AnyHtmlElement) -> SyntaxNode { + match n { + AnyHtmlElement::HtmlElement(it) => it.into(), + AnyHtmlElement::HtmlSelfClosingElement(it) => it.into(), + } + } +} +impl From for SyntaxElement { + fn from(n: AnyHtmlElement) -> SyntaxElement { + let node: SyntaxNode = n.into(); + node.into() + } +} +impl std::fmt::Display for AnyHtmlElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for HtmlAttribute { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for HtmlAttributeInitializerClause { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for HtmlClosingElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for HtmlDirective { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for HtmlElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for HtmlName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for HtmlOpeningElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for HtmlRoot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for HtmlSelfClosingElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for HtmlString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +#[derive(Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct HtmlBogus { + syntax: SyntaxNode, +} +impl HtmlBogus { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn items(&self) -> SyntaxElementChildren { + support::elements(&self.syntax) + } +} +impl AstNode for HtmlBogus { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_BOGUS as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_BOGUS + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for HtmlBogus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HtmlBogus") + .field("items", &DebugSyntaxElementChildren(self.items())) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: HtmlBogus) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: HtmlBogus) -> SyntaxElement { + n.syntax.into() + } +} +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct HtmlAttributeList { + syntax_list: SyntaxList, +} +impl HtmlAttributeList { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { + syntax_list: syntax.into_list(), + } + } +} +impl AstNode for HtmlAttributeList { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_ATTRIBUTE_LIST as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_ATTRIBUTE_LIST + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(HtmlAttributeList { + syntax_list: syntax.into_list(), + }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + self.syntax_list.node() + } + fn into_syntax(self) -> SyntaxNode { + self.syntax_list.into_node() + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlAttributeList { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for e in self.iter() { + seq.serialize_element(&e)?; + } + seq.end() + } +} +impl AstNodeList for HtmlAttributeList { + type Language = Language; + type Node = HtmlAttribute; + fn syntax_list(&self) -> &SyntaxList { + &self.syntax_list + } + fn into_syntax_list(self) -> SyntaxList { + self.syntax_list + } +} +impl Debug for HtmlAttributeList { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("HtmlAttributeList ")?; + f.debug_list().entries(self.iter()).finish() + } +} +impl IntoIterator for &HtmlAttributeList { + type Item = HtmlAttribute; + type IntoIter = AstNodeListIterator; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} +impl IntoIterator for HtmlAttributeList { + type Item = HtmlAttribute; + type IntoIter = AstNodeListIterator; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct HtmlElementList { + syntax_list: SyntaxList, +} +impl HtmlElementList { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { + syntax_list: syntax.into_list(), + } + } +} +impl AstNode for HtmlElementList { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(HTML_ELEMENT_LIST as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == HTML_ELEMENT_LIST + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(HtmlElementList { + syntax_list: syntax.into_list(), + }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + self.syntax_list.node() + } + fn into_syntax(self) -> SyntaxNode { + self.syntax_list.into_node() + } +} +#[cfg(feature = "serde")] +impl Serialize for HtmlElementList { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for e in self.iter() { + seq.serialize_element(&e)?; + } + seq.end() + } +} +impl AstNodeList for HtmlElementList { + type Language = Language; + type Node = AnyHtmlElement; + fn syntax_list(&self) -> &SyntaxList { + &self.syntax_list + } + fn into_syntax_list(self) -> SyntaxList { + self.syntax_list + } +} +impl Debug for HtmlElementList { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("HtmlElementList ")?; + f.debug_list().entries(self.iter()).finish() + } +} +impl IntoIterator for &HtmlElementList { + type Item = AnyHtmlElement; + type IntoIter = AstNodeListIterator; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} +impl IntoIterator for HtmlElementList { + type Item = AnyHtmlElement; + type IntoIter = AstNodeListIterator; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} +#[derive(Clone)] +pub struct DebugSyntaxElementChildren(pub SyntaxElementChildren); +impl Debug for DebugSyntaxElementChildren { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_list() + .entries(self.clone().0.map(DebugSyntaxElement)) + .finish() + } +} +struct DebugSyntaxElement(SyntaxElement); +impl Debug for DebugSyntaxElement { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.0 { + SyntaxElement::Node(node) => { + map_syntax_node ! (node . clone () , node => std :: fmt :: Debug :: fmt (& node , f)) + } + SyntaxElement::Token(token) => Debug::fmt(token, f), + } + } +} diff --git a/crates/biome_html_syntax/src/generated/nodes_mut.rs b/crates/biome_html_syntax/src/generated/nodes_mut.rs new file mode 100644 index 000000000000..21dfbdf5b624 --- /dev/null +++ b/crates/biome_html_syntax/src/generated/nodes_mut.rs @@ -0,0 +1,205 @@ +//! Generated file, do not edit by hand, see `xtask/codegen` + +use crate::{generated::nodes::*, HtmlSyntaxToken as SyntaxToken}; +use biome_rowan::AstNode; +use std::iter::once; +impl HtmlAttribute { + pub fn with_name(self, element: HtmlName) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_initializer(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 1usize..=1usize, + once(element.map(|element| element.into_syntax().into())), + )) + } +} +impl HtmlAttributeInitializerClause { + pub fn with_eq_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_value(self, element: HtmlString) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } +} +impl HtmlClosingElement { + pub fn with_l_angle_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_slash_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: HtmlName) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_angle_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } +} +impl HtmlDirective { + pub fn with_l_angle_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_excl_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_content(self, element: HtmlString) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_angle_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } +} +impl HtmlElement { + pub fn with_opening_element(self, element: HtmlOpeningElement) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_children(self, element: HtmlElementList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_closing_element(self, element: HtmlClosingElement) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } +} +impl HtmlName { + pub fn with_value_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } +} +impl HtmlOpeningElement { + pub fn with_l_angle_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: HtmlName) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_attributes(self, element: HtmlAttributeList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_angle_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } +} +impl HtmlRoot { + pub fn with_bom_token(self, element: Option) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(element.map(|element| element.into()))), + ) + } + pub fn with_directive(self, element: HtmlDirective) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_tags(self, element: HtmlElementList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_eof_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } +} +impl HtmlSelfClosingElement { + pub fn with_l_angle_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: HtmlName) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_attributes(self, element: HtmlAttributeList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_slash_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } + pub fn with_r_angle_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(4usize..=4usize, once(Some(element.into()))), + ) + } +} +impl HtmlString { + pub fn with_value_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } +} diff --git a/crates/biome_html_syntax/src/lib.rs b/crates/biome_html_syntax/src/lib.rs new file mode 100644 index 000000000000..126deb0eb83c --- /dev/null +++ b/crates/biome_html_syntax/src/lib.rs @@ -0,0 +1,107 @@ +#[macro_use] +mod generated; +mod file_source; +mod syntax_node; + +pub use self::generated::*; +pub use biome_rowan::{TextLen, TextRange, TextSize, TokenAtOffset, TriviaPieceKind, WalkEvent}; +pub use file_source::HtmlFileSource; +pub use syntax_node::*; + +use biome_rowan::{RawSyntaxKind, TokenText}; + +impl From for HtmlSyntaxKind { + fn from(d: u16) -> HtmlSyntaxKind { + assert!(d <= (HtmlSyntaxKind::__LAST as u16)); + unsafe { std::mem::transmute::(d) } + } +} + +impl From for u16 { + fn from(k: HtmlSyntaxKind) -> u16 { + k as u16 + } +} + +impl HtmlSyntaxKind { + pub fn is_trivia(self) -> bool { + matches!(self, HtmlSyntaxKind::NEWLINE | HtmlSyntaxKind::WHITESPACE) + } + + pub fn is_comments(self) -> bool { + matches!(self, HtmlSyntaxKind::COMMENT) + } + + #[inline] + pub const fn is_keyword(self) -> bool { + matches!(self, T![null] | T![true] | T![false]) + } +} + +impl biome_rowan::SyntaxKind for HtmlSyntaxKind { + const TOMBSTONE: Self = HtmlSyntaxKind::TOMBSTONE; + const EOF: Self = HtmlSyntaxKind::EOF; + + fn is_bogus(&self) -> bool { + matches!(self, HtmlSyntaxKind::HTML_BOGUS) + } + + fn to_bogus(&self) -> Self { + HtmlSyntaxKind::HTML_BOGUS + } + + #[inline] + fn to_raw(&self) -> RawSyntaxKind { + RawSyntaxKind(*self as u16) + } + + #[inline] + fn from_raw(raw: RawSyntaxKind) -> Self { + Self::from(raw.0) + } + + fn is_root(&self) -> bool { + matches!(self, HtmlSyntaxKind::HTML_ROOT) + } + + fn is_list(&self) -> bool { + HtmlSyntaxKind::is_list(*self) + } + + fn to_string(&self) -> Option<&'static str> { + HtmlSyntaxKind::to_string(self) + } +} + +impl TryFrom for TriviaPieceKind { + type Error = (); + + fn try_from(value: HtmlSyntaxKind) -> Result { + if value.is_trivia() { + match value { + HtmlSyntaxKind::NEWLINE => Ok(TriviaPieceKind::Newline), + HtmlSyntaxKind::WHITESPACE => Ok(TriviaPieceKind::Whitespace), + _ => unreachable!("Not Trivia"), + } + } else if value.is_comments() { + match value { + HtmlSyntaxKind::COMMENT => Ok(TriviaPieceKind::SingleLineComment), + _ => unreachable!("Not Comment"), + } + } else { + Err(()) + } + } +} + +/// Text of `token`, excluding all trivia and removing quotes if `token` is a string literal. +pub fn inner_string_text(token: &HtmlSyntaxToken) -> TokenText { + let mut text = token.token_text_trimmed(); + if token.kind() == HtmlSyntaxKind::HTML_STRING_LITERAL { + // remove string delimiters + // SAFETY: string literal token have a delimiters at the start and the end of the string + let range = TextRange::new(1.into(), text.len() - TextSize::from(1)); + text = text.slice(range); + } + text +} diff --git a/crates/biome_html_syntax/src/syntax_node.rs b/crates/biome_html_syntax/src/syntax_node.rs new file mode 100644 index 000000000000..66e9992a635c --- /dev/null +++ b/crates/biome_html_syntax/src/syntax_node.rs @@ -0,0 +1,24 @@ +//! This module defines the Concrete Syntax Tree used by Biome. +//! +//! The tree is entirely lossless, whitespace, comments, and errors are preserved. +//! It also provides traversal methods including parent, children, and siblings of nodes. +//! +//! This is a simple wrapper around the `rowan` crate which does most of the heavy lifting and is language agnostic. + +use crate::{HtmlRoot, HtmlSyntaxKind}; +use biome_rowan::Language; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct HtmlLanguage; + +impl Language for HtmlLanguage { + type Kind = HtmlSyntaxKind; + type Root = HtmlRoot; +} + +pub type HtmlSyntaxNode = biome_rowan::SyntaxNode; +pub type HtmlSyntaxToken = biome_rowan::SyntaxToken; +pub type HtmlSyntaxElement = biome_rowan::SyntaxElement; +pub type HtmlSyntaxNodeChildren = biome_rowan::SyntaxNodeChildren; +pub type HtmlSyntaxElementChildren = biome_rowan::SyntaxElementChildren; +pub type HtmlSyntaxList = biome_rowan::SyntaxList; diff --git a/xtask/codegen/html.ungram b/xtask/codegen/html.ungram new file mode 100644 index 000000000000..175a35e6b70f --- /dev/null +++ b/xtask/codegen/html.ungram @@ -0,0 +1,115 @@ +// CSS Un-Grammar. +// +// This grammar specifies the structure of Rust's concrete syntax tree. +// It does not specify parsing rules (ambiguities, precedence, etc are out of scope). +// Tokens are processed -- contextual keywords are recognised, compound operators glued. +// +// Legend: +// +// // -- comment +// Name = -- non-terminal definition +// 'ident' -- token (terminal) +// A B -- sequence +// A | B -- alternation +// A* -- zero or more repetition +// (A (',' A)* ','?) -- repetition of node A separated by ',' and allowing a trailing comma +// (A (',' A)*) -- repetition of node A separated by ',' without a trailing comma +// A? -- zero or one repetition +// (A) -- same as A +// label:A -- suggested name for field of AST node + +// NOTES +// +// - SyntaxNode, SyntaxToken and SyntaxElement will be stripped from the codegen +// - Bogus nodes are special nodes used to keep track of broken code; they are +// not part of the grammar but they will appear inside the green tree + + +/////////////// +// BOGUS NODES +/////////////// +// SyntaxElement is a generic data structure that is meant to track nodes and tokens +// in cases where we care about both types +// +// As Bogus* node will need to yield both tokens and nodes without discrimination, +// and their children will need to yield nodes and tokens as well. +// For this reason, SyntaxElement = SyntaxElement +SyntaxElement = SyntaxElement + +HtmlBogus = SyntaxElement* + +HtmlRoot = + bom: 'UNICODE_BOM'? + directive: HtmlDirective + tags: HtmlElementList + eof: 'EOF' + +// +// ^^^^^^^^^^^^^^^ +// TODO: add better representation +HtmlDirective = + '<' + '!' + content: HtmlString + '>' + +// ================================== +// Elements (AKA tags) +// ================================== + +HtmlElementList = AnyHtmlElement* + +AnyHtmlElement = + HtmlSelfClosingElement + | HtmlElement + + +// +HtmlSelfClosingElement = + '<' + name: HtmlName + attributes: HtmlAttributeList + '/' + '>' + +HtmlElement = + opening_element: HtmlOpeningElement + children: HtmlElementList + closing_element: HtmlClosingElement + + +// +// ^^ ^ +HtmlOpeningElement = + '<' + name: HtmlName + attributes: HtmlAttributeList + '>' + +// +HtmlClosingElement = + '<' + '/' + name: HtmlName + '>' + +// ================================== +// Attributes +// ================================== + +HtmlAttributeList = HtmlAttribute* + +HtmlAttribute = + name: HtmlName + initializer: HtmlAttributeInitializerClause? + + +// +// ^^^^ +HtmlAttributeInitializerClause = + '=' + value: HtmlString + + +HtmlString = value: 'html_string_literal' +HtmlName = value: 'html_ident' diff --git a/xtask/codegen/src/ast.rs b/xtask/codegen/src/ast.rs index dce0e81e033c..6b1900ba2770 100644 --- a/xtask/codegen/src/ast.rs +++ b/xtask/codegen/src/ast.rs @@ -6,17 +6,18 @@ use std::str::FromStr; use std::vec; use super::{ - kinds_src::{AstSrc, Field}, + js_kinds_src::{AstSrc, Field}, to_lower_snake_case, Mode, }; use crate::css_kinds_src::CSS_KINDS_SRC; use crate::generate_node_factory::generate_node_factory; use crate::generate_nodes_mut::generate_nodes_mut; use crate::generate_syntax_factory::generate_syntax_factory; -use crate::json_kinds_src::JSON_KINDS_SRC; -use crate::kinds_src::{ +use crate::html_kinds_src::HTML_KINDS_SRC; +use crate::js_kinds_src::{ AstEnumSrc, AstListSeparatorConfiguration, AstListSrc, AstNodeSrc, TokenKind, JS_KINDS_SRC, }; +use crate::json_kinds_src::JSON_KINDS_SRC; use crate::termcolorful::{println_string_with_fg_color, Color}; use crate::ALL_LANGUAGE_KIND; use crate::{ @@ -26,6 +27,7 @@ use crate::{ use biome_ungrammar::{Grammar, Rule, Token}; use std::fmt::Write; use xtask::{project_root, Result}; + // these node won't generate any code pub const SYNTAX_ELEMENT_TYPE: &str = "SyntaxElement"; @@ -65,6 +67,7 @@ pub(crate) fn load_ast(language: LanguageKind) -> AstSrc { LanguageKind::Js => load_js_ast(), LanguageKind::Css => load_css_ast(), LanguageKind::Json => load_json_ast(), + LanguageKind::Html => load_html_ast(), } } @@ -82,6 +85,7 @@ pub(crate) fn generate_syntax(ast: AstSrc, mode: &Mode, language_kind: LanguageK LanguageKind::Js => JS_KINDS_SRC, LanguageKind::Css => CSS_KINDS_SRC, LanguageKind::Json => JSON_KINDS_SRC, + LanguageKind::Html => HTML_KINDS_SRC, }; let ast_nodes_file = syntax_generated_path.join("nodes.rs"); @@ -182,6 +186,12 @@ pub(crate) fn load_json_ast() -> AstSrc { make_ast(&grammar) } +pub(crate) fn load_html_ast() -> AstSrc { + let grammar_src = include_str!("../html.ungram"); + let grammar: Grammar = grammar_src.parse().unwrap(); + make_ast(&grammar) +} + pub(crate) fn append_css_property_value_implied_alternatives(variants: Vec) -> Vec { let mut cloned = variants.clone(); if !cloned.iter().any(|v| v == "CssWideKeyword") { diff --git a/xtask/codegen/src/css_kinds_src.rs b/xtask/codegen/src/css_kinds_src.rs index 7828ef07eaa9..973577f64568 100644 --- a/xtask/codegen/src/css_kinds_src.rs +++ b/xtask/codegen/src/css_kinds_src.rs @@ -1,4 +1,4 @@ -use crate::kinds_src::KindsSrc; +use crate::kind_src::KindsSrc; pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { punct: &[ diff --git a/xtask/codegen/src/formatter.rs b/xtask/codegen/src/formatter.rs index 3b29c22795bc..b5adc3973df9 100644 --- a/xtask/codegen/src/formatter.rs +++ b/xtask/codegen/src/formatter.rs @@ -780,6 +780,11 @@ fn get_node_concept( } _ => NodeConcept::Auxiliary, }, + + LanguageKind::Html => match name { + _ if name.ends_with("Value") => NodeConcept::Value, + _ => NodeConcept::Auxiliary, + }, } } } @@ -841,6 +846,7 @@ impl LanguageKind { LanguageKind::Js => "JsFormatter", LanguageKind::Css => "CssFormatter", LanguageKind::Json => "JsonFormatter", + LanguageKind::Html => "HtmlFormatter", }; Ident::new(name, Span::call_site()) @@ -851,6 +857,7 @@ impl LanguageKind { LanguageKind::Js => "JsFormatContext", LanguageKind::Css => "CssFormatContext", LanguageKind::Json => "JsonFormatContext", + LanguageKind::Html => "HtmlFormatContext", }; Ident::new(name, Span::call_site()) diff --git a/xtask/codegen/src/generate_macros.rs b/xtask/codegen/src/generate_macros.rs index 199807b5e1cc..87a3668c74b5 100644 --- a/xtask/codegen/src/generate_macros.rs +++ b/xtask/codegen/src/generate_macros.rs @@ -1,4 +1,4 @@ -use super::kinds_src::AstSrc; +use super::js_kinds_src::AstSrc; use crate::{to_upper_snake_case, LanguageKind, Result}; use quote::{format_ident, quote}; diff --git a/xtask/codegen/src/generate_node_factory.rs b/xtask/codegen/src/generate_node_factory.rs index c8608520e478..e0a4a2a43cf1 100644 --- a/xtask/codegen/src/generate_node_factory.rs +++ b/xtask/codegen/src/generate_node_factory.rs @@ -1,6 +1,6 @@ -use super::kinds_src::AstSrc; +use super::js_kinds_src::AstSrc; use crate::to_lower_snake_case; -use crate::{kinds_src::Field, to_upper_snake_case, LanguageKind}; +use crate::{js_kinds_src::Field, to_upper_snake_case, LanguageKind}; use quote::{format_ident, quote}; use xtask::Result; diff --git a/xtask/codegen/src/generate_nodes.rs b/xtask/codegen/src/generate_nodes.rs index 0b12e5c2bf9b..e29589e888ef 100644 --- a/xtask/codegen/src/generate_nodes.rs +++ b/xtask/codegen/src/generate_nodes.rs @@ -1,6 +1,7 @@ use crate::css_kinds_src::CSS_KINDS_SRC; +use crate::html_kinds_src::HTML_KINDS_SRC; +use crate::js_kinds_src::{AstNodeSrc, AstSrc, Field, TokenKind, JS_KINDS_SRC}; use crate::json_kinds_src::JSON_KINDS_SRC; -use crate::kinds_src::{AstNodeSrc, AstSrc, Field, TokenKind, JS_KINDS_SRC}; use crate::{to_lower_snake_case, to_upper_snake_case, LanguageKind}; use proc_macro2::{Literal, TokenStream}; use quote::{format_ident, quote}; @@ -947,6 +948,7 @@ pub(crate) fn token_kind_to_code( LanguageKind::Js => JS_KINDS_SRC, LanguageKind::Css => CSS_KINDS_SRC, LanguageKind::Json => JSON_KINDS_SRC, + LanguageKind::Html => HTML_KINDS_SRC, }; if kind_source.literals.contains(&kind_variant_name.as_str()) || kind_source.tokens.contains(&kind_variant_name.as_str()) diff --git a/xtask/codegen/src/generate_nodes_mut.rs b/xtask/codegen/src/generate_nodes_mut.rs index c85937c95cfe..e096454bc366 100644 --- a/xtask/codegen/src/generate_nodes_mut.rs +++ b/xtask/codegen/src/generate_nodes_mut.rs @@ -1,4 +1,4 @@ -use crate::kinds_src::{AstSrc, Field}; +use crate::js_kinds_src::{AstSrc, Field}; use crate::LanguageKind; use quote::{format_ident, quote}; use xtask::Result; diff --git a/xtask/codegen/src/generate_syntax_factory.rs b/xtask/codegen/src/generate_syntax_factory.rs index 27e964c12e7c..008c21066154 100644 --- a/xtask/codegen/src/generate_syntax_factory.rs +++ b/xtask/codegen/src/generate_syntax_factory.rs @@ -1,4 +1,4 @@ -use super::kinds_src::AstSrc; +use super::js_kinds_src::AstSrc; use crate::generate_nodes::{get_field_predicate, group_fields_for_ordering, token_kind_to_code}; use crate::{to_upper_snake_case, LanguageKind}; use proc_macro2::TokenStream; @@ -22,6 +22,11 @@ pub fn generate_syntax_factory(ast: &AstSrc, language_kind: LanguageKind) -> Res quote! { JsonSyntaxKind }, quote! { JsonSyntaxFactory }, ), + LanguageKind::Html => ( + quote! { biome_html_syntax }, + quote! { HtmlSyntaxKind }, + quote! { HtmlSyntaxFactory }, + ), }; let normal_node_arms = ast.nodes.iter().map(|node| { let kind = format_ident!("{}", to_upper_snake_case(&node.name)); diff --git a/xtask/codegen/src/generate_syntax_kinds.rs b/xtask/codegen/src/generate_syntax_kinds.rs index dc61989e1417..9a956eb7392b 100644 --- a/xtask/codegen/src/generate_syntax_kinds.rs +++ b/xtask/codegen/src/generate_syntax_kinds.rs @@ -1,9 +1,8 @@ +use crate::kind_src::KindsSrc; use crate::{to_upper_snake_case, LanguageKind, Result}; use proc_macro2::{Literal, Punct, Spacing}; use quote::{format_ident, quote}; -use super::kinds_src::KindsSrc; - pub fn generate_syntax_kinds(grammar: KindsSrc, language_kind: LanguageKind) -> Result { let syntax_kind = language_kind.syntax_kind(); let punctuation_values = grammar.punct.iter().map(|(token, _name)| { @@ -123,6 +122,19 @@ pub fn generate_syntax_kinds(grammar: KindsSrc, language_kind: LanguageKind) -> } } } + LanguageKind::Html => { + quote! { + pub const fn to_string(&self) -> Option<&'static str> { + let tok = match self { + #(#punctuation => #punctuation_strings,)* + #(#full_keywords => #all_keyword_to_strings,)* + HTML_STRING_LITERAL => "string literal", + _ => return None, + }; + Some(tok) + } + } + } }; let ast = quote! { diff --git a/xtask/codegen/src/html_kinds_src.rs b/xtask/codegen/src/html_kinds_src.rs new file mode 100644 index 000000000000..e1bbc332a6e7 --- /dev/null +++ b/xtask/codegen/src/html_kinds_src.rs @@ -0,0 +1,38 @@ +use crate::kind_src::KindsSrc; + +pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { + punct: &[ + ("<", "L_ANGLE"), + (">", "R_ANGLE"), + ("/", "SLASH"), + ("=", "EQ"), + ("!", "BANG"), + ], + keywords: &["null", "true", "false"], + literals: &["HTML_STRING_LITERAL"], + tokens: &[ + "ERROR_TOKEN", + "NEWLINE", + "WHITESPACE", + "IDENT", + "COMMENT", + "HTML_IDENT", + ], + nodes: &[ + "HTML_ROOT", + "HTML_DIRECTIVE", + "HTML_SELF_CLOSING_TAG", + "HTML_ELEMENT", + "HTML_OPENING_ELEMENT", + "HTML_CLOSING_ELEMENT", + "HTML_SELF_CLOSING_ELEMENT", + "HTML_ATTRIBUTE", + "HTML_ATTRIBUTE_INITIALIZER_CLAUSE", + "HTML_STRING", + "HTML_NAME", + "HTML_ELEMENT_LIST", + "HTML_ATTRIBUTE_LIST", + // Bogus nodes + "HTML_BOGUS", + ], +}; diff --git a/xtask/codegen/src/kinds_src.rs b/xtask/codegen/src/js_kinds_src.rs similarity index 98% rename from xtask/codegen/src/kinds_src.rs rename to xtask/codegen/src/js_kinds_src.rs index 62197be5a024..901c8279decd 100644 --- a/xtask/codegen/src/kinds_src.rs +++ b/xtask/codegen/src/js_kinds_src.rs @@ -2,21 +2,13 @@ //! Based on the rust analyzer parser and ast definitions use crate::css_kinds_src::CSS_KINDS_SRC; +use crate::html_kinds_src::HTML_KINDS_SRC; use crate::json_kinds_src::JSON_KINDS_SRC; +use crate::kind_src::{KindsSrc, LANGUAGE_PREFIXES}; use crate::LanguageKind; use quote::format_ident; use std::collections::BTreeMap; -const LANGUAGE_PREFIXES: [&str; 6] = ["js_", "ts_", "jsx_", "tsx_", "css_", "json_"]; - -pub struct KindsSrc<'a> { - pub punct: &'a [(&'a str, &'a str)], - pub keywords: &'a [&'a str], - pub literals: &'a [&'a str], - pub tokens: &'a [&'a str], - pub nodes: &'a [&'a str], -} - pub const JS_KINDS_SRC: KindsSrc = KindsSrc { punct: &[ (";", "SEMICOLON"), @@ -699,6 +691,7 @@ impl Field { LanguageKind::Js => JS_KINDS_SRC, LanguageKind::Css => CSS_KINDS_SRC, LanguageKind::Json => JSON_KINDS_SRC, + LanguageKind::Html => HTML_KINDS_SRC, }; // we need to replace "-" with "_" for the keywords diff --git a/xtask/codegen/src/json_kinds_src.rs b/xtask/codegen/src/json_kinds_src.rs index 99a9384f8d98..26fa02a5afd6 100644 --- a/xtask/codegen/src/json_kinds_src.rs +++ b/xtask/codegen/src/json_kinds_src.rs @@ -1,4 +1,4 @@ -use crate::kinds_src::KindsSrc; +use crate::kind_src::KindsSrc; pub const JSON_KINDS_SRC: KindsSrc = KindsSrc { punct: &[ diff --git a/xtask/codegen/src/kind_src.rs b/xtask/codegen/src/kind_src.rs new file mode 100644 index 000000000000..45e6227e2374 --- /dev/null +++ b/xtask/codegen/src/kind_src.rs @@ -0,0 +1,9 @@ +pub const LANGUAGE_PREFIXES: [&str; 7] = ["js_", "ts_", "jsx_", "tsx_", "css_", "json_", "html_"]; + +pub struct KindsSrc<'a> { + pub punct: &'a [(&'a str, &'a str)], + pub keywords: &'a [&'a str], + pub literals: &'a [&'a str], + pub tokens: &'a [&'a str], + pub nodes: &'a [&'a str], +} diff --git a/xtask/codegen/src/lib.rs b/xtask/codegen/src/lib.rs index c99dc29b78cf..d51f0aa0d6c5 100644 --- a/xtask/codegen/src/lib.rs +++ b/xtask/codegen/src/lib.rs @@ -11,8 +11,11 @@ mod generate_nodes; mod generate_nodes_mut; mod generate_syntax_factory; mod generate_syntax_kinds; +mod js_kinds_src; mod json_kinds_src; -mod kinds_src; + +mod html_kinds_src; +mod kind_src; mod parser_tests; pub mod promote_rule; mod termcolorful; @@ -41,6 +44,7 @@ pub enum LanguageKind { Js, Css, Json, + Html, } impl std::fmt::Display for LanguageKind { @@ -49,12 +53,17 @@ impl std::fmt::Display for LanguageKind { LanguageKind::Js => write!(f, "js"), LanguageKind::Css => write!(f, "css"), LanguageKind::Json => write!(f, "json"), + LanguageKind::Html => write!(f, "html"), } } } -pub const ALL_LANGUAGE_KIND: [LanguageKind; 3] = - [LanguageKind::Js, LanguageKind::Css, LanguageKind::Json]; +pub const ALL_LANGUAGE_KIND: [LanguageKind; 4] = [ + LanguageKind::Js, + LanguageKind::Css, + LanguageKind::Json, + LanguageKind::Html, +]; impl FromStr for LanguageKind { type Err = String; @@ -64,6 +73,7 @@ impl FromStr for LanguageKind { "js" => Ok(LanguageKind::Js), "css" => Ok(LanguageKind::Css), "json" => Ok(LanguageKind::Json), + "html" => Ok(LanguageKind::Html), _ => Err(format!( "Language {} not supported, please use: `js`, `css` or `json`", kind @@ -82,6 +92,7 @@ impl LanguageKind { LanguageKind::Js => quote! { JsSyntaxKind }, LanguageKind::Css => quote! { CssSyntaxKind }, LanguageKind::Json => quote! { JsonSyntaxKind }, + LanguageKind::Html => quote! { HtmlSyntaxKind }, } } @@ -90,6 +101,7 @@ impl LanguageKind { LanguageKind::Js => quote! { JsSyntaxNode }, LanguageKind::Css => quote! { CssSyntaxNode }, LanguageKind::Json => quote! { JsonSyntaxNode }, + LanguageKind::Html => quote! { HtmlSyntaxNode }, } } @@ -98,6 +110,7 @@ impl LanguageKind { LanguageKind::Js => quote! { JsSyntaxElement }, LanguageKind::Css => quote! { CssSyntaxElement }, LanguageKind::Json => quote! { JsonSyntaxElement }, + LanguageKind::Html => quote! { HtmlSyntaxElement }, } } @@ -106,6 +119,7 @@ impl LanguageKind { LanguageKind::Js => quote! { JsSyntaxToken }, LanguageKind::Css => quote! { CssSyntaxToken }, LanguageKind::Json => quote! { JsonSyntaxToken }, + LanguageKind::Html => quote! { HtmlSyntaxToken }, } } @@ -114,6 +128,7 @@ impl LanguageKind { LanguageKind::Js => quote! { JsSyntaxElementChildren }, LanguageKind::Css => quote! { CssSyntaxElementChildren }, LanguageKind::Json => quote! { JsonSyntaxElementChildren }, + LanguageKind::Html => quote! { HtmlSyntaxElementChildren }, } } @@ -122,6 +137,7 @@ impl LanguageKind { LanguageKind::Js => quote! { JsSyntaxList }, LanguageKind::Css => quote! { CssSyntaxList }, LanguageKind::Json => quote! { JsonSyntaxList }, + LanguageKind::Html => quote! { HtmlSyntaxList }, } } @@ -130,6 +146,7 @@ impl LanguageKind { LanguageKind::Js => quote! { JsLanguage }, LanguageKind::Css => quote! { CssLanguage }, LanguageKind::Json => quote! { JsonLanguage }, + LanguageKind::Html => quote! { HtmlLanguage }, } } @@ -138,6 +155,7 @@ impl LanguageKind { LanguageKind::Js => "biome_js_formatter", LanguageKind::Css => "biome_css_formatter", LanguageKind::Json => "biome_json_formatter", + LanguageKind::Html => "biome_html_formatter", } } @@ -146,6 +164,7 @@ impl LanguageKind { LanguageKind::Js => "biome_js_syntax", LanguageKind::Css => "biome_css_syntax", LanguageKind::Json => "biome_json_syntax", + LanguageKind::Html => "biome_html_syntax", } } @@ -154,6 +173,7 @@ impl LanguageKind { LanguageKind::Js => "biome_js_factory", LanguageKind::Css => "biome_css_factory", LanguageKind::Json => "biome_json_factory", + LanguageKind::Html => "biome_html_factory", } } } From e78e927f30ba806fbf7415e7cffdefc06aacf6e4 Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Fri, 12 Jan 2024 23:26:10 +0200 Subject: [PATCH 32/82] Refactor CSS Parser to rename CssRule to CssQualifiedRule (#1546) --- .../src/generated/node_factory.rs | 21 +- .../src/generated/syntax_factory.rs | 49 ++- .../biome_css_formatter/src/css/any/rule.rs | 2 +- .../src/css/auxiliary/mod.rs | 2 +- .../auxiliary/{rule.rs => qualified_rule.rs} | 11 +- crates/biome_css_formatter/src/generated.rs | 36 +- crates/biome_css_parser/src/syntax/mod.rs | 14 +- .../src/syntax/parse_error.rs | 2 +- .../at_rule/at_rule_container_error.css.snap | 8 +- .../at_rule/at_rule_font_face_error.css.snap | 4 +- .../error/at_rule/at_rule_keyframes.css.snap | 12 +- .../at_rule/at_rule_keyframes_1.css.snap | 8 +- .../error/css_unfinished_block.css.snap | 4 +- .../property/property_generic_error.css.snap | 4 +- .../selector/attribute_selector.css.snap | 12 +- .../selector/class_selector_err.css.snap | 4 +- .../error/selector/id_selector_err.css.snap | 4 +- .../error/selector/invalid_selector.css.snap | 8 +- .../selector/missing_identifier.css.snap | 4 +- ..._class_function_compound_selector.css.snap | 28 +- ...s_function_compound_selector_list.css.snap | 32 +- .../pseudo_class_function_identifier.css.snap | 40 +- .../pseudo_class_function_nth.css.snap | 68 +-- ...s_function_relative_selector_list.css.snap | 32 +- .../pseudo_class_function_selector.css.snap | 20 +- ...eudo_class_function_selector_list.css.snap | 32 +- .../pseudo_class_function_value_list.css.snap | 24 +- .../pseudo_class_identifier.css.snap | 12 +- .../error/selector/pseudo_element.css.snap | 20 +- ...seudo_element_function_identifier.css.snap | 8 +- .../pseudo_element_function_selector.css.snap | 20 +- .../error/selector/traling_comma.css.snap | 4 +- .../at_rule_container_complex.css.snap | 8 +- .../ok/at_rule/at_rule_document.css.snap | 12 +- .../ok/at_rule/at_rule_layer.css.snap | 40 +- .../ok/at_rule/at_rule_media_complex.css.snap | 8 +- .../ok/at_rule/at_rule_scope.css.snap | 32 +- .../at_rule/at_rule_starting_style.css.snap | 8 +- .../ok/at_rule/at_rule_supports.css.snap | 92 ++-- .../css_test_suite/ok/class_selector.css.snap | 4 +- .../ok/declaration_list.css.snap | 44 +- .../css_test_suite/ok/dimension.css.snap | 8 +- .../css_test_suite/ok/function/calc.css.snap | 96 ++--- .../ok/function/linear-gradient.css.snap | 4 +- .../ok/function/unknow.css.snap | 4 +- .../css_test_suite/ok/function/url.css.snap | 4 +- .../css_test_suite/ok/function/var.css.snap | 4 +- .../ok/property/property_all.css.snap | 4 +- .../ok/property/property_border.css.snap | 4 +- .../ok/property/property_generic.css.snap | 4 +- .../ok/property/property_z-index.css.snap | 4 +- .../ok/selector/attribute.css.snap | 192 ++++----- .../css_test_suite/ok/selector/class.css.snap | 4 +- .../ok/selector/complex.css.snap | 48 +-- .../css_test_suite/ok/selector/id.css.snap | 4 +- .../pseudo_class_an_plus_b.css.snap | 396 +++++++++--------- .../pseudo_class/pseudo_class_any.css.snap | 44 +- .../pseudo_class/pseudo_class_basic.css.snap | 76 ++-- .../pseudo_class_current.css.snap | 8 +- .../pseudo_class/pseudo_class_dir.css.snap | 24 +- .../pseudo_class/pseudo_class_future.css.snap | 8 +- .../pseudo_class/pseudo_class_has.css.snap | 44 +- .../pseudo_class/pseudo_class_host.css.snap | 20 +- .../pseudo_class_host_context.css.snap | 12 +- .../pseudo_class/pseudo_class_ident.css.snap | 32 +- .../pseudo_class/pseudo_class_is.css.snap | 64 +-- .../pseudo_class/pseudo_class_lang.css.snap | 32 +- .../pseudo_class_matches.css.snap | 8 +- .../pseudo_class/pseudo_class_module.css.snap | 12 +- .../pseudo_class/pseudo_class_not.css.snap | 96 ++--- .../pseudo_class/pseudo_class_past.css.snap | 8 +- .../pseudo_class/pseudo_class_where.css.snap | 44 +- .../ok/selector/pseudo_element/basic.css.snap | 76 ++-- .../pseudo_element/cue-region.css.snap | 12 +- .../ok/selector/pseudo_element/cue.css.snap | 24 +- .../selector/pseudo_element/escaped.css.snap | 4 +- .../pseudo_element/highlight.css.snap | 12 +- .../ok/selector/pseudo_element/part.css.snap | 8 +- .../pseudo_element/presudo_complex.css.snap | 24 +- .../selector/pseudo_element/slotted.css.snap | 16 +- .../ok/selector/subselector.css.snap | 16 +- .../css_test_suite/ok/selector/type.css.snap | 16 +- .../ok/selector/universal.css.snap | 12 +- .../ok/values/url_value.css.snap | 4 +- crates/biome_css_syntax/src/generated/kind.rs | 2 +- .../biome_css_syntax/src/generated/macros.rs | 8 +- .../biome_css_syntax/src/generated/nodes.rs | 196 ++++----- .../src/generated/nodes_mut.rs | 28 +- crates/biome_unicode_table/src/tables.rs | 8 +- xtask/codegen/css.ungram | 4 +- xtask/codegen/src/css_kinds_src.rs | 2 +- 91 files changed, 1300 insertions(+), 1281 deletions(-) rename crates/biome_css_formatter/src/css/auxiliary/{rule.rs => qualified_rule.rs} (55%) diff --git a/crates/biome_css_factory/src/generated/node_factory.rs b/crates/biome_css_factory/src/generated/node_factory.rs index 4ec0a158057f..2bc2566c3cc6 100644 --- a/crates/biome_css_factory/src/generated/node_factory.rs +++ b/crates/biome_css_factory/src/generated/node_factory.rs @@ -1508,6 +1508,18 @@ pub fn css_pseudo_element_selector( ], )) } +pub fn css_qualified_rule( + prelude: CssSelectorList, + block: AnyCssDeclarationListBlock, +) -> CssQualifiedRule { + CssQualifiedRule::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_QUALIFIED_RULE, + [ + Some(SyntaxElement::Node(prelude.into_syntax())), + Some(SyntaxElement::Node(block.into_syntax())), + ], + )) +} pub fn css_query_feature_boolean(name: CssIdentifier) -> CssQueryFeatureBoolean { CssQueryFeatureBoolean::unwrap_cast(SyntaxNode::new_detached( CssSyntaxKind::CSS_QUERY_FEATURE_BOOLEAN, @@ -1662,15 +1674,6 @@ impl CssRootBuilder { )) } } -pub fn css_rule(prelude: CssSelectorList, block: AnyCssDeclarationListBlock) -> CssRule { - CssRule::unwrap_cast(SyntaxNode::new_detached( - CssSyntaxKind::CSS_RULE, - [ - Some(SyntaxElement::Node(prelude.into_syntax())), - Some(SyntaxElement::Node(block.into_syntax())), - ], - )) -} pub fn css_rule_list_block( l_curly_token: SyntaxToken, rules: CssRuleList, diff --git a/crates/biome_css_factory/src/generated/syntax_factory.rs b/crates/biome_css_factory/src/generated/syntax_factory.rs index bebd68d45115..f45a2c70f5c5 100644 --- a/crates/biome_css_factory/src/generated/syntax_factory.rs +++ b/crates/biome_css_factory/src/generated/syntax_factory.rs @@ -3060,6 +3060,32 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(CSS_PSEUDO_ELEMENT_SELECTOR, children) } + CSS_QUALIFIED_RULE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if CssSelectorList::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if AnyCssDeclarationListBlock::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_QUALIFIED_RULE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_QUALIFIED_RULE, children) + } CSS_QUERY_FEATURE_BOOLEAN => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<1usize> = RawNodeSlots::default(); @@ -3362,29 +3388,6 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(CSS_ROOT, children) } - CSS_RULE => { - let mut elements = (&children).into_iter(); - let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); - let mut current_element = elements.next(); - if let Some(element) = ¤t_element { - if CssSelectorList::can_cast(element.kind()) { - slots.mark_present(); - current_element = elements.next(); - } - } - slots.next_slot(); - if let Some(element) = ¤t_element { - if AnyCssDeclarationListBlock::can_cast(element.kind()) { - slots.mark_present(); - current_element = elements.next(); - } - } - slots.next_slot(); - if current_element.is_some() { - return RawSyntaxNode::new(CSS_RULE.to_bogus(), children.into_iter().map(Some)); - } - slots.into_node(CSS_RULE, children) - } CSS_RULE_LIST_BLOCK => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default(); diff --git a/crates/biome_css_formatter/src/css/any/rule.rs b/crates/biome_css_formatter/src/css/any/rule.rs index 0be82e00fc37..0f058c2bad47 100644 --- a/crates/biome_css_formatter/src/css/any/rule.rs +++ b/crates/biome_css_formatter/src/css/any/rule.rs @@ -8,7 +8,7 @@ impl FormatRule for FormatAnyCssRule { type Context = CssFormatContext; fn fmt(&self, node: &AnyCssRule, f: &mut CssFormatter) -> FormatResult<()> { match node { - AnyCssRule::CssRule(node) => node.format().fmt(f), + AnyCssRule::CssQualifiedRule(node) => node.format().fmt(f), AnyCssRule::CssAtRule(node) => node.format().fmt(f), AnyCssRule::CssBogusRule(node) => node.format().fmt(f), } diff --git a/crates/biome_css_formatter/src/css/auxiliary/mod.rs b/crates/biome_css_formatter/src/css/auxiliary/mod.rs index 3b917003873f..09ea6fbd98fe 100644 --- a/crates/biome_css_formatter/src/css/auxiliary/mod.rs +++ b/crates/biome_css_formatter/src/css/auxiliary/mod.rs @@ -48,6 +48,7 @@ pub(crate) mod nth_offset; pub(crate) mod page_at_rule_block; pub(crate) mod parameter; pub(crate) mod parenthesized_expression; +pub(crate) mod qualified_rule; pub(crate) mod query_feature_boolean; pub(crate) mod query_feature_plain; pub(crate) mod query_feature_range; @@ -55,7 +56,6 @@ pub(crate) mod query_feature_range_comparison; pub(crate) mod query_feature_range_interval; pub(crate) mod query_feature_reverse_range; pub(crate) mod root; -pub(crate) mod rule; pub(crate) mod rule_list_block; pub(crate) mod scope_edge; pub(crate) mod scope_range_end; diff --git a/crates/biome_css_formatter/src/css/auxiliary/rule.rs b/crates/biome_css_formatter/src/css/auxiliary/qualified_rule.rs similarity index 55% rename from crates/biome_css_formatter/src/css/auxiliary/rule.rs rename to crates/biome_css_formatter/src/css/auxiliary/qualified_rule.rs index d13d03468c71..5bf62ecbca0f 100644 --- a/crates/biome_css_formatter/src/css/auxiliary/rule.rs +++ b/crates/biome_css_formatter/src/css/auxiliary/qualified_rule.rs @@ -1,11 +1,12 @@ use crate::prelude::*; -use biome_css_syntax::{CssRule, CssRuleFields}; +use biome_css_syntax::{CssQualifiedRule, CssQualifiedRuleFields}; use biome_formatter::write; + #[derive(Debug, Clone, Default)] -pub(crate) struct FormatCssRule; -impl FormatNodeRule for FormatCssRule { - fn fmt_fields(&self, node: &CssRule, f: &mut CssFormatter) -> FormatResult<()> { - let CssRuleFields { prelude, block } = node.as_fields(); +pub(crate) struct FormatCssQualifiedRule; +impl FormatNodeRule for FormatCssQualifiedRule { + fn fmt_fields(&self, node: &CssQualifiedRule, f: &mut CssFormatter) -> FormatResult<()> { + let CssQualifiedRuleFields { prelude, block } = node.as_fields(); write!( f, diff --git a/crates/biome_css_formatter/src/generated.rs b/crates/biome_css_formatter/src/generated.rs index 96fedf619d21..956a5e4b4b28 100644 --- a/crates/biome_css_formatter/src/generated.rs +++ b/crates/biome_css_formatter/src/generated.rs @@ -30,30 +30,44 @@ impl IntoFormat for biome_css_syntax::CssRoot { FormatOwnedWithRule::new(self, crate::css::auxiliary::root::FormatCssRoot::default()) } } -impl FormatRule for crate::css::auxiliary::rule::FormatCssRule { +impl FormatRule + for crate::css::auxiliary::qualified_rule::FormatCssQualifiedRule +{ type Context = CssFormatContext; #[inline(always)] - fn fmt(&self, node: &biome_css_syntax::CssRule, f: &mut CssFormatter) -> FormatResult<()> { - FormatNodeRule::::fmt(self, node, f) + fn fmt( + &self, + node: &biome_css_syntax::CssQualifiedRule, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) } } -impl AsFormat for biome_css_syntax::CssRule { +impl AsFormat for biome_css_syntax::CssQualifiedRule { type Format<'a> = FormatRefWithRule< 'a, - biome_css_syntax::CssRule, - crate::css::auxiliary::rule::FormatCssRule, + biome_css_syntax::CssQualifiedRule, + crate::css::auxiliary::qualified_rule::FormatCssQualifiedRule, >; fn format(&self) -> Self::Format<'_> { #![allow(clippy::default_constructed_unit_structs)] - FormatRefWithRule::new(self, crate::css::auxiliary::rule::FormatCssRule::default()) + FormatRefWithRule::new( + self, + crate::css::auxiliary::qualified_rule::FormatCssQualifiedRule::default(), + ) } } -impl IntoFormat for biome_css_syntax::CssRule { - type Format = - FormatOwnedWithRule; +impl IntoFormat for biome_css_syntax::CssQualifiedRule { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssQualifiedRule, + crate::css::auxiliary::qualified_rule::FormatCssQualifiedRule, + >; fn into_format(self) -> Self::Format { #![allow(clippy::default_constructed_unit_structs)] - FormatOwnedWithRule::new(self, crate::css::auxiliary::rule::FormatCssRule::default()) + FormatOwnedWithRule::new( + self, + crate::css::auxiliary::qualified_rule::FormatCssQualifiedRule::default(), + ) } } impl FormatRule for crate::css::statements::at_rule::FormatCssAtRule { diff --git a/crates/biome_css_parser/src/syntax/mod.rs b/crates/biome_css_parser/src/syntax/mod.rs index 9ecc4e51630f..d058bc5e5805 100644 --- a/crates/biome_css_parser/src/syntax/mod.rs +++ b/crates/biome_css_parser/src/syntax/mod.rs @@ -70,7 +70,7 @@ impl ParseRecovery for RuleListParseRecovery { const RECOVERED_KIND: Self::Kind = CSS_BOGUS_RULE; fn is_at_recovered(&self, p: &mut Self::Parser<'_>) -> bool { - is_at_at_rule(p) || is_at_rule(p) + is_at_at_rule(p) || is_at_qualified_rule(p) } } @@ -82,8 +82,8 @@ impl ParseNodeList for RuleList { fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax { if is_at_at_rule(p) { parse_at_rule(p) - } else if is_at_rule(p) { - parse_rule(p) + } else if is_at_qualified_rule(p) { + parse_qualified_rule(p) } else { Absent } @@ -103,13 +103,13 @@ impl ParseNodeList for RuleList { } #[inline] -pub(crate) fn is_at_rule(p: &mut CssParser) -> bool { +pub(crate) fn is_at_qualified_rule(p: &mut CssParser) -> bool { is_at_selector(p) } #[inline] -pub(crate) fn parse_rule(p: &mut CssParser) -> ParsedSyntax { - if !is_at_rule(p) { +pub(crate) fn parse_qualified_rule(p: &mut CssParser) -> ParsedSyntax { + if !is_at_qualified_rule(p) { return Absent; } @@ -118,7 +118,7 @@ pub(crate) fn parse_rule(p: &mut CssParser) -> ParsedSyntax { SelectorList::default().parse_list(p); let kind = if parse_or_recover_declaration_list_block(p).is_ok() { - CSS_RULE + CSS_QUALIFIED_RULE } else { CSS_BOGUS_RULE }; diff --git a/crates/biome_css_parser/src/syntax/parse_error.rs b/crates/biome_css_parser/src/syntax/parse_error.rs index 02ad7ae38244..31f101ac0d5b 100644 --- a/crates/biome_css_parser/src/syntax/parse_error.rs +++ b/crates/biome_css_parser/src/syntax/parse_error.rs @@ -73,7 +73,7 @@ pub(crate) fn expected_selector(p: &CssParser, range: TextRange) -> ParseDiagnos } pub(crate) fn expected_any_rule(p: &CssParser, range: TextRange) -> ParseDiagnostic { - expected_any(&["rule", "at rule"], range, p) + expected_any(&["qualified rule", "at rule"], range, p) } pub(crate) fn expected_any_declaration_or_at_rule( diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container_error.css.snap index 43b39febb7e4..468cbaa4a03f 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container_error.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container_error.css.snap @@ -220,7 +220,7 @@ CssRoot { ], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -263,7 +263,7 @@ CssRoot { ], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -612,7 +612,7 @@ CssRoot { 0: CONTAINER_KW@203..213 "container" [] [Whitespace(" ")] 1: CSS_BOGUS@213..221 0: INHERIT_KW@213..221 "inherit" [] [Whitespace(" ")] - 6: CSS_RULE@221..240 + 6: CSS_QUALIFIED_RULE@221..240 0: CSS_SELECTOR_LIST@221..237 0: CSS_COMPLEX_SELECTOR@221..227 0: CSS_COMPOUND_SELECTOR@221..226 @@ -640,7 +640,7 @@ CssRoot { 0: CONTAINER_KW@243..253 "container" [] [Whitespace(" ")] 1: CSS_CUSTOM_IDENTIFIER@253..257 0: IDENT@253..257 "not" [] [Whitespace(" ")] - 8: CSS_RULE@257..277 + 8: CSS_QUALIFIED_RULE@257..277 0: CSS_SELECTOR_LIST@257..273 0: CSS_COMPLEX_SELECTOR@257..263 0: CSS_COMPOUND_SELECTOR@257..262 diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_face_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_face_error.css.snap index f6fe937ab0d6..01ef8aa02fab 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_face_error.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_face_error.css.snap @@ -26,7 +26,7 @@ CssRoot { ], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -101,7 +101,7 @@ CssRoot { 0: AT@0..1 "@" [] [] 1: CSS_BOGUS_AT_RULE@1..11 0: FONT_FACE_KW@1..11 "font-face" [] [Whitespace(" ")] - 1: CSS_RULE@11..17 + 1: CSS_QUALIFIED_RULE@11..17 0: CSS_SELECTOR_LIST@11..15 0: CSS_COMPOUND_SELECTOR@11..15 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes.css.snap index b13a684cdaac..d88b7260906b 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes.css.snap @@ -501,7 +501,7 @@ CssRoot { }, }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1122,7 +1122,7 @@ CssRoot { 0: R_PAREN@446..447 ")" [] [] 1: SEMICOLON@447..448 ";" [] [] 2: R_CURLY@448..451 "}" [Newline("\n"), Whitespace("\t")] [] - 6: CSS_RULE@451..492 + 6: CSS_QUALIFIED_RULE@451..492 0: CSS_SELECTOR_LIST@451..457 0: CSS_COMPOUND_SELECTOR@451..457 0: (empty) @@ -1481,7 +1481,7 @@ at_rule_keyframes.css:42:2 parse ━━━━━━━━━━━━━━━ at_rule_keyframes.css:47:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - × Expected a rule, or an at rule but instead found '}'. + × Expected a qualified rule, or an at rule but instead found '}'. 45 │ transform: translateX(100%); 46 │ } @@ -1490,7 +1490,7 @@ at_rule_keyframes.css:47:1 parse ━━━━━━━━━━━━━━━ 48 │ 49 │ @keyframes slidein { - i Expected a rule, or an at rule here. + i Expected a qualified rule, or an at rule here. 45 │ transform: translateX(100%); 46 │ } @@ -1551,7 +1551,7 @@ at_rule_keyframes.css:56:2 parse ━━━━━━━━━━━━━━━ at_rule_keyframes.css:57:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - × Expected a rule, or an at rule but instead found '}'. + × Expected a qualified rule, or an at rule but instead found '}'. 55 │ transform: translateX(100%); 56 │ } @@ -1560,7 +1560,7 @@ at_rule_keyframes.css:57:1 parse ━━━━━━━━━━━━━━━ 58 │ 59 │ @keyframes slidein { - i Expected a rule, or an at rule here. + i Expected a qualified rule, or an at rule here. 55 │ transform: translateX(100%); 56 │ } diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes_1.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes_1.css.snap index 7aeebbba89ed..d8fe383dbd0b 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes_1.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes_1.css.snap @@ -138,7 +138,7 @@ CssRoot { }, }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -417,7 +417,7 @@ CssRoot { 0: CSS_KEYFRAMES_IDENT_SELECTOR@87..93 0: FROM_KW@87..93 "from" [Newline("\n"), Whitespace("\t")] [] 2: R_CURLY@93..96 "}" [Newline("\n"), Whitespace("\t")] [] - 2: CSS_RULE@96..120 + 2: CSS_QUALIFIED_RULE@96..120 0: CSS_SELECTOR_LIST@96..101 0: CSS_COMPOUND_SELECTOR@96..101 0: (empty) @@ -601,7 +601,7 @@ at_rule_keyframes_1.css:12:2 parse ━━━━━━━━━━━━━━━ at_rule_keyframes_1.css:16:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - × Expected a rule, or an at rule but instead found '}'. + × Expected a qualified rule, or an at rule but instead found '}'. 14 │ color: blue; 15 │ } @@ -610,7 +610,7 @@ at_rule_keyframes_1.css:16:1 parse ━━━━━━━━━━━━━━━ 17 │ 18 │ @keyframes name2 { - i Expected a rule, or an at rule here. + i Expected a qualified rule, or an at rule here. 14 │ color: blue; 15 │ } diff --git a/crates/biome_css_parser/tests/css_test_suite/error/css_unfinished_block.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/css_unfinished_block.css.snap index 7853e8f9acf8..9cc87e525f59 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/css_unfinished_block.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/css_unfinished_block.css.snap @@ -17,7 +17,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -49,7 +49,7 @@ CssRoot { 0: CSS_ROOT@0..10 0: (empty) 1: CSS_RULE_LIST@0..9 - 0: CSS_RULE@0..9 + 0: CSS_QUALIFIED_RULE@0..9 0: CSS_SELECTOR_LIST@0..8 0: CSS_COMPOUND_SELECTOR@0..8 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/property/property_generic_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/property/property_generic_error.css.snap index f3a83237529e..bb419e7d6461 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/property/property_generic_error.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/property/property_generic_error.css.snap @@ -20,7 +20,7 @@ div { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -94,7 +94,7 @@ CssRoot { 0: CSS_ROOT@0..66 0: (empty) 1: CSS_RULE_LIST@0..66 - 0: CSS_RULE@0..66 + 0: CSS_QUALIFIED_RULE@0..66 0: CSS_SELECTOR_LIST@0..4 0: CSS_COMPOUND_SELECTOR@0..4 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/attribute_selector.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/attribute_selector.css.snap index 6c1cdf0a306d..9378520ac75c 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/attribute_selector.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/attribute_selector.css.snap @@ -22,7 +22,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -48,7 +48,7 @@ CssRoot { r_curly_token: R_CURLY@10..11 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -86,7 +86,7 @@ CssRoot { r_curly_token: R_CURLY@30..31 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -162,7 +162,7 @@ CssRoot { 0: CSS_ROOT@0..66 0: (empty) 1: CSS_RULE_LIST@0..65 - 0: CSS_RULE@0..11 + 0: CSS_QUALIFIED_RULE@0..11 0: CSS_SELECTOR_LIST@0..9 0: CSS_COMPOUND_SELECTOR@0..9 0: (empty) @@ -177,7 +177,7 @@ CssRoot { 0: L_CURLY@9..10 "{" [] [] 1: CSS_DECLARATION_LIST@10..10 2: R_CURLY@10..11 "}" [] [] - 1: CSS_RULE@11..31 + 1: CSS_QUALIFIED_RULE@11..31 0: CSS_SELECTOR_LIST@11..29 0: CSS_COMPOUND_SELECTOR@11..29 0: (empty) @@ -200,7 +200,7 @@ CssRoot { 0: L_CURLY@29..30 "{" [] [] 1: CSS_DECLARATION_LIST@30..30 2: R_CURLY@30..31 "}" [] [] - 2: CSS_RULE@31..50 + 2: CSS_QUALIFIED_RULE@31..50 0: CSS_SELECTOR_LIST@31..48 0: CSS_COMPOUND_SELECTOR@31..48 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/class_selector_err.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/class_selector_err.css.snap index 5a9bdeb8b1dd..8ec8abbc5cd6 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/class_selector_err.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/class_selector_err.css.snap @@ -17,7 +17,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -53,7 +53,7 @@ CssRoot { 0: CSS_ROOT@0..12 0: (empty) 1: CSS_RULE_LIST@0..11 - 0: CSS_RULE@0..11 + 0: CSS_QUALIFIED_RULE@0..11 0: CSS_SELECTOR_LIST@0..9 0: CSS_COMPOUND_SELECTOR@0..9 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/id_selector_err.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/id_selector_err.css.snap index e365e757c700..0053d6e90b82 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/id_selector_err.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/id_selector_err.css.snap @@ -17,7 +17,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -53,7 +53,7 @@ CssRoot { 0: CSS_ROOT@0..12 0: (empty) 1: CSS_RULE_LIST@0..11 - 0: CSS_RULE@0..11 + 0: CSS_QUALIFIED_RULE@0..11 0: CSS_SELECTOR_LIST@0..9 0: CSS_COMPOUND_SELECTOR@0..9 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/invalid_selector.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/invalid_selector.css.snap index 8b0f4b7f8114..2f18c8b6976e 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/invalid_selector.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/invalid_selector.css.snap @@ -24,7 +24,7 @@ CssRoot { BANG@0..1 "!" [] [], ], }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -58,7 +58,7 @@ CssRoot { 1: CSS_RULE_LIST@0..15 0: CSS_BOGUS_RULE@0..1 0: BANG@0..1 "!" [] [] - 1: CSS_RULE@1..15 + 1: CSS_QUALIFIED_RULE@1..15 0: CSS_SELECTOR_LIST@1..11 0: CSS_COMPOUND_SELECTOR@1..11 0: (empty) @@ -81,14 +81,14 @@ CssRoot { ``` invalid_selector.css:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - × Expected a rule, or an at rule but instead found '!'. + × Expected a qualified rule, or an at rule but instead found '!'. > 1 │ !.selector { │ ^ 2 │ 3 │ } - i Expected a rule, or an at rule here. + i Expected a qualified rule, or an at rule here. > 1 │ !.selector { │ ^ diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/missing_identifier.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/missing_identifier.css.snap index 4896d634eed4..a03dac4b2f27 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/missing_identifier.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/missing_identifier.css.snap @@ -17,7 +17,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -58,7 +58,7 @@ CssRoot { 0: CSS_ROOT@0..9 0: (empty) 1: CSS_RULE_LIST@0..8 - 0: CSS_RULE@0..8 + 0: CSS_QUALIFIED_RULE@0..8 0: CSS_SELECTOR_LIST@0..6 0: CSS_COMPOUND_SELECTOR@0..2 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_compound_selector.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_compound_selector.css.snap index 20d6b1cbf159..d547698486cb 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_compound_selector.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_compound_selector.css.snap @@ -24,7 +24,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -48,7 +48,7 @@ CssRoot { r_curly_token: R_CURLY@8..9 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -73,7 +73,7 @@ CssRoot { r_curly_token: R_CURLY@19..20 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -117,7 +117,7 @@ CssRoot { r_curly_token: R_CURLY@42..43 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -160,7 +160,7 @@ CssRoot { r_curly_token: R_CURLY@64..65 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -204,7 +204,7 @@ CssRoot { r_curly_token: R_CURLY@86..87 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -247,7 +247,7 @@ CssRoot { r_curly_token: R_CURLY@107..108 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -327,7 +327,7 @@ CssRoot { 0: CSS_ROOT@0..134 0: (empty) 1: CSS_RULE_LIST@0..133 - 0: CSS_RULE@0..9 + 0: CSS_QUALIFIED_RULE@0..9 0: CSS_SELECTOR_LIST@0..7 0: CSS_COMPOUND_SELECTOR@0..7 0: (empty) @@ -342,7 +342,7 @@ CssRoot { 0: L_CURLY@7..8 "{" [] [] 1: CSS_DECLARATION_LIST@8..8 2: R_CURLY@8..9 "}" [] [] - 1: CSS_RULE@9..20 + 1: CSS_QUALIFIED_RULE@9..20 0: CSS_SELECTOR_LIST@9..18 0: CSS_COMPOUND_SELECTOR@9..18 0: (empty) @@ -358,7 +358,7 @@ CssRoot { 0: L_CURLY@18..19 "{" [] [] 1: CSS_DECLARATION_LIST@19..19 2: R_CURLY@19..20 "}" [] [] - 2: CSS_RULE@20..43 + 2: CSS_QUALIFIED_RULE@20..43 0: CSS_SELECTOR_LIST@20..41 0: CSS_COMPOUND_SELECTOR@20..41 0: (empty) @@ -386,7 +386,7 @@ CssRoot { 0: L_CURLY@41..42 "{" [] [] 1: CSS_DECLARATION_LIST@42..42 2: R_CURLY@42..43 "}" [] [] - 3: CSS_RULE@43..65 + 3: CSS_QUALIFIED_RULE@43..65 0: CSS_SELECTOR_LIST@43..63 0: CSS_COMPOUND_SELECTOR@43..63 0: (empty) @@ -413,7 +413,7 @@ CssRoot { 0: L_CURLY@63..64 "{" [] [] 1: CSS_DECLARATION_LIST@64..64 2: R_CURLY@64..65 "}" [] [] - 4: CSS_RULE@65..87 + 4: CSS_QUALIFIED_RULE@65..87 0: CSS_SELECTOR_LIST@65..85 0: CSS_COMPOUND_SELECTOR@65..85 0: (empty) @@ -441,7 +441,7 @@ CssRoot { 0: L_CURLY@85..86 "{" [] [] 1: CSS_DECLARATION_LIST@86..86 2: R_CURLY@86..87 "}" [] [] - 5: CSS_RULE@87..108 + 5: CSS_QUALIFIED_RULE@87..108 0: CSS_SELECTOR_LIST@87..106 0: CSS_COMPOUND_SELECTOR@87..106 0: (empty) @@ -468,7 +468,7 @@ CssRoot { 0: L_CURLY@106..107 "{" [] [] 1: CSS_DECLARATION_LIST@107..107 2: R_CURLY@107..108 "}" [] [] - 6: CSS_RULE@108..122 + 6: CSS_QUALIFIED_RULE@108..122 0: CSS_SELECTOR_LIST@108..120 0: CSS_COMPOUND_SELECTOR@108..120 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_compound_selector_list.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_compound_selector_list.css.snap index a9ed2955ac85..9a55638c1624 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_compound_selector_list.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_compound_selector_list.css.snap @@ -25,7 +25,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -50,7 +50,7 @@ CssRoot { r_curly_token: R_CURLY@8..9 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -76,7 +76,7 @@ CssRoot { r_curly_token: R_CURLY@19..20 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -108,7 +108,7 @@ CssRoot { r_curly_token: R_CURLY@38..39 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -139,7 +139,7 @@ CssRoot { r_curly_token: R_CURLY@56..57 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -185,7 +185,7 @@ CssRoot { r_curly_token: R_CURLY@78..79 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -232,7 +232,7 @@ CssRoot { r_curly_token: R_CURLY@101..102 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -277,7 +277,7 @@ CssRoot { r_curly_token: R_CURLY@122..123 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -361,7 +361,7 @@ CssRoot { 0: CSS_ROOT@0..149 0: (empty) 1: CSS_RULE_LIST@0..148 - 0: CSS_RULE@0..9 + 0: CSS_QUALIFIED_RULE@0..9 0: CSS_SELECTOR_LIST@0..7 0: CSS_COMPOUND_SELECTOR@0..7 0: (empty) @@ -377,7 +377,7 @@ CssRoot { 0: L_CURLY@7..8 "{" [] [] 1: CSS_DECLARATION_LIST@8..8 2: R_CURLY@8..9 "}" [] [] - 1: CSS_RULE@9..20 + 1: CSS_QUALIFIED_RULE@9..20 0: CSS_SELECTOR_LIST@9..18 0: CSS_COMPOUND_SELECTOR@9..18 0: (empty) @@ -394,7 +394,7 @@ CssRoot { 0: L_CURLY@18..19 "{" [] [] 1: CSS_DECLARATION_LIST@19..19 2: R_CURLY@19..20 "}" [] [] - 2: CSS_RULE@20..39 + 2: CSS_QUALIFIED_RULE@20..39 0: CSS_SELECTOR_LIST@20..37 0: CSS_COMPOUND_SELECTOR@20..37 0: (empty) @@ -414,7 +414,7 @@ CssRoot { 0: L_CURLY@37..38 "{" [] [] 1: CSS_DECLARATION_LIST@38..38 2: R_CURLY@38..39 "}" [] [] - 3: CSS_RULE@39..57 + 3: CSS_QUALIFIED_RULE@39..57 0: CSS_SELECTOR_LIST@39..55 0: CSS_COMPOUND_SELECTOR@39..55 0: (empty) @@ -433,7 +433,7 @@ CssRoot { 0: L_CURLY@55..56 "{" [] [] 1: CSS_DECLARATION_LIST@56..56 2: R_CURLY@56..57 "}" [] [] - 4: CSS_RULE@57..79 + 4: CSS_QUALIFIED_RULE@57..79 0: CSS_SELECTOR_LIST@57..77 0: CSS_COMPOUND_SELECTOR@57..77 0: (empty) @@ -462,7 +462,7 @@ CssRoot { 0: L_CURLY@77..78 "{" [] [] 1: CSS_DECLARATION_LIST@78..78 2: R_CURLY@78..79 "}" [] [] - 5: CSS_RULE@79..102 + 5: CSS_QUALIFIED_RULE@79..102 0: CSS_SELECTOR_LIST@79..100 0: CSS_COMPOUND_SELECTOR@79..100 0: (empty) @@ -492,7 +492,7 @@ CssRoot { 0: L_CURLY@100..101 "{" [] [] 1: CSS_DECLARATION_LIST@101..101 2: R_CURLY@101..102 "}" [] [] - 6: CSS_RULE@102..123 + 6: CSS_QUALIFIED_RULE@102..123 0: CSS_SELECTOR_LIST@102..121 0: CSS_COMPOUND_SELECTOR@102..121 0: (empty) @@ -520,7 +520,7 @@ CssRoot { 0: L_CURLY@121..122 "{" [] [] 1: CSS_DECLARATION_LIST@122..122 2: R_CURLY@122..123 "}" [] [] - 7: CSS_RULE@123..137 + 7: CSS_QUALIFIED_RULE@123..137 0: CSS_SELECTOR_LIST@123..135 0: CSS_COMPOUND_SELECTOR@123..135 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_identifier.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_identifier.css.snap index 47f5d92fab2e..69c6ed34982a 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_identifier.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_identifier.css.snap @@ -27,7 +27,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -51,7 +51,7 @@ CssRoot { r_curly_token: R_CURLY@7..8 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -76,7 +76,7 @@ CssRoot { r_curly_token: R_CURLY@17..18 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -103,7 +103,7 @@ CssRoot { r_curly_token: R_CURLY@29..30 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -132,7 +132,7 @@ CssRoot { r_curly_token: R_CURLY@45..46 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -163,7 +163,7 @@ CssRoot { r_curly_token: R_CURLY@63..64 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -193,7 +193,7 @@ CssRoot { r_curly_token: R_CURLY@80..81 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -226,7 +226,7 @@ CssRoot { r_curly_token: R_CURLY@99..100 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -260,7 +260,7 @@ CssRoot { r_curly_token: R_CURLY@119..120 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -291,7 +291,7 @@ CssRoot { r_curly_token: R_CURLY@142..143 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -361,7 +361,7 @@ CssRoot { 0: CSS_ROOT@0..182 0: (empty) 1: CSS_RULE_LIST@0..181 - 0: CSS_RULE@0..8 + 0: CSS_QUALIFIED_RULE@0..8 0: CSS_SELECTOR_LIST@0..6 0: CSS_COMPOUND_SELECTOR@0..6 0: (empty) @@ -376,7 +376,7 @@ CssRoot { 0: L_CURLY@6..7 "{" [] [] 1: CSS_DECLARATION_LIST@7..7 2: R_CURLY@7..8 "}" [] [] - 1: CSS_RULE@8..18 + 1: CSS_QUALIFIED_RULE@8..18 0: CSS_SELECTOR_LIST@8..16 0: CSS_COMPOUND_SELECTOR@8..16 0: (empty) @@ -392,7 +392,7 @@ CssRoot { 0: L_CURLY@16..17 "{" [] [] 1: CSS_DECLARATION_LIST@17..17 2: R_CURLY@17..18 "}" [] [] - 2: CSS_RULE@18..30 + 2: CSS_QUALIFIED_RULE@18..30 0: CSS_SELECTOR_LIST@18..28 0: CSS_COMPOUND_SELECTOR@18..28 0: (empty) @@ -409,7 +409,7 @@ CssRoot { 0: L_CURLY@28..29 "{" [] [] 1: CSS_DECLARATION_LIST@29..29 2: R_CURLY@29..30 "}" [] [] - 3: CSS_RULE@30..46 + 3: CSS_QUALIFIED_RULE@30..46 0: CSS_SELECTOR_LIST@30..44 0: CSS_COMPOUND_SELECTOR@30..44 0: (empty) @@ -426,7 +426,7 @@ CssRoot { 0: L_CURLY@44..45 "{" [] [] 1: CSS_DECLARATION_LIST@45..45 2: R_CURLY@45..46 "}" [] [] - 4: CSS_RULE@46..64 + 4: CSS_QUALIFIED_RULE@46..64 0: CSS_SELECTOR_LIST@46..62 0: CSS_COMPOUND_SELECTOR@46..62 0: (empty) @@ -445,7 +445,7 @@ CssRoot { 0: L_CURLY@62..63 "{" [] [] 1: CSS_DECLARATION_LIST@63..63 2: R_CURLY@63..64 "}" [] [] - 5: CSS_RULE@64..81 + 5: CSS_QUALIFIED_RULE@64..81 0: CSS_SELECTOR_LIST@64..79 0: CSS_COMPOUND_SELECTOR@64..79 0: (empty) @@ -463,7 +463,7 @@ CssRoot { 0: L_CURLY@79..80 "{" [] [] 1: CSS_DECLARATION_LIST@80..80 2: R_CURLY@80..81 "}" [] [] - 6: CSS_RULE@81..100 + 6: CSS_QUALIFIED_RULE@81..100 0: CSS_SELECTOR_LIST@81..98 0: CSS_COMPOUND_SELECTOR@81..98 0: (empty) @@ -483,7 +483,7 @@ CssRoot { 0: L_CURLY@98..99 "{" [] [] 1: CSS_DECLARATION_LIST@99..99 2: R_CURLY@99..100 "}" [] [] - 7: CSS_RULE@100..120 + 7: CSS_QUALIFIED_RULE@100..120 0: CSS_SELECTOR_LIST@100..118 0: CSS_COMPOUND_SELECTOR@100..118 0: (empty) @@ -504,7 +504,7 @@ CssRoot { 0: L_CURLY@118..119 "{" [] [] 1: CSS_DECLARATION_LIST@119..119 2: R_CURLY@119..120 "}" [] [] - 8: CSS_RULE@120..143 + 8: CSS_QUALIFIED_RULE@120..143 0: CSS_SELECTOR_LIST@120..141 0: CSS_COMPOUND_SELECTOR@120..141 0: (empty) @@ -523,7 +523,7 @@ CssRoot { 0: L_CURLY@141..142 "{" [] [] 1: CSS_DECLARATION_LIST@142..142 2: R_CURLY@142..143 "}" [] [] - 9: CSS_RULE@143..167 + 9: CSS_QUALIFIED_RULE@143..167 0: CSS_SELECTOR_LIST@143..165 0: CSS_COMPOUND_SELECTOR@143..165 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_nth.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_nth.css.snap index 794252ba6119..651b3e3c5ad5 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_nth.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_nth.css.snap @@ -33,7 +33,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -63,7 +63,7 @@ CssRoot { r_curly_token: R_CURLY@21..22 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -104,7 +104,7 @@ CssRoot { r_curly_token: R_CURLY@41..42 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -151,7 +151,7 @@ CssRoot { r_curly_token: R_CURLY@67..68 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -197,7 +197,7 @@ CssRoot { r_curly_token: R_CURLY@92..93 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -236,7 +236,7 @@ CssRoot { r_curly_token: R_CURLY@115..116 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -278,7 +278,7 @@ CssRoot { r_curly_token: R_CURLY@144..145 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -318,7 +318,7 @@ CssRoot { r_curly_token: R_CURLY@166..167 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -362,7 +362,7 @@ CssRoot { r_curly_token: R_CURLY@190..191 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -405,7 +405,7 @@ CssRoot { r_curly_token: R_CURLY@213..214 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -442,7 +442,7 @@ CssRoot { r_curly_token: R_CURLY@238..239 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -478,7 +478,7 @@ CssRoot { r_curly_token: R_CURLY@262..263 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -529,7 +529,7 @@ CssRoot { r_curly_token: R_CURLY@291..292 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -581,7 +581,7 @@ CssRoot { r_curly_token: R_CURLY@319..320 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -620,7 +620,7 @@ CssRoot { r_curly_token: R_CURLY@346..347 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -658,7 +658,7 @@ CssRoot { r_curly_token: R_CURLY@372..373 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -697,7 +697,7 @@ CssRoot { r_curly_token: R_CURLY@391..392 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -786,7 +786,7 @@ CssRoot { 0: CSS_ROOT@0..433 0: (empty) 1: CSS_RULE_LIST@0..433 - 0: CSS_RULE@0..22 + 0: CSS_QUALIFIED_RULE@0..22 0: CSS_SELECTOR_LIST@0..20 0: CSS_COMPOUND_SELECTOR@0..20 0: (empty) @@ -804,7 +804,7 @@ CssRoot { 0: L_CURLY@20..21 "{" [] [] 1: CSS_DECLARATION_LIST@21..21 2: R_CURLY@21..22 "}" [] [] - 1: CSS_RULE@22..42 + 1: CSS_QUALIFIED_RULE@22..42 0: CSS_SELECTOR_LIST@22..40 0: CSS_COMPOUND_SELECTOR@22..40 0: (empty) @@ -830,7 +830,7 @@ CssRoot { 0: L_CURLY@40..41 "{" [] [] 1: CSS_DECLARATION_LIST@41..41 2: R_CURLY@41..42 "}" [] [] - 2: CSS_RULE@42..68 + 2: CSS_QUALIFIED_RULE@42..68 0: CSS_SELECTOR_LIST@42..66 0: CSS_COMPOUND_SELECTOR@42..66 0: (empty) @@ -860,7 +860,7 @@ CssRoot { 0: L_CURLY@66..67 "{" [] [] 1: CSS_DECLARATION_LIST@67..67 2: R_CURLY@67..68 "}" [] [] - 3: CSS_RULE@68..93 + 3: CSS_QUALIFIED_RULE@68..93 0: CSS_SELECTOR_LIST@68..91 0: CSS_COMPOUND_SELECTOR@68..91 0: (empty) @@ -889,7 +889,7 @@ CssRoot { 0: L_CURLY@91..92 "{" [] [] 1: CSS_DECLARATION_LIST@92..92 2: R_CURLY@92..93 "}" [] [] - 4: CSS_RULE@93..116 + 4: CSS_QUALIFIED_RULE@93..116 0: CSS_SELECTOR_LIST@93..114 0: CSS_COMPOUND_SELECTOR@93..114 0: (empty) @@ -916,7 +916,7 @@ CssRoot { 0: L_CURLY@114..115 "{" [] [] 1: CSS_DECLARATION_LIST@115..115 2: R_CURLY@115..116 "}" [] [] - 5: CSS_RULE@116..145 + 5: CSS_QUALIFIED_RULE@116..145 0: CSS_SELECTOR_LIST@116..143 0: CSS_COMPOUND_SELECTOR@116..143 0: (empty) @@ -943,7 +943,7 @@ CssRoot { 0: L_CURLY@143..144 "{" [] [] 1: CSS_DECLARATION_LIST@144..144 2: R_CURLY@144..145 "}" [] [] - 6: CSS_RULE@145..167 + 6: CSS_QUALIFIED_RULE@145..167 0: CSS_SELECTOR_LIST@145..165 0: CSS_COMPOUND_SELECTOR@145..165 0: (empty) @@ -969,7 +969,7 @@ CssRoot { 0: L_CURLY@165..166 "{" [] [] 1: CSS_DECLARATION_LIST@166..166 2: R_CURLY@166..167 "}" [] [] - 7: CSS_RULE@167..191 + 7: CSS_QUALIFIED_RULE@167..191 0: CSS_SELECTOR_LIST@167..189 0: CSS_COMPOUND_SELECTOR@167..189 0: (empty) @@ -997,7 +997,7 @@ CssRoot { 0: L_CURLY@189..190 "{" [] [] 1: CSS_DECLARATION_LIST@190..190 2: R_CURLY@190..191 "}" [] [] - 8: CSS_RULE@191..214 + 8: CSS_QUALIFIED_RULE@191..214 0: CSS_SELECTOR_LIST@191..212 0: CSS_COMPOUND_SELECTOR@191..212 0: (empty) @@ -1024,7 +1024,7 @@ CssRoot { 0: L_CURLY@212..213 "{" [] [] 1: CSS_DECLARATION_LIST@213..213 2: R_CURLY@213..214 "}" [] [] - 9: CSS_RULE@214..239 + 9: CSS_QUALIFIED_RULE@214..239 0: CSS_SELECTOR_LIST@214..237 0: CSS_COMPOUND_SELECTOR@214..237 0: (empty) @@ -1047,7 +1047,7 @@ CssRoot { 0: L_CURLY@237..238 "{" [] [] 1: CSS_DECLARATION_LIST@238..238 2: R_CURLY@238..239 "}" [] [] - 10: CSS_RULE@239..263 + 10: CSS_QUALIFIED_RULE@239..263 0: CSS_SELECTOR_LIST@239..261 0: CSS_COMPOUND_SELECTOR@239..261 0: (empty) @@ -1069,7 +1069,7 @@ CssRoot { 0: L_CURLY@261..262 "{" [] [] 1: CSS_DECLARATION_LIST@262..262 2: R_CURLY@262..263 "}" [] [] - 11: CSS_RULE@263..292 + 11: CSS_QUALIFIED_RULE@263..292 0: CSS_SELECTOR_LIST@263..290 0: CSS_COMPOUND_SELECTOR@263..290 0: (empty) @@ -1102,7 +1102,7 @@ CssRoot { 0: L_CURLY@290..291 "{" [] [] 1: CSS_DECLARATION_LIST@291..291 2: R_CURLY@291..292 "}" [] [] - 12: CSS_RULE@292..320 + 12: CSS_QUALIFIED_RULE@292..320 0: CSS_SELECTOR_LIST@292..318 0: CSS_COMPOUND_SELECTOR@292..318 0: (empty) @@ -1134,7 +1134,7 @@ CssRoot { 0: L_CURLY@318..319 "{" [] [] 1: CSS_DECLARATION_LIST@319..319 2: R_CURLY@319..320 "}" [] [] - 13: CSS_RULE@320..347 + 13: CSS_QUALIFIED_RULE@320..347 0: CSS_SELECTOR_LIST@320..345 0: CSS_COMPOUND_SELECTOR@320..345 0: (empty) @@ -1159,7 +1159,7 @@ CssRoot { 0: L_CURLY@345..346 "{" [] [] 1: CSS_DECLARATION_LIST@346..346 2: R_CURLY@346..347 "}" [] [] - 14: CSS_RULE@347..373 + 14: CSS_QUALIFIED_RULE@347..373 0: CSS_SELECTOR_LIST@347..371 0: CSS_COMPOUND_SELECTOR@347..371 0: (empty) @@ -1183,7 +1183,7 @@ CssRoot { 0: L_CURLY@371..372 "{" [] [] 1: CSS_DECLARATION_LIST@372..372 2: R_CURLY@372..373 "}" [] [] - 15: CSS_RULE@373..392 + 15: CSS_QUALIFIED_RULE@373..392 0: CSS_SELECTOR_LIST@373..390 0: CSS_COMPOUND_SELECTOR@373..390 0: (empty) @@ -1207,7 +1207,7 @@ CssRoot { 0: L_CURLY@390..391 "{" [] [] 1: CSS_DECLARATION_LIST@391..391 2: R_CURLY@391..392 "}" [] [] - 16: CSS_RULE@392..415 + 16: CSS_QUALIFIED_RULE@392..415 0: CSS_SELECTOR_LIST@392..413 0: CSS_COMPOUND_SELECTOR@392..413 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_relative_selector_list.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_relative_selector_list.css.snap index 3d1e87a6b1ec..5b9b9244ee71 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_relative_selector_list.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_relative_selector_list.css.snap @@ -25,7 +25,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -50,7 +50,7 @@ CssRoot { r_curly_token: R_CURLY@7..8 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -76,7 +76,7 @@ CssRoot { r_curly_token: R_CURLY@17..18 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -108,7 +108,7 @@ CssRoot { r_curly_token: R_CURLY@35..36 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -139,7 +139,7 @@ CssRoot { r_curly_token: R_CURLY@52..53 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -173,7 +173,7 @@ CssRoot { r_curly_token: R_CURLY@77..78 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -238,7 +238,7 @@ CssRoot { r_curly_token: R_CURLY@107..108 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -294,7 +294,7 @@ CssRoot { r_curly_token: R_CURLY@127..128 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -384,7 +384,7 @@ CssRoot { 0: CSS_ROOT@0..152 0: (empty) 1: CSS_RULE_LIST@0..151 - 0: CSS_RULE@0..8 + 0: CSS_QUALIFIED_RULE@0..8 0: CSS_SELECTOR_LIST@0..6 0: CSS_COMPOUND_SELECTOR@0..6 0: (empty) @@ -400,7 +400,7 @@ CssRoot { 0: L_CURLY@6..7 "{" [] [] 1: CSS_DECLARATION_LIST@7..7 2: R_CURLY@7..8 "}" [] [] - 1: CSS_RULE@8..18 + 1: CSS_QUALIFIED_RULE@8..18 0: CSS_SELECTOR_LIST@8..16 0: CSS_COMPOUND_SELECTOR@8..16 0: (empty) @@ -417,7 +417,7 @@ CssRoot { 0: L_CURLY@16..17 "{" [] [] 1: CSS_DECLARATION_LIST@17..17 2: R_CURLY@17..18 "}" [] [] - 2: CSS_RULE@18..36 + 2: CSS_QUALIFIED_RULE@18..36 0: CSS_SELECTOR_LIST@18..34 0: CSS_COMPOUND_SELECTOR@18..34 0: (empty) @@ -437,7 +437,7 @@ CssRoot { 0: L_CURLY@34..35 "{" [] [] 1: CSS_DECLARATION_LIST@35..35 2: R_CURLY@35..36 "}" [] [] - 3: CSS_RULE@36..53 + 3: CSS_QUALIFIED_RULE@36..53 0: CSS_SELECTOR_LIST@36..51 0: CSS_COMPOUND_SELECTOR@36..51 0: (empty) @@ -456,7 +456,7 @@ CssRoot { 0: L_CURLY@51..52 "{" [] [] 1: CSS_DECLARATION_LIST@52..52 2: R_CURLY@52..53 "}" [] [] - 4: CSS_RULE@53..78 + 4: CSS_QUALIFIED_RULE@53..78 0: CSS_SELECTOR_LIST@53..76 0: CSS_COMPOUND_SELECTOR@53..76 0: (empty) @@ -478,7 +478,7 @@ CssRoot { 0: L_CURLY@76..77 "{" [] [] 1: CSS_DECLARATION_LIST@77..77 2: R_CURLY@77..78 "}" [] [] - 5: CSS_RULE@78..108 + 5: CSS_QUALIFIED_RULE@78..108 0: CSS_SELECTOR_LIST@78..106 0: CSS_COMPOUND_SELECTOR@78..106 0: (empty) @@ -520,7 +520,7 @@ CssRoot { 0: L_CURLY@106..107 "{" [] [] 1: CSS_DECLARATION_LIST@107..107 2: R_CURLY@107..108 "}" [] [] - 6: CSS_RULE@108..128 + 6: CSS_QUALIFIED_RULE@108..128 0: CSS_SELECTOR_LIST@108..126 0: CSS_COMPOUND_SELECTOR@108..126 0: (empty) @@ -556,7 +556,7 @@ CssRoot { 0: L_CURLY@126..127 "{" [] [] 1: CSS_DECLARATION_LIST@127..127 2: R_CURLY@127..128 "}" [] [] - 7: CSS_RULE@128..141 + 7: CSS_QUALIFIED_RULE@128..141 0: CSS_SELECTOR_LIST@128..139 0: CSS_COMPOUND_SELECTOR@128..139 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector.css.snap index f64526d0632b..667b25ceb658 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector.css.snap @@ -22,7 +22,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -46,7 +46,7 @@ CssRoot { r_curly_token: R_CURLY@10..11 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -71,7 +71,7 @@ CssRoot { r_curly_token: R_CURLY@23..24 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -115,7 +115,7 @@ CssRoot { r_curly_token: R_CURLY@48..49 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -158,7 +158,7 @@ CssRoot { r_curly_token: R_CURLY@72..73 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -268,7 +268,7 @@ CssRoot { 0: CSS_ROOT@0..117 0: (empty) 1: CSS_RULE_LIST@0..116 - 0: CSS_RULE@0..11 + 0: CSS_QUALIFIED_RULE@0..11 0: CSS_SELECTOR_LIST@0..9 0: CSS_COMPOUND_SELECTOR@0..9 0: (empty) @@ -283,7 +283,7 @@ CssRoot { 0: L_CURLY@9..10 "{" [] [] 1: CSS_DECLARATION_LIST@10..10 2: R_CURLY@10..11 "}" [] [] - 1: CSS_RULE@11..24 + 1: CSS_QUALIFIED_RULE@11..24 0: CSS_SELECTOR_LIST@11..22 0: CSS_COMPOUND_SELECTOR@11..22 0: (empty) @@ -299,7 +299,7 @@ CssRoot { 0: L_CURLY@22..23 "{" [] [] 1: CSS_DECLARATION_LIST@23..23 2: R_CURLY@23..24 "}" [] [] - 2: CSS_RULE@24..49 + 2: CSS_QUALIFIED_RULE@24..49 0: CSS_SELECTOR_LIST@24..47 0: CSS_COMPOUND_SELECTOR@24..47 0: (empty) @@ -327,7 +327,7 @@ CssRoot { 0: L_CURLY@47..48 "{" [] [] 1: CSS_DECLARATION_LIST@48..48 2: R_CURLY@48..49 "}" [] [] - 3: CSS_RULE@49..73 + 3: CSS_QUALIFIED_RULE@49..73 0: CSS_SELECTOR_LIST@49..71 0: CSS_COMPOUND_SELECTOR@49..71 0: (empty) @@ -354,7 +354,7 @@ CssRoot { 0: L_CURLY@71..72 "{" [] [] 1: CSS_DECLARATION_LIST@72..72 2: R_CURLY@72..73 "}" [] [] - 4: CSS_RULE@73..96 + 4: CSS_QUALIFIED_RULE@73..96 0: CSS_SELECTOR_LIST@73..94 0: CSS_COMPOUND_SELECTOR@73..94 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector_list.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector_list.css.snap index ae6e0063db30..a0bbe739cff8 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector_list.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector_list.css.snap @@ -25,7 +25,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -50,7 +50,7 @@ CssRoot { r_curly_token: R_CURLY@9..10 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -76,7 +76,7 @@ CssRoot { r_curly_token: R_CURLY@21..22 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -108,7 +108,7 @@ CssRoot { r_curly_token: R_CURLY@41..42 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -139,7 +139,7 @@ CssRoot { r_curly_token: R_CURLY@60..61 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -173,7 +173,7 @@ CssRoot { r_curly_token: R_CURLY@87..88 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -235,7 +235,7 @@ CssRoot { r_curly_token: R_CURLY@119..120 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -288,7 +288,7 @@ CssRoot { r_curly_token: R_CURLY@141..142 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -372,7 +372,7 @@ CssRoot { 0: CSS_ROOT@0..170 0: (empty) 1: CSS_RULE_LIST@0..169 - 0: CSS_RULE@0..10 + 0: CSS_QUALIFIED_RULE@0..10 0: CSS_SELECTOR_LIST@0..8 0: CSS_COMPOUND_SELECTOR@0..8 0: (empty) @@ -388,7 +388,7 @@ CssRoot { 0: L_CURLY@8..9 "{" [] [] 1: CSS_DECLARATION_LIST@9..9 2: R_CURLY@9..10 "}" [] [] - 1: CSS_RULE@10..22 + 1: CSS_QUALIFIED_RULE@10..22 0: CSS_SELECTOR_LIST@10..20 0: CSS_COMPOUND_SELECTOR@10..20 0: (empty) @@ -405,7 +405,7 @@ CssRoot { 0: L_CURLY@20..21 "{" [] [] 1: CSS_DECLARATION_LIST@21..21 2: R_CURLY@21..22 "}" [] [] - 2: CSS_RULE@22..42 + 2: CSS_QUALIFIED_RULE@22..42 0: CSS_SELECTOR_LIST@22..40 0: CSS_COMPOUND_SELECTOR@22..40 0: (empty) @@ -425,7 +425,7 @@ CssRoot { 0: L_CURLY@40..41 "{" [] [] 1: CSS_DECLARATION_LIST@41..41 2: R_CURLY@41..42 "}" [] [] - 3: CSS_RULE@42..61 + 3: CSS_QUALIFIED_RULE@42..61 0: CSS_SELECTOR_LIST@42..59 0: CSS_COMPOUND_SELECTOR@42..59 0: (empty) @@ -444,7 +444,7 @@ CssRoot { 0: L_CURLY@59..60 "{" [] [] 1: CSS_DECLARATION_LIST@60..60 2: R_CURLY@60..61 "}" [] [] - 4: CSS_RULE@61..88 + 4: CSS_QUALIFIED_RULE@61..88 0: CSS_SELECTOR_LIST@61..86 0: CSS_COMPOUND_SELECTOR@61..86 0: (empty) @@ -466,7 +466,7 @@ CssRoot { 0: L_CURLY@86..87 "{" [] [] 1: CSS_DECLARATION_LIST@87..87 2: R_CURLY@87..88 "}" [] [] - 5: CSS_RULE@88..120 + 5: CSS_QUALIFIED_RULE@88..120 0: CSS_SELECTOR_LIST@88..118 0: CSS_COMPOUND_SELECTOR@88..118 0: (empty) @@ -506,7 +506,7 @@ CssRoot { 0: L_CURLY@118..119 "{" [] [] 1: CSS_DECLARATION_LIST@119..119 2: R_CURLY@119..120 "}" [] [] - 6: CSS_RULE@120..142 + 6: CSS_QUALIFIED_RULE@120..142 0: CSS_SELECTOR_LIST@120..140 0: CSS_COMPOUND_SELECTOR@120..140 0: (empty) @@ -540,7 +540,7 @@ CssRoot { 0: L_CURLY@140..141 "{" [] [] 1: CSS_DECLARATION_LIST@141..141 2: R_CURLY@141..142 "}" [] [] - 7: CSS_RULE@142..157 + 7: CSS_QUALIFIED_RULE@142..157 0: CSS_SELECTOR_LIST@142..155 0: CSS_COMPOUND_SELECTOR@142..155 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_value_list.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_value_list.css.snap index 8e43de178115..39286568e2c2 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_value_list.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_value_list.css.snap @@ -23,7 +23,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -48,7 +48,7 @@ CssRoot { r_curly_token: R_CURLY@8..9 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -74,7 +74,7 @@ CssRoot { r_curly_token: R_CURLY@19..20 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -109,7 +109,7 @@ CssRoot { r_curly_token: R_CURLY@42..43 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -143,7 +143,7 @@ CssRoot { r_curly_token: R_CURLY@64..65 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -175,7 +175,7 @@ CssRoot { r_curly_token: R_CURLY@80..81 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -249,7 +249,7 @@ CssRoot { 0: CSS_ROOT@0..110 0: (empty) 1: CSS_RULE_LIST@0..109 - 0: CSS_RULE@0..9 + 0: CSS_QUALIFIED_RULE@0..9 0: CSS_SELECTOR_LIST@0..7 0: CSS_COMPOUND_SELECTOR@0..7 0: (empty) @@ -265,7 +265,7 @@ CssRoot { 0: L_CURLY@7..8 "{" [] [] 1: CSS_DECLARATION_LIST@8..8 2: R_CURLY@8..9 "}" [] [] - 1: CSS_RULE@9..20 + 1: CSS_QUALIFIED_RULE@9..20 0: CSS_SELECTOR_LIST@9..18 0: CSS_COMPOUND_SELECTOR@9..18 0: (empty) @@ -282,7 +282,7 @@ CssRoot { 0: L_CURLY@18..19 "{" [] [] 1: CSS_DECLARATION_LIST@19..19 2: R_CURLY@19..20 "}" [] [] - 2: CSS_RULE@20..43 + 2: CSS_QUALIFIED_RULE@20..43 0: CSS_SELECTOR_LIST@20..41 0: CSS_COMPOUND_SELECTOR@20..41 0: (empty) @@ -305,7 +305,7 @@ CssRoot { 0: L_CURLY@41..42 "{" [] [] 1: CSS_DECLARATION_LIST@42..42 2: R_CURLY@42..43 "}" [] [] - 3: CSS_RULE@43..65 + 3: CSS_QUALIFIED_RULE@43..65 0: CSS_SELECTOR_LIST@43..63 0: CSS_COMPOUND_SELECTOR@43..63 0: (empty) @@ -327,7 +327,7 @@ CssRoot { 0: L_CURLY@63..64 "{" [] [] 1: CSS_DECLARATION_LIST@64..64 2: R_CURLY@64..65 "}" [] [] - 4: CSS_RULE@65..81 + 4: CSS_QUALIFIED_RULE@65..81 0: CSS_SELECTOR_LIST@65..79 0: CSS_COMPOUND_SELECTOR@65..79 0: (empty) @@ -349,7 +349,7 @@ CssRoot { 0: L_CURLY@79..80 "{" [] [] 1: CSS_DECLARATION_LIST@80..80 2: R_CURLY@80..81 "}" [] [] - 5: CSS_RULE@81..96 + 5: CSS_QUALIFIED_RULE@81..96 0: CSS_SELECTOR_LIST@81..94 0: CSS_COMPOUND_SELECTOR@81..94 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_identifier.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_identifier.css.snap index 0f520610e9d7..23fc7fe7e023 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_identifier.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_identifier.css.snap @@ -20,7 +20,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -40,7 +40,7 @@ CssRoot { r_curly_token: R_CURLY@3..4 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -66,7 +66,7 @@ CssRoot { r_curly_token: R_CURLY@14..15 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -120,7 +120,7 @@ CssRoot { 0: CSS_ROOT@0..29 0: (empty) 1: CSS_RULE_LIST@0..28 - 0: CSS_RULE@0..4 + 0: CSS_QUALIFIED_RULE@0..4 0: CSS_SELECTOR_LIST@0..2 0: CSS_COMPOUND_SELECTOR@0..2 0: (empty) @@ -132,7 +132,7 @@ CssRoot { 0: L_CURLY@2..3 "{" [] [] 1: CSS_DECLARATION_LIST@3..3 2: R_CURLY@3..4 "}" [] [] - 1: CSS_RULE@4..15 + 1: CSS_QUALIFIED_RULE@4..15 0: CSS_SELECTOR_LIST@4..13 0: CSS_COMPOUND_SELECTOR@4..13 0: (empty) @@ -148,7 +148,7 @@ CssRoot { 0: L_CURLY@13..14 "{" [] [] 1: CSS_DECLARATION_LIST@14..14 2: R_CURLY@14..15 "}" [] [] - 2: CSS_RULE@15..26 + 2: CSS_QUALIFIED_RULE@15..26 0: CSS_SELECTOR_LIST@15..24 0: CSS_COMPOUND_SELECTOR@15..24 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element.css.snap index 14a6c56bab58..ef80e69de48a 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element.css.snap @@ -25,7 +25,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -44,7 +44,7 @@ CssRoot { r_curly_token: R_CURLY@4..5 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -70,7 +70,7 @@ CssRoot { r_curly_token: R_CURLY@16..17 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -106,7 +106,7 @@ CssRoot { r_curly_token: R_CURLY@32..33 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -138,7 +138,7 @@ CssRoot { r_curly_token: R_CURLY@50..51 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -183,7 +183,7 @@ CssRoot { 0: CSS_ROOT@0..79 0: (empty) 1: CSS_RULE_LIST@0..78 - 0: CSS_RULE@0..5 + 0: CSS_QUALIFIED_RULE@0..5 0: CSS_SELECTOR_LIST@0..3 0: CSS_COMPOUND_SELECTOR@0..3 0: (empty) @@ -196,7 +196,7 @@ CssRoot { 0: L_CURLY@3..4 "{" [] [] 1: CSS_DECLARATION_LIST@4..4 2: R_CURLY@4..5 "}" [] [] - 1: CSS_RULE@5..17 + 1: CSS_QUALIFIED_RULE@5..17 0: CSS_SELECTOR_LIST@5..15 0: CSS_COMPOUND_SELECTOR@5..15 0: (empty) @@ -212,7 +212,7 @@ CssRoot { 0: L_CURLY@15..16 "{" [] [] 1: CSS_DECLARATION_LIST@16..16 2: R_CURLY@16..17 "}" [] [] - 2: CSS_RULE@17..33 + 2: CSS_QUALIFIED_RULE@17..33 0: CSS_SELECTOR_LIST@17..31 0: CSS_COMPOUND_SELECTOR@17..31 0: (empty) @@ -235,7 +235,7 @@ CssRoot { 0: L_CURLY@31..32 "{" [] [] 1: CSS_DECLARATION_LIST@32..32 2: R_CURLY@32..33 "}" [] [] - 3: CSS_RULE@33..51 + 3: CSS_QUALIFIED_RULE@33..51 0: CSS_SELECTOR_LIST@33..49 0: CSS_COMPOUND_SELECTOR@33..49 0: (empty) @@ -254,7 +254,7 @@ CssRoot { 0: L_CURLY@49..50 "{" [] [] 1: CSS_DECLARATION_LIST@50..50 2: R_CURLY@50..51 "}" [] [] - 4: CSS_RULE@51..78 + 4: CSS_QUALIFIED_RULE@51..78 0: CSS_SELECTOR_LIST@51..76 0: CSS_COMPOUND_SELECTOR@51..76 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element_function_identifier.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element_function_identifier.css.snap index 161e706f9170..e2021e0cd874 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element_function_identifier.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element_function_identifier.css.snap @@ -19,7 +19,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -53,7 +53,7 @@ CssRoot { r_curly_token: R_CURLY@24..25 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -109,7 +109,7 @@ CssRoot { 0: CSS_ROOT@0..55 0: (empty) 1: CSS_RULE_LIST@0..54 - 0: CSS_RULE@0..25 + 0: CSS_QUALIFIED_RULE@0..25 0: CSS_SELECTOR_LIST@0..23 0: CSS_COMPOUND_SELECTOR@0..23 0: (empty) @@ -130,7 +130,7 @@ CssRoot { 0: L_CURLY@23..24 "{" [] [] 1: CSS_DECLARATION_LIST@24..24 2: R_CURLY@24..25 "}" [] [] - 1: CSS_RULE@25..41 + 1: CSS_QUALIFIED_RULE@25..41 0: CSS_SELECTOR_LIST@25..39 0: CSS_COMPOUND_SELECTOR@25..39 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element_function_selector.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element_function_selector.css.snap index ade9c4a21d86..2c9961491368 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element_function_selector.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_element_function_selector.css.snap @@ -21,7 +21,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -47,7 +47,7 @@ CssRoot { r_curly_token: R_CURLY@9..10 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -74,7 +74,7 @@ CssRoot { r_curly_token: R_CURLY@21..22 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -120,7 +120,7 @@ CssRoot { r_curly_token: R_CURLY@45..46 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -165,7 +165,7 @@ CssRoot { r_curly_token: R_CURLY@68..69 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -229,7 +229,7 @@ CssRoot { 0: CSS_ROOT@0..92 0: (empty) 1: CSS_RULE_LIST@0..91 - 0: CSS_RULE@0..10 + 0: CSS_QUALIFIED_RULE@0..10 0: CSS_SELECTOR_LIST@0..8 0: CSS_COMPOUND_SELECTOR@0..8 0: (empty) @@ -245,7 +245,7 @@ CssRoot { 0: L_CURLY@8..9 "{" [] [] 1: CSS_DECLARATION_LIST@9..9 2: R_CURLY@9..10 "}" [] [] - 1: CSS_RULE@10..22 + 1: CSS_QUALIFIED_RULE@10..22 0: CSS_SELECTOR_LIST@10..20 0: CSS_COMPOUND_SELECTOR@10..20 0: (empty) @@ -262,7 +262,7 @@ CssRoot { 0: L_CURLY@20..21 "{" [] [] 1: CSS_DECLARATION_LIST@21..21 2: R_CURLY@21..22 "}" [] [] - 2: CSS_RULE@22..46 + 2: CSS_QUALIFIED_RULE@22..46 0: CSS_SELECTOR_LIST@22..44 0: CSS_COMPOUND_SELECTOR@22..44 0: (empty) @@ -291,7 +291,7 @@ CssRoot { 0: L_CURLY@44..45 "{" [] [] 1: CSS_DECLARATION_LIST@45..45 2: R_CURLY@45..46 "}" [] [] - 3: CSS_RULE@46..69 + 3: CSS_QUALIFIED_RULE@46..69 0: CSS_SELECTOR_LIST@46..67 0: CSS_COMPOUND_SELECTOR@46..67 0: (empty) @@ -319,7 +319,7 @@ CssRoot { 0: L_CURLY@67..68 "{" [] [] 1: CSS_DECLARATION_LIST@68..68 2: R_CURLY@68..69 "}" [] [] - 4: CSS_RULE@69..91 + 4: CSS_QUALIFIED_RULE@69..91 0: CSS_SELECTOR_LIST@69..89 0: CSS_COMPOUND_SELECTOR@69..89 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/traling_comma.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/traling_comma.css.snap index 18705283f977..ee4d80b5d469 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/traling_comma.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/traling_comma.css.snap @@ -19,7 +19,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -53,7 +53,7 @@ CssRoot { 0: CSS_ROOT@0..16 0: (empty) 1: CSS_RULE_LIST@0..15 - 0: CSS_RULE@0..15 + 0: CSS_QUALIFIED_RULE@0..15 0: CSS_SELECTOR_LIST@0..11 0: CSS_COMPOUND_SELECTOR@0..9 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_complex.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_complex.css.snap index 23dbc444747c..8238ab494e09 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_complex.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_complex.css.snap @@ -1180,7 +1180,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@1237..1238 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1217,7 +1217,7 @@ CssRoot { r_curly_token: R_CURLY@1277..1280 "}" [Newline("\n"), Whitespace("\t")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2060,7 +2060,7 @@ CssRoot { 3: CSS_RULE_LIST_BLOCK@1237..1321 0: L_CURLY@1237..1238 "{" [] [] 1: CSS_RULE_LIST@1238..1319 - 0: CSS_RULE@1238..1280 + 0: CSS_QUALIFIED_RULE@1238..1280 0: CSS_SELECTOR_LIST@1238..1247 0: CSS_COMPOUND_SELECTOR@1238..1247 0: (empty) @@ -2084,7 +2084,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@1276..1277 ";" [] [] 2: R_CURLY@1277..1280 "}" [Newline("\n"), Whitespace("\t")] [] - 1: CSS_RULE@1280..1319 + 1: CSS_QUALIFIED_RULE@1280..1319 0: CSS_SELECTOR_LIST@1280..1286 0: CSS_COMPOUND_SELECTOR@1280..1286 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_document.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_document.css.snap index af30e77f5365..007855b5d171 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_document.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_document.css.snap @@ -58,7 +58,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@42..43 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -152,7 +152,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@216..217 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -325,7 +325,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@517..518 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -401,7 +401,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@42..70 0: L_CURLY@42..43 "{" [] [] 1: CSS_RULE_LIST@43..68 - 0: CSS_RULE@43..68 + 0: CSS_QUALIFIED_RULE@43..68 0: CSS_SELECTOR_LIST@43..48 0: CSS_COMPOUND_SELECTOR@43..48 0: (empty) @@ -468,7 +468,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@216..269 0: L_CURLY@216..217 "{" [] [] 1: CSS_RULE_LIST@217..267 - 0: CSS_RULE@217..267 + 0: CSS_QUALIFIED_RULE@217..267 0: CSS_SELECTOR_LIST@217..224 0: CSS_COMPOUND_SELECTOR@217..224 0: (empty) @@ -590,7 +590,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@517..560 0: L_CURLY@517..518 "{" [] [] 1: CSS_RULE_LIST@518..557 - 0: CSS_RULE@518..557 + 0: CSS_QUALIFIED_RULE@518..557 0: CSS_SELECTOR_LIST@518..529 0: CSS_COMPOUND_SELECTOR@518..529 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_layer.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_layer.css.snap index 0a5738f9ac47..af928bbccddc 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_layer.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_layer.css.snap @@ -384,7 +384,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@350..351 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -440,7 +440,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@403..404 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -492,7 +492,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@450..451 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -565,7 +565,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@499..500 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -643,7 +643,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@568..569 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -700,7 +700,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@618..619 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -766,7 +766,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@669..670 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -851,7 +851,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@844..845 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -896,7 +896,7 @@ CssRoot { }, }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -948,7 +948,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@933..934 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1340,7 +1340,7 @@ CssRoot { 1: CSS_RULE_LIST_BLOCK@350..384 0: L_CURLY@350..351 "{" [] [] 1: CSS_RULE_LIST@351..382 - 0: CSS_RULE@351..382 + 0: CSS_QUALIFIED_RULE@351..382 0: CSS_SELECTOR_LIST@351..360 0: CSS_COMPOUND_SELECTOR@351..360 0: (empty) @@ -1376,7 +1376,7 @@ CssRoot { 1: CSS_RULE_LIST_BLOCK@403..484 0: L_CURLY@403..404 "{" [] [] 1: CSS_RULE_LIST@404..482 - 0: CSS_RULE@404..434 + 0: CSS_QUALIFIED_RULE@404..434 0: CSS_SELECTOR_LIST@404..413 0: CSS_COMPOUND_SELECTOR@404..413 0: (empty) @@ -1412,7 +1412,7 @@ CssRoot { 1: CSS_RULE_LIST_BLOCK@450..482 0: L_CURLY@450..451 "{" [] [] 1: CSS_RULE_LIST@451..479 - 0: CSS_RULE@451..479 + 0: CSS_QUALIFIED_RULE@451..479 0: CSS_SELECTOR_LIST@451..461 0: CSS_COMPOUND_SELECTOR@451..456 0: (empty) @@ -1457,7 +1457,7 @@ CssRoot { 1: CSS_RULE_LIST_BLOCK@499..531 0: L_CURLY@499..500 "{" [] [] 1: CSS_RULE_LIST@500..529 - 0: CSS_RULE@500..529 + 0: CSS_QUALIFIED_RULE@500..529 0: CSS_SELECTOR_LIST@500..511 0: CSS_COMPOUND_SELECTOR@500..511 0: (empty) @@ -1510,7 +1510,7 @@ CssRoot { 1: CSS_RULE_LIST_BLOCK@568..602 0: L_CURLY@568..569 "{" [] [] 1: CSS_RULE_LIST@569..599 - 0: CSS_RULE@569..599 + 0: CSS_QUALIFIED_RULE@569..599 0: CSS_SELECTOR_LIST@569..574 0: CSS_COMPOUND_SELECTOR@569..574 0: (empty) @@ -1547,7 +1547,7 @@ CssRoot { 1: CSS_RULE_LIST_BLOCK@618..642 0: L_CURLY@618..619 "{" [] [] 1: CSS_RULE_LIST@619..639 - 0: CSS_RULE@619..639 + 0: CSS_QUALIFIED_RULE@619..639 0: CSS_SELECTOR_LIST@619..624 0: CSS_COMPOUND_SELECTOR@619..624 0: (empty) @@ -1587,7 +1587,7 @@ CssRoot { 1: CSS_RULE_LIST_BLOCK@669..790 0: L_CURLY@669..670 "{" [] [] 1: CSS_RULE_LIST@670..788 - 0: CSS_RULE@670..788 + 0: CSS_QUALIFIED_RULE@670..788 0: CSS_SELECTOR_LIST@670..763 0: CSS_COMPOUND_SELECTOR@670..763 0: (empty) @@ -1644,7 +1644,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@844..887 0: L_CURLY@844..845 "{" [] [] 1: CSS_RULE_LIST@845..884 - 0: CSS_RULE@845..884 + 0: CSS_QUALIFIED_RULE@845..884 0: CSS_SELECTOR_LIST@845..856 0: CSS_COMPOUND_SELECTOR@845..856 0: (empty) @@ -1672,7 +1672,7 @@ CssRoot { 1: SEMICOLON@879..880 ";" [] [] 2: R_CURLY@880..884 "}" [Newline("\n"), Whitespace("\t\t")] [] 2: R_CURLY@884..887 "}" [Newline("\n"), Whitespace("\t")] [] - 1: CSS_RULE@887..917 + 1: CSS_QUALIFIED_RULE@887..917 0: CSS_SELECTOR_LIST@887..896 0: CSS_COMPOUND_SELECTOR@887..896 0: (empty) @@ -1708,7 +1708,7 @@ CssRoot { 1: CSS_RULE_LIST_BLOCK@933..965 0: L_CURLY@933..934 "{" [] [] 1: CSS_RULE_LIST@934..962 - 0: CSS_RULE@934..962 + 0: CSS_QUALIFIED_RULE@934..962 0: CSS_SELECTOR_LIST@934..944 0: CSS_COMPOUND_SELECTOR@934..939 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_media_complex.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_media_complex.css.snap index 16b15d7028e3..09f283b06271 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_media_complex.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_media_complex.css.snap @@ -796,7 +796,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@767..768 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -833,7 +833,7 @@ CssRoot { r_curly_token: R_CURLY@812..816 "}" [Newline("\n"), Whitespace("\t\t")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1399,7 +1399,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@767..861 0: L_CURLY@767..768 "{" [] [] 1: CSS_RULE_LIST@768..859 - 0: CSS_RULE@768..816 + 0: CSS_QUALIFIED_RULE@768..816 0: CSS_SELECTOR_LIST@768..780 0: CSS_COMPOUND_SELECTOR@768..780 0: (empty) @@ -1423,7 +1423,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@811..812 ";" [] [] 2: R_CURLY@812..816 "}" [Newline("\n"), Whitespace("\t\t")] [] - 1: CSS_RULE@816..859 + 1: CSS_QUALIFIED_RULE@816..859 0: CSS_SELECTOR_LIST@816..823 0: CSS_COMPOUND_SELECTOR@816..823 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_scope.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_scope.css.snap index 5822e4151dfb..dc0dbdc1d0bd 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_scope.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_scope.css.snap @@ -79,7 +79,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@7..8 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -146,7 +146,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@112..113 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -225,7 +225,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@215..216 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -261,7 +261,7 @@ CssRoot { r_curly_token: R_CURLY@243..245 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -361,7 +361,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@319..320 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -397,7 +397,7 @@ CssRoot { r_curly_token: R_CURLY@347..349 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -479,7 +479,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@407..408 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -515,7 +515,7 @@ CssRoot { r_curly_token: R_CURLY@435..437 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -783,7 +783,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@7..87 0: L_CURLY@7..8 "{" [] [] 1: CSS_RULE_LIST@8..85 - 0: CSS_RULE@8..85 + 0: CSS_QUALIFIED_RULE@8..85 0: CSS_SELECTOR_LIST@8..59 0: CSS_COMPOUND_SELECTOR@8..59 0: (empty) @@ -827,7 +827,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@112..188 0: L_CURLY@112..113 "{" [] [] 1: CSS_RULE_LIST@113..186 - 0: CSS_RULE@113..186 + 0: CSS_QUALIFIED_RULE@113..186 0: CSS_SELECTOR_LIST@113..162 0: CSS_COMPOUND_SELECTOR@113..162 0: (empty) @@ -880,7 +880,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@215..276 0: L_CURLY@215..216 "{" [] [] 1: CSS_RULE_LIST@216..274 - 0: CSS_RULE@216..245 + 0: CSS_QUALIFIED_RULE@216..245 0: CSS_SELECTOR_LIST@216..221 0: CSS_COMPOUND_SELECTOR@216..221 0: (empty) @@ -904,7 +904,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@242..243 ";" [] [] 2: R_CURLY@243..245 "}" [Newline("\n")] [] - 1: CSS_RULE@245..274 + 1: CSS_QUALIFIED_RULE@245..274 0: CSS_SELECTOR_LIST@245..256 0: CSS_COMPOUND_SELECTOR@245..256 0: (empty) @@ -971,7 +971,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@319..380 0: L_CURLY@319..320 "{" [] [] 1: CSS_RULE_LIST@320..378 - 0: CSS_RULE@320..349 + 0: CSS_QUALIFIED_RULE@320..349 0: CSS_SELECTOR_LIST@320..325 0: CSS_COMPOUND_SELECTOR@320..325 0: (empty) @@ -995,7 +995,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@346..347 ";" [] [] 2: R_CURLY@347..349 "}" [Newline("\n")] [] - 1: CSS_RULE@349..378 + 1: CSS_QUALIFIED_RULE@349..378 0: CSS_SELECTOR_LIST@349..360 0: CSS_COMPOUND_SELECTOR@349..360 0: (empty) @@ -1050,7 +1050,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@407..468 0: L_CURLY@407..408 "{" [] [] 1: CSS_RULE_LIST@408..466 - 0: CSS_RULE@408..437 + 0: CSS_QUALIFIED_RULE@408..437 0: CSS_SELECTOR_LIST@408..413 0: CSS_COMPOUND_SELECTOR@408..413 0: (empty) @@ -1074,7 +1074,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@434..435 ";" [] [] 2: R_CURLY@435..437 "}" [Newline("\n")] [] - 1: CSS_RULE@437..466 + 1: CSS_QUALIFIED_RULE@437..466 0: CSS_SELECTOR_LIST@437..448 0: CSS_COMPOUND_SELECTOR@437..448 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_starting_style.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_starting_style.css.snap index 9bd15c775fad..ea1cfeb70273 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_starting_style.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_starting_style.css.snap @@ -38,7 +38,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@16..17 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -115,7 +115,7 @@ CssRoot { }, }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -180,7 +180,7 @@ CssRoot { 1: CSS_RULE_LIST_BLOCK@16..168 0: L_CURLY@16..17 "{" [] [] 1: CSS_RULE_LIST@17..166 - 0: CSS_RULE@17..59 + 0: CSS_QUALIFIED_RULE@17..59 0: CSS_SELECTOR_LIST@17..22 0: CSS_COMPOUND_SELECTOR@17..22 0: (empty) @@ -233,7 +233,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@127..128 ";" [] [] 2: R_CURLY@128..132 "}" [Newline("\n"), Whitespace("\t\t")] [] - 1: CSS_RULE@132..163 + 1: CSS_QUALIFIED_RULE@132..163 0: CSS_SELECTOR_LIST@132..140 0: CSS_COMPOUND_SELECTOR@132..140 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_supports.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_supports.css.snap index 1a35263127e8..97287eecbe23 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_supports.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_supports.css.snap @@ -206,7 +206,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@26..27 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -271,7 +271,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@84..85 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -340,7 +340,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@151..152 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1480,7 +1480,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@1485..1486 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -1566,7 +1566,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@1569..1570 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1673,7 +1673,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@1680..1681 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1740,7 +1740,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@1732..1733 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1803,7 +1803,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@1786..1787 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1870,7 +1870,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@1842..1843 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1953,7 +1953,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@1898..1899 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2040,7 +2040,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@1956..1957 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2127,7 +2127,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@2035..2036 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2290,7 +2290,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@2206..2207 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2397,7 +2397,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@2281..2282 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -2536,7 +2536,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@2367..2368 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2794,7 +2794,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@2610..2611 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2884,7 +2884,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@2690..2691 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2953,7 +2953,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@2744..2745 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3030,7 +3030,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@2817..2818 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -3119,7 +3119,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@2900..2901 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3182,7 +3182,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@2963..2964 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3234,7 +3234,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@3010..3011 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3301,7 +3301,7 @@ CssRoot { block: CssRuleListBlock { l_curly_token: L_CURLY@3074..3075 "{" [] [], rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3378,7 +3378,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@26..56 0: L_CURLY@26..27 "{" [] [] 1: CSS_RULE_LIST@27..54 - 0: CSS_RULE@27..54 + 0: CSS_QUALIFIED_RULE@27..54 0: CSS_SELECTOR_LIST@27..33 0: CSS_COMPOUND_SELECTOR@27..33 0: (empty) @@ -3421,7 +3421,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@84..191 0: L_CURLY@84..85 "{" [] [] 1: CSS_RULE_LIST@85..189 - 0: CSS_RULE@85..111 + 0: CSS_QUALIFIED_RULE@85..111 0: CSS_SELECTOR_LIST@85..92 0: CSS_COMPOUND_SELECTOR@85..92 0: (empty) @@ -3469,7 +3469,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@151..189 0: L_CURLY@151..152 "{" [] [] 1: CSS_RULE_LIST@152..186 - 0: CSS_RULE@152..186 + 0: CSS_QUALIFIED_RULE@152..186 0: CSS_SELECTOR_LIST@152..163 0: CSS_COMPOUND_SELECTOR@152..163 0: (empty) @@ -4239,7 +4239,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@1485..1532 0: L_CURLY@1485..1486 "{" [] [] 1: CSS_RULE_LIST@1486..1530 - 0: CSS_RULE@1486..1530 + 0: CSS_QUALIFIED_RULE@1486..1530 0: CSS_SELECTOR_LIST@1486..1507 0: CSS_COMPLEX_SELECTOR@1486..1507 0: CSS_COMPOUND_SELECTOR@1486..1501 @@ -4296,7 +4296,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@1569..1616 0: L_CURLY@1569..1570 "{" [] [] 1: CSS_RULE_LIST@1570..1614 - 0: CSS_RULE@1570..1614 + 0: CSS_QUALIFIED_RULE@1570..1614 0: CSS_SELECTOR_LIST@1570..1588 0: CSS_COMPOUND_SELECTOR@1570..1588 0: (empty) @@ -4366,7 +4366,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@1680..1707 0: L_CURLY@1680..1681 "{" [] [] 1: CSS_RULE_LIST@1681..1705 - 0: CSS_RULE@1681..1705 + 0: CSS_QUALIFIED_RULE@1681..1705 0: CSS_SELECTOR_LIST@1681..1685 0: CSS_COMPOUND_SELECTOR@1681..1685 0: (empty) @@ -4411,7 +4411,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@1732..1759 0: L_CURLY@1732..1733 "{" [] [] 1: CSS_RULE_LIST@1733..1757 - 0: CSS_RULE@1733..1757 + 0: CSS_QUALIFIED_RULE@1733..1757 0: CSS_SELECTOR_LIST@1733..1737 0: CSS_COMPOUND_SELECTOR@1733..1737 0: (empty) @@ -4453,7 +4453,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@1786..1813 0: L_CURLY@1786..1787 "{" [] [] 1: CSS_RULE_LIST@1787..1811 - 0: CSS_RULE@1787..1811 + 0: CSS_QUALIFIED_RULE@1787..1811 0: CSS_SELECTOR_LIST@1787..1791 0: CSS_COMPOUND_SELECTOR@1787..1791 0: (empty) @@ -4498,7 +4498,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@1842..1869 0: L_CURLY@1842..1843 "{" [] [] 1: CSS_RULE_LIST@1843..1867 - 0: CSS_RULE@1843..1867 + 0: CSS_QUALIFIED_RULE@1843..1867 0: CSS_SELECTOR_LIST@1843..1847 0: CSS_COMPOUND_SELECTOR@1843..1847 0: (empty) @@ -4551,7 +4551,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@1898..1925 0: L_CURLY@1898..1899 "{" [] [] 1: CSS_RULE_LIST@1899..1923 - 0: CSS_RULE@1899..1923 + 0: CSS_QUALIFIED_RULE@1899..1923 0: CSS_SELECTOR_LIST@1899..1903 0: CSS_COMPOUND_SELECTOR@1899..1903 0: (empty) @@ -4607,7 +4607,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@1956..1983 0: L_CURLY@1956..1957 "{" [] [] 1: CSS_RULE_LIST@1957..1981 - 0: CSS_RULE@1957..1981 + 0: CSS_QUALIFIED_RULE@1957..1981 0: CSS_SELECTOR_LIST@1957..1961 0: CSS_COMPOUND_SELECTOR@1957..1961 0: (empty) @@ -4663,7 +4663,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@2035..2062 0: L_CURLY@2035..2036 "{" [] [] 1: CSS_RULE_LIST@2036..2060 - 0: CSS_RULE@2036..2060 + 0: CSS_QUALIFIED_RULE@2036..2060 0: CSS_SELECTOR_LIST@2036..2040 0: CSS_COMPOUND_SELECTOR@2036..2040 0: (empty) @@ -4770,7 +4770,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@2206..2245 0: L_CURLY@2206..2207 "{" [] [] 1: CSS_RULE_LIST@2207..2243 - 0: CSS_RULE@2207..2243 + 0: CSS_QUALIFIED_RULE@2207..2243 0: CSS_SELECTOR_LIST@2207..2214 0: CSS_COMPOUND_SELECTOR@2207..2214 0: (empty) @@ -4842,7 +4842,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@2281..2322 0: L_CURLY@2281..2282 "{" [] [] 1: CSS_RULE_LIST@2282..2320 - 0: CSS_RULE@2282..2320 + 0: CSS_QUALIFIED_RULE@2282..2320 0: CSS_SELECTOR_LIST@2282..2302 0: CSS_COMPLEX_SELECTOR@2282..2291 0: CSS_COMPOUND_SELECTOR@2282..2287 @@ -4938,7 +4938,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@2367..2456 0: L_CURLY@2367..2368 "{" [] [] 1: CSS_RULE_LIST@2368..2454 - 0: CSS_RULE@2368..2454 + 0: CSS_QUALIFIED_RULE@2368..2454 0: CSS_SELECTOR_LIST@2368..2437 0: CSS_COMPOUND_SELECTOR@2368..2437 0: (empty) @@ -5111,7 +5111,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@2610..2657 0: L_CURLY@2610..2611 "{" [] [] 1: CSS_RULE_LIST@2611..2655 - 0: CSS_RULE@2611..2655 + 0: CSS_QUALIFIED_RULE@2611..2655 0: CSS_SELECTOR_LIST@2611..2629 0: CSS_COMPOUND_SELECTOR@2611..2629 0: (empty) @@ -5169,7 +5169,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@2690..2722 0: L_CURLY@2690..2691 "{" [] [] 1: CSS_RULE_LIST@2691..2720 - 0: CSS_RULE@2691..2720 + 0: CSS_QUALIFIED_RULE@2691..2720 0: CSS_SELECTOR_LIST@2691..2697 0: CSS_COMPOUND_SELECTOR@2691..2697 0: (empty) @@ -5214,7 +5214,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@2744..2771 0: L_CURLY@2744..2745 "{" [] [] 1: CSS_RULE_LIST@2745..2769 - 0: CSS_RULE@2745..2769 + 0: CSS_QUALIFIED_RULE@2745..2769 0: CSS_SELECTOR_LIST@2745..2749 0: CSS_COMPOUND_SELECTOR@2745..2749 0: (empty) @@ -5267,7 +5267,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@2817..2864 0: L_CURLY@2817..2818 "{" [] [] 1: CSS_RULE_LIST@2818..2862 - 0: CSS_RULE@2818..2862 + 0: CSS_QUALIFIED_RULE@2818..2862 0: CSS_SELECTOR_LIST@2818..2839 0: CSS_COMPLEX_SELECTOR@2818..2839 0: CSS_COMPOUND_SELECTOR@2818..2833 @@ -5325,7 +5325,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@2900..2927 0: L_CURLY@2900..2901 "{" [] [] 1: CSS_RULE_LIST@2901..2925 - 0: CSS_RULE@2901..2925 + 0: CSS_QUALIFIED_RULE@2901..2925 0: CSS_SELECTOR_LIST@2901..2905 0: CSS_COMPOUND_SELECTOR@2901..2905 0: (empty) @@ -5367,7 +5367,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@2963..2990 0: L_CURLY@2963..2964 "{" [] [] 1: CSS_RULE_LIST@2964..2988 - 0: CSS_RULE@2964..2988 + 0: CSS_QUALIFIED_RULE@2964..2988 0: CSS_SELECTOR_LIST@2964..2968 0: CSS_COMPOUND_SELECTOR@2964..2968 0: (empty) @@ -5402,7 +5402,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@3010..3037 0: L_CURLY@3010..3011 "{" [] [] 1: CSS_RULE_LIST@3011..3035 - 0: CSS_RULE@3011..3035 + 0: CSS_QUALIFIED_RULE@3011..3035 0: CSS_SELECTOR_LIST@3011..3015 0: CSS_COMPOUND_SELECTOR@3011..3015 0: (empty) @@ -5446,7 +5446,7 @@ CssRoot { 2: CSS_RULE_LIST_BLOCK@3074..3119 0: L_CURLY@3074..3075 "{" [] [] 1: CSS_RULE_LIST@3075..3117 - 0: CSS_RULE@3075..3117 + 0: CSS_QUALIFIED_RULE@3075..3117 0: CSS_SELECTOR_LIST@3075..3086 0: CSS_COMPOUND_SELECTOR@3075..3086 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/class_selector.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/class_selector.css.snap index 258cf13a8840..9d9783b0d205 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/class_selector.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/class_selector.css.snap @@ -17,7 +17,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -49,7 +49,7 @@ CssRoot { 0: CSS_ROOT@0..11 0: (empty) 1: CSS_RULE_LIST@0..10 - 0: CSS_RULE@0..10 + 0: CSS_QUALIFIED_RULE@0..10 0: CSS_SELECTOR_LIST@0..8 0: CSS_COMPOUND_SELECTOR@0..8 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/declaration_list.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/declaration_list.css.snap index ef1d903566cc..5cc7f1e83081 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/declaration_list.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/declaration_list.css.snap @@ -62,7 +62,7 @@ div { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -112,7 +112,7 @@ CssRoot { r_curly_token: R_CURLY@39..41 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -148,7 +148,7 @@ CssRoot { r_curly_token: R_CURLY@62..64 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -188,7 +188,7 @@ CssRoot { r_curly_token: R_CURLY@85..87 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -238,7 +238,7 @@ CssRoot { r_curly_token: R_CURLY@109..111 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -298,7 +298,7 @@ CssRoot { r_curly_token: R_CURLY@135..137 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -361,7 +361,7 @@ CssRoot { r_curly_token: R_CURLY@163..165 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -402,7 +402,7 @@ CssRoot { r_curly_token: R_CURLY@186..188 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -437,7 +437,7 @@ CssRoot { r_curly_token: R_CURLY@214..216 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -481,7 +481,7 @@ CssRoot { r_curly_token: R_CURLY@251..253 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -534,7 +534,7 @@ CssRoot { r_curly_token: R_CURLY@328..330 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -641,7 +641,7 @@ CssRoot { 0: CSS_ROOT@0..456 0: (empty) 1: CSS_RULE_LIST@0..455 - 0: CSS_RULE@0..41 + 0: CSS_QUALIFIED_RULE@0..41 0: CSS_SELECTOR_LIST@0..2 0: CSS_COMPOUND_SELECTOR@0..2 0: (empty) @@ -674,7 +674,7 @@ CssRoot { 1: (empty) 3: SEMICOLON@38..39 ";" [] [] 2: R_CURLY@39..41 "}" [Newline("\n")] [] - 1: CSS_RULE@41..64 + 1: CSS_QUALIFIED_RULE@41..64 0: CSS_SELECTOR_LIST@41..45 0: CSS_COMPOUND_SELECTOR@41..45 0: (empty) @@ -698,7 +698,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@61..62 ";" [] [] 2: R_CURLY@62..64 "}" [Newline("\n")] [] - 2: CSS_RULE@64..87 + 2: CSS_QUALIFIED_RULE@64..87 0: CSS_SELECTOR_LIST@64..68 0: CSS_COMPOUND_SELECTOR@64..68 0: (empty) @@ -725,7 +725,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@84..85 ";" [] [] 2: R_CURLY@85..87 "}" [Newline("\n")] [] - 3: CSS_RULE@87..111 + 3: CSS_QUALIFIED_RULE@87..111 0: CSS_SELECTOR_LIST@87..91 0: CSS_COMPOUND_SELECTOR@87..91 0: (empty) @@ -757,7 +757,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@108..109 ";" [] [] 2: R_CURLY@109..111 "}" [Newline("\n")] [] - 4: CSS_RULE@111..137 + 4: CSS_QUALIFIED_RULE@111..137 0: CSS_SELECTOR_LIST@111..115 0: CSS_COMPOUND_SELECTOR@111..115 0: (empty) @@ -795,7 +795,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@134..135 ";" [] [] 2: R_CURLY@135..137 "}" [Newline("\n")] [] - 5: CSS_RULE@137..165 + 5: CSS_QUALIFIED_RULE@137..165 0: CSS_SELECTOR_LIST@137..141 0: CSS_COMPOUND_SELECTOR@137..141 0: (empty) @@ -835,7 +835,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@162..163 ";" [] [] 2: R_CURLY@163..165 "}" [Newline("\n")] [] - 6: CSS_RULE@165..188 + 6: CSS_QUALIFIED_RULE@165..188 0: CSS_SELECTOR_LIST@165..169 0: CSS_COMPOUND_SELECTOR@165..169 0: (empty) @@ -862,7 +862,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@185..186 ";" [] [] 2: R_CURLY@186..188 "}" [Newline("\n")] [] - 7: CSS_RULE@188..216 + 7: CSS_QUALIFIED_RULE@188..216 0: CSS_SELECTOR_LIST@188..192 0: CSS_COMPOUND_SELECTOR@188..192 0: (empty) @@ -885,7 +885,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@213..214 ";" [] [] 2: R_CURLY@214..216 "}" [Newline("\n")] [] - 8: CSS_RULE@216..253 + 8: CSS_QUALIFIED_RULE@216..253 0: CSS_SELECTOR_LIST@216..222 0: CSS_COMPOUND_SELECTOR@216..222 0: (empty) @@ -914,7 +914,7 @@ CssRoot { 1: IMPORTANT_KW@241..250 "important" [] [] 1: SEMICOLON@250..251 ";" [] [] 2: R_CURLY@251..253 "}" [Newline("\n")] [] - 9: CSS_RULE@253..330 + 9: CSS_QUALIFIED_RULE@253..330 0: CSS_SELECTOR_LIST@253..259 0: CSS_COMPOUND_SELECTOR@253..259 0: (empty) @@ -948,7 +948,7 @@ CssRoot { 1: IMPORTANT_KW@318..327 "important" [] [] 1: SEMICOLON@327..328 ";" [] [] 2: R_CURLY@328..330 "}" [Newline("\n")] [] - 10: CSS_RULE@330..455 + 10: CSS_QUALIFIED_RULE@330..455 0: CSS_SELECTOR_LIST@330..336 0: CSS_COMPOUND_SELECTOR@330..336 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/dimension.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/dimension.css.snap index 469255dd0ca9..875939da4e60 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/dimension.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/dimension.css.snap @@ -96,7 +96,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1270,7 +1270,7 @@ CssRoot { r_curly_token: R_CURLY@915..917 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1335,7 +1335,7 @@ CssRoot { 0: CSS_ROOT@0..979 0: (empty) 1: CSS_RULE_LIST@0..979 - 0: CSS_RULE@0..917 + 0: CSS_QUALIFIED_RULE@0..917 0: CSS_SELECTOR_LIST@0..10 0: CSS_COMPOUND_SELECTOR@0..10 0: (empty) @@ -2141,7 +2141,7 @@ CssRoot { 1: (empty) 143: SEMICOLON@914..915 ";" [] [] 2: R_CURLY@915..917 "}" [Newline("\n")] [] - 1: CSS_RULE@917..979 + 1: CSS_QUALIFIED_RULE@917..979 0: CSS_SELECTOR_LIST@917..939 0: CSS_COMPOUND_SELECTOR@917..939 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/function/calc.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/function/calc.css.snap index 4cbacdc95ed9..149b3c638acb 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/function/calc.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/function/calc.css.snap @@ -164,7 +164,7 @@ div { width: calc(50% - ( ( 4% ) * 0.5 ) ); } CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -229,7 +229,7 @@ CssRoot { r_curly_token: R_CURLY@40..42 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1408,7 +1408,7 @@ CssRoot { r_curly_token: R_CURLY@846..848 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1826,7 +1826,7 @@ CssRoot { r_curly_token: R_CURLY@1103..1105 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1950,7 +1950,7 @@ CssRoot { r_curly_token: R_CURLY@1214..1216 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1969,7 +1969,7 @@ CssRoot { r_curly_token: R_CURLY@1223..1320 "}" [Newline("\n"), Whitespace(" "), Comments("/*height: -webkit-cal ..."), Newline("\n"), Whitespace(" "), Comments("/*width: -moz-calc((5 ..."), Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2030,7 +2030,7 @@ CssRoot { r_curly_token: R_CURLY@1350..1351 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2092,7 +2092,7 @@ CssRoot { r_curly_token: R_CURLY@1389..1390 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2172,7 +2172,7 @@ CssRoot { r_curly_token: R_CURLY@1435..1436 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2296,7 +2296,7 @@ CssRoot { r_curly_token: R_CURLY@1549..1551 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2395,7 +2395,7 @@ CssRoot { r_curly_token: R_CURLY@1706..1708 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2505,7 +2505,7 @@ CssRoot { r_curly_token: R_CURLY@1835..1837 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3428,7 +3428,7 @@ CssRoot { r_curly_token: R_CURLY@2475..2477 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3642,7 +3642,7 @@ CssRoot { r_curly_token: R_CURLY@2633..2635 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3706,7 +3706,7 @@ CssRoot { r_curly_token: R_CURLY@2671..2673 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3825,7 +3825,7 @@ CssRoot { r_curly_token: R_CURLY@2757..2759 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3944,7 +3944,7 @@ CssRoot { r_curly_token: R_CURLY@2843..2845 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -4067,7 +4067,7 @@ CssRoot { r_curly_token: R_CURLY@2927..2929 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -4190,7 +4190,7 @@ CssRoot { r_curly_token: R_CURLY@3011..3013 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -4263,7 +4263,7 @@ CssRoot { r_curly_token: R_CURLY@3059..3061 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -4340,7 +4340,7 @@ CssRoot { r_curly_token: R_CURLY@3108..3110 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -4392,7 +4392,7 @@ CssRoot { r_curly_token: R_CURLY@3140..3142 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -4454,7 +4454,7 @@ CssRoot { r_curly_token: R_CURLY@3174..3176 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -4533,7 +4533,7 @@ CssRoot { r_curly_token: R_CURLY@3232..3234 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -4847,7 +4847,7 @@ CssRoot { 0: CSS_ROOT@0..3559 0: (empty) 1: CSS_RULE_LIST@0..3559 - 0: CSS_RULE@0..42 + 0: CSS_QUALIFIED_RULE@0..42 0: CSS_SELECTOR_LIST@0..6 0: CSS_COMPOUND_SELECTOR@0..6 0: (empty) @@ -4888,7 +4888,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@39..40 ";" [] [] 2: R_CURLY@40..42 "}" [Newline("\n")] [] - 1: CSS_RULE@42..848 + 1: CSS_QUALIFIED_RULE@42..848 0: CSS_SELECTOR_LIST@42..48 0: CSS_COMPOUND_SELECTOR@42..48 0: (empty) @@ -5635,7 +5635,7 @@ CssRoot { 1: (empty) 45: SEMICOLON@845..846 ";" [] [] 2: R_CURLY@846..848 "}" [Newline("\n")] [] - 2: CSS_RULE@848..1105 + 2: CSS_QUALIFIED_RULE@848..1105 0: CSS_SELECTOR_LIST@848..855 0: CSS_COMPOUND_SELECTOR@848..855 0: (empty) @@ -5895,7 +5895,7 @@ CssRoot { 1: (empty) 11: SEMICOLON@1102..1103 ";" [] [] 2: R_CURLY@1103..1105 "}" [Newline("\n")] [] - 3: CSS_RULE@1105..1216 + 3: CSS_QUALIFIED_RULE@1105..1216 0: CSS_SELECTOR_LIST@1105..1113 0: CSS_COMPOUND_SELECTOR@1105..1113 0: (empty) @@ -5974,7 +5974,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@1213..1214 ";" [] [] 2: R_CURLY@1214..1216 "}" [Newline("\n")] [] - 4: CSS_RULE@1216..1320 + 4: CSS_QUALIFIED_RULE@1216..1320 0: CSS_SELECTOR_LIST@1216..1222 0: CSS_COMPOUND_SELECTOR@1216..1222 0: (empty) @@ -5987,7 +5987,7 @@ CssRoot { 0: L_CURLY@1222..1223 "{" [] [] 1: CSS_DECLARATION_LIST@1223..1223 2: R_CURLY@1223..1320 "}" [Newline("\n"), Whitespace(" "), Comments("/*height: -webkit-cal ..."), Newline("\n"), Whitespace(" "), Comments("/*width: -moz-calc((5 ..."), Newline("\n")] [] - 5: CSS_RULE@1320..1351 + 5: CSS_QUALIFIED_RULE@1320..1351 0: CSS_SELECTOR_LIST@1320..1325 0: CSS_COMPOUND_SELECTOR@1320..1325 0: (empty) @@ -6026,7 +6026,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@1348..1350 ";" [] [Whitespace(" ")] 2: R_CURLY@1350..1351 "}" [] [] - 6: CSS_RULE@1351..1390 + 6: CSS_QUALIFIED_RULE@1351..1390 0: CSS_SELECTOR_LIST@1351..1356 0: CSS_COMPOUND_SELECTOR@1351..1356 0: (empty) @@ -6066,7 +6066,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@1387..1389 ";" [] [Whitespace(" ")] 2: R_CURLY@1389..1390 "}" [] [] - 7: CSS_RULE@1390..1436 + 7: CSS_QUALIFIED_RULE@1390..1436 0: CSS_SELECTOR_LIST@1390..1395 0: CSS_COMPOUND_SELECTOR@1390..1395 0: (empty) @@ -6118,7 +6118,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@1433..1435 ";" [] [Whitespace(" ")] 2: R_CURLY@1435..1436 "}" [] [] - 8: CSS_RULE@1436..1551 + 8: CSS_QUALIFIED_RULE@1436..1551 0: CSS_SELECTOR_LIST@1436..1444 0: CSS_COMPOUND_SELECTOR@1436..1444 0: (empty) @@ -6197,7 +6197,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@1548..1549 ";" [] [] 2: R_CURLY@1549..1551 "}" [Newline("\n")] [] - 9: CSS_RULE@1551..1708 + 9: CSS_QUALIFIED_RULE@1551..1708 0: CSS_SELECTOR_LIST@1551..1559 0: CSS_COMPOUND_SELECTOR@1551..1559 0: (empty) @@ -6260,7 +6260,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@1705..1706 ";" [] [] 2: R_CURLY@1706..1708 "}" [Newline("\n")] [] - 10: CSS_RULE@1708..1837 + 10: CSS_QUALIFIED_RULE@1708..1837 0: CSS_SELECTOR_LIST@1708..1716 0: CSS_COMPOUND_SELECTOR@1708..1716 0: (empty) @@ -6330,7 +6330,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@1834..1835 ";" [] [] 2: R_CURLY@1835..1837 "}" [Newline("\n")] [] - 11: CSS_RULE@1837..2477 + 11: CSS_QUALIFIED_RULE@1837..2477 0: CSS_SELECTOR_LIST@1837..1845 0: CSS_COMPOUND_SELECTOR@1837..1845 0: (empty) @@ -6909,7 +6909,7 @@ CssRoot { 1: (empty) 35: SEMICOLON@2474..2475 ";" [] [] 2: R_CURLY@2475..2477 "}" [Newline("\n")] [] - 12: CSS_RULE@2477..2635 + 12: CSS_QUALIFIED_RULE@2477..2635 0: CSS_SELECTOR_LIST@2477..2488 0: CSS_COMPOUND_SELECTOR@2477..2488 0: (empty) @@ -7046,7 +7046,7 @@ CssRoot { 1: (empty) 5: SEMICOLON@2632..2633 ";" [] [] 2: R_CURLY@2633..2635 "}" [Newline("\n")] [] - 13: CSS_RULE@2635..2673 + 13: CSS_QUALIFIED_RULE@2635..2673 0: CSS_SELECTOR_LIST@2635..2642 0: CSS_COMPOUND_SELECTOR@2635..2642 0: (empty) @@ -7087,7 +7087,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@2670..2671 ";" [] [] 2: R_CURLY@2671..2673 "}" [Newline("\n")] [] - 14: CSS_RULE@2673..2759 + 14: CSS_QUALIFIED_RULE@2673..2759 0: CSS_SELECTOR_LIST@2673..2680 0: CSS_COMPOUND_SELECTOR@2673..2680 0: (empty) @@ -7162,7 +7162,7 @@ CssRoot { 1: (empty) 3: SEMICOLON@2756..2757 ";" [] [] 2: R_CURLY@2757..2759 "}" [Newline("\n")] [] - 15: CSS_RULE@2759..2845 + 15: CSS_QUALIFIED_RULE@2759..2845 0: CSS_SELECTOR_LIST@2759..2766 0: CSS_COMPOUND_SELECTOR@2759..2766 0: (empty) @@ -7237,7 +7237,7 @@ CssRoot { 1: (empty) 3: SEMICOLON@2842..2843 ";" [] [] 2: R_CURLY@2843..2845 "}" [Newline("\n")] [] - 16: CSS_RULE@2845..2929 + 16: CSS_QUALIFIED_RULE@2845..2929 0: CSS_SELECTOR_LIST@2845..2853 0: CSS_COMPOUND_SELECTOR@2845..2853 0: (empty) @@ -7314,7 +7314,7 @@ CssRoot { 1: (empty) 3: SEMICOLON@2926..2927 ";" [] [] 2: R_CURLY@2927..2929 "}" [Newline("\n")] [] - 17: CSS_RULE@2929..3013 + 17: CSS_QUALIFIED_RULE@2929..3013 0: CSS_SELECTOR_LIST@2929..2937 0: CSS_COMPOUND_SELECTOR@2929..2937 0: (empty) @@ -7391,7 +7391,7 @@ CssRoot { 1: (empty) 3: SEMICOLON@3010..3011 ";" [] [] 2: R_CURLY@3011..3013 "}" [Newline("\n")] [] - 18: CSS_RULE@3013..3061 + 18: CSS_QUALIFIED_RULE@3013..3061 0: CSS_SELECTOR_LIST@3013..3021 0: CSS_COMPOUND_SELECTOR@3013..3021 0: (empty) @@ -7437,7 +7437,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@3058..3059 ";" [] [] 2: R_CURLY@3059..3061 "}" [Newline("\n")] [] - 19: CSS_RULE@3061..3110 + 19: CSS_QUALIFIED_RULE@3061..3110 0: CSS_SELECTOR_LIST@3061..3070 0: CSS_COMPOUND_SELECTOR@3061..3070 0: (empty) @@ -7485,7 +7485,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@3107..3108 ";" [] [] 2: R_CURLY@3108..3110 "}" [Newline("\n")] [] - 20: CSS_RULE@3110..3142 + 20: CSS_QUALIFIED_RULE@3110..3142 0: CSS_SELECTOR_LIST@3110..3118 0: CSS_COMPOUND_SELECTOR@3110..3118 0: (empty) @@ -7518,7 +7518,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@3139..3140 ";" [] [] 2: R_CURLY@3140..3142 "}" [Newline("\n")] [] - 21: CSS_RULE@3142..3176 + 21: CSS_QUALIFIED_RULE@3142..3176 0: CSS_SELECTOR_LIST@3142..3149 0: CSS_COMPOUND_SELECTOR@3142..3149 0: (empty) @@ -7557,7 +7557,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@3173..3174 ";" [] [] 2: R_CURLY@3174..3176 "}" [Newline("\n")] [] - 22: CSS_RULE@3176..3234 + 22: CSS_QUALIFIED_RULE@3176..3234 0: CSS_SELECTOR_LIST@3176..3183 0: CSS_COMPOUND_SELECTOR@3176..3183 0: (empty) @@ -7607,7 +7607,7 @@ CssRoot { 1: (empty) 1: SEMICOLON@3231..3232 ";" [] [] 2: R_CURLY@3232..3234 "}" [Newline("\n")] [] - 23: CSS_RULE@3234..3559 + 23: CSS_QUALIFIED_RULE@3234..3559 0: CSS_SELECTOR_LIST@3234..3242 0: CSS_COMPOUND_SELECTOR@3234..3242 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/function/linear-gradient.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/function/linear-gradient.css.snap index 8e3f7befa0ac..c750ffadd5fe 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/function/linear-gradient.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/function/linear-gradient.css.snap @@ -30,7 +30,7 @@ a { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -768,7 +768,7 @@ CssRoot { 0: CSS_ROOT@0..748 0: (empty) 1: CSS_RULE_LIST@0..748 - 0: CSS_RULE@0..748 + 0: CSS_QUALIFIED_RULE@0..748 0: CSS_SELECTOR_LIST@0..2 0: CSS_COMPOUND_SELECTOR@0..2 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/function/unknow.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/function/unknow.css.snap index 696826d1bcaa..754f1ba3bc30 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/function/unknow.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/function/unknow.css.snap @@ -20,7 +20,7 @@ div { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -144,7 +144,7 @@ CssRoot { 0: CSS_ROOT@0..78 0: (empty) 1: CSS_RULE_LIST@0..78 - 0: CSS_RULE@0..78 + 0: CSS_QUALIFIED_RULE@0..78 0: CSS_SELECTOR_LIST@0..4 0: CSS_COMPOUND_SELECTOR@0..4 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/function/url.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/function/url.css.snap index 34ab2ca176b3..f42479f205fe 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/function/url.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/function/url.css.snap @@ -64,7 +64,7 @@ a { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -658,7 +658,7 @@ CssRoot { 0: CSS_ROOT@0..1251 0: (empty) 1: CSS_RULE_LIST@0..1251 - 0: CSS_RULE@0..1251 + 0: CSS_QUALIFIED_RULE@0..1251 0: CSS_SELECTOR_LIST@0..2 0: CSS_COMPOUND_SELECTOR@0..2 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/function/var.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/function/var.css.snap index c034c814283e..eefa483a66b8 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/function/var.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/function/var.css.snap @@ -20,7 +20,7 @@ div { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -152,7 +152,7 @@ CssRoot { 0: CSS_ROOT@0..77 0: (empty) 1: CSS_RULE_LIST@0..77 - 0: CSS_RULE@0..77 + 0: CSS_QUALIFIED_RULE@0..77 0: CSS_SELECTOR_LIST@0..4 0: CSS_COMPOUND_SELECTOR@0..4 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/property/property_all.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/property/property_all.css.snap index c5a30e53c063..0a7660106719 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/property/property_all.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/property/property_all.css.snap @@ -32,7 +32,7 @@ div { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -273,7 +273,7 @@ CssRoot { 0: CSS_ROOT@0..335 0: (empty) 1: CSS_RULE_LIST@0..335 - 0: CSS_RULE@0..335 + 0: CSS_QUALIFIED_RULE@0..335 0: CSS_SELECTOR_LIST@0..4 0: CSS_COMPOUND_SELECTOR@0..4 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/property/property_border.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/property/property_border.css.snap index d58bf49ef9ec..7321d369b067 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/property/property_border.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/property/property_border.css.snap @@ -65,7 +65,7 @@ div { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -746,7 +746,7 @@ CssRoot { 0: CSS_ROOT@0..973 0: (empty) 1: CSS_RULE_LIST@0..972 - 0: CSS_RULE@0..972 + 0: CSS_QUALIFIED_RULE@0..972 0: CSS_SELECTOR_LIST@0..4 0: CSS_COMPOUND_SELECTOR@0..4 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/property/property_generic.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/property/property_generic.css.snap index c66f68d2b1b4..bba5d8a83d60 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/property/property_generic.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/property/property_generic.css.snap @@ -41,7 +41,7 @@ div { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -487,7 +487,7 @@ CssRoot { 0: CSS_ROOT@0..827 0: (empty) 1: CSS_RULE_LIST@0..827 - 0: CSS_RULE@0..827 + 0: CSS_QUALIFIED_RULE@0..827 0: CSS_SELECTOR_LIST@0..4 0: CSS_COMPOUND_SELECTOR@0..4 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/property/property_z-index.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/property/property_z-index.css.snap index 36f5e072ec34..1dfb1603487c 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/property/property_z-index.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/property/property_z-index.css.snap @@ -43,7 +43,7 @@ div { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -362,7 +362,7 @@ CssRoot { 0: CSS_ROOT@0..528 0: (empty) 1: CSS_RULE_LIST@0..527 - 0: CSS_RULE@0..527 + 0: CSS_QUALIFIED_RULE@0..527 0: CSS_SELECTOR_LIST@0..4 0: CSS_COMPOUND_SELECTOR@0..4 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/attribute.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/attribute.css.snap index ae74f99c249f..74fb0e406864 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/attribute.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/attribute.css.snap @@ -64,7 +64,7 @@ a[href='te"s"t'] {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -90,7 +90,7 @@ CssRoot { r_curly_token: R_CURLY@9..10 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -124,7 +124,7 @@ CssRoot { r_curly_token: R_CURLY@24..25 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -158,7 +158,7 @@ CssRoot { r_curly_token: R_CURLY@41..42 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -192,7 +192,7 @@ CssRoot { r_curly_token: R_CURLY@62..63 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -226,7 +226,7 @@ CssRoot { r_curly_token: R_CURLY@91..92 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -260,7 +260,7 @@ CssRoot { r_curly_token: R_CURLY@110..111 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -294,7 +294,7 @@ CssRoot { r_curly_token: R_CURLY@126..127 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -328,7 +328,7 @@ CssRoot { r_curly_token: R_CURLY@141..142 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -362,7 +362,7 @@ CssRoot { r_curly_token: R_CURLY@159..160 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -396,7 +396,7 @@ CssRoot { r_curly_token: R_CURLY@180..181 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -430,7 +430,7 @@ CssRoot { r_curly_token: R_CURLY@207..208 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -464,7 +464,7 @@ CssRoot { r_curly_token: R_CURLY@234..235 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -498,7 +498,7 @@ CssRoot { r_curly_token: R_CURLY@254..255 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -532,7 +532,7 @@ CssRoot { r_curly_token: R_CURLY@274..275 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -558,7 +558,7 @@ CssRoot { r_curly_token: R_CURLY@283..284 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -584,7 +584,7 @@ CssRoot { r_curly_token: R_CURLY@294..295 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -610,7 +610,7 @@ CssRoot { r_curly_token: R_CURLY@309..310 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -649,7 +649,7 @@ CssRoot { r_curly_token: R_CURLY@332..333 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -688,7 +688,7 @@ CssRoot { r_curly_token: R_CURLY@363..364 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -722,7 +722,7 @@ CssRoot { r_curly_token: R_CURLY@392..393 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -756,7 +756,7 @@ CssRoot { r_curly_token: R_CURLY@427..428 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -790,7 +790,7 @@ CssRoot { r_curly_token: R_CURLY@468..469 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -816,7 +816,7 @@ CssRoot { r_curly_token: R_CURLY@484..485 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -850,7 +850,7 @@ CssRoot { r_curly_token: R_CURLY@504..505 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -888,7 +888,7 @@ CssRoot { r_curly_token: R_CURLY@525..526 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -920,7 +920,7 @@ CssRoot { r_curly_token: R_CURLY@540..541 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -952,7 +952,7 @@ CssRoot { r_curly_token: R_CURLY@558..559 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -986,7 +986,7 @@ CssRoot { r_curly_token: R_CURLY@575..576 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1031,7 +1031,7 @@ CssRoot { r_curly_token: R_CURLY@613..614 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1076,7 +1076,7 @@ CssRoot { r_curly_token: R_CURLY@654..655 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1115,7 +1115,7 @@ CssRoot { r_curly_token: R_CURLY@674..675 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1154,7 +1154,7 @@ CssRoot { r_curly_token: R_CURLY@694..695 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1180,7 +1180,7 @@ CssRoot { r_curly_token: R_CURLY@709..710 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1206,7 +1206,7 @@ CssRoot { r_curly_token: R_CURLY@726..727 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1232,7 +1232,7 @@ CssRoot { r_curly_token: R_CURLY@746..747 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1266,7 +1266,7 @@ CssRoot { r_curly_token: R_CURLY@758..759 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1300,7 +1300,7 @@ CssRoot { r_curly_token: R_CURLY@770..771 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1334,7 +1334,7 @@ CssRoot { r_curly_token: R_CURLY@782..783 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1368,7 +1368,7 @@ CssRoot { r_curly_token: R_CURLY@812..813 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1402,7 +1402,7 @@ CssRoot { r_curly_token: R_CURLY@826..827 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1441,7 +1441,7 @@ CssRoot { r_curly_token: R_CURLY@846..847 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1482,7 +1482,7 @@ CssRoot { r_curly_token: R_CURLY@868..869 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1523,7 +1523,7 @@ CssRoot { r_curly_token: R_CURLY@885..886 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1554,7 +1554,7 @@ CssRoot { r_curly_token: R_CURLY@896..897 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1585,7 +1585,7 @@ CssRoot { r_curly_token: R_CURLY@913..914 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1614,7 +1614,7 @@ CssRoot { r_curly_token: R_CURLY@923..924 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1643,7 +1643,7 @@ CssRoot { r_curly_token: R_CURLY@935..936 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1683,7 +1683,7 @@ CssRoot { 0: CSS_ROOT@0..953 0: (empty) 1: CSS_RULE_LIST@0..952 - 0: CSS_RULE@0..10 + 0: CSS_QUALIFIED_RULE@0..10 0: CSS_SELECTOR_LIST@0..8 0: CSS_COMPOUND_SELECTOR@0..8 0: (empty) @@ -1701,7 +1701,7 @@ CssRoot { 0: L_CURLY@8..9 "{" [] [] 1: CSS_DECLARATION_LIST@9..9 2: R_CURLY@9..10 "}" [] [] - 1: CSS_RULE@10..25 + 1: CSS_QUALIFIED_RULE@10..25 0: CSS_SELECTOR_LIST@10..23 0: CSS_COMPOUND_SELECTOR@10..23 0: (empty) @@ -1724,7 +1724,7 @@ CssRoot { 0: L_CURLY@23..24 "{" [] [] 1: CSS_DECLARATION_LIST@24..24 2: R_CURLY@24..25 "}" [] [] - 2: CSS_RULE@25..42 + 2: CSS_QUALIFIED_RULE@25..42 0: CSS_SELECTOR_LIST@25..40 0: CSS_COMPOUND_SELECTOR@25..40 0: (empty) @@ -1747,7 +1747,7 @@ CssRoot { 0: L_CURLY@40..41 "{" [] [] 1: CSS_DECLARATION_LIST@41..41 2: R_CURLY@41..42 "}" [] [] - 3: CSS_RULE@42..63 + 3: CSS_QUALIFIED_RULE@42..63 0: CSS_SELECTOR_LIST@42..61 0: CSS_COMPOUND_SELECTOR@42..61 0: (empty) @@ -1770,7 +1770,7 @@ CssRoot { 0: L_CURLY@61..62 "{" [] [] 1: CSS_DECLARATION_LIST@62..62 2: R_CURLY@62..63 "}" [] [] - 4: CSS_RULE@63..92 + 4: CSS_QUALIFIED_RULE@63..92 0: CSS_SELECTOR_LIST@63..90 0: CSS_COMPOUND_SELECTOR@63..90 0: (empty) @@ -1793,7 +1793,7 @@ CssRoot { 0: L_CURLY@90..91 "{" [] [] 1: CSS_DECLARATION_LIST@91..91 2: R_CURLY@91..92 "}" [] [] - 5: CSS_RULE@92..111 + 5: CSS_QUALIFIED_RULE@92..111 0: CSS_SELECTOR_LIST@92..109 0: CSS_COMPOUND_SELECTOR@92..109 0: (empty) @@ -1816,7 +1816,7 @@ CssRoot { 0: L_CURLY@109..110 "{" [] [] 1: CSS_DECLARATION_LIST@110..110 2: R_CURLY@110..111 "}" [] [] - 6: CSS_RULE@111..127 + 6: CSS_QUALIFIED_RULE@111..127 0: CSS_SELECTOR_LIST@111..125 0: CSS_COMPOUND_SELECTOR@111..125 0: (empty) @@ -1839,7 +1839,7 @@ CssRoot { 0: L_CURLY@125..126 "{" [] [] 1: CSS_DECLARATION_LIST@126..126 2: R_CURLY@126..127 "}" [] [] - 7: CSS_RULE@127..142 + 7: CSS_QUALIFIED_RULE@127..142 0: CSS_SELECTOR_LIST@127..140 0: CSS_COMPOUND_SELECTOR@127..140 0: (empty) @@ -1862,7 +1862,7 @@ CssRoot { 0: L_CURLY@140..141 "{" [] [] 1: CSS_DECLARATION_LIST@141..141 2: R_CURLY@141..142 "}" [] [] - 8: CSS_RULE@142..160 + 8: CSS_QUALIFIED_RULE@142..160 0: CSS_SELECTOR_LIST@142..158 0: CSS_COMPOUND_SELECTOR@142..158 0: (empty) @@ -1885,7 +1885,7 @@ CssRoot { 0: L_CURLY@158..159 "{" [] [] 1: CSS_DECLARATION_LIST@159..159 2: R_CURLY@159..160 "}" [] [] - 9: CSS_RULE@160..181 + 9: CSS_QUALIFIED_RULE@160..181 0: CSS_SELECTOR_LIST@160..179 0: CSS_COMPOUND_SELECTOR@160..179 0: (empty) @@ -1908,7 +1908,7 @@ CssRoot { 0: L_CURLY@179..180 "{" [] [] 1: CSS_DECLARATION_LIST@180..180 2: R_CURLY@180..181 "}" [] [] - 10: CSS_RULE@181..208 + 10: CSS_QUALIFIED_RULE@181..208 0: CSS_SELECTOR_LIST@181..206 0: CSS_COMPOUND_SELECTOR@181..206 0: (empty) @@ -1931,7 +1931,7 @@ CssRoot { 0: L_CURLY@206..207 "{" [] [] 1: CSS_DECLARATION_LIST@207..207 2: R_CURLY@207..208 "}" [] [] - 11: CSS_RULE@208..235 + 11: CSS_QUALIFIED_RULE@208..235 0: CSS_SELECTOR_LIST@208..233 0: CSS_COMPOUND_SELECTOR@208..233 0: (empty) @@ -1954,7 +1954,7 @@ CssRoot { 0: L_CURLY@233..234 "{" [] [] 1: CSS_DECLARATION_LIST@234..234 2: R_CURLY@234..235 "}" [] [] - 12: CSS_RULE@235..255 + 12: CSS_QUALIFIED_RULE@235..255 0: CSS_SELECTOR_LIST@235..253 0: CSS_COMPOUND_SELECTOR@235..253 0: (empty) @@ -1977,7 +1977,7 @@ CssRoot { 0: L_CURLY@253..254 "{" [] [] 1: CSS_DECLARATION_LIST@254..254 2: R_CURLY@254..255 "}" [] [] - 13: CSS_RULE@255..275 + 13: CSS_QUALIFIED_RULE@255..275 0: CSS_SELECTOR_LIST@255..273 0: CSS_COMPOUND_SELECTOR@255..273 0: (empty) @@ -2000,7 +2000,7 @@ CssRoot { 0: L_CURLY@273..274 "{" [] [] 1: CSS_DECLARATION_LIST@274..274 2: R_CURLY@274..275 "}" [] [] - 14: CSS_RULE@275..284 + 14: CSS_QUALIFIED_RULE@275..284 0: CSS_SELECTOR_LIST@275..282 0: CSS_COMPOUND_SELECTOR@275..282 0: (empty) @@ -2018,7 +2018,7 @@ CssRoot { 0: L_CURLY@282..283 "{" [] [] 1: CSS_DECLARATION_LIST@283..283 2: R_CURLY@283..284 "}" [] [] - 15: CSS_RULE@284..295 + 15: CSS_QUALIFIED_RULE@284..295 0: CSS_SELECTOR_LIST@284..293 0: CSS_COMPOUND_SELECTOR@284..293 0: (empty) @@ -2036,7 +2036,7 @@ CssRoot { 0: L_CURLY@293..294 "{" [] [] 1: CSS_DECLARATION_LIST@294..294 2: R_CURLY@294..295 "}" [] [] - 16: CSS_RULE@295..310 + 16: CSS_QUALIFIED_RULE@295..310 0: CSS_SELECTOR_LIST@295..308 0: CSS_COMPOUND_SELECTOR@295..308 0: (empty) @@ -2054,7 +2054,7 @@ CssRoot { 0: L_CURLY@308..309 "{" [] [] 1: CSS_DECLARATION_LIST@309..309 2: R_CURLY@309..310 "}" [] [] - 17: CSS_RULE@310..333 + 17: CSS_QUALIFIED_RULE@310..333 0: CSS_SELECTOR_LIST@310..331 0: CSS_COMPOUND_SELECTOR@310..331 0: (empty) @@ -2080,7 +2080,7 @@ CssRoot { 0: L_CURLY@331..332 "{" [] [] 1: CSS_DECLARATION_LIST@332..332 2: R_CURLY@332..333 "}" [] [] - 18: CSS_RULE@333..364 + 18: CSS_QUALIFIED_RULE@333..364 0: CSS_SELECTOR_LIST@333..362 0: CSS_COMPOUND_SELECTOR@333..362 0: (empty) @@ -2106,7 +2106,7 @@ CssRoot { 0: L_CURLY@362..363 "{" [] [] 1: CSS_DECLARATION_LIST@363..363 2: R_CURLY@363..364 "}" [] [] - 19: CSS_RULE@364..393 + 19: CSS_QUALIFIED_RULE@364..393 0: CSS_SELECTOR_LIST@364..391 0: CSS_COMPOUND_SELECTOR@364..391 0: (empty) @@ -2129,7 +2129,7 @@ CssRoot { 0: L_CURLY@391..392 "{" [] [] 1: CSS_DECLARATION_LIST@392..392 2: R_CURLY@392..393 "}" [] [] - 20: CSS_RULE@393..428 + 20: CSS_QUALIFIED_RULE@393..428 0: CSS_SELECTOR_LIST@393..426 0: CSS_COMPOUND_SELECTOR@393..426 0: (empty) @@ -2152,7 +2152,7 @@ CssRoot { 0: L_CURLY@426..427 "{" [] [] 1: CSS_DECLARATION_LIST@427..427 2: R_CURLY@427..428 "}" [] [] - 21: CSS_RULE@428..469 + 21: CSS_QUALIFIED_RULE@428..469 0: CSS_SELECTOR_LIST@428..467 0: CSS_COMPOUND_SELECTOR@428..467 0: (empty) @@ -2175,7 +2175,7 @@ CssRoot { 0: L_CURLY@467..468 "{" [] [] 1: CSS_DECLARATION_LIST@468..468 2: R_CURLY@468..469 "}" [] [] - 22: CSS_RULE@469..485 + 22: CSS_QUALIFIED_RULE@469..485 0: CSS_SELECTOR_LIST@469..483 0: CSS_COMPOUND_SELECTOR@469..483 0: (empty) @@ -2193,7 +2193,7 @@ CssRoot { 0: L_CURLY@483..484 "{" [] [] 1: CSS_DECLARATION_LIST@484..484 2: R_CURLY@484..485 "}" [] [] - 23: CSS_RULE@485..505 + 23: CSS_QUALIFIED_RULE@485..505 0: CSS_SELECTOR_LIST@485..503 0: CSS_COMPOUND_SELECTOR@485..503 0: (empty) @@ -2216,7 +2216,7 @@ CssRoot { 0: L_CURLY@503..504 "{" [] [] 1: CSS_DECLARATION_LIST@504..504 2: R_CURLY@504..505 "}" [] [] - 24: CSS_RULE@505..526 + 24: CSS_QUALIFIED_RULE@505..526 0: CSS_SELECTOR_LIST@505..524 0: CSS_COMPOUND_SELECTOR@505..524 0: (empty) @@ -2242,7 +2242,7 @@ CssRoot { 0: L_CURLY@524..525 "{" [] [] 1: CSS_DECLARATION_LIST@525..525 2: R_CURLY@525..526 "}" [] [] - 25: CSS_RULE@526..541 + 25: CSS_QUALIFIED_RULE@526..541 0: CSS_SELECTOR_LIST@526..539 0: CSS_COMPOUND_SELECTOR@526..539 0: (empty) @@ -2264,7 +2264,7 @@ CssRoot { 0: L_CURLY@539..540 "{" [] [] 1: CSS_DECLARATION_LIST@540..540 2: R_CURLY@540..541 "}" [] [] - 26: CSS_RULE@541..559 + 26: CSS_QUALIFIED_RULE@541..559 0: CSS_SELECTOR_LIST@541..557 0: CSS_COMPOUND_SELECTOR@541..557 0: (empty) @@ -2286,7 +2286,7 @@ CssRoot { 0: L_CURLY@557..558 "{" [] [] 1: CSS_DECLARATION_LIST@558..558 2: R_CURLY@558..559 "}" [] [] - 27: CSS_RULE@559..576 + 27: CSS_QUALIFIED_RULE@559..576 0: CSS_SELECTOR_LIST@559..574 0: CSS_COMPOUND_SELECTOR@559..574 0: (empty) @@ -2309,7 +2309,7 @@ CssRoot { 0: L_CURLY@574..575 "{" [] [] 1: CSS_DECLARATION_LIST@575..575 2: R_CURLY@575..576 "}" [] [] - 28: CSS_RULE@576..614 + 28: CSS_QUALIFIED_RULE@576..614 0: CSS_SELECTOR_LIST@576..612 0: CSS_COMPOUND_SELECTOR@576..612 0: (empty) @@ -2340,7 +2340,7 @@ CssRoot { 0: L_CURLY@612..613 "{" [] [] 1: CSS_DECLARATION_LIST@613..613 2: R_CURLY@613..614 "}" [] [] - 29: CSS_RULE@614..655 + 29: CSS_QUALIFIED_RULE@614..655 0: CSS_SELECTOR_LIST@614..653 0: CSS_COMPOUND_SELECTOR@614..653 0: (empty) @@ -2371,7 +2371,7 @@ CssRoot { 0: L_CURLY@653..654 "{" [] [] 1: CSS_DECLARATION_LIST@654..654 2: R_CURLY@654..655 "}" [] [] - 30: CSS_RULE@655..675 + 30: CSS_QUALIFIED_RULE@655..675 0: CSS_SELECTOR_LIST@655..673 0: CSS_COMPOUND_SELECTOR@655..673 0: (empty) @@ -2397,7 +2397,7 @@ CssRoot { 0: L_CURLY@673..674 "{" [] [] 1: CSS_DECLARATION_LIST@674..674 2: R_CURLY@674..675 "}" [] [] - 31: CSS_RULE@675..695 + 31: CSS_QUALIFIED_RULE@675..695 0: CSS_SELECTOR_LIST@675..693 0: CSS_COMPOUND_SELECTOR@675..693 0: (empty) @@ -2423,7 +2423,7 @@ CssRoot { 0: L_CURLY@693..694 "{" [] [] 1: CSS_DECLARATION_LIST@694..694 2: R_CURLY@694..695 "}" [] [] - 32: CSS_RULE@695..710 + 32: CSS_QUALIFIED_RULE@695..710 0: CSS_SELECTOR_LIST@695..708 0: CSS_COMPOUND_SELECTOR@695..708 0: (empty) @@ -2441,7 +2441,7 @@ CssRoot { 0: L_CURLY@708..709 "{" [] [] 1: CSS_DECLARATION_LIST@709..709 2: R_CURLY@709..710 "}" [] [] - 33: CSS_RULE@710..727 + 33: CSS_QUALIFIED_RULE@710..727 0: CSS_SELECTOR_LIST@710..725 0: CSS_COMPOUND_SELECTOR@710..725 0: (empty) @@ -2459,7 +2459,7 @@ CssRoot { 0: L_CURLY@725..726 "{" [] [] 1: CSS_DECLARATION_LIST@726..726 2: R_CURLY@726..727 "}" [] [] - 34: CSS_RULE@727..747 + 34: CSS_QUALIFIED_RULE@727..747 0: CSS_SELECTOR_LIST@727..745 0: CSS_COMPOUND_SELECTOR@727..745 0: (empty) @@ -2477,7 +2477,7 @@ CssRoot { 0: L_CURLY@745..746 "{" [] [] 1: CSS_DECLARATION_LIST@746..746 2: R_CURLY@746..747 "}" [] [] - 35: CSS_RULE@747..759 + 35: CSS_QUALIFIED_RULE@747..759 0: CSS_SELECTOR_LIST@747..757 0: CSS_COMPOUND_SELECTOR@747..757 0: (empty) @@ -2500,7 +2500,7 @@ CssRoot { 0: L_CURLY@757..758 "{" [] [] 1: CSS_DECLARATION_LIST@758..758 2: R_CURLY@758..759 "}" [] [] - 36: CSS_RULE@759..771 + 36: CSS_QUALIFIED_RULE@759..771 0: CSS_SELECTOR_LIST@759..769 0: CSS_COMPOUND_SELECTOR@759..769 0: (empty) @@ -2523,7 +2523,7 @@ CssRoot { 0: L_CURLY@769..770 "{" [] [] 1: CSS_DECLARATION_LIST@770..770 2: R_CURLY@770..771 "}" [] [] - 37: CSS_RULE@771..783 + 37: CSS_QUALIFIED_RULE@771..783 0: CSS_SELECTOR_LIST@771..781 0: CSS_COMPOUND_SELECTOR@771..781 0: (empty) @@ -2546,7 +2546,7 @@ CssRoot { 0: L_CURLY@781..782 "{" [] [] 1: CSS_DECLARATION_LIST@782..782 2: R_CURLY@782..783 "}" [] [] - 38: CSS_RULE@783..813 + 38: CSS_QUALIFIED_RULE@783..813 0: CSS_SELECTOR_LIST@783..811 0: CSS_COMPOUND_SELECTOR@783..811 0: (empty) @@ -2569,7 +2569,7 @@ CssRoot { 0: L_CURLY@811..812 "{" [] [] 1: CSS_DECLARATION_LIST@812..812 2: R_CURLY@812..813 "}" [] [] - 39: CSS_RULE@813..827 + 39: CSS_QUALIFIED_RULE@813..827 0: CSS_SELECTOR_LIST@813..824 0: CSS_COMPOUND_SELECTOR@813..824 0: (empty) @@ -2592,7 +2592,7 @@ CssRoot { 0: L_CURLY@824..826 "{" [] [Whitespace(" ")] 1: CSS_DECLARATION_LIST@826..826 2: R_CURLY@826..827 "}" [] [] - 40: CSS_RULE@827..847 + 40: CSS_QUALIFIED_RULE@827..847 0: CSS_SELECTOR_LIST@827..845 0: CSS_COMPOUND_SELECTOR@827..845 0: (empty) @@ -2618,7 +2618,7 @@ CssRoot { 0: L_CURLY@845..846 "{" [] [] 1: CSS_DECLARATION_LIST@846..846 2: R_CURLY@846..847 "}" [] [] - 41: CSS_RULE@847..869 + 41: CSS_QUALIFIED_RULE@847..869 0: CSS_SELECTOR_LIST@847..867 0: CSS_COMPOUND_SELECTOR@847..867 0: (empty) @@ -2645,7 +2645,7 @@ CssRoot { 0: L_CURLY@867..868 "{" [] [] 1: CSS_DECLARATION_LIST@868..868 2: R_CURLY@868..869 "}" [] [] - 42: CSS_RULE@869..886 + 42: CSS_QUALIFIED_RULE@869..886 0: CSS_SELECTOR_LIST@869..884 0: CSS_COMPOUND_SELECTOR@869..884 0: (empty) @@ -2672,7 +2672,7 @@ CssRoot { 0: L_CURLY@884..885 "{" [] [] 1: CSS_DECLARATION_LIST@885..885 2: R_CURLY@885..886 "}" [] [] - 43: CSS_RULE@886..897 + 43: CSS_QUALIFIED_RULE@886..897 0: CSS_SELECTOR_LIST@886..895 0: CSS_COMPOUND_SELECTOR@886..895 0: (empty) @@ -2693,7 +2693,7 @@ CssRoot { 0: L_CURLY@895..896 "{" [] [] 1: CSS_DECLARATION_LIST@896..896 2: R_CURLY@896..897 "}" [] [] - 44: CSS_RULE@897..914 + 44: CSS_QUALIFIED_RULE@897..914 0: CSS_SELECTOR_LIST@897..912 0: CSS_COMPOUND_SELECTOR@897..912 0: (empty) @@ -2714,7 +2714,7 @@ CssRoot { 0: L_CURLY@912..913 "{" [] [] 1: CSS_DECLARATION_LIST@913..913 2: R_CURLY@913..914 "}" [] [] - 45: CSS_RULE@914..924 + 45: CSS_QUALIFIED_RULE@914..924 0: CSS_SELECTOR_LIST@914..922 0: CSS_COMPOUND_SELECTOR@914..922 0: (empty) @@ -2734,7 +2734,7 @@ CssRoot { 0: L_CURLY@922..923 "{" [] [] 1: CSS_DECLARATION_LIST@923..923 2: R_CURLY@923..924 "}" [] [] - 46: CSS_RULE@924..936 + 46: CSS_QUALIFIED_RULE@924..936 0: CSS_SELECTOR_LIST@924..934 0: CSS_COMPOUND_SELECTOR@924..934 0: (empty) @@ -2754,7 +2754,7 @@ CssRoot { 0: L_CURLY@934..935 "{" [] [] 1: CSS_DECLARATION_LIST@935..935 2: R_CURLY@935..936 "}" [] [] - 47: CSS_RULE@936..952 + 47: CSS_QUALIFIED_RULE@936..952 0: CSS_SELECTOR_LIST@936..950 0: CSS_COMPOUND_SELECTOR@936..950 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/class.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/class.css.snap index 2858c6094b0e..eedeba0a5e8a 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/class.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/class.css.snap @@ -19,7 +19,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -77,7 +77,7 @@ CssRoot { 0: CSS_ROOT@0..34 0: (empty) 1: CSS_RULE_LIST@0..33 - 0: CSS_RULE@0..33 + 0: CSS_QUALIFIED_RULE@0..33 0: CSS_SELECTOR_LIST@0..29 0: CSS_COMPOUND_SELECTOR@0..9 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/complex.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/complex.css.snap index baedcddccf6c..038989b996c5 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/complex.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/complex.css.snap @@ -53,7 +53,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -89,7 +89,7 @@ CssRoot { r_curly_token: R_CURLY@13..14 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssComplexSelector { @@ -138,7 +138,7 @@ CssRoot { r_curly_token: R_CURLY@36..39 "}" [Newline("\n"), Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -174,7 +174,7 @@ CssRoot { r_curly_token: R_CURLY@54..55 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssComplexSelector { @@ -223,7 +223,7 @@ CssRoot { r_curly_token: R_CURLY@77..80 "}" [Newline("\n"), Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -259,7 +259,7 @@ CssRoot { r_curly_token: R_CURLY@95..96 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssComplexSelector { @@ -308,7 +308,7 @@ CssRoot { r_curly_token: R_CURLY@118..121 "}" [Newline("\n"), Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -344,7 +344,7 @@ CssRoot { r_curly_token: R_CURLY@137..138 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssComplexSelector { @@ -393,7 +393,7 @@ CssRoot { r_curly_token: R_CURLY@162..165 "}" [Newline("\n"), Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssComplexSelector { @@ -477,7 +477,7 @@ CssRoot { r_curly_token: R_CURLY@209..212 "}" [Newline("\n"), Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -513,7 +513,7 @@ CssRoot { r_curly_token: R_CURLY@225..226 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssComplexSelector { @@ -562,7 +562,7 @@ CssRoot { r_curly_token: R_CURLY@246..249 "}" [Newline("\n"), Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssComplexSelector { @@ -663,7 +663,7 @@ CssRoot { 0: CSS_ROOT@0..299 0: (empty) 1: CSS_RULE_LIST@0..298 - 0: CSS_RULE@0..14 + 0: CSS_QUALIFIED_RULE@0..14 0: CSS_SELECTOR_LIST@0..12 0: CSS_COMPLEX_SELECTOR@0..12 0: CSS_COMPOUND_SELECTOR@0..5 @@ -687,7 +687,7 @@ CssRoot { 0: L_CURLY@12..13 "{" [] [] 1: CSS_DECLARATION_LIST@13..13 2: R_CURLY@13..14 "}" [] [] - 1: CSS_RULE@14..39 + 1: CSS_QUALIFIED_RULE@14..39 0: CSS_SELECTOR_LIST@14..35 0: CSS_COMPLEX_SELECTOR@14..35 0: CSS_COMPLEX_SELECTOR@14..29 @@ -720,7 +720,7 @@ CssRoot { 0: L_CURLY@35..36 "{" [] [] 1: CSS_DECLARATION_LIST@36..36 2: R_CURLY@36..39 "}" [Newline("\n"), Newline("\n")] [] - 2: CSS_RULE@39..55 + 2: CSS_QUALIFIED_RULE@39..55 0: CSS_SELECTOR_LIST@39..53 0: CSS_COMPLEX_SELECTOR@39..53 0: CSS_COMPOUND_SELECTOR@39..46 @@ -744,7 +744,7 @@ CssRoot { 0: L_CURLY@53..54 "{" [] [] 1: CSS_DECLARATION_LIST@54..54 2: R_CURLY@54..55 "}" [] [] - 3: CSS_RULE@55..80 + 3: CSS_QUALIFIED_RULE@55..80 0: CSS_SELECTOR_LIST@55..76 0: CSS_COMPLEX_SELECTOR@55..76 0: CSS_COMPLEX_SELECTOR@55..70 @@ -777,7 +777,7 @@ CssRoot { 0: L_CURLY@76..77 "{" [] [] 1: CSS_DECLARATION_LIST@77..77 2: R_CURLY@77..80 "}" [Newline("\n"), Newline("\n")] [] - 4: CSS_RULE@80..96 + 4: CSS_QUALIFIED_RULE@80..96 0: CSS_SELECTOR_LIST@80..94 0: CSS_COMPLEX_SELECTOR@80..94 0: CSS_COMPOUND_SELECTOR@80..87 @@ -801,7 +801,7 @@ CssRoot { 0: L_CURLY@94..95 "{" [] [] 1: CSS_DECLARATION_LIST@95..95 2: R_CURLY@95..96 "}" [] [] - 5: CSS_RULE@96..121 + 5: CSS_QUALIFIED_RULE@96..121 0: CSS_SELECTOR_LIST@96..117 0: CSS_COMPLEX_SELECTOR@96..117 0: CSS_COMPLEX_SELECTOR@96..111 @@ -834,7 +834,7 @@ CssRoot { 0: L_CURLY@117..118 "{" [] [] 1: CSS_DECLARATION_LIST@118..118 2: R_CURLY@118..121 "}" [Newline("\n"), Newline("\n")] [] - 6: CSS_RULE@121..138 + 6: CSS_QUALIFIED_RULE@121..138 0: CSS_SELECTOR_LIST@121..136 0: CSS_COMPLEX_SELECTOR@121..136 0: CSS_COMPOUND_SELECTOR@121..128 @@ -858,7 +858,7 @@ CssRoot { 0: L_CURLY@136..137 "{" [] [] 1: CSS_DECLARATION_LIST@137..137 2: R_CURLY@137..138 "}" [] [] - 7: CSS_RULE@138..165 + 7: CSS_QUALIFIED_RULE@138..165 0: CSS_SELECTOR_LIST@138..161 0: CSS_COMPLEX_SELECTOR@138..161 0: CSS_COMPLEX_SELECTOR@138..154 @@ -891,7 +891,7 @@ CssRoot { 0: L_CURLY@161..162 "{" [] [] 1: CSS_DECLARATION_LIST@162..162 2: R_CURLY@162..165 "}" [Newline("\n"), Newline("\n")] [] - 8: CSS_RULE@165..212 + 8: CSS_QUALIFIED_RULE@165..212 0: CSS_SELECTOR_LIST@165..208 0: CSS_COMPLEX_SELECTOR@165..208 0: CSS_COMPLEX_SELECTOR@165..198 @@ -947,7 +947,7 @@ CssRoot { 0: L_CURLY@208..209 "{" [] [] 1: CSS_DECLARATION_LIST@209..209 2: R_CURLY@209..212 "}" [Newline("\n"), Newline("\n")] [] - 9: CSS_RULE@212..226 + 9: CSS_QUALIFIED_RULE@212..226 0: CSS_SELECTOR_LIST@212..224 0: CSS_COMPLEX_SELECTOR@212..224 0: CSS_COMPOUND_SELECTOR@212..218 @@ -971,7 +971,7 @@ CssRoot { 0: L_CURLY@224..225 "{" [] [] 1: CSS_DECLARATION_LIST@225..225 2: R_CURLY@225..226 "}" [] [] - 10: CSS_RULE@226..249 + 10: CSS_QUALIFIED_RULE@226..249 0: CSS_SELECTOR_LIST@226..245 0: CSS_COMPLEX_SELECTOR@226..245 0: CSS_COMPLEX_SELECTOR@226..239 @@ -1004,7 +1004,7 @@ CssRoot { 0: L_CURLY@245..246 "{" [] [] 1: CSS_DECLARATION_LIST@246..246 2: R_CURLY@246..249 "}" [Newline("\n"), Newline("\n")] [] - 11: CSS_RULE@249..298 + 11: CSS_QUALIFIED_RULE@249..298 0: CSS_SELECTOR_LIST@249..294 0: CSS_COMPLEX_SELECTOR@249..294 0: CSS_COMPLEX_SELECTOR@249..279 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/id.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/id.css.snap index 7ba7c13efed3..b323e325aa89 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/id.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/id.css.snap @@ -19,7 +19,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -77,7 +77,7 @@ CssRoot { 0: CSS_ROOT@0..34 0: (empty) 1: CSS_RULE_LIST@0..33 - 0: CSS_RULE@0..33 + 0: CSS_QUALIFIED_RULE@0..33 0: CSS_SELECTOR_LIST@0..29 0: CSS_COMPOUND_SELECTOR@0..9 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_an_plus_b.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_an_plus_b.css.snap index d51a83b43e8a..5288bac75d8f 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_an_plus_b.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_an_plus_b.css.snap @@ -124,7 +124,7 @@ tr:nth-child(even of :not([hidden])) {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -163,7 +163,7 @@ CssRoot { r_curly_token: R_CURLY@18..19 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -202,7 +202,7 @@ CssRoot { r_curly_token: R_CURLY@39..40 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -241,7 +241,7 @@ CssRoot { r_curly_token: R_CURLY@61..62 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -280,7 +280,7 @@ CssRoot { r_curly_token: R_CURLY@82..83 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -319,7 +319,7 @@ CssRoot { r_curly_token: R_CURLY@102..103 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -358,7 +358,7 @@ CssRoot { r_curly_token: R_CURLY@123..124 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -397,7 +397,7 @@ CssRoot { r_curly_token: R_CURLY@144..145 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -436,7 +436,7 @@ CssRoot { r_curly_token: R_CURLY@166..167 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -475,7 +475,7 @@ CssRoot { r_curly_token: R_CURLY@187..188 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -514,7 +514,7 @@ CssRoot { r_curly_token: R_CURLY@209..210 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -553,7 +553,7 @@ CssRoot { r_curly_token: R_CURLY@232..233 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -592,7 +592,7 @@ CssRoot { r_curly_token: R_CURLY@254..255 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -631,7 +631,7 @@ CssRoot { r_curly_token: R_CURLY@275..276 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -670,7 +670,7 @@ CssRoot { r_curly_token: R_CURLY@297..298 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -709,7 +709,7 @@ CssRoot { r_curly_token: R_CURLY@320..321 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -748,7 +748,7 @@ CssRoot { r_curly_token: R_CURLY@341..342 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -787,7 +787,7 @@ CssRoot { r_curly_token: R_CURLY@363..364 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -826,7 +826,7 @@ CssRoot { r_curly_token: R_CURLY@386..387 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -865,7 +865,7 @@ CssRoot { r_curly_token: R_CURLY@408..409 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -904,7 +904,7 @@ CssRoot { r_curly_token: R_CURLY@429..430 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -943,7 +943,7 @@ CssRoot { r_curly_token: R_CURLY@451..452 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -982,7 +982,7 @@ CssRoot { r_curly_token: R_CURLY@473..474 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1021,7 +1021,7 @@ CssRoot { r_curly_token: R_CURLY@496..497 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1058,7 +1058,7 @@ CssRoot { r_curly_token: R_CURLY@515..516 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1095,7 +1095,7 @@ CssRoot { r_curly_token: R_CURLY@535..536 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1132,7 +1132,7 @@ CssRoot { r_curly_token: R_CURLY@556..557 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1169,7 +1169,7 @@ CssRoot { r_curly_token: R_CURLY@576..577 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1206,7 +1206,7 @@ CssRoot { r_curly_token: R_CURLY@595..596 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1243,7 +1243,7 @@ CssRoot { r_curly_token: R_CURLY@615..616 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1280,7 +1280,7 @@ CssRoot { r_curly_token: R_CURLY@635..636 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1317,7 +1317,7 @@ CssRoot { r_curly_token: R_CURLY@656..657 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1354,7 +1354,7 @@ CssRoot { r_curly_token: R_CURLY@676..677 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1391,7 +1391,7 @@ CssRoot { r_curly_token: R_CURLY@697..698 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1428,7 +1428,7 @@ CssRoot { r_curly_token: R_CURLY@719..720 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1465,7 +1465,7 @@ CssRoot { r_curly_token: R_CURLY@740..741 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1502,7 +1502,7 @@ CssRoot { r_curly_token: R_CURLY@760..761 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1539,7 +1539,7 @@ CssRoot { r_curly_token: R_CURLY@781..782 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1576,7 +1576,7 @@ CssRoot { r_curly_token: R_CURLY@802..803 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1613,7 +1613,7 @@ CssRoot { r_curly_token: R_CURLY@824..825 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1650,7 +1650,7 @@ CssRoot { r_curly_token: R_CURLY@844..845 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1687,7 +1687,7 @@ CssRoot { r_curly_token: R_CURLY@865..866 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1724,7 +1724,7 @@ CssRoot { r_curly_token: R_CURLY@887..888 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1761,7 +1761,7 @@ CssRoot { r_curly_token: R_CURLY@908..909 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1798,7 +1798,7 @@ CssRoot { r_curly_token: R_CURLY@928..929 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1835,7 +1835,7 @@ CssRoot { r_curly_token: R_CURLY@949..950 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1872,7 +1872,7 @@ CssRoot { r_curly_token: R_CURLY@970..971 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1909,7 +1909,7 @@ CssRoot { r_curly_token: R_CURLY@992..993 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1941,7 +1941,7 @@ CssRoot { r_curly_token: R_CURLY@1009..1010 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1973,7 +1973,7 @@ CssRoot { r_curly_token: R_CURLY@1027..1028 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2005,7 +2005,7 @@ CssRoot { r_curly_token: R_CURLY@1045..1046 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2039,7 +2039,7 @@ CssRoot { r_curly_token: R_CURLY@1063..1064 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2073,7 +2073,7 @@ CssRoot { r_curly_token: R_CURLY@1082..1083 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2107,7 +2107,7 @@ CssRoot { r_curly_token: R_CURLY@1101..1102 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2139,7 +2139,7 @@ CssRoot { r_curly_token: R_CURLY@1118..1119 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2171,7 +2171,7 @@ CssRoot { r_curly_token: R_CURLY@1136..1137 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2203,7 +2203,7 @@ CssRoot { r_curly_token: R_CURLY@1154..1155 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2237,7 +2237,7 @@ CssRoot { r_curly_token: R_CURLY@1172..1173 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2271,7 +2271,7 @@ CssRoot { r_curly_token: R_CURLY@1191..1192 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2305,7 +2305,7 @@ CssRoot { r_curly_token: R_CURLY@1210..1211 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2337,7 +2337,7 @@ CssRoot { r_curly_token: R_CURLY@1227..1228 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2369,7 +2369,7 @@ CssRoot { r_curly_token: R_CURLY@1245..1246 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2401,7 +2401,7 @@ CssRoot { r_curly_token: R_CURLY@1263..1264 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2440,7 +2440,7 @@ CssRoot { r_curly_token: R_CURLY@1295..1296 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2479,7 +2479,7 @@ CssRoot { r_curly_token: R_CURLY@1316..1317 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2518,7 +2518,7 @@ CssRoot { r_curly_token: R_CURLY@1336..1337 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2547,7 +2547,7 @@ CssRoot { r_curly_token: R_CURLY@1356..1357 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2576,7 +2576,7 @@ CssRoot { r_curly_token: R_CURLY@1375..1376 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2605,7 +2605,7 @@ CssRoot { r_curly_token: R_CURLY@1394..1395 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2634,7 +2634,7 @@ CssRoot { r_curly_token: R_CURLY@1414..1415 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2663,7 +2663,7 @@ CssRoot { r_curly_token: R_CURLY@1434..1435 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2692,7 +2692,7 @@ CssRoot { r_curly_token: R_CURLY@1454..1455 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2731,7 +2731,7 @@ CssRoot { r_curly_token: R_CURLY@1507..1508 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2770,7 +2770,7 @@ CssRoot { r_curly_token: R_CURLY@1565..1566 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2809,7 +2809,7 @@ CssRoot { r_curly_token: R_CURLY@1594..1595 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2848,7 +2848,7 @@ CssRoot { r_curly_token: R_CURLY@1628..1629 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2887,7 +2887,7 @@ CssRoot { r_curly_token: R_CURLY@1650..1651 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2926,7 +2926,7 @@ CssRoot { r_curly_token: R_CURLY@1675..1676 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -2965,7 +2965,7 @@ CssRoot { r_curly_token: R_CURLY@1697..1698 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3004,7 +3004,7 @@ CssRoot { r_curly_token: R_CURLY@1724..1725 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3033,7 +3033,7 @@ CssRoot { r_curly_token: R_CURLY@1742..1743 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3072,7 +3072,7 @@ CssRoot { r_curly_token: R_CURLY@1760..1761 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3101,7 +3101,7 @@ CssRoot { r_curly_token: R_CURLY@1782..1783 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3140,7 +3140,7 @@ CssRoot { r_curly_token: R_CURLY@1805..1806 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3177,7 +3177,7 @@ CssRoot { r_curly_token: R_CURLY@1824..1825 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3214,7 +3214,7 @@ CssRoot { r_curly_token: R_CURLY@1842..1843 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3251,7 +3251,7 @@ CssRoot { r_curly_token: R_CURLY@1860..1861 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3288,7 +3288,7 @@ CssRoot { r_curly_token: R_CURLY@1877..1878 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3325,7 +3325,7 @@ CssRoot { r_curly_token: R_CURLY@1895..1896 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3362,7 +3362,7 @@ CssRoot { r_curly_token: R_CURLY@1913..1914 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3399,7 +3399,7 @@ CssRoot { r_curly_token: R_CURLY@1930..1931 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3436,7 +3436,7 @@ CssRoot { r_curly_token: R_CURLY@1948..1949 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3473,7 +3473,7 @@ CssRoot { r_curly_token: R_CURLY@1966..1967 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3526,7 +3526,7 @@ CssRoot { r_curly_token: R_CURLY@1993..1994 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3579,7 +3579,7 @@ CssRoot { r_curly_token: R_CURLY@2025..2026 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3645,7 +3645,7 @@ CssRoot { r_curly_token: R_CURLY@2057..2058 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3711,7 +3711,7 @@ CssRoot { r_curly_token: R_CURLY@2090..2091 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3769,7 +3769,7 @@ CssRoot { r_curly_token: R_CURLY@2126..2127 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -3840,7 +3840,7 @@ CssRoot { r_curly_token: R_CURLY@2166..2167 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -3905,7 +3905,7 @@ CssRoot { 0: CSS_ROOT@0..2195 0: (empty) 1: CSS_RULE_LIST@0..2194 - 0: CSS_RULE@0..19 + 0: CSS_QUALIFIED_RULE@0..19 0: CSS_SELECTOR_LIST@0..17 0: CSS_COMPOUND_SELECTOR@0..17 0: (empty) @@ -3932,7 +3932,7 @@ CssRoot { 0: L_CURLY@17..18 "{" [] [] 1: CSS_DECLARATION_LIST@18..18 2: R_CURLY@18..19 "}" [] [] - 1: CSS_RULE@19..40 + 1: CSS_QUALIFIED_RULE@19..40 0: CSS_SELECTOR_LIST@19..38 0: CSS_COMPOUND_SELECTOR@19..38 0: (empty) @@ -3959,7 +3959,7 @@ CssRoot { 0: L_CURLY@38..39 "{" [] [] 1: CSS_DECLARATION_LIST@39..39 2: R_CURLY@39..40 "}" [] [] - 2: CSS_RULE@40..62 + 2: CSS_QUALIFIED_RULE@40..62 0: CSS_SELECTOR_LIST@40..60 0: CSS_COMPOUND_SELECTOR@40..60 0: (empty) @@ -3986,7 +3986,7 @@ CssRoot { 0: L_CURLY@60..61 "{" [] [] 1: CSS_DECLARATION_LIST@61..61 2: R_CURLY@61..62 "}" [] [] - 3: CSS_RULE@62..83 + 3: CSS_QUALIFIED_RULE@62..83 0: CSS_SELECTOR_LIST@62..81 0: CSS_COMPOUND_SELECTOR@62..81 0: (empty) @@ -4013,7 +4013,7 @@ CssRoot { 0: L_CURLY@81..82 "{" [] [] 1: CSS_DECLARATION_LIST@82..82 2: R_CURLY@82..83 "}" [] [] - 4: CSS_RULE@83..103 + 4: CSS_QUALIFIED_RULE@83..103 0: CSS_SELECTOR_LIST@83..101 0: CSS_COMPOUND_SELECTOR@83..101 0: (empty) @@ -4040,7 +4040,7 @@ CssRoot { 0: L_CURLY@101..102 "{" [] [] 1: CSS_DECLARATION_LIST@102..102 2: R_CURLY@102..103 "}" [] [] - 5: CSS_RULE@103..124 + 5: CSS_QUALIFIED_RULE@103..124 0: CSS_SELECTOR_LIST@103..122 0: CSS_COMPOUND_SELECTOR@103..122 0: (empty) @@ -4067,7 +4067,7 @@ CssRoot { 0: L_CURLY@122..123 "{" [] [] 1: CSS_DECLARATION_LIST@123..123 2: R_CURLY@123..124 "}" [] [] - 6: CSS_RULE@124..145 + 6: CSS_QUALIFIED_RULE@124..145 0: CSS_SELECTOR_LIST@124..143 0: CSS_COMPOUND_SELECTOR@124..143 0: (empty) @@ -4094,7 +4094,7 @@ CssRoot { 0: L_CURLY@143..144 "{" [] [] 1: CSS_DECLARATION_LIST@144..144 2: R_CURLY@144..145 "}" [] [] - 7: CSS_RULE@145..167 + 7: CSS_QUALIFIED_RULE@145..167 0: CSS_SELECTOR_LIST@145..165 0: CSS_COMPOUND_SELECTOR@145..165 0: (empty) @@ -4121,7 +4121,7 @@ CssRoot { 0: L_CURLY@165..166 "{" [] [] 1: CSS_DECLARATION_LIST@166..166 2: R_CURLY@166..167 "}" [] [] - 8: CSS_RULE@167..188 + 8: CSS_QUALIFIED_RULE@167..188 0: CSS_SELECTOR_LIST@167..186 0: CSS_COMPOUND_SELECTOR@167..186 0: (empty) @@ -4148,7 +4148,7 @@ CssRoot { 0: L_CURLY@186..187 "{" [] [] 1: CSS_DECLARATION_LIST@187..187 2: R_CURLY@187..188 "}" [] [] - 9: CSS_RULE@188..210 + 9: CSS_QUALIFIED_RULE@188..210 0: CSS_SELECTOR_LIST@188..208 0: CSS_COMPOUND_SELECTOR@188..208 0: (empty) @@ -4175,7 +4175,7 @@ CssRoot { 0: L_CURLY@208..209 "{" [] [] 1: CSS_DECLARATION_LIST@209..209 2: R_CURLY@209..210 "}" [] [] - 10: CSS_RULE@210..233 + 10: CSS_QUALIFIED_RULE@210..233 0: CSS_SELECTOR_LIST@210..231 0: CSS_COMPOUND_SELECTOR@210..231 0: (empty) @@ -4202,7 +4202,7 @@ CssRoot { 0: L_CURLY@231..232 "{" [] [] 1: CSS_DECLARATION_LIST@232..232 2: R_CURLY@232..233 "}" [] [] - 11: CSS_RULE@233..255 + 11: CSS_QUALIFIED_RULE@233..255 0: CSS_SELECTOR_LIST@233..253 0: CSS_COMPOUND_SELECTOR@233..253 0: (empty) @@ -4229,7 +4229,7 @@ CssRoot { 0: L_CURLY@253..254 "{" [] [] 1: CSS_DECLARATION_LIST@254..254 2: R_CURLY@254..255 "}" [] [] - 12: CSS_RULE@255..276 + 12: CSS_QUALIFIED_RULE@255..276 0: CSS_SELECTOR_LIST@255..274 0: CSS_COMPOUND_SELECTOR@255..274 0: (empty) @@ -4256,7 +4256,7 @@ CssRoot { 0: L_CURLY@274..275 "{" [] [] 1: CSS_DECLARATION_LIST@275..275 2: R_CURLY@275..276 "}" [] [] - 13: CSS_RULE@276..298 + 13: CSS_QUALIFIED_RULE@276..298 0: CSS_SELECTOR_LIST@276..296 0: CSS_COMPOUND_SELECTOR@276..296 0: (empty) @@ -4283,7 +4283,7 @@ CssRoot { 0: L_CURLY@296..297 "{" [] [] 1: CSS_DECLARATION_LIST@297..297 2: R_CURLY@297..298 "}" [] [] - 14: CSS_RULE@298..321 + 14: CSS_QUALIFIED_RULE@298..321 0: CSS_SELECTOR_LIST@298..319 0: CSS_COMPOUND_SELECTOR@298..319 0: (empty) @@ -4310,7 +4310,7 @@ CssRoot { 0: L_CURLY@319..320 "{" [] [] 1: CSS_DECLARATION_LIST@320..320 2: R_CURLY@320..321 "}" [] [] - 15: CSS_RULE@321..342 + 15: CSS_QUALIFIED_RULE@321..342 0: CSS_SELECTOR_LIST@321..340 0: CSS_COMPOUND_SELECTOR@321..340 0: (empty) @@ -4337,7 +4337,7 @@ CssRoot { 0: L_CURLY@340..341 "{" [] [] 1: CSS_DECLARATION_LIST@341..341 2: R_CURLY@341..342 "}" [] [] - 16: CSS_RULE@342..364 + 16: CSS_QUALIFIED_RULE@342..364 0: CSS_SELECTOR_LIST@342..362 0: CSS_COMPOUND_SELECTOR@342..362 0: (empty) @@ -4364,7 +4364,7 @@ CssRoot { 0: L_CURLY@362..363 "{" [] [] 1: CSS_DECLARATION_LIST@363..363 2: R_CURLY@363..364 "}" [] [] - 17: CSS_RULE@364..387 + 17: CSS_QUALIFIED_RULE@364..387 0: CSS_SELECTOR_LIST@364..385 0: CSS_COMPOUND_SELECTOR@364..385 0: (empty) @@ -4391,7 +4391,7 @@ CssRoot { 0: L_CURLY@385..386 "{" [] [] 1: CSS_DECLARATION_LIST@386..386 2: R_CURLY@386..387 "}" [] [] - 18: CSS_RULE@387..409 + 18: CSS_QUALIFIED_RULE@387..409 0: CSS_SELECTOR_LIST@387..407 0: CSS_COMPOUND_SELECTOR@387..407 0: (empty) @@ -4418,7 +4418,7 @@ CssRoot { 0: L_CURLY@407..408 "{" [] [] 1: CSS_DECLARATION_LIST@408..408 2: R_CURLY@408..409 "}" [] [] - 19: CSS_RULE@409..430 + 19: CSS_QUALIFIED_RULE@409..430 0: CSS_SELECTOR_LIST@409..428 0: CSS_COMPOUND_SELECTOR@409..428 0: (empty) @@ -4445,7 +4445,7 @@ CssRoot { 0: L_CURLY@428..429 "{" [] [] 1: CSS_DECLARATION_LIST@429..429 2: R_CURLY@429..430 "}" [] [] - 20: CSS_RULE@430..452 + 20: CSS_QUALIFIED_RULE@430..452 0: CSS_SELECTOR_LIST@430..450 0: CSS_COMPOUND_SELECTOR@430..450 0: (empty) @@ -4472,7 +4472,7 @@ CssRoot { 0: L_CURLY@450..451 "{" [] [] 1: CSS_DECLARATION_LIST@451..451 2: R_CURLY@451..452 "}" [] [] - 21: CSS_RULE@452..474 + 21: CSS_QUALIFIED_RULE@452..474 0: CSS_SELECTOR_LIST@452..472 0: CSS_COMPOUND_SELECTOR@452..472 0: (empty) @@ -4499,7 +4499,7 @@ CssRoot { 0: L_CURLY@472..473 "{" [] [] 1: CSS_DECLARATION_LIST@473..473 2: R_CURLY@473..474 "}" [] [] - 22: CSS_RULE@474..497 + 22: CSS_QUALIFIED_RULE@474..497 0: CSS_SELECTOR_LIST@474..495 0: CSS_COMPOUND_SELECTOR@474..495 0: (empty) @@ -4526,7 +4526,7 @@ CssRoot { 0: L_CURLY@495..496 "{" [] [] 1: CSS_DECLARATION_LIST@496..496 2: R_CURLY@496..497 "}" [] [] - 23: CSS_RULE@497..516 + 23: CSS_QUALIFIED_RULE@497..516 0: CSS_SELECTOR_LIST@497..514 0: CSS_COMPOUND_SELECTOR@497..514 0: (empty) @@ -4552,7 +4552,7 @@ CssRoot { 0: L_CURLY@514..515 "{" [] [] 1: CSS_DECLARATION_LIST@515..515 2: R_CURLY@515..516 "}" [] [] - 24: CSS_RULE@516..536 + 24: CSS_QUALIFIED_RULE@516..536 0: CSS_SELECTOR_LIST@516..534 0: CSS_COMPOUND_SELECTOR@516..534 0: (empty) @@ -4578,7 +4578,7 @@ CssRoot { 0: L_CURLY@534..535 "{" [] [] 1: CSS_DECLARATION_LIST@535..535 2: R_CURLY@535..536 "}" [] [] - 25: CSS_RULE@536..557 + 25: CSS_QUALIFIED_RULE@536..557 0: CSS_SELECTOR_LIST@536..555 0: CSS_COMPOUND_SELECTOR@536..555 0: (empty) @@ -4604,7 +4604,7 @@ CssRoot { 0: L_CURLY@555..556 "{" [] [] 1: CSS_DECLARATION_LIST@556..556 2: R_CURLY@556..557 "}" [] [] - 26: CSS_RULE@557..577 + 26: CSS_QUALIFIED_RULE@557..577 0: CSS_SELECTOR_LIST@557..575 0: CSS_COMPOUND_SELECTOR@557..575 0: (empty) @@ -4630,7 +4630,7 @@ CssRoot { 0: L_CURLY@575..576 "{" [] [] 1: CSS_DECLARATION_LIST@576..576 2: R_CURLY@576..577 "}" [] [] - 27: CSS_RULE@577..596 + 27: CSS_QUALIFIED_RULE@577..596 0: CSS_SELECTOR_LIST@577..594 0: CSS_COMPOUND_SELECTOR@577..594 0: (empty) @@ -4656,7 +4656,7 @@ CssRoot { 0: L_CURLY@594..595 "{" [] [] 1: CSS_DECLARATION_LIST@595..595 2: R_CURLY@595..596 "}" [] [] - 28: CSS_RULE@596..616 + 28: CSS_QUALIFIED_RULE@596..616 0: CSS_SELECTOR_LIST@596..614 0: CSS_COMPOUND_SELECTOR@596..614 0: (empty) @@ -4682,7 +4682,7 @@ CssRoot { 0: L_CURLY@614..615 "{" [] [] 1: CSS_DECLARATION_LIST@615..615 2: R_CURLY@615..616 "}" [] [] - 29: CSS_RULE@616..636 + 29: CSS_QUALIFIED_RULE@616..636 0: CSS_SELECTOR_LIST@616..634 0: CSS_COMPOUND_SELECTOR@616..634 0: (empty) @@ -4708,7 +4708,7 @@ CssRoot { 0: L_CURLY@634..635 "{" [] [] 1: CSS_DECLARATION_LIST@635..635 2: R_CURLY@635..636 "}" [] [] - 30: CSS_RULE@636..657 + 30: CSS_QUALIFIED_RULE@636..657 0: CSS_SELECTOR_LIST@636..655 0: CSS_COMPOUND_SELECTOR@636..655 0: (empty) @@ -4734,7 +4734,7 @@ CssRoot { 0: L_CURLY@655..656 "{" [] [] 1: CSS_DECLARATION_LIST@656..656 2: R_CURLY@656..657 "}" [] [] - 31: CSS_RULE@657..677 + 31: CSS_QUALIFIED_RULE@657..677 0: CSS_SELECTOR_LIST@657..675 0: CSS_COMPOUND_SELECTOR@657..675 0: (empty) @@ -4760,7 +4760,7 @@ CssRoot { 0: L_CURLY@675..676 "{" [] [] 1: CSS_DECLARATION_LIST@676..676 2: R_CURLY@676..677 "}" [] [] - 32: CSS_RULE@677..698 + 32: CSS_QUALIFIED_RULE@677..698 0: CSS_SELECTOR_LIST@677..696 0: CSS_COMPOUND_SELECTOR@677..696 0: (empty) @@ -4786,7 +4786,7 @@ CssRoot { 0: L_CURLY@696..697 "{" [] [] 1: CSS_DECLARATION_LIST@697..697 2: R_CURLY@697..698 "}" [] [] - 33: CSS_RULE@698..720 + 33: CSS_QUALIFIED_RULE@698..720 0: CSS_SELECTOR_LIST@698..718 0: CSS_COMPOUND_SELECTOR@698..718 0: (empty) @@ -4812,7 +4812,7 @@ CssRoot { 0: L_CURLY@718..719 "{" [] [] 1: CSS_DECLARATION_LIST@719..719 2: R_CURLY@719..720 "}" [] [] - 34: CSS_RULE@720..741 + 34: CSS_QUALIFIED_RULE@720..741 0: CSS_SELECTOR_LIST@720..739 0: CSS_COMPOUND_SELECTOR@720..739 0: (empty) @@ -4838,7 +4838,7 @@ CssRoot { 0: L_CURLY@739..740 "{" [] [] 1: CSS_DECLARATION_LIST@740..740 2: R_CURLY@740..741 "}" [] [] - 35: CSS_RULE@741..761 + 35: CSS_QUALIFIED_RULE@741..761 0: CSS_SELECTOR_LIST@741..759 0: CSS_COMPOUND_SELECTOR@741..759 0: (empty) @@ -4864,7 +4864,7 @@ CssRoot { 0: L_CURLY@759..760 "{" [] [] 1: CSS_DECLARATION_LIST@760..760 2: R_CURLY@760..761 "}" [] [] - 36: CSS_RULE@761..782 + 36: CSS_QUALIFIED_RULE@761..782 0: CSS_SELECTOR_LIST@761..780 0: CSS_COMPOUND_SELECTOR@761..780 0: (empty) @@ -4890,7 +4890,7 @@ CssRoot { 0: L_CURLY@780..781 "{" [] [] 1: CSS_DECLARATION_LIST@781..781 2: R_CURLY@781..782 "}" [] [] - 37: CSS_RULE@782..803 + 37: CSS_QUALIFIED_RULE@782..803 0: CSS_SELECTOR_LIST@782..801 0: CSS_COMPOUND_SELECTOR@782..801 0: (empty) @@ -4916,7 +4916,7 @@ CssRoot { 0: L_CURLY@801..802 "{" [] [] 1: CSS_DECLARATION_LIST@802..802 2: R_CURLY@802..803 "}" [] [] - 38: CSS_RULE@803..825 + 38: CSS_QUALIFIED_RULE@803..825 0: CSS_SELECTOR_LIST@803..823 0: CSS_COMPOUND_SELECTOR@803..823 0: (empty) @@ -4942,7 +4942,7 @@ CssRoot { 0: L_CURLY@823..824 "{" [] [] 1: CSS_DECLARATION_LIST@824..824 2: R_CURLY@824..825 "}" [] [] - 39: CSS_RULE@825..845 + 39: CSS_QUALIFIED_RULE@825..845 0: CSS_SELECTOR_LIST@825..843 0: CSS_COMPOUND_SELECTOR@825..843 0: (empty) @@ -4968,7 +4968,7 @@ CssRoot { 0: L_CURLY@843..844 "{" [] [] 1: CSS_DECLARATION_LIST@844..844 2: R_CURLY@844..845 "}" [] [] - 40: CSS_RULE@845..866 + 40: CSS_QUALIFIED_RULE@845..866 0: CSS_SELECTOR_LIST@845..864 0: CSS_COMPOUND_SELECTOR@845..864 0: (empty) @@ -4994,7 +4994,7 @@ CssRoot { 0: L_CURLY@864..865 "{" [] [] 1: CSS_DECLARATION_LIST@865..865 2: R_CURLY@865..866 "}" [] [] - 41: CSS_RULE@866..888 + 41: CSS_QUALIFIED_RULE@866..888 0: CSS_SELECTOR_LIST@866..886 0: CSS_COMPOUND_SELECTOR@866..886 0: (empty) @@ -5020,7 +5020,7 @@ CssRoot { 0: L_CURLY@886..887 "{" [] [] 1: CSS_DECLARATION_LIST@887..887 2: R_CURLY@887..888 "}" [] [] - 42: CSS_RULE@888..909 + 42: CSS_QUALIFIED_RULE@888..909 0: CSS_SELECTOR_LIST@888..907 0: CSS_COMPOUND_SELECTOR@888..907 0: (empty) @@ -5046,7 +5046,7 @@ CssRoot { 0: L_CURLY@907..908 "{" [] [] 1: CSS_DECLARATION_LIST@908..908 2: R_CURLY@908..909 "}" [] [] - 43: CSS_RULE@909..929 + 43: CSS_QUALIFIED_RULE@909..929 0: CSS_SELECTOR_LIST@909..927 0: CSS_COMPOUND_SELECTOR@909..927 0: (empty) @@ -5072,7 +5072,7 @@ CssRoot { 0: L_CURLY@927..928 "{" [] [] 1: CSS_DECLARATION_LIST@928..928 2: R_CURLY@928..929 "}" [] [] - 44: CSS_RULE@929..950 + 44: CSS_QUALIFIED_RULE@929..950 0: CSS_SELECTOR_LIST@929..948 0: CSS_COMPOUND_SELECTOR@929..948 0: (empty) @@ -5098,7 +5098,7 @@ CssRoot { 0: L_CURLY@948..949 "{" [] [] 1: CSS_DECLARATION_LIST@949..949 2: R_CURLY@949..950 "}" [] [] - 45: CSS_RULE@950..971 + 45: CSS_QUALIFIED_RULE@950..971 0: CSS_SELECTOR_LIST@950..969 0: CSS_COMPOUND_SELECTOR@950..969 0: (empty) @@ -5124,7 +5124,7 @@ CssRoot { 0: L_CURLY@969..970 "{" [] [] 1: CSS_DECLARATION_LIST@970..970 2: R_CURLY@970..971 "}" [] [] - 46: CSS_RULE@971..993 + 46: CSS_QUALIFIED_RULE@971..993 0: CSS_SELECTOR_LIST@971..991 0: CSS_COMPOUND_SELECTOR@971..991 0: (empty) @@ -5150,7 +5150,7 @@ CssRoot { 0: L_CURLY@991..992 "{" [] [] 1: CSS_DECLARATION_LIST@992..992 2: R_CURLY@992..993 "}" [] [] - 47: CSS_RULE@993..1010 + 47: CSS_QUALIFIED_RULE@993..1010 0: CSS_SELECTOR_LIST@993..1008 0: CSS_COMPOUND_SELECTOR@993..1008 0: (empty) @@ -5173,7 +5173,7 @@ CssRoot { 0: L_CURLY@1008..1009 "{" [] [] 1: CSS_DECLARATION_LIST@1009..1009 2: R_CURLY@1009..1010 "}" [] [] - 48: CSS_RULE@1010..1028 + 48: CSS_QUALIFIED_RULE@1010..1028 0: CSS_SELECTOR_LIST@1010..1026 0: CSS_COMPOUND_SELECTOR@1010..1026 0: (empty) @@ -5196,7 +5196,7 @@ CssRoot { 0: L_CURLY@1026..1027 "{" [] [] 1: CSS_DECLARATION_LIST@1027..1027 2: R_CURLY@1027..1028 "}" [] [] - 49: CSS_RULE@1028..1046 + 49: CSS_QUALIFIED_RULE@1028..1046 0: CSS_SELECTOR_LIST@1028..1044 0: CSS_COMPOUND_SELECTOR@1028..1044 0: (empty) @@ -5219,7 +5219,7 @@ CssRoot { 0: L_CURLY@1044..1045 "{" [] [] 1: CSS_DECLARATION_LIST@1045..1045 2: R_CURLY@1045..1046 "}" [] [] - 50: CSS_RULE@1046..1064 + 50: CSS_QUALIFIED_RULE@1046..1064 0: CSS_SELECTOR_LIST@1046..1062 0: CSS_COMPOUND_SELECTOR@1046..1062 0: (empty) @@ -5243,7 +5243,7 @@ CssRoot { 0: L_CURLY@1062..1063 "{" [] [] 1: CSS_DECLARATION_LIST@1063..1063 2: R_CURLY@1063..1064 "}" [] [] - 51: CSS_RULE@1064..1083 + 51: CSS_QUALIFIED_RULE@1064..1083 0: CSS_SELECTOR_LIST@1064..1081 0: CSS_COMPOUND_SELECTOR@1064..1081 0: (empty) @@ -5267,7 +5267,7 @@ CssRoot { 0: L_CURLY@1081..1082 "{" [] [] 1: CSS_DECLARATION_LIST@1082..1082 2: R_CURLY@1082..1083 "}" [] [] - 52: CSS_RULE@1083..1102 + 52: CSS_QUALIFIED_RULE@1083..1102 0: CSS_SELECTOR_LIST@1083..1100 0: CSS_COMPOUND_SELECTOR@1083..1100 0: (empty) @@ -5291,7 +5291,7 @@ CssRoot { 0: L_CURLY@1100..1101 "{" [] [] 1: CSS_DECLARATION_LIST@1101..1101 2: R_CURLY@1101..1102 "}" [] [] - 53: CSS_RULE@1102..1119 + 53: CSS_QUALIFIED_RULE@1102..1119 0: CSS_SELECTOR_LIST@1102..1117 0: CSS_COMPOUND_SELECTOR@1102..1117 0: (empty) @@ -5314,7 +5314,7 @@ CssRoot { 0: L_CURLY@1117..1118 "{" [] [] 1: CSS_DECLARATION_LIST@1118..1118 2: R_CURLY@1118..1119 "}" [] [] - 54: CSS_RULE@1119..1137 + 54: CSS_QUALIFIED_RULE@1119..1137 0: CSS_SELECTOR_LIST@1119..1135 0: CSS_COMPOUND_SELECTOR@1119..1135 0: (empty) @@ -5337,7 +5337,7 @@ CssRoot { 0: L_CURLY@1135..1136 "{" [] [] 1: CSS_DECLARATION_LIST@1136..1136 2: R_CURLY@1136..1137 "}" [] [] - 55: CSS_RULE@1137..1155 + 55: CSS_QUALIFIED_RULE@1137..1155 0: CSS_SELECTOR_LIST@1137..1153 0: CSS_COMPOUND_SELECTOR@1137..1153 0: (empty) @@ -5360,7 +5360,7 @@ CssRoot { 0: L_CURLY@1153..1154 "{" [] [] 1: CSS_DECLARATION_LIST@1154..1154 2: R_CURLY@1154..1155 "}" [] [] - 56: CSS_RULE@1155..1173 + 56: CSS_QUALIFIED_RULE@1155..1173 0: CSS_SELECTOR_LIST@1155..1171 0: CSS_COMPOUND_SELECTOR@1155..1171 0: (empty) @@ -5384,7 +5384,7 @@ CssRoot { 0: L_CURLY@1171..1172 "{" [] [] 1: CSS_DECLARATION_LIST@1172..1172 2: R_CURLY@1172..1173 "}" [] [] - 57: CSS_RULE@1173..1192 + 57: CSS_QUALIFIED_RULE@1173..1192 0: CSS_SELECTOR_LIST@1173..1190 0: CSS_COMPOUND_SELECTOR@1173..1190 0: (empty) @@ -5408,7 +5408,7 @@ CssRoot { 0: L_CURLY@1190..1191 "{" [] [] 1: CSS_DECLARATION_LIST@1191..1191 2: R_CURLY@1191..1192 "}" [] [] - 58: CSS_RULE@1192..1211 + 58: CSS_QUALIFIED_RULE@1192..1211 0: CSS_SELECTOR_LIST@1192..1209 0: CSS_COMPOUND_SELECTOR@1192..1209 0: (empty) @@ -5432,7 +5432,7 @@ CssRoot { 0: L_CURLY@1209..1210 "{" [] [] 1: CSS_DECLARATION_LIST@1210..1210 2: R_CURLY@1210..1211 "}" [] [] - 59: CSS_RULE@1211..1228 + 59: CSS_QUALIFIED_RULE@1211..1228 0: CSS_SELECTOR_LIST@1211..1226 0: CSS_COMPOUND_SELECTOR@1211..1226 0: (empty) @@ -5454,7 +5454,7 @@ CssRoot { 0: L_CURLY@1226..1227 "{" [] [] 1: CSS_DECLARATION_LIST@1227..1227 2: R_CURLY@1227..1228 "}" [] [] - 60: CSS_RULE@1228..1246 + 60: CSS_QUALIFIED_RULE@1228..1246 0: CSS_SELECTOR_LIST@1228..1244 0: CSS_COMPOUND_SELECTOR@1228..1244 0: (empty) @@ -5476,7 +5476,7 @@ CssRoot { 0: L_CURLY@1244..1245 "{" [] [] 1: CSS_DECLARATION_LIST@1245..1245 2: R_CURLY@1245..1246 "}" [] [] - 61: CSS_RULE@1246..1264 + 61: CSS_QUALIFIED_RULE@1246..1264 0: CSS_SELECTOR_LIST@1246..1262 0: CSS_COMPOUND_SELECTOR@1246..1262 0: (empty) @@ -5498,7 +5498,7 @@ CssRoot { 0: L_CURLY@1262..1263 "{" [] [] 1: CSS_DECLARATION_LIST@1263..1263 2: R_CURLY@1263..1264 "}" [] [] - 62: CSS_RULE@1264..1296 + 62: CSS_QUALIFIED_RULE@1264..1296 0: CSS_SELECTOR_LIST@1264..1294 0: CSS_COMPOUND_SELECTOR@1264..1294 0: (empty) @@ -5525,7 +5525,7 @@ CssRoot { 0: L_CURLY@1294..1295 "{" [] [] 1: CSS_DECLARATION_LIST@1295..1295 2: R_CURLY@1295..1296 "}" [] [] - 63: CSS_RULE@1296..1317 + 63: CSS_QUALIFIED_RULE@1296..1317 0: CSS_SELECTOR_LIST@1296..1315 0: CSS_COMPOUND_SELECTOR@1296..1315 0: (empty) @@ -5552,7 +5552,7 @@ CssRoot { 0: L_CURLY@1315..1316 "{" [] [] 1: CSS_DECLARATION_LIST@1316..1316 2: R_CURLY@1316..1317 "}" [] [] - 64: CSS_RULE@1317..1337 + 64: CSS_QUALIFIED_RULE@1317..1337 0: CSS_SELECTOR_LIST@1317..1335 0: CSS_COMPOUND_SELECTOR@1317..1335 0: (empty) @@ -5579,7 +5579,7 @@ CssRoot { 0: L_CURLY@1335..1336 "{" [] [] 1: CSS_DECLARATION_LIST@1336..1336 2: R_CURLY@1336..1337 "}" [] [] - 65: CSS_RULE@1337..1357 + 65: CSS_QUALIFIED_RULE@1337..1357 0: CSS_SELECTOR_LIST@1337..1355 0: CSS_COMPOUND_SELECTOR@1337..1355 0: (empty) @@ -5599,7 +5599,7 @@ CssRoot { 0: L_CURLY@1355..1356 "{" [] [] 1: CSS_DECLARATION_LIST@1356..1356 2: R_CURLY@1356..1357 "}" [] [] - 66: CSS_RULE@1357..1376 + 66: CSS_QUALIFIED_RULE@1357..1376 0: CSS_SELECTOR_LIST@1357..1374 0: CSS_COMPOUND_SELECTOR@1357..1374 0: (empty) @@ -5619,7 +5619,7 @@ CssRoot { 0: L_CURLY@1374..1375 "{" [] [] 1: CSS_DECLARATION_LIST@1375..1375 2: R_CURLY@1375..1376 "}" [] [] - 67: CSS_RULE@1376..1395 + 67: CSS_QUALIFIED_RULE@1376..1395 0: CSS_SELECTOR_LIST@1376..1393 0: CSS_COMPOUND_SELECTOR@1376..1393 0: (empty) @@ -5639,7 +5639,7 @@ CssRoot { 0: L_CURLY@1393..1394 "{" [] [] 1: CSS_DECLARATION_LIST@1394..1394 2: R_CURLY@1394..1395 "}" [] [] - 68: CSS_RULE@1395..1415 + 68: CSS_QUALIFIED_RULE@1395..1415 0: CSS_SELECTOR_LIST@1395..1413 0: CSS_COMPOUND_SELECTOR@1395..1413 0: (empty) @@ -5659,7 +5659,7 @@ CssRoot { 0: L_CURLY@1413..1414 "{" [] [] 1: CSS_DECLARATION_LIST@1414..1414 2: R_CURLY@1414..1415 "}" [] [] - 69: CSS_RULE@1415..1435 + 69: CSS_QUALIFIED_RULE@1415..1435 0: CSS_SELECTOR_LIST@1415..1433 0: CSS_COMPOUND_SELECTOR@1415..1433 0: (empty) @@ -5679,7 +5679,7 @@ CssRoot { 0: L_CURLY@1433..1434 "{" [] [] 1: CSS_DECLARATION_LIST@1434..1434 2: R_CURLY@1434..1435 "}" [] [] - 70: CSS_RULE@1435..1455 + 70: CSS_QUALIFIED_RULE@1435..1455 0: CSS_SELECTOR_LIST@1435..1453 0: CSS_COMPOUND_SELECTOR@1435..1453 0: (empty) @@ -5699,7 +5699,7 @@ CssRoot { 0: L_CURLY@1453..1454 "{" [] [] 1: CSS_DECLARATION_LIST@1454..1454 2: R_CURLY@1454..1455 "}" [] [] - 71: CSS_RULE@1455..1508 + 71: CSS_QUALIFIED_RULE@1455..1508 0: CSS_SELECTOR_LIST@1455..1506 0: CSS_COMPOUND_SELECTOR@1455..1506 0: (empty) @@ -5726,7 +5726,7 @@ CssRoot { 0: L_CURLY@1506..1507 "{" [] [] 1: CSS_DECLARATION_LIST@1507..1507 2: R_CURLY@1507..1508 "}" [] [] - 72: CSS_RULE@1508..1566 + 72: CSS_QUALIFIED_RULE@1508..1566 0: CSS_SELECTOR_LIST@1508..1564 0: CSS_COMPOUND_SELECTOR@1508..1564 0: (empty) @@ -5753,7 +5753,7 @@ CssRoot { 0: L_CURLY@1564..1565 "{" [] [] 1: CSS_DECLARATION_LIST@1565..1565 2: R_CURLY@1565..1566 "}" [] [] - 73: CSS_RULE@1566..1595 + 73: CSS_QUALIFIED_RULE@1566..1595 0: CSS_SELECTOR_LIST@1566..1593 0: CSS_COMPOUND_SELECTOR@1566..1593 0: (empty) @@ -5780,7 +5780,7 @@ CssRoot { 0: L_CURLY@1593..1594 "{" [] [] 1: CSS_DECLARATION_LIST@1594..1594 2: R_CURLY@1594..1595 "}" [] [] - 74: CSS_RULE@1595..1629 + 74: CSS_QUALIFIED_RULE@1595..1629 0: CSS_SELECTOR_LIST@1595..1627 0: CSS_COMPOUND_SELECTOR@1595..1627 0: (empty) @@ -5807,7 +5807,7 @@ CssRoot { 0: L_CURLY@1627..1628 "{" [] [] 1: CSS_DECLARATION_LIST@1628..1628 2: R_CURLY@1628..1629 "}" [] [] - 75: CSS_RULE@1629..1651 + 75: CSS_QUALIFIED_RULE@1629..1651 0: CSS_SELECTOR_LIST@1629..1649 0: CSS_COMPOUND_SELECTOR@1629..1649 0: (empty) @@ -5834,7 +5834,7 @@ CssRoot { 0: L_CURLY@1649..1650 "{" [] [] 1: CSS_DECLARATION_LIST@1650..1650 2: R_CURLY@1650..1651 "}" [] [] - 76: CSS_RULE@1651..1676 + 76: CSS_QUALIFIED_RULE@1651..1676 0: CSS_SELECTOR_LIST@1651..1674 0: CSS_COMPOUND_SELECTOR@1651..1674 0: (empty) @@ -5861,7 +5861,7 @@ CssRoot { 0: L_CURLY@1674..1675 "{" [] [] 1: CSS_DECLARATION_LIST@1675..1675 2: R_CURLY@1675..1676 "}" [] [] - 77: CSS_RULE@1676..1698 + 77: CSS_QUALIFIED_RULE@1676..1698 0: CSS_SELECTOR_LIST@1676..1696 0: CSS_COMPOUND_SELECTOR@1676..1696 0: (empty) @@ -5888,7 +5888,7 @@ CssRoot { 0: L_CURLY@1696..1697 "{" [] [] 1: CSS_DECLARATION_LIST@1697..1697 2: R_CURLY@1697..1698 "}" [] [] - 78: CSS_RULE@1698..1725 + 78: CSS_QUALIFIED_RULE@1698..1725 0: CSS_SELECTOR_LIST@1698..1723 0: CSS_COMPOUND_SELECTOR@1698..1723 0: (empty) @@ -5915,7 +5915,7 @@ CssRoot { 0: L_CURLY@1723..1724 "{" [] [] 1: CSS_DECLARATION_LIST@1724..1724 2: R_CURLY@1724..1725 "}" [] [] - 79: CSS_RULE@1725..1743 + 79: CSS_QUALIFIED_RULE@1725..1743 0: CSS_SELECTOR_LIST@1725..1741 0: CSS_COMPOUND_SELECTOR@1725..1741 0: (empty) @@ -5935,7 +5935,7 @@ CssRoot { 0: L_CURLY@1741..1742 "{" [] [] 1: CSS_DECLARATION_LIST@1742..1742 2: R_CURLY@1742..1743 "}" [] [] - 80: CSS_RULE@1743..1761 + 80: CSS_QUALIFIED_RULE@1743..1761 0: CSS_SELECTOR_LIST@1743..1759 0: CSS_COMPOUND_SELECTOR@1743..1759 0: (empty) @@ -5962,7 +5962,7 @@ CssRoot { 0: L_CURLY@1759..1760 "{" [] [] 1: CSS_DECLARATION_LIST@1760..1760 2: R_CURLY@1760..1761 "}" [] [] - 81: CSS_RULE@1761..1783 + 81: CSS_QUALIFIED_RULE@1761..1783 0: CSS_SELECTOR_LIST@1761..1781 0: CSS_COMPOUND_SELECTOR@1761..1781 0: (empty) @@ -5982,7 +5982,7 @@ CssRoot { 0: L_CURLY@1781..1782 "{" [] [] 1: CSS_DECLARATION_LIST@1782..1782 2: R_CURLY@1782..1783 "}" [] [] - 82: CSS_RULE@1783..1806 + 82: CSS_QUALIFIED_RULE@1783..1806 0: CSS_SELECTOR_LIST@1783..1804 0: CSS_COMPOUND_SELECTOR@1783..1804 0: (empty) @@ -6009,7 +6009,7 @@ CssRoot { 0: L_CURLY@1804..1805 "{" [] [] 1: CSS_DECLARATION_LIST@1805..1805 2: R_CURLY@1805..1806 "}" [] [] - 83: CSS_RULE@1806..1825 + 83: CSS_QUALIFIED_RULE@1806..1825 0: CSS_SELECTOR_LIST@1806..1823 0: CSS_COMPOUND_SELECTOR@1806..1823 0: (empty) @@ -6034,7 +6034,7 @@ CssRoot { 0: L_CURLY@1823..1824 "{" [] [] 1: CSS_DECLARATION_LIST@1824..1824 2: R_CURLY@1824..1825 "}" [] [] - 84: CSS_RULE@1825..1843 + 84: CSS_QUALIFIED_RULE@1825..1843 0: CSS_SELECTOR_LIST@1825..1841 0: CSS_COMPOUND_SELECTOR@1825..1841 0: (empty) @@ -6059,7 +6059,7 @@ CssRoot { 0: L_CURLY@1841..1842 "{" [] [] 1: CSS_DECLARATION_LIST@1842..1842 2: R_CURLY@1842..1843 "}" [] [] - 85: CSS_RULE@1843..1861 + 85: CSS_QUALIFIED_RULE@1843..1861 0: CSS_SELECTOR_LIST@1843..1859 0: CSS_COMPOUND_SELECTOR@1843..1859 0: (empty) @@ -6084,7 +6084,7 @@ CssRoot { 0: L_CURLY@1859..1860 "{" [] [] 1: CSS_DECLARATION_LIST@1860..1860 2: R_CURLY@1860..1861 "}" [] [] - 86: CSS_RULE@1861..1878 + 86: CSS_QUALIFIED_RULE@1861..1878 0: CSS_SELECTOR_LIST@1861..1876 0: CSS_COMPOUND_SELECTOR@1861..1876 0: (empty) @@ -6109,7 +6109,7 @@ CssRoot { 0: L_CURLY@1876..1877 "{" [] [] 1: CSS_DECLARATION_LIST@1877..1877 2: R_CURLY@1877..1878 "}" [] [] - 87: CSS_RULE@1878..1896 + 87: CSS_QUALIFIED_RULE@1878..1896 0: CSS_SELECTOR_LIST@1878..1894 0: CSS_COMPOUND_SELECTOR@1878..1894 0: (empty) @@ -6134,7 +6134,7 @@ CssRoot { 0: L_CURLY@1894..1895 "{" [] [] 1: CSS_DECLARATION_LIST@1895..1895 2: R_CURLY@1895..1896 "}" [] [] - 88: CSS_RULE@1896..1914 + 88: CSS_QUALIFIED_RULE@1896..1914 0: CSS_SELECTOR_LIST@1896..1912 0: CSS_COMPOUND_SELECTOR@1896..1912 0: (empty) @@ -6159,7 +6159,7 @@ CssRoot { 0: L_CURLY@1912..1913 "{" [] [] 1: CSS_DECLARATION_LIST@1913..1913 2: R_CURLY@1913..1914 "}" [] [] - 89: CSS_RULE@1914..1931 + 89: CSS_QUALIFIED_RULE@1914..1931 0: CSS_SELECTOR_LIST@1914..1929 0: CSS_COMPOUND_SELECTOR@1914..1929 0: (empty) @@ -6184,7 +6184,7 @@ CssRoot { 0: L_CURLY@1929..1930 "{" [] [] 1: CSS_DECLARATION_LIST@1930..1930 2: R_CURLY@1930..1931 "}" [] [] - 90: CSS_RULE@1931..1949 + 90: CSS_QUALIFIED_RULE@1931..1949 0: CSS_SELECTOR_LIST@1931..1947 0: CSS_COMPOUND_SELECTOR@1931..1947 0: (empty) @@ -6209,7 +6209,7 @@ CssRoot { 0: L_CURLY@1947..1948 "{" [] [] 1: CSS_DECLARATION_LIST@1948..1948 2: R_CURLY@1948..1949 "}" [] [] - 91: CSS_RULE@1949..1967 + 91: CSS_QUALIFIED_RULE@1949..1967 0: CSS_SELECTOR_LIST@1949..1965 0: CSS_COMPOUND_SELECTOR@1949..1965 0: (empty) @@ -6234,7 +6234,7 @@ CssRoot { 0: L_CURLY@1965..1966 "{" [] [] 1: CSS_DECLARATION_LIST@1966..1966 2: R_CURLY@1966..1967 "}" [] [] - 92: CSS_RULE@1967..1994 + 92: CSS_QUALIFIED_RULE@1967..1994 0: CSS_SELECTOR_LIST@1967..1992 0: CSS_COMPOUND_SELECTOR@1967..1992 0: (empty) @@ -6270,7 +6270,7 @@ CssRoot { 0: L_CURLY@1992..1993 "{" [] [] 1: CSS_DECLARATION_LIST@1993..1993 2: R_CURLY@1993..1994 "}" [] [] - 93: CSS_RULE@1994..2026 + 93: CSS_QUALIFIED_RULE@1994..2026 0: CSS_SELECTOR_LIST@1994..2024 0: CSS_COMPOUND_SELECTOR@1994..2024 0: (empty) @@ -6306,7 +6306,7 @@ CssRoot { 0: L_CURLY@2024..2025 "{" [] [] 1: CSS_DECLARATION_LIST@2025..2025 2: R_CURLY@2025..2026 "}" [] [] - 94: CSS_RULE@2026..2058 + 94: CSS_QUALIFIED_RULE@2026..2058 0: CSS_SELECTOR_LIST@2026..2056 0: CSS_COMPOUND_SELECTOR@2026..2056 0: (empty) @@ -6351,7 +6351,7 @@ CssRoot { 0: L_CURLY@2056..2057 "{" [] [] 1: CSS_DECLARATION_LIST@2057..2057 2: R_CURLY@2057..2058 "}" [] [] - 95: CSS_RULE@2058..2091 + 95: CSS_QUALIFIED_RULE@2058..2091 0: CSS_SELECTOR_LIST@2058..2089 0: CSS_COMPOUND_SELECTOR@2058..2089 0: (empty) @@ -6396,7 +6396,7 @@ CssRoot { 0: L_CURLY@2089..2090 "{" [] [] 1: CSS_DECLARATION_LIST@2090..2090 2: R_CURLY@2090..2091 "}" [] [] - 96: CSS_RULE@2091..2127 + 96: CSS_QUALIFIED_RULE@2091..2127 0: CSS_SELECTOR_LIST@2091..2125 0: CSS_COMPOUND_SELECTOR@2091..2125 0: (empty) @@ -6435,7 +6435,7 @@ CssRoot { 0: L_CURLY@2125..2126 "{" [] [] 1: CSS_DECLARATION_LIST@2126..2126 2: R_CURLY@2126..2127 "}" [] [] - 97: CSS_RULE@2127..2167 + 97: CSS_QUALIFIED_RULE@2127..2167 0: CSS_SELECTOR_LIST@2127..2165 0: CSS_COMPOUND_SELECTOR@2127..2165 0: (empty) @@ -6483,7 +6483,7 @@ CssRoot { 0: L_CURLY@2165..2166 "{" [] [] 1: CSS_DECLARATION_LIST@2166..2166 2: R_CURLY@2166..2167 "}" [] [] - 98: CSS_RULE@2167..2194 + 98: CSS_QUALIFIED_RULE@2167..2194 0: CSS_SELECTOR_LIST@2167..2192 0: CSS_COMPLEX_SELECTOR@2167..2192 0: CSS_COMPOUND_SELECTOR@2167..2184 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_any.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_any.css.snap index 976f3bede679..4340549c4afd 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_any.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_any.css.snap @@ -34,7 +34,7 @@ h1:-webkit-any(.h1class, #bar) {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -69,7 +69,7 @@ CssRoot { r_curly_token: R_CURLY@24..25 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -104,7 +104,7 @@ CssRoot { r_curly_token: R_CURLY@47..48 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -183,7 +183,7 @@ CssRoot { r_curly_token: R_CURLY@93..94 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -262,7 +262,7 @@ CssRoot { r_curly_token: R_CURLY@136..137 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -310,7 +310,7 @@ CssRoot { r_curly_token: R_CURLY@166..167 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -358,7 +358,7 @@ CssRoot { r_curly_token: R_CURLY@193..194 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -419,7 +419,7 @@ CssRoot { r_curly_token: R_CURLY@231..232 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -480,7 +480,7 @@ CssRoot { r_curly_token: R_CURLY@266..267 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -670,7 +670,7 @@ CssRoot { r_curly_token: R_CURLY@374..375 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -871,7 +871,7 @@ CssRoot { r_curly_token: R_CURLY@524..525 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -932,7 +932,7 @@ CssRoot { 0: CSS_ROOT@0..554 0: (empty) 1: CSS_RULE_LIST@0..553 - 0: CSS_RULE@0..25 + 0: CSS_QUALIFIED_RULE@0..25 0: CSS_SELECTOR_LIST@0..23 0: CSS_COMPOUND_SELECTOR@0..23 0: (empty) @@ -956,7 +956,7 @@ CssRoot { 0: L_CURLY@23..24 "{" [] [] 1: CSS_DECLARATION_LIST@24..24 2: R_CURLY@24..25 "}" [] [] - 1: CSS_RULE@25..48 + 1: CSS_QUALIFIED_RULE@25..48 0: CSS_SELECTOR_LIST@25..46 0: CSS_COMPOUND_SELECTOR@25..46 0: (empty) @@ -980,7 +980,7 @@ CssRoot { 0: L_CURLY@46..47 "{" [] [] 1: CSS_DECLARATION_LIST@47..47 2: R_CURLY@47..48 "}" [] [] - 2: CSS_RULE@48..94 + 2: CSS_QUALIFIED_RULE@48..94 0: CSS_SELECTOR_LIST@48..92 0: CSS_COMPLEX_SELECTOR@48..92 0: CSS_COMPOUND_SELECTOR@48..83 @@ -1034,7 +1034,7 @@ CssRoot { 0: L_CURLY@92..93 "{" [] [] 1: CSS_DECLARATION_LIST@93..93 2: R_CURLY@93..94 "}" [] [] - 3: CSS_RULE@94..137 + 3: CSS_QUALIFIED_RULE@94..137 0: CSS_SELECTOR_LIST@94..135 0: CSS_COMPLEX_SELECTOR@94..135 0: CSS_COMPOUND_SELECTOR@94..126 @@ -1088,7 +1088,7 @@ CssRoot { 0: L_CURLY@135..136 "{" [] [] 1: CSS_DECLARATION_LIST@136..136 2: R_CURLY@136..137 "}" [] [] - 4: CSS_RULE@137..167 + 4: CSS_QUALIFIED_RULE@137..167 0: CSS_SELECTOR_LIST@137..165 0: CSS_COMPOUND_SELECTOR@137..165 0: (empty) @@ -1121,7 +1121,7 @@ CssRoot { 0: L_CURLY@165..166 "{" [] [] 1: CSS_DECLARATION_LIST@166..166 2: R_CURLY@166..167 "}" [] [] - 5: CSS_RULE@167..194 + 5: CSS_QUALIFIED_RULE@167..194 0: CSS_SELECTOR_LIST@167..192 0: CSS_COMPOUND_SELECTOR@167..192 0: (empty) @@ -1154,7 +1154,7 @@ CssRoot { 0: L_CURLY@192..193 "{" [] [] 1: CSS_DECLARATION_LIST@193..193 2: R_CURLY@193..194 "}" [] [] - 6: CSS_RULE@194..232 + 6: CSS_QUALIFIED_RULE@194..232 0: CSS_SELECTOR_LIST@194..230 0: CSS_COMPOUND_SELECTOR@194..230 0: (empty) @@ -1196,7 +1196,7 @@ CssRoot { 0: L_CURLY@230..231 "{" [] [] 1: CSS_DECLARATION_LIST@231..231 2: R_CURLY@231..232 "}" [] [] - 7: CSS_RULE@232..267 + 7: CSS_QUALIFIED_RULE@232..267 0: CSS_SELECTOR_LIST@232..265 0: CSS_COMPOUND_SELECTOR@232..265 0: (empty) @@ -1238,7 +1238,7 @@ CssRoot { 0: L_CURLY@265..266 "{" [] [] 1: CSS_DECLARATION_LIST@266..266 2: R_CURLY@266..267 "}" [] [] - 8: CSS_RULE@267..375 + 8: CSS_QUALIFIED_RULE@267..375 0: CSS_SELECTOR_LIST@267..373 0: CSS_COMPOUND_SELECTOR@267..284 0: (empty) @@ -1369,7 +1369,7 @@ CssRoot { 0: L_CURLY@373..374 "{" [] [] 1: CSS_DECLARATION_LIST@374..374 2: R_CURLY@374..375 "}" [] [] - 9: CSS_RULE@375..525 + 9: CSS_QUALIFIED_RULE@375..525 0: CSS_SELECTOR_LIST@375..523 0: CSS_COMPLEX_SELECTOR@375..419 0: CSS_COMPOUND_SELECTOR@375..393 @@ -1504,7 +1504,7 @@ CssRoot { 0: L_CURLY@523..524 "{" [] [] 1: CSS_DECLARATION_LIST@524..524 2: R_CURLY@524..525 "}" [] [] - 10: CSS_RULE@525..553 + 10: CSS_QUALIFIED_RULE@525..553 0: CSS_SELECTOR_LIST@525..551 0: CSS_COMPLEX_SELECTOR@525..551 0: CSS_COMPOUND_SELECTOR@525..545 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_basic.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_basic.css.snap index eb0250e26b08..6103a68e1234 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_basic.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_basic.css.snap @@ -35,7 +35,7 @@ a:hOvEr {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -58,7 +58,7 @@ CssRoot { r_curly_token: R_CURLY@7..8 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -81,7 +81,7 @@ CssRoot { r_curly_token: R_CURLY@20..21 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -109,7 +109,7 @@ CssRoot { r_curly_token: R_CURLY@36..37 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -128,7 +128,7 @@ CssRoot { r_curly_token: R_CURLY@51..52 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -156,7 +156,7 @@ CssRoot { r_curly_token: R_CURLY@67..68 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -175,7 +175,7 @@ CssRoot { r_curly_token: R_CURLY@76..77 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -256,7 +256,7 @@ CssRoot { r_curly_token: R_CURLY@121..122 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -311,7 +311,7 @@ CssRoot { r_curly_token: R_CURLY@151..152 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -431,7 +431,7 @@ CssRoot { r_curly_token: R_CURLY@212..213 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -462,7 +462,7 @@ CssRoot { r_curly_token: R_CURLY@257..258 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -501,7 +501,7 @@ CssRoot { r_curly_token: R_CURLY@308..309 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -546,7 +546,7 @@ CssRoot { r_curly_token: R_CURLY@364..365 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -582,7 +582,7 @@ CssRoot { r_curly_token: R_CURLY@375..376 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -605,7 +605,7 @@ CssRoot { r_curly_token: R_CURLY@389..390 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -633,7 +633,7 @@ CssRoot { r_curly_token: R_CURLY@411..412 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -669,7 +669,7 @@ CssRoot { r_curly_token: R_CURLY@430..431 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -714,7 +714,7 @@ CssRoot { r_curly_token: R_CURLY@451..452 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -742,7 +742,7 @@ CssRoot { r_curly_token: R_CURLY@462..463 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -789,7 +789,7 @@ CssRoot { 0: CSS_ROOT@0..490 0: (empty) 1: CSS_RULE_LIST@0..489 - 0: CSS_RULE@0..8 + 0: CSS_QUALIFIED_RULE@0..8 0: CSS_SELECTOR_LIST@0..6 0: CSS_COMPOUND_SELECTOR@0..6 0: (empty) @@ -804,7 +804,7 @@ CssRoot { 0: L_CURLY@6..7 "{" [] [] 1: CSS_DECLARATION_LIST@7..7 2: R_CURLY@7..8 "}" [] [] - 1: CSS_RULE@8..21 + 1: CSS_QUALIFIED_RULE@8..21 0: CSS_SELECTOR_LIST@8..19 0: CSS_COMPOUND_SELECTOR@8..19 0: (empty) @@ -819,7 +819,7 @@ CssRoot { 0: L_CURLY@19..20 "{" [] [] 1: CSS_DECLARATION_LIST@20..20 2: R_CURLY@20..21 "}" [] [] - 2: CSS_RULE@21..37 + 2: CSS_QUALIFIED_RULE@21..37 0: CSS_SELECTOR_LIST@21..35 0: CSS_COMPOUND_SELECTOR@21..35 0: (empty) @@ -837,7 +837,7 @@ CssRoot { 0: L_CURLY@35..36 "{" [] [] 1: CSS_DECLARATION_LIST@36..36 2: R_CURLY@36..37 "}" [] [] - 3: CSS_RULE@37..52 + 3: CSS_QUALIFIED_RULE@37..52 0: CSS_SELECTOR_LIST@37..50 0: CSS_COMPOUND_SELECTOR@37..50 0: (empty) @@ -850,7 +850,7 @@ CssRoot { 0: L_CURLY@50..51 "{" [] [] 1: CSS_DECLARATION_LIST@51..51 2: R_CURLY@51..52 "}" [] [] - 4: CSS_RULE@52..68 + 4: CSS_QUALIFIED_RULE@52..68 0: CSS_SELECTOR_LIST@52..66 0: CSS_COMPOUND_SELECTOR@52..66 0: (empty) @@ -868,7 +868,7 @@ CssRoot { 0: L_CURLY@66..67 "{" [] [] 1: CSS_DECLARATION_LIST@67..67 2: R_CURLY@67..68 "}" [] [] - 5: CSS_RULE@68..77 + 5: CSS_QUALIFIED_RULE@68..77 0: CSS_SELECTOR_LIST@68..75 0: CSS_COMPOUND_SELECTOR@68..75 0: (empty) @@ -881,7 +881,7 @@ CssRoot { 0: L_CURLY@75..76 "{" [] [] 1: CSS_DECLARATION_LIST@76..76 2: R_CURLY@76..77 "}" [] [] - 6: CSS_RULE@77..122 + 6: CSS_QUALIFIED_RULE@77..122 0: CSS_SELECTOR_LIST@77..120 0: CSS_COMPLEX_SELECTOR@77..120 0: CSS_COMPOUND_SELECTOR@77..116 @@ -938,7 +938,7 @@ CssRoot { 0: L_CURLY@120..121 "{" [] [] 1: CSS_DECLARATION_LIST@121..121 2: R_CURLY@121..122 "}" [] [] - 7: CSS_RULE@122..152 + 7: CSS_QUALIFIED_RULE@122..152 0: CSS_SELECTOR_LIST@122..150 0: CSS_COMPOUND_SELECTOR@122..150 0: (empty) @@ -975,7 +975,7 @@ CssRoot { 0: L_CURLY@150..151 "{" [] [] 1: CSS_DECLARATION_LIST@151..151 2: R_CURLY@151..152 "}" [] [] - 8: CSS_RULE@152..213 + 8: CSS_QUALIFIED_RULE@152..213 0: CSS_SELECTOR_LIST@152..211 0: CSS_COMPOUND_SELECTOR@152..211 0: (empty) @@ -1057,7 +1057,7 @@ CssRoot { 0: L_CURLY@211..212 "{" [] [] 1: CSS_DECLARATION_LIST@212..212 2: R_CURLY@212..213 "}" [] [] - 9: CSS_RULE@213..258 + 9: CSS_QUALIFIED_RULE@213..258 0: CSS_SELECTOR_LIST@213..256 0: CSS_COMPOUND_SELECTOR@213..256 0: (empty) @@ -1077,7 +1077,7 @@ CssRoot { 0: L_CURLY@256..257 "{" [] [] 1: CSS_DECLARATION_LIST@257..257 2: R_CURLY@257..258 "}" [] [] - 10: CSS_RULE@258..309 + 10: CSS_QUALIFIED_RULE@258..309 0: CSS_SELECTOR_LIST@258..307 0: CSS_COMPOUND_SELECTOR@258..307 0: (empty) @@ -1102,7 +1102,7 @@ CssRoot { 0: L_CURLY@307..308 "{" [] [] 1: CSS_DECLARATION_LIST@308..308 2: R_CURLY@308..309 "}" [] [] - 11: CSS_RULE@309..365 + 11: CSS_QUALIFIED_RULE@309..365 0: CSS_SELECTOR_LIST@309..363 0: CSS_COMPOUND_SELECTOR@309..363 0: (empty) @@ -1131,7 +1131,7 @@ CssRoot { 0: L_CURLY@363..364 "{" [] [] 1: CSS_DECLARATION_LIST@364..364 2: R_CURLY@364..365 "}" [] [] - 12: CSS_RULE@365..376 + 12: CSS_QUALIFIED_RULE@365..376 0: CSS_SELECTOR_LIST@365..374 0: CSS_COMPOUND_SELECTOR@365..374 0: (empty) @@ -1156,7 +1156,7 @@ CssRoot { 0: L_CURLY@374..375 "{" [] [] 1: CSS_DECLARATION_LIST@375..375 2: R_CURLY@375..376 "}" [] [] - 13: CSS_RULE@376..390 + 13: CSS_QUALIFIED_RULE@376..390 0: CSS_SELECTOR_LIST@376..388 0: CSS_COMPOUND_SELECTOR@376..388 0: (empty) @@ -1171,7 +1171,7 @@ CssRoot { 0: L_CURLY@388..389 "{" [] [] 1: CSS_DECLARATION_LIST@389..389 2: R_CURLY@389..390 "}" [] [] - 14: CSS_RULE@390..412 + 14: CSS_QUALIFIED_RULE@390..412 0: CSS_SELECTOR_LIST@390..410 0: CSS_COMPOUND_SELECTOR@390..410 0: (empty) @@ -1189,7 +1189,7 @@ CssRoot { 0: L_CURLY@410..411 "{" [] [] 1: CSS_DECLARATION_LIST@411..411 2: R_CURLY@411..412 "}" [] [] - 15: CSS_RULE@412..431 + 15: CSS_QUALIFIED_RULE@412..431 0: CSS_SELECTOR_LIST@412..429 0: CSS_COMPOUND_SELECTOR@412..429 0: (empty) @@ -1212,7 +1212,7 @@ CssRoot { 0: L_CURLY@429..430 "{" [] [] 1: CSS_DECLARATION_LIST@430..430 2: R_CURLY@430..431 "}" [] [] - 16: CSS_RULE@431..452 + 16: CSS_QUALIFIED_RULE@431..452 0: CSS_SELECTOR_LIST@431..450 0: CSS_COMPLEX_SELECTOR@431..450 0: CSS_COMPOUND_SELECTOR@431..435 @@ -1243,7 +1243,7 @@ CssRoot { 0: L_CURLY@450..451 "{" [] [] 1: CSS_DECLARATION_LIST@451..451 2: R_CURLY@451..452 "}" [] [] - 17: CSS_RULE@452..463 + 17: CSS_QUALIFIED_RULE@452..463 0: CSS_SELECTOR_LIST@452..461 0: CSS_COMPOUND_SELECTOR@452..461 0: (empty) @@ -1261,7 +1261,7 @@ CssRoot { 0: L_CURLY@461..462 "{" [] [] 1: CSS_DECLARATION_LIST@462..462 2: R_CURLY@462..463 "}" [] [] - 18: CSS_RULE@463..489 + 18: CSS_QUALIFIED_RULE@463..489 0: CSS_SELECTOR_LIST@463..487 0: CSS_COMPLEX_SELECTOR@463..487 0: CSS_COMPOUND_SELECTOR@463..484 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_current.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_current.css.snap index 8c29e316d43c..53a1fca06d7f 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_current.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_current.css.snap @@ -18,7 +18,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -86,7 +86,7 @@ CssRoot { r_curly_token: R_CURLY@25..26 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -180,7 +180,7 @@ CssRoot { 0: CSS_ROOT@0..59 0: (empty) 1: CSS_RULE_LIST@0..58 - 0: CSS_RULE@0..26 + 0: CSS_QUALIFIED_RULE@0..26 0: CSS_SELECTOR_LIST@0..24 0: CSS_COMPOUND_SELECTOR@0..24 0: (empty) @@ -228,7 +228,7 @@ CssRoot { 0: L_CURLY@24..25 "{" [] [] 1: CSS_DECLARATION_LIST@25..25 2: R_CURLY@25..26 "}" [] [] - 1: CSS_RULE@26..58 + 1: CSS_QUALIFIED_RULE@26..58 0: CSS_SELECTOR_LIST@26..56 0: CSS_COMPLEX_SELECTOR@26..56 0: CSS_COMPOUND_SELECTOR@26..50 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_dir.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_dir.css.snap index 58e7b4269478..6e432d2675d0 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_dir.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_dir.css.snap @@ -22,7 +22,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -48,7 +48,7 @@ CssRoot { r_curly_token: R_CURLY@11..12 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -74,7 +74,7 @@ CssRoot { r_curly_token: R_CURLY@30..31 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -100,7 +100,7 @@ CssRoot { r_curly_token: R_CURLY@43..44 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -126,7 +126,7 @@ CssRoot { r_curly_token: R_CURLY@62..63 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -152,7 +152,7 @@ CssRoot { r_curly_token: R_CURLY@81..82 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -204,7 +204,7 @@ CssRoot { 0: CSS_ROOT@0..101 0: (empty) 1: CSS_RULE_LIST@0..100 - 0: CSS_RULE@0..12 + 0: CSS_QUALIFIED_RULE@0..12 0: CSS_SELECTOR_LIST@0..10 0: CSS_COMPOUND_SELECTOR@0..10 0: (empty) @@ -222,7 +222,7 @@ CssRoot { 0: L_CURLY@10..11 "{" [] [] 1: CSS_DECLARATION_LIST@11..11 2: R_CURLY@11..12 "}" [] [] - 1: CSS_RULE@12..31 + 1: CSS_QUALIFIED_RULE@12..31 0: CSS_SELECTOR_LIST@12..29 0: CSS_COMPOUND_SELECTOR@12..29 0: (empty) @@ -240,7 +240,7 @@ CssRoot { 0: L_CURLY@29..30 "{" [] [] 1: CSS_DECLARATION_LIST@30..30 2: R_CURLY@30..31 "}" [] [] - 2: CSS_RULE@31..44 + 2: CSS_QUALIFIED_RULE@31..44 0: CSS_SELECTOR_LIST@31..42 0: CSS_COMPOUND_SELECTOR@31..42 0: (empty) @@ -258,7 +258,7 @@ CssRoot { 0: L_CURLY@42..43 "{" [] [] 1: CSS_DECLARATION_LIST@43..43 2: R_CURLY@43..44 "}" [] [] - 3: CSS_RULE@44..63 + 3: CSS_QUALIFIED_RULE@44..63 0: CSS_SELECTOR_LIST@44..61 0: CSS_COMPOUND_SELECTOR@44..61 0: (empty) @@ -276,7 +276,7 @@ CssRoot { 0: L_CURLY@61..62 "{" [] [] 1: CSS_DECLARATION_LIST@62..62 2: R_CURLY@62..63 "}" [] [] - 4: CSS_RULE@63..82 + 4: CSS_QUALIFIED_RULE@63..82 0: CSS_SELECTOR_LIST@63..80 0: CSS_COMPOUND_SELECTOR@63..80 0: (empty) @@ -294,7 +294,7 @@ CssRoot { 0: L_CURLY@80..81 "{" [] [] 1: CSS_DECLARATION_LIST@81..81 2: R_CURLY@81..82 "}" [] [] - 5: CSS_RULE@82..100 + 5: CSS_QUALIFIED_RULE@82..100 0: CSS_SELECTOR_LIST@82..98 0: CSS_COMPLEX_SELECTOR@82..98 0: CSS_COMPOUND_SELECTOR@82..92 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_future.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_future.css.snap index 6ca3858ae96a..d07ddebd976b 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_future.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_future.css.snap @@ -18,7 +18,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -86,7 +86,7 @@ CssRoot { r_curly_token: R_CURLY@24..25 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -180,7 +180,7 @@ CssRoot { 0: CSS_ROOT@0..57 0: (empty) 1: CSS_RULE_LIST@0..56 - 0: CSS_RULE@0..25 + 0: CSS_QUALIFIED_RULE@0..25 0: CSS_SELECTOR_LIST@0..23 0: CSS_COMPOUND_SELECTOR@0..23 0: (empty) @@ -228,7 +228,7 @@ CssRoot { 0: L_CURLY@23..24 "{" [] [] 1: CSS_DECLARATION_LIST@24..24 2: R_CURLY@24..25 "}" [] [] - 1: CSS_RULE@25..56 + 1: CSS_QUALIFIED_RULE@25..56 0: CSS_SELECTOR_LIST@25..54 0: CSS_COMPLEX_SELECTOR@25..54 0: CSS_COMPOUND_SELECTOR@25..48 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_has.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_has.css.snap index 64f37593a338..20aeadb610df 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_has.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_has.css.snap @@ -27,7 +27,7 @@ a:has(> img) .div {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -70,7 +70,7 @@ CssRoot { r_curly_token: R_CURLY@14..15 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -113,7 +113,7 @@ CssRoot { r_curly_token: R_CURLY@36..37 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -156,7 +156,7 @@ CssRoot { r_curly_token: R_CURLY@60..61 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -213,7 +213,7 @@ CssRoot { r_curly_token: R_CURLY@98..99 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -256,7 +256,7 @@ CssRoot { r_curly_token: R_CURLY@118..119 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -299,7 +299,7 @@ CssRoot { r_curly_token: R_CURLY@134..135 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -428,7 +428,7 @@ CssRoot { r_curly_token: R_CURLY@179..180 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -542,7 +542,7 @@ CssRoot { r_curly_token: R_CURLY@224..225 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -585,7 +585,7 @@ CssRoot { r_curly_token: R_CURLY@238..239 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -667,7 +667,7 @@ CssRoot { r_curly_token: R_CURLY@281..282 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -736,7 +736,7 @@ CssRoot { 0: CSS_ROOT@0..304 0: (empty) 1: CSS_RULE_LIST@0..303 - 0: CSS_RULE@0..15 + 0: CSS_QUALIFIED_RULE@0..15 0: CSS_SELECTOR_LIST@0..13 0: CSS_COMPOUND_SELECTOR@0..13 0: (empty) @@ -765,7 +765,7 @@ CssRoot { 0: L_CURLY@13..14 "{" [] [] 1: CSS_DECLARATION_LIST@14..14 2: R_CURLY@14..15 "}" [] [] - 1: CSS_RULE@15..37 + 1: CSS_QUALIFIED_RULE@15..37 0: CSS_SELECTOR_LIST@15..35 0: CSS_COMPOUND_SELECTOR@15..35 0: (empty) @@ -794,7 +794,7 @@ CssRoot { 0: L_CURLY@35..36 "{" [] [] 1: CSS_DECLARATION_LIST@36..36 2: R_CURLY@36..37 "}" [] [] - 2: CSS_RULE@37..61 + 2: CSS_QUALIFIED_RULE@37..61 0: CSS_SELECTOR_LIST@37..59 0: CSS_COMPOUND_SELECTOR@37..59 0: (empty) @@ -823,7 +823,7 @@ CssRoot { 0: L_CURLY@59..60 "{" [] [] 1: CSS_DECLARATION_LIST@60..60 2: R_CURLY@60..61 "}" [] [] - 3: CSS_RULE@61..99 + 3: CSS_QUALIFIED_RULE@61..99 0: CSS_SELECTOR_LIST@61..97 0: CSS_COMPOUND_SELECTOR@61..97 0: (empty) @@ -862,7 +862,7 @@ CssRoot { 0: L_CURLY@97..98 "{" [] [] 1: CSS_DECLARATION_LIST@98..98 2: R_CURLY@98..99 "}" [] [] - 4: CSS_RULE@99..119 + 4: CSS_QUALIFIED_RULE@99..119 0: CSS_SELECTOR_LIST@99..117 0: CSS_COMPOUND_SELECTOR@99..117 0: (empty) @@ -891,7 +891,7 @@ CssRoot { 0: L_CURLY@117..118 "{" [] [] 1: CSS_DECLARATION_LIST@118..118 2: R_CURLY@118..119 "}" [] [] - 5: CSS_RULE@119..135 + 5: CSS_QUALIFIED_RULE@119..135 0: CSS_SELECTOR_LIST@119..133 0: CSS_COMPOUND_SELECTOR@119..133 0: (empty) @@ -920,7 +920,7 @@ CssRoot { 0: L_CURLY@133..134 "{" [] [] 1: CSS_DECLARATION_LIST@134..134 2: R_CURLY@134..135 "}" [] [] - 6: CSS_RULE@135..180 + 6: CSS_QUALIFIED_RULE@135..180 0: CSS_SELECTOR_LIST@135..178 0: CSS_COMPOUND_SELECTOR@135..178 0: (empty) @@ -1010,7 +1010,7 @@ CssRoot { 0: L_CURLY@178..179 "{" [] [] 1: CSS_DECLARATION_LIST@179..179 2: R_CURLY@179..180 "}" [] [] - 7: CSS_RULE@180..225 + 7: CSS_QUALIFIED_RULE@180..225 0: CSS_SELECTOR_LIST@180..223 0: CSS_COMPOUND_SELECTOR@180..223 0: (empty) @@ -1090,7 +1090,7 @@ CssRoot { 0: L_CURLY@223..224 "{" [] [] 1: CSS_DECLARATION_LIST@224..224 2: R_CURLY@224..225 "}" [] [] - 8: CSS_RULE@225..239 + 8: CSS_QUALIFIED_RULE@225..239 0: CSS_SELECTOR_LIST@225..237 0: CSS_COMPOUND_SELECTOR@225..237 0: (empty) @@ -1119,7 +1119,7 @@ CssRoot { 0: L_CURLY@237..238 "{" [] [] 1: CSS_DECLARATION_LIST@238..238 2: R_CURLY@238..239 "}" [] [] - 9: CSS_RULE@239..282 + 9: CSS_QUALIFIED_RULE@239..282 0: CSS_SELECTOR_LIST@239..280 0: CSS_COMPLEX_SELECTOR@239..280 0: CSS_COMPOUND_SELECTOR@239..276 @@ -1175,7 +1175,7 @@ CssRoot { 0: L_CURLY@280..281 "{" [] [] 1: CSS_DECLARATION_LIST@281..281 2: R_CURLY@281..282 "}" [] [] - 10: CSS_RULE@282..303 + 10: CSS_QUALIFIED_RULE@282..303 0: CSS_SELECTOR_LIST@282..301 0: CSS_COMPLEX_SELECTOR@282..301 0: CSS_COMPOUND_SELECTOR@282..295 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_host.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_host.css.snap index 45b026c11023..e508381b99c0 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_host.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_host.css.snap @@ -21,7 +21,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -56,7 +56,7 @@ CssRoot { r_curly_token: R_CURLY@32..33 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -91,7 +91,7 @@ CssRoot { r_curly_token: R_CURLY@72..73 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -126,7 +126,7 @@ CssRoot { r_curly_token: R_CURLY@90..91 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -149,7 +149,7 @@ CssRoot { r_curly_token: R_CURLY@99..100 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -210,7 +210,7 @@ CssRoot { 0: CSS_ROOT@0..124 0: (empty) 1: CSS_RULE_LIST@0..123 - 0: CSS_RULE@0..33 + 0: CSS_QUALIFIED_RULE@0..33 0: CSS_SELECTOR_LIST@0..31 0: CSS_COMPOUND_SELECTOR@0..31 0: (empty) @@ -234,7 +234,7 @@ CssRoot { 0: L_CURLY@31..32 "{" [] [] 1: CSS_DECLARATION_LIST@32..32 2: R_CURLY@32..33 "}" [] [] - 1: CSS_RULE@33..73 + 1: CSS_QUALIFIED_RULE@33..73 0: CSS_SELECTOR_LIST@33..71 0: CSS_COMPOUND_SELECTOR@33..71 0: (empty) @@ -258,7 +258,7 @@ CssRoot { 0: L_CURLY@71..72 "{" [] [] 1: CSS_DECLARATION_LIST@72..72 2: R_CURLY@72..73 "}" [] [] - 2: CSS_RULE@73..91 + 2: CSS_QUALIFIED_RULE@73..91 0: CSS_SELECTOR_LIST@73..89 0: CSS_COMPOUND_SELECTOR@73..89 0: (empty) @@ -282,7 +282,7 @@ CssRoot { 0: L_CURLY@89..90 "{" [] [] 1: CSS_DECLARATION_LIST@90..90 2: R_CURLY@90..91 "}" [] [] - 3: CSS_RULE@91..100 + 3: CSS_QUALIFIED_RULE@91..100 0: CSS_SELECTOR_LIST@91..98 0: CSS_COMPOUND_SELECTOR@91..98 0: (empty) @@ -297,7 +297,7 @@ CssRoot { 0: L_CURLY@98..99 "{" [] [] 1: CSS_DECLARATION_LIST@99..99 2: R_CURLY@99..100 "}" [] [] - 4: CSS_RULE@100..123 + 4: CSS_QUALIFIED_RULE@100..123 0: CSS_SELECTOR_LIST@100..121 0: CSS_COMPLEX_SELECTOR@100..121 0: CSS_COMPOUND_SELECTOR@100..115 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_host_context.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_host_context.css.snap index 79d14b2dbbfb..98ba98ddbb59 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_host_context.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_host_context.css.snap @@ -19,7 +19,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -52,7 +52,7 @@ CssRoot { r_curly_token: R_CURLY@19..20 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -85,7 +85,7 @@ CssRoot { r_curly_token: R_CURLY@46..47 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -144,7 +144,7 @@ CssRoot { 0: CSS_ROOT@0..74 0: (empty) 1: CSS_RULE_LIST@0..73 - 0: CSS_RULE@0..20 + 0: CSS_QUALIFIED_RULE@0..20 0: CSS_SELECTOR_LIST@0..18 0: CSS_COMPOUND_SELECTOR@0..18 0: (empty) @@ -167,7 +167,7 @@ CssRoot { 0: L_CURLY@18..19 "{" [] [] 1: CSS_DECLARATION_LIST@19..19 2: R_CURLY@19..20 "}" [] [] - 1: CSS_RULE@20..47 + 1: CSS_QUALIFIED_RULE@20..47 0: CSS_SELECTOR_LIST@20..45 0: CSS_COMPOUND_SELECTOR@20..45 0: (empty) @@ -190,7 +190,7 @@ CssRoot { 0: L_CURLY@45..46 "{" [] [] 1: CSS_DECLARATION_LIST@46..46 2: R_CURLY@46..47 "}" [] [] - 2: CSS_RULE@47..73 + 2: CSS_QUALIFIED_RULE@47..73 0: CSS_SELECTOR_LIST@47..71 0: CSS_COMPLEX_SELECTOR@47..71 0: CSS_COMPOUND_SELECTOR@47..65 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_ident.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_ident.css.snap index 8e21b566faf9..f4b0297e77e9 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_ident.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_ident.css.snap @@ -24,7 +24,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -47,7 +47,7 @@ CssRoot { r_curly_token: R_CURLY@16..17 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -70,7 +70,7 @@ CssRoot { r_curly_token: R_CURLY@32..33 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -93,7 +93,7 @@ CssRoot { r_curly_token: R_CURLY@42..43 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -116,7 +116,7 @@ CssRoot { r_curly_token: R_CURLY@53..54 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -139,7 +139,7 @@ CssRoot { r_curly_token: R_CURLY@66..67 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -162,7 +162,7 @@ CssRoot { r_curly_token: R_CURLY@79..80 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -185,7 +185,7 @@ CssRoot { r_curly_token: R_CURLY@89..90 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -219,7 +219,7 @@ CssRoot { 0: CSS_ROOT@0..101 0: (empty) 1: CSS_RULE_LIST@0..100 - 0: CSS_RULE@0..17 + 0: CSS_QUALIFIED_RULE@0..17 0: CSS_SELECTOR_LIST@0..15 0: CSS_COMPOUND_SELECTOR@0..15 0: (empty) @@ -234,7 +234,7 @@ CssRoot { 0: L_CURLY@15..16 "{" [] [] 1: CSS_DECLARATION_LIST@16..16 2: R_CURLY@16..17 "}" [] [] - 1: CSS_RULE@17..33 + 1: CSS_QUALIFIED_RULE@17..33 0: CSS_SELECTOR_LIST@17..31 0: CSS_COMPOUND_SELECTOR@17..31 0: (empty) @@ -249,7 +249,7 @@ CssRoot { 0: L_CURLY@31..32 "{" [] [] 1: CSS_DECLARATION_LIST@32..32 2: R_CURLY@32..33 "}" [] [] - 2: CSS_RULE@33..43 + 2: CSS_QUALIFIED_RULE@33..43 0: CSS_SELECTOR_LIST@33..41 0: CSS_COMPOUND_SELECTOR@33..41 0: (empty) @@ -264,7 +264,7 @@ CssRoot { 0: L_CURLY@41..42 "{" [] [] 1: CSS_DECLARATION_LIST@42..42 2: R_CURLY@42..43 "}" [] [] - 3: CSS_RULE@43..54 + 3: CSS_QUALIFIED_RULE@43..54 0: CSS_SELECTOR_LIST@43..52 0: CSS_COMPOUND_SELECTOR@43..52 0: (empty) @@ -279,7 +279,7 @@ CssRoot { 0: L_CURLY@52..53 "{" [] [] 1: CSS_DECLARATION_LIST@53..53 2: R_CURLY@53..54 "}" [] [] - 4: CSS_RULE@54..67 + 4: CSS_QUALIFIED_RULE@54..67 0: CSS_SELECTOR_LIST@54..65 0: CSS_COMPOUND_SELECTOR@54..65 0: (empty) @@ -294,7 +294,7 @@ CssRoot { 0: L_CURLY@65..66 "{" [] [] 1: CSS_DECLARATION_LIST@66..66 2: R_CURLY@66..67 "}" [] [] - 5: CSS_RULE@67..80 + 5: CSS_QUALIFIED_RULE@67..80 0: CSS_SELECTOR_LIST@67..78 0: CSS_COMPOUND_SELECTOR@67..78 0: (empty) @@ -309,7 +309,7 @@ CssRoot { 0: L_CURLY@78..79 "{" [] [] 1: CSS_DECLARATION_LIST@79..79 2: R_CURLY@79..80 "}" [] [] - 6: CSS_RULE@80..90 + 6: CSS_QUALIFIED_RULE@80..90 0: CSS_SELECTOR_LIST@80..88 0: CSS_COMPOUND_SELECTOR@80..88 0: (empty) @@ -324,7 +324,7 @@ CssRoot { 0: L_CURLY@88..89 "{" [] [] 1: CSS_DECLARATION_LIST@89..89 2: R_CURLY@89..90 "}" [] [] - 7: CSS_RULE@90..100 + 7: CSS_QUALIFIED_RULE@90..100 0: CSS_SELECTOR_LIST@90..98 0: CSS_COMPOUND_SELECTOR@90..98 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_is.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_is.css.snap index 901fc0080de4..757fb78a1e68 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_is.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_is.css.snap @@ -32,7 +32,7 @@ a:is(:not(:hover)) {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -80,7 +80,7 @@ CssRoot { r_curly_token: R_CURLY@12..13 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -139,7 +139,7 @@ CssRoot { r_curly_token: R_CURLY@30..31 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -218,7 +218,7 @@ CssRoot { r_curly_token: R_CURLY@67..68 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -273,7 +273,7 @@ CssRoot { r_curly_token: R_CURLY@90..91 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssComplexSelector { @@ -454,7 +454,7 @@ CssRoot { r_curly_token: R_CURLY@158..159 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -511,7 +511,7 @@ CssRoot { r_curly_token: R_CURLY@182..183 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -574,7 +574,7 @@ CssRoot { r_curly_token: R_CURLY@208..209 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -653,7 +653,7 @@ CssRoot { r_curly_token: R_CURLY@241..242 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -708,7 +708,7 @@ CssRoot { r_curly_token: R_CURLY@266..267 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -762,7 +762,7 @@ CssRoot { r_curly_token: R_CURLY@295..296 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -822,7 +822,7 @@ CssRoot { r_curly_token: R_CURLY@317..318 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -974,7 +974,7 @@ CssRoot { r_curly_token: R_CURLY@382..383 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -1053,7 +1053,7 @@ CssRoot { r_curly_token: R_CURLY@428..429 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -1132,7 +1132,7 @@ CssRoot { r_curly_token: R_CURLY@471..472 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -1211,7 +1211,7 @@ CssRoot { r_curly_token: R_CURLY@513..514 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -1301,7 +1301,7 @@ CssRoot { 0: CSS_ROOT@0..552 0: (empty) 1: CSS_RULE_LIST@0..551 - 0: CSS_RULE@0..13 + 0: CSS_QUALIFIED_RULE@0..13 0: CSS_SELECTOR_LIST@0..11 0: CSS_COMPLEX_SELECTOR@0..11 0: CSS_COMPOUND_SELECTOR@0..7 @@ -1334,7 +1334,7 @@ CssRoot { 0: L_CURLY@11..12 "{" [] [] 1: CSS_DECLARATION_LIST@12..12 2: R_CURLY@12..13 "}" [] [] - 1: CSS_RULE@13..31 + 1: CSS_QUALIFIED_RULE@13..31 0: CSS_SELECTOR_LIST@13..29 0: CSS_COMPLEX_SELECTOR@13..29 0: CSS_COMPOUND_SELECTOR@13..25 @@ -1375,7 +1375,7 @@ CssRoot { 0: L_CURLY@29..30 "{" [] [] 1: CSS_DECLARATION_LIST@30..30 2: R_CURLY@30..31 "}" [] [] - 2: CSS_RULE@31..68 + 2: CSS_QUALIFIED_RULE@31..68 0: CSS_SELECTOR_LIST@31..66 0: CSS_COMPLEX_SELECTOR@31..66 0: CSS_COMPOUND_SELECTOR@31..57 @@ -1429,7 +1429,7 @@ CssRoot { 0: L_CURLY@66..67 "{" [] [] 1: CSS_DECLARATION_LIST@67..67 2: R_CURLY@67..68 "}" [] [] - 3: CSS_RULE@68..91 + 3: CSS_QUALIFIED_RULE@68..91 0: CSS_SELECTOR_LIST@68..89 0: CSS_COMPOUND_SELECTOR@68..89 0: (empty) @@ -1466,7 +1466,7 @@ CssRoot { 0: L_CURLY@89..90 "{" [] [] 1: CSS_DECLARATION_LIST@90..90 2: R_CURLY@90..91 "}" [] [] - 4: CSS_RULE@91..159 + 4: CSS_QUALIFIED_RULE@91..159 0: CSS_SELECTOR_LIST@91..157 0: CSS_COMPLEX_SELECTOR@91..157 0: CSS_COMPLEX_SELECTOR@91..137 @@ -1594,7 +1594,7 @@ CssRoot { 0: L_CURLY@157..158 "{" [] [] 1: CSS_DECLARATION_LIST@158..158 2: R_CURLY@158..159 "}" [] [] - 5: CSS_RULE@159..183 + 5: CSS_QUALIFIED_RULE@159..183 0: CSS_SELECTOR_LIST@159..181 0: CSS_COMPOUND_SELECTOR@159..181 0: (empty) @@ -1632,7 +1632,7 @@ CssRoot { 0: L_CURLY@181..182 "{" [] [] 1: CSS_DECLARATION_LIST@182..182 2: R_CURLY@182..183 "}" [] [] - 6: CSS_RULE@183..209 + 6: CSS_QUALIFIED_RULE@183..209 0: CSS_SELECTOR_LIST@183..207 0: CSS_COMPOUND_SELECTOR@183..207 0: (empty) @@ -1674,7 +1674,7 @@ CssRoot { 0: L_CURLY@207..208 "{" [] [] 1: CSS_DECLARATION_LIST@208..208 2: R_CURLY@208..209 "}" [] [] - 7: CSS_RULE@209..242 + 7: CSS_QUALIFIED_RULE@209..242 0: CSS_SELECTOR_LIST@209..240 0: CSS_COMPLEX_SELECTOR@209..240 0: CSS_COMPOUND_SELECTOR@209..229 @@ -1729,7 +1729,7 @@ CssRoot { 0: L_CURLY@240..241 "{" [] [] 1: CSS_DECLARATION_LIST@241..241 2: R_CURLY@241..242 "}" [] [] - 8: CSS_RULE@242..267 + 8: CSS_QUALIFIED_RULE@242..267 0: CSS_SELECTOR_LIST@242..265 0: CSS_COMPOUND_SELECTOR@242..265 0: (empty) @@ -1766,7 +1766,7 @@ CssRoot { 0: L_CURLY@265..266 "{" [] [] 1: CSS_DECLARATION_LIST@266..266 2: R_CURLY@266..267 "}" [] [] - 9: CSS_RULE@267..296 + 9: CSS_QUALIFIED_RULE@267..296 0: CSS_SELECTOR_LIST@267..294 0: CSS_COMPOUND_SELECTOR@267..294 0: (empty) @@ -1802,7 +1802,7 @@ CssRoot { 0: L_CURLY@294..295 "{" [] [] 1: CSS_DECLARATION_LIST@295..295 2: R_CURLY@295..296 "}" [] [] - 10: CSS_RULE@296..318 + 10: CSS_QUALIFIED_RULE@296..318 0: CSS_SELECTOR_LIST@296..316 0: CSS_COMPOUND_SELECTOR@296..316 0: (empty) @@ -1842,7 +1842,7 @@ CssRoot { 0: L_CURLY@316..317 "{" [] [] 1: CSS_DECLARATION_LIST@317..317 2: R_CURLY@317..318 "}" [] [] - 11: CSS_RULE@318..383 + 11: CSS_QUALIFIED_RULE@318..383 0: CSS_SELECTOR_LIST@318..381 0: CSS_COMPLEX_SELECTOR@318..381 0: CSS_COMPOUND_SELECTOR@318..352 @@ -1950,7 +1950,7 @@ CssRoot { 0: L_CURLY@381..382 "{" [] [] 1: CSS_DECLARATION_LIST@382..382 2: R_CURLY@382..383 "}" [] [] - 12: CSS_RULE@383..429 + 12: CSS_QUALIFIED_RULE@383..429 0: CSS_SELECTOR_LIST@383..427 0: CSS_COMPLEX_SELECTOR@383..427 0: CSS_COMPOUND_SELECTOR@383..418 @@ -2004,7 +2004,7 @@ CssRoot { 0: L_CURLY@427..428 "{" [] [] 1: CSS_DECLARATION_LIST@428..428 2: R_CURLY@428..429 "}" [] [] - 13: CSS_RULE@429..472 + 13: CSS_QUALIFIED_RULE@429..472 0: CSS_SELECTOR_LIST@429..470 0: CSS_COMPLEX_SELECTOR@429..470 0: CSS_COMPOUND_SELECTOR@429..461 @@ -2058,7 +2058,7 @@ CssRoot { 0: L_CURLY@470..471 "{" [] [] 1: CSS_DECLARATION_LIST@471..471 2: R_CURLY@471..472 "}" [] [] - 14: CSS_RULE@472..514 + 14: CSS_QUALIFIED_RULE@472..514 0: CSS_SELECTOR_LIST@472..512 0: CSS_COMPLEX_SELECTOR@472..512 0: CSS_COMPOUND_SELECTOR@472..503 @@ -2112,7 +2112,7 @@ CssRoot { 0: L_CURLY@512..513 "{" [] [] 1: CSS_DECLARATION_LIST@513..513 2: R_CURLY@513..514 "}" [] [] - 15: CSS_RULE@514..551 + 15: CSS_QUALIFIED_RULE@514..551 0: CSS_SELECTOR_LIST@514..549 0: CSS_COMPLEX_SELECTOR@514..549 0: CSS_COMPOUND_SELECTOR@514..540 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_lang.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_lang.css.snap index 5dad619a2d9d..0ccb6b81ec9b 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_lang.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_lang.css.snap @@ -24,7 +24,7 @@ html:lang(de, fr) {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -52,7 +52,7 @@ CssRoot { r_curly_token: R_CURLY@16..17 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -80,7 +80,7 @@ CssRoot { r_curly_token: R_CURLY@35..36 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -113,7 +113,7 @@ CssRoot { r_curly_token: R_CURLY@55..56 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -146,7 +146,7 @@ CssRoot { r_curly_token: R_CURLY@72..73 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -183,7 +183,7 @@ CssRoot { r_curly_token: R_CURLY@93..94 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -224,7 +224,7 @@ CssRoot { r_curly_token: R_CURLY@113..114 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -265,7 +265,7 @@ CssRoot { r_curly_token: R_CURLY@130..131 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -319,7 +319,7 @@ CssRoot { 0: CSS_ROOT@0..155 0: (empty) 1: CSS_RULE_LIST@0..154 - 0: CSS_RULE@0..17 + 0: CSS_QUALIFIED_RULE@0..17 0: CSS_SELECTOR_LIST@0..15 0: CSS_COMPOUND_SELECTOR@0..15 0: (empty) @@ -338,7 +338,7 @@ CssRoot { 0: L_CURLY@15..16 "{" [] [] 1: CSS_DECLARATION_LIST@16..16 2: R_CURLY@16..17 "}" [] [] - 1: CSS_RULE@17..36 + 1: CSS_QUALIFIED_RULE@17..36 0: CSS_SELECTOR_LIST@17..34 0: CSS_COMPOUND_SELECTOR@17..34 0: (empty) @@ -357,7 +357,7 @@ CssRoot { 0: L_CURLY@34..35 "{" [] [] 1: CSS_DECLARATION_LIST@35..35 2: R_CURLY@35..36 "}" [] [] - 2: CSS_RULE@36..56 + 2: CSS_QUALIFIED_RULE@36..56 0: CSS_SELECTOR_LIST@36..54 0: CSS_COMPOUND_SELECTOR@36..54 0: (empty) @@ -379,7 +379,7 @@ CssRoot { 0: L_CURLY@54..55 "{" [] [] 1: CSS_DECLARATION_LIST@55..55 2: R_CURLY@55..56 "}" [] [] - 3: CSS_RULE@56..73 + 3: CSS_QUALIFIED_RULE@56..73 0: CSS_SELECTOR_LIST@56..71 0: CSS_COMPOUND_SELECTOR@56..71 0: (empty) @@ -401,7 +401,7 @@ CssRoot { 0: L_CURLY@71..72 "{" [] [] 1: CSS_DECLARATION_LIST@72..72 2: R_CURLY@72..73 "}" [] [] - 4: CSS_RULE@73..94 + 4: CSS_QUALIFIED_RULE@73..94 0: CSS_SELECTOR_LIST@73..92 0: CSS_COMPOUND_SELECTOR@73..92 0: (empty) @@ -426,7 +426,7 @@ CssRoot { 0: L_CURLY@92..93 "{" [] [] 1: CSS_DECLARATION_LIST@93..93 2: R_CURLY@93..94 "}" [] [] - 5: CSS_RULE@94..114 + 5: CSS_QUALIFIED_RULE@94..114 0: CSS_SELECTOR_LIST@94..112 0: CSS_COMPLEX_SELECTOR@94..112 0: CSS_COMPOUND_SELECTOR@94..108 @@ -454,7 +454,7 @@ CssRoot { 0: L_CURLY@112..113 "{" [] [] 1: CSS_DECLARATION_LIST@113..113 2: R_CURLY@113..114 "}" [] [] - 6: CSS_RULE@114..131 + 6: CSS_QUALIFIED_RULE@114..131 0: CSS_SELECTOR_LIST@114..129 0: CSS_COMPLEX_SELECTOR@114..129 0: CSS_COMPOUND_SELECTOR@114..125 @@ -482,7 +482,7 @@ CssRoot { 0: L_CURLY@129..130 "{" [] [] 1: CSS_DECLARATION_LIST@130..130 2: R_CURLY@130..131 "}" [] [] - 7: CSS_RULE@131..154 + 7: CSS_QUALIFIED_RULE@131..154 0: CSS_SELECTOR_LIST@131..152 0: CSS_COMPLEX_SELECTOR@131..152 0: CSS_COMPOUND_SELECTOR@131..144 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_matches.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_matches.css.snap index edf1f3da11e6..94846a976af3 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_matches.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_matches.css.snap @@ -18,7 +18,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -66,7 +66,7 @@ CssRoot { r_curly_token: R_CURLY@17..18 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -136,7 +136,7 @@ CssRoot { 0: CSS_ROOT@0..42 0: (empty) 1: CSS_RULE_LIST@0..41 - 0: CSS_RULE@0..18 + 0: CSS_QUALIFIED_RULE@0..18 0: CSS_SELECTOR_LIST@0..16 0: CSS_COMPLEX_SELECTOR@0..16 0: CSS_COMPOUND_SELECTOR@0..12 @@ -169,7 +169,7 @@ CssRoot { 0: L_CURLY@16..17 "{" [] [] 1: CSS_DECLARATION_LIST@17..17 2: R_CURLY@17..18 "}" [] [] - 1: CSS_RULE@18..41 + 1: CSS_QUALIFIED_RULE@18..41 0: CSS_SELECTOR_LIST@18..39 0: CSS_COMPLEX_SELECTOR@18..39 0: CSS_COMPOUND_SELECTOR@18..35 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module.css.snap index 0833eb58c187..c5c6bccba111 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module.css.snap @@ -19,7 +19,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -67,7 +67,7 @@ CssRoot { r_curly_token: R_CURLY@21..22 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -130,7 +130,7 @@ CssRoot { r_curly_token: R_CURLY@49..50 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -204,7 +204,7 @@ CssRoot { 0: CSS_ROOT@0..79 0: (empty) 1: CSS_RULE_LIST@0..78 - 0: CSS_RULE@0..22 + 0: CSS_QUALIFIED_RULE@0..22 0: CSS_SELECTOR_LIST@0..20 0: CSS_COMPOUND_SELECTOR@0..20 0: (empty) @@ -237,7 +237,7 @@ CssRoot { 0: L_CURLY@20..21 "{" [] [] 1: CSS_DECLARATION_LIST@21..21 2: R_CURLY@21..22 "}" [] [] - 1: CSS_RULE@22..50 + 1: CSS_QUALIFIED_RULE@22..50 0: CSS_SELECTOR_LIST@22..48 0: CSS_COMPOUND_SELECTOR@22..48 0: (empty) @@ -280,7 +280,7 @@ CssRoot { 0: L_CURLY@48..49 "{" [] [] 1: CSS_DECLARATION_LIST@49..49 2: R_CURLY@49..50 "}" [] [] - 2: CSS_RULE@50..78 + 2: CSS_QUALIFIED_RULE@50..78 0: CSS_SELECTOR_LIST@50..76 0: CSS_COMPLEX_SELECTOR@50..76 0: CSS_COMPOUND_SELECTOR@50..70 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_not.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_not.css.snap index 2c28dbce48bb..ab0b57dad75f 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_not.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_not.css.snap @@ -42,7 +42,7 @@ ul li:not(:first-of-type) {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -77,7 +77,7 @@ CssRoot { r_curly_token: R_CURLY@9..10 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -112,7 +112,7 @@ CssRoot { r_curly_token: R_CURLY@26..27 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -147,7 +147,7 @@ CssRoot { r_curly_token: R_CURLY@39..40 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -194,7 +194,7 @@ CssRoot { r_curly_token: R_CURLY@65..66 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -232,7 +232,7 @@ CssRoot { r_curly_token: R_CURLY@79..80 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -298,7 +298,7 @@ CssRoot { r_curly_token: R_CURLY@109..110 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -346,7 +346,7 @@ CssRoot { r_curly_token: R_CURLY@134..135 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -397,7 +397,7 @@ CssRoot { r_curly_token: R_CURLY@158..159 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -439,7 +439,7 @@ CssRoot { r_curly_token: R_CURLY@175..176 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -487,7 +487,7 @@ CssRoot { r_curly_token: R_CURLY@191..192 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -555,7 +555,7 @@ CssRoot { r_curly_token: R_CURLY@219..220 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -616,7 +616,7 @@ CssRoot { r_curly_token: R_CURLY@245..246 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -671,7 +671,7 @@ CssRoot { r_curly_token: R_CURLY@266..267 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -752,7 +752,7 @@ CssRoot { r_curly_token: R_CURLY@318..319 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -805,7 +805,7 @@ CssRoot { r_curly_token: R_CURLY@342..343 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -871,7 +871,7 @@ CssRoot { r_curly_token: R_CURLY@372..373 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -909,7 +909,7 @@ CssRoot { r_curly_token: R_CURLY@386..387 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -956,7 +956,7 @@ CssRoot { r_curly_token: R_CURLY@412..413 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1000,7 +1000,7 @@ CssRoot { r_curly_token: R_CURLY@429..430 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1060,7 +1060,7 @@ CssRoot { r_curly_token: R_CURLY@452..453 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1171,7 +1171,7 @@ CssRoot { r_curly_token: R_CURLY@497..498 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -1282,7 +1282,7 @@ CssRoot { r_curly_token: R_CURLY@542..543 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -1339,7 +1339,7 @@ CssRoot { r_curly_token: R_CURLY@571..572 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -1400,7 +1400,7 @@ CssRoot { 0: CSS_ROOT@0..589 0: (empty) 1: CSS_RULE_LIST@0..588 - 0: CSS_RULE@0..10 + 0: CSS_QUALIFIED_RULE@0..10 0: CSS_SELECTOR_LIST@0..8 0: CSS_COMPOUND_SELECTOR@0..8 0: (empty) @@ -1424,7 +1424,7 @@ CssRoot { 0: L_CURLY@8..9 "{" [] [] 1: CSS_DECLARATION_LIST@9..9 2: R_CURLY@9..10 "}" [] [] - 1: CSS_RULE@10..27 + 1: CSS_QUALIFIED_RULE@10..27 0: CSS_SELECTOR_LIST@10..25 0: CSS_COMPOUND_SELECTOR@10..25 0: (empty) @@ -1448,7 +1448,7 @@ CssRoot { 0: L_CURLY@25..26 "{" [] [] 1: CSS_DECLARATION_LIST@26..26 2: R_CURLY@26..27 "}" [] [] - 2: CSS_RULE@27..40 + 2: CSS_QUALIFIED_RULE@27..40 0: CSS_SELECTOR_LIST@27..38 0: CSS_COMPOUND_SELECTOR@27..38 0: (empty) @@ -1472,7 +1472,7 @@ CssRoot { 0: L_CURLY@38..39 "{" [] [] 1: CSS_DECLARATION_LIST@39..39 2: R_CURLY@39..40 "}" [] [] - 3: CSS_RULE@40..66 + 3: CSS_QUALIFIED_RULE@40..66 0: CSS_SELECTOR_LIST@40..64 0: CSS_COMPOUND_SELECTOR@40..64 0: (empty) @@ -1504,7 +1504,7 @@ CssRoot { 0: L_CURLY@64..65 "{" [] [] 1: CSS_DECLARATION_LIST@65..65 2: R_CURLY@65..66 "}" [] [] - 4: CSS_RULE@66..80 + 4: CSS_QUALIFIED_RULE@66..80 0: CSS_SELECTOR_LIST@66..78 0: CSS_COMPOUND_SELECTOR@66..78 0: (empty) @@ -1530,7 +1530,7 @@ CssRoot { 0: L_CURLY@78..79 "{" [] [] 1: CSS_DECLARATION_LIST@79..79 2: R_CURLY@79..80 "}" [] [] - 5: CSS_RULE@80..110 + 5: CSS_QUALIFIED_RULE@80..110 0: CSS_SELECTOR_LIST@80..108 0: CSS_COMPOUND_SELECTOR@80..108 0: (empty) @@ -1574,7 +1574,7 @@ CssRoot { 0: L_CURLY@108..109 "{" [] [] 1: CSS_DECLARATION_LIST@109..109 2: R_CURLY@109..110 "}" [] [] - 6: CSS_RULE@110..135 + 6: CSS_QUALIFIED_RULE@110..135 0: CSS_SELECTOR_LIST@110..133 0: CSS_COMPOUND_SELECTOR@110..133 0: (empty) @@ -1606,7 +1606,7 @@ CssRoot { 0: L_CURLY@133..134 "{" [] [] 1: CSS_DECLARATION_LIST@134..134 2: R_CURLY@134..135 "}" [] [] - 7: CSS_RULE@135..159 + 7: CSS_QUALIFIED_RULE@135..159 0: CSS_SELECTOR_LIST@135..157 0: CSS_COMPOUND_SELECTOR@135..157 0: (empty) @@ -1641,7 +1641,7 @@ CssRoot { 0: L_CURLY@157..158 "{" [] [] 1: CSS_DECLARATION_LIST@158..158 2: R_CURLY@158..159 "}" [] [] - 8: CSS_RULE@159..176 + 8: CSS_QUALIFIED_RULE@159..176 0: CSS_SELECTOR_LIST@159..174 0: CSS_COMPOUND_SELECTOR@159..174 0: (empty) @@ -1669,7 +1669,7 @@ CssRoot { 0: L_CURLY@174..175 "{" [] [] 1: CSS_DECLARATION_LIST@175..175 2: R_CURLY@175..176 "}" [] [] - 9: CSS_RULE@176..192 + 9: CSS_QUALIFIED_RULE@176..192 0: CSS_SELECTOR_LIST@176..190 0: CSS_COMPLEX_SELECTOR@176..190 0: CSS_COMPOUND_SELECTOR@176..181 @@ -1702,7 +1702,7 @@ CssRoot { 0: L_CURLY@190..191 "{" [] [] 1: CSS_DECLARATION_LIST@191..191 2: R_CURLY@191..192 "}" [] [] - 10: CSS_RULE@192..220 + 10: CSS_QUALIFIED_RULE@192..220 0: CSS_SELECTOR_LIST@192..218 0: CSS_COMPLEX_SELECTOR@192..218 0: CSS_COMPOUND_SELECTOR@192..197 @@ -1749,7 +1749,7 @@ CssRoot { 0: L_CURLY@218..219 "{" [] [] 1: CSS_DECLARATION_LIST@219..219 2: R_CURLY@219..220 "}" [] [] - 11: CSS_RULE@220..246 + 11: CSS_QUALIFIED_RULE@220..246 0: CSS_SELECTOR_LIST@220..244 0: CSS_COMPLEX_SELECTOR@220..244 0: CSS_COMPOUND_SELECTOR@220..225 @@ -1791,7 +1791,7 @@ CssRoot { 0: L_CURLY@244..245 "{" [] [] 1: CSS_DECLARATION_LIST@245..245 2: R_CURLY@245..246 "}" [] [] - 12: CSS_RULE@246..267 + 12: CSS_QUALIFIED_RULE@246..267 0: CSS_SELECTOR_LIST@246..265 0: CSS_COMPLEX_SELECTOR@246..265 0: CSS_COMPOUND_SELECTOR@246..249 @@ -1828,7 +1828,7 @@ CssRoot { 0: L_CURLY@265..266 "{" [] [] 1: CSS_DECLARATION_LIST@266..266 2: R_CURLY@266..267 "}" [] [] - 13: CSS_RULE@267..319 + 13: CSS_QUALIFIED_RULE@267..319 0: CSS_SELECTOR_LIST@267..317 0: CSS_COMPLEX_SELECTOR@267..317 0: CSS_COMPOUND_SELECTOR@267..273 @@ -1882,7 +1882,7 @@ CssRoot { 0: L_CURLY@317..318 "{" [] [] 1: CSS_DECLARATION_LIST@318..318 2: R_CURLY@318..319 "}" [] [] - 14: CSS_RULE@319..343 + 14: CSS_QUALIFIED_RULE@319..343 0: CSS_SELECTOR_LIST@319..341 0: CSS_COMPOUND_SELECTOR@319..341 0: (empty) @@ -1918,7 +1918,7 @@ CssRoot { 0: L_CURLY@341..342 "{" [] [] 1: CSS_DECLARATION_LIST@342..342 2: R_CURLY@342..343 "}" [] [] - 15: CSS_RULE@343..373 + 15: CSS_QUALIFIED_RULE@343..373 0: CSS_SELECTOR_LIST@343..371 0: CSS_COMPOUND_SELECTOR@343..371 0: (empty) @@ -1962,7 +1962,7 @@ CssRoot { 0: L_CURLY@371..372 "{" [] [] 1: CSS_DECLARATION_LIST@372..372 2: R_CURLY@372..373 "}" [] [] - 16: CSS_RULE@373..387 + 16: CSS_QUALIFIED_RULE@373..387 0: CSS_SELECTOR_LIST@373..385 0: CSS_COMPOUND_SELECTOR@373..385 0: (empty) @@ -1988,7 +1988,7 @@ CssRoot { 0: L_CURLY@385..386 "{" [] [] 1: CSS_DECLARATION_LIST@386..386 2: R_CURLY@386..387 "}" [] [] - 17: CSS_RULE@387..413 + 17: CSS_QUALIFIED_RULE@387..413 0: CSS_SELECTOR_LIST@387..411 0: CSS_COMPOUND_SELECTOR@387..411 0: (empty) @@ -2020,7 +2020,7 @@ CssRoot { 0: L_CURLY@411..412 "{" [] [] 1: CSS_DECLARATION_LIST@412..412 2: R_CURLY@412..413 "}" [] [] - 18: CSS_RULE@413..430 + 18: CSS_QUALIFIED_RULE@413..430 0: CSS_SELECTOR_LIST@413..428 0: CSS_COMPOUND_SELECTOR@413..428 0: (empty) @@ -2049,7 +2049,7 @@ CssRoot { 0: L_CURLY@428..429 "{" [] [] 1: CSS_DECLARATION_LIST@429..429 2: R_CURLY@429..430 "}" [] [] - 19: CSS_RULE@430..453 + 19: CSS_QUALIFIED_RULE@430..453 0: CSS_SELECTOR_LIST@430..451 0: CSS_COMPOUND_SELECTOR@430..451 0: (empty) @@ -2089,7 +2089,7 @@ CssRoot { 0: L_CURLY@451..452 "{" [] [] 1: CSS_DECLARATION_LIST@452..452 2: R_CURLY@452..453 "}" [] [] - 20: CSS_RULE@453..498 + 20: CSS_QUALIFIED_RULE@453..498 0: CSS_SELECTOR_LIST@453..496 0: CSS_COMPOUND_SELECTOR@453..496 0: (empty) @@ -2167,7 +2167,7 @@ CssRoot { 0: L_CURLY@496..497 "{" [] [] 1: CSS_DECLARATION_LIST@497..497 2: R_CURLY@497..498 "}" [] [] - 21: CSS_RULE@498..543 + 21: CSS_QUALIFIED_RULE@498..543 0: CSS_SELECTOR_LIST@498..541 0: CSS_COMPOUND_SELECTOR@498..541 0: (empty) @@ -2245,7 +2245,7 @@ CssRoot { 0: L_CURLY@541..542 "{" [] [] 1: CSS_DECLARATION_LIST@542..542 2: R_CURLY@542..543 "}" [] [] - 22: CSS_RULE@543..572 + 22: CSS_QUALIFIED_RULE@543..572 0: CSS_SELECTOR_LIST@543..570 0: CSS_COMPLEX_SELECTOR@543..570 0: CSS_COMPOUND_SELECTOR@543..546 @@ -2283,7 +2283,7 @@ CssRoot { 0: L_CURLY@570..571 "{" [] [] 1: CSS_DECLARATION_LIST@571..571 2: R_CURLY@571..572 "}" [] [] - 23: CSS_RULE@572..588 + 23: CSS_QUALIFIED_RULE@572..588 0: CSS_SELECTOR_LIST@572..586 0: CSS_COMPLEX_SELECTOR@572..586 0: CSS_COMPOUND_SELECTOR@572..580 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_past.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_past.css.snap index 22e7b7942723..1667c29fed8f 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_past.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_past.css.snap @@ -18,7 +18,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -86,7 +86,7 @@ CssRoot { r_curly_token: R_CURLY@22..23 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -180,7 +180,7 @@ CssRoot { 0: CSS_ROOT@0..53 0: (empty) 1: CSS_RULE_LIST@0..52 - 0: CSS_RULE@0..23 + 0: CSS_QUALIFIED_RULE@0..23 0: CSS_SELECTOR_LIST@0..21 0: CSS_COMPOUND_SELECTOR@0..21 0: (empty) @@ -228,7 +228,7 @@ CssRoot { 0: L_CURLY@21..22 "{" [] [] 1: CSS_DECLARATION_LIST@22..22 2: R_CURLY@22..23 "}" [] [] - 1: CSS_RULE@23..52 + 1: CSS_QUALIFIED_RULE@23..52 0: CSS_SELECTOR_LIST@23..50 0: CSS_COMPLEX_SELECTOR@23..50 0: CSS_COMPOUND_SELECTOR@23..44 diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_where.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_where.css.snap index caa5464e5fd5..fbb60291239f 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_where.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_where.css.snap @@ -27,7 +27,7 @@ a:where(:not(:hover)) {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -75,7 +75,7 @@ CssRoot { r_curly_token: R_CURLY@15..16 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -134,7 +134,7 @@ CssRoot { r_curly_token: R_CURLY@36..37 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -213,7 +213,7 @@ CssRoot { r_curly_token: R_CURLY@76..77 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -268,7 +268,7 @@ CssRoot { r_curly_token: R_CURLY@102..103 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssComplexSelector { @@ -449,7 +449,7 @@ CssRoot { r_curly_token: R_CURLY@179..180 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -506,7 +506,7 @@ CssRoot { r_curly_token: R_CURLY@206..207 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -569,7 +569,7 @@ CssRoot { r_curly_token: R_CURLY@235..236 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -648,7 +648,7 @@ CssRoot { r_curly_token: R_CURLY@274..275 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -703,7 +703,7 @@ CssRoot { r_curly_token: R_CURLY@302..303 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -757,7 +757,7 @@ CssRoot { r_curly_token: R_CURLY@334..335 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -828,7 +828,7 @@ CssRoot { 0: CSS_ROOT@0..361 0: (empty) 1: CSS_RULE_LIST@0..360 - 0: CSS_RULE@0..16 + 0: CSS_QUALIFIED_RULE@0..16 0: CSS_SELECTOR_LIST@0..14 0: CSS_COMPLEX_SELECTOR@0..14 0: CSS_COMPOUND_SELECTOR@0..10 @@ -861,7 +861,7 @@ CssRoot { 0: L_CURLY@14..15 "{" [] [] 1: CSS_DECLARATION_LIST@15..15 2: R_CURLY@15..16 "}" [] [] - 1: CSS_RULE@16..37 + 1: CSS_QUALIFIED_RULE@16..37 0: CSS_SELECTOR_LIST@16..35 0: CSS_COMPLEX_SELECTOR@16..35 0: CSS_COMPOUND_SELECTOR@16..31 @@ -902,7 +902,7 @@ CssRoot { 0: L_CURLY@35..36 "{" [] [] 1: CSS_DECLARATION_LIST@36..36 2: R_CURLY@36..37 "}" [] [] - 2: CSS_RULE@37..77 + 2: CSS_QUALIFIED_RULE@37..77 0: CSS_SELECTOR_LIST@37..75 0: CSS_COMPLEX_SELECTOR@37..75 0: CSS_COMPOUND_SELECTOR@37..66 @@ -956,7 +956,7 @@ CssRoot { 0: L_CURLY@75..76 "{" [] [] 1: CSS_DECLARATION_LIST@76..76 2: R_CURLY@76..77 "}" [] [] - 3: CSS_RULE@77..103 + 3: CSS_QUALIFIED_RULE@77..103 0: CSS_SELECTOR_LIST@77..101 0: CSS_COMPOUND_SELECTOR@77..101 0: (empty) @@ -993,7 +993,7 @@ CssRoot { 0: L_CURLY@101..102 "{" [] [] 1: CSS_DECLARATION_LIST@102..102 2: R_CURLY@102..103 "}" [] [] - 4: CSS_RULE@103..180 + 4: CSS_QUALIFIED_RULE@103..180 0: CSS_SELECTOR_LIST@103..178 0: CSS_COMPLEX_SELECTOR@103..178 0: CSS_COMPLEX_SELECTOR@103..155 @@ -1121,7 +1121,7 @@ CssRoot { 0: L_CURLY@178..179 "{" [] [] 1: CSS_DECLARATION_LIST@179..179 2: R_CURLY@179..180 "}" [] [] - 5: CSS_RULE@180..207 + 5: CSS_QUALIFIED_RULE@180..207 0: CSS_SELECTOR_LIST@180..205 0: CSS_COMPOUND_SELECTOR@180..205 0: (empty) @@ -1159,7 +1159,7 @@ CssRoot { 0: L_CURLY@205..206 "{" [] [] 1: CSS_DECLARATION_LIST@206..206 2: R_CURLY@206..207 "}" [] [] - 6: CSS_RULE@207..236 + 6: CSS_QUALIFIED_RULE@207..236 0: CSS_SELECTOR_LIST@207..234 0: CSS_COMPOUND_SELECTOR@207..234 0: (empty) @@ -1201,7 +1201,7 @@ CssRoot { 0: L_CURLY@234..235 "{" [] [] 1: CSS_DECLARATION_LIST@235..235 2: R_CURLY@235..236 "}" [] [] - 7: CSS_RULE@236..275 + 7: CSS_QUALIFIED_RULE@236..275 0: CSS_SELECTOR_LIST@236..273 0: CSS_COMPLEX_SELECTOR@236..273 0: CSS_COMPOUND_SELECTOR@236..262 @@ -1256,7 +1256,7 @@ CssRoot { 0: L_CURLY@273..274 "{" [] [] 1: CSS_DECLARATION_LIST@274..274 2: R_CURLY@274..275 "}" [] [] - 8: CSS_RULE@275..303 + 8: CSS_QUALIFIED_RULE@275..303 0: CSS_SELECTOR_LIST@275..301 0: CSS_COMPOUND_SELECTOR@275..301 0: (empty) @@ -1293,7 +1293,7 @@ CssRoot { 0: L_CURLY@301..302 "{" [] [] 1: CSS_DECLARATION_LIST@302..302 2: R_CURLY@302..303 "}" [] [] - 9: CSS_RULE@303..335 + 9: CSS_QUALIFIED_RULE@303..335 0: CSS_SELECTOR_LIST@303..333 0: CSS_COMPOUND_SELECTOR@303..333 0: (empty) @@ -1329,7 +1329,7 @@ CssRoot { 0: L_CURLY@333..334 "{" [] [] 1: CSS_DECLARATION_LIST@334..334 2: R_CURLY@334..335 "}" [] [] - 10: CSS_RULE@335..360 + 10: CSS_QUALIFIED_RULE@335..360 0: CSS_SELECTOR_LIST@335..358 0: CSS_COMPOUND_SELECTOR@335..358 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/basic.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/basic.css.snap index c68e81ff4acf..4a456172de02 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/basic.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/basic.css.snap @@ -50,7 +50,7 @@ a, b > .foo::before {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -78,7 +78,7 @@ CssRoot { r_curly_token: R_CURLY@10..11 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -106,7 +106,7 @@ CssRoot { r_curly_token: R_CURLY@31..32 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -134,7 +134,7 @@ CssRoot { r_curly_token: R_CURLY@45..46 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -162,7 +162,7 @@ CssRoot { r_curly_token: R_CURLY@60..61 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -202,7 +202,7 @@ CssRoot { r_curly_token: R_CURLY@77..78 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -230,7 +230,7 @@ CssRoot { r_curly_token: R_CURLY@99..100 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -287,7 +287,7 @@ CssRoot { r_curly_token: R_CURLY@136..137 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -310,7 +310,7 @@ CssRoot { r_curly_token: R_CURLY@156..157 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -333,7 +333,7 @@ CssRoot { r_curly_token: R_CURLY@169..170 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -364,7 +364,7 @@ CssRoot { r_curly_token: R_CURLY@206..207 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -387,7 +387,7 @@ CssRoot { r_curly_token: R_CURLY@224..225 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -410,7 +410,7 @@ CssRoot { r_curly_token: R_CURLY@240..241 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -443,7 +443,7 @@ CssRoot { r_curly_token: R_CURLY@257..258 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -478,7 +478,7 @@ CssRoot { r_curly_token: R_CURLY@276..277 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -501,7 +501,7 @@ CssRoot { r_curly_token: R_CURLY@297..298 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -524,7 +524,7 @@ CssRoot { r_curly_token: R_CURLY@315..316 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -553,7 +553,7 @@ CssRoot { r_curly_token: R_CURLY@353..354 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -581,7 +581,7 @@ CssRoot { r_curly_token: R_CURLY@367..368 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -645,7 +645,7 @@ CssRoot { 0: CSS_ROOT@0..393 0: (empty) 1: CSS_RULE_LIST@0..392 - 0: CSS_RULE@0..11 + 0: CSS_QUALIFIED_RULE@0..11 0: CSS_SELECTOR_LIST@0..9 0: CSS_COMPOUND_SELECTOR@0..9 0: (empty) @@ -663,7 +663,7 @@ CssRoot { 0: L_CURLY@9..10 "{" [] [] 1: CSS_DECLARATION_LIST@10..10 2: R_CURLY@10..11 "}" [] [] - 1: CSS_RULE@11..32 + 1: CSS_QUALIFIED_RULE@11..32 0: CSS_SELECTOR_LIST@11..30 0: CSS_COMPOUND_SELECTOR@11..30 0: (empty) @@ -681,7 +681,7 @@ CssRoot { 0: L_CURLY@30..31 "{" [] [] 1: CSS_DECLARATION_LIST@31..31 2: R_CURLY@31..32 "}" [] [] - 2: CSS_RULE@32..46 + 2: CSS_QUALIFIED_RULE@32..46 0: CSS_SELECTOR_LIST@32..44 0: CSS_COMPOUND_SELECTOR@32..44 0: (empty) @@ -699,7 +699,7 @@ CssRoot { 0: L_CURLY@44..45 "{" [] [] 1: CSS_DECLARATION_LIST@45..45 2: R_CURLY@45..46 "}" [] [] - 3: CSS_RULE@46..61 + 3: CSS_QUALIFIED_RULE@46..61 0: CSS_SELECTOR_LIST@46..59 0: CSS_COMPOUND_SELECTOR@46..59 0: (empty) @@ -717,7 +717,7 @@ CssRoot { 0: L_CURLY@59..60 "{" [] [] 1: CSS_DECLARATION_LIST@60..60 2: R_CURLY@60..61 "}" [] [] - 4: CSS_RULE@61..78 + 4: CSS_QUALIFIED_RULE@61..78 0: CSS_SELECTOR_LIST@61..76 0: CSS_COMPOUND_SELECTOR@61..76 0: (empty) @@ -744,7 +744,7 @@ CssRoot { 0: L_CURLY@76..77 "{" [] [] 1: CSS_DECLARATION_LIST@77..77 2: R_CURLY@77..78 "}" [] [] - 5: CSS_RULE@78..100 + 5: CSS_QUALIFIED_RULE@78..100 0: CSS_SELECTOR_LIST@78..98 0: CSS_COMPOUND_SELECTOR@78..98 0: (empty) @@ -762,7 +762,7 @@ CssRoot { 0: L_CURLY@98..99 "{" [] [] 1: CSS_DECLARATION_LIST@99..99 2: R_CURLY@99..100 "}" [] [] - 6: CSS_RULE@100..137 + 6: CSS_QUALIFIED_RULE@100..137 0: CSS_SELECTOR_LIST@100..135 0: CSS_COMPOUND_SELECTOR@100..135 0: (empty) @@ -800,7 +800,7 @@ CssRoot { 0: L_CURLY@135..136 "{" [] [] 1: CSS_DECLARATION_LIST@136..136 2: R_CURLY@136..137 "}" [] [] - 7: CSS_RULE@137..157 + 7: CSS_QUALIFIED_RULE@137..157 0: CSS_SELECTOR_LIST@137..155 0: CSS_COMPOUND_SELECTOR@137..155 0: (empty) @@ -815,7 +815,7 @@ CssRoot { 0: L_CURLY@155..156 "{" [] [] 1: CSS_DECLARATION_LIST@156..156 2: R_CURLY@156..157 "}" [] [] - 8: CSS_RULE@157..170 + 8: CSS_QUALIFIED_RULE@157..170 0: CSS_SELECTOR_LIST@157..168 0: CSS_COMPOUND_SELECTOR@157..168 0: (empty) @@ -830,7 +830,7 @@ CssRoot { 0: L_CURLY@168..169 "{" [] [] 1: CSS_DECLARATION_LIST@169..169 2: R_CURLY@169..170 "}" [] [] - 9: CSS_RULE@170..207 + 9: CSS_QUALIFIED_RULE@170..207 0: CSS_SELECTOR_LIST@170..205 0: CSS_COMPOUND_SELECTOR@170..205 0: (empty) @@ -851,7 +851,7 @@ CssRoot { 0: L_CURLY@205..206 "{" [] [] 1: CSS_DECLARATION_LIST@206..206 2: R_CURLY@206..207 "}" [] [] - 10: CSS_RULE@207..225 + 10: CSS_QUALIFIED_RULE@207..225 0: CSS_SELECTOR_LIST@207..223 0: CSS_COMPOUND_SELECTOR@207..223 0: (empty) @@ -866,7 +866,7 @@ CssRoot { 0: L_CURLY@223..224 "{" [] [] 1: CSS_DECLARATION_LIST@224..224 2: R_CURLY@224..225 "}" [] [] - 11: CSS_RULE@225..241 + 11: CSS_QUALIFIED_RULE@225..241 0: CSS_SELECTOR_LIST@225..239 0: CSS_COMPOUND_SELECTOR@225..239 0: (empty) @@ -881,7 +881,7 @@ CssRoot { 0: L_CURLY@239..240 "{" [] [] 1: CSS_DECLARATION_LIST@240..240 2: R_CURLY@240..241 "}" [] [] - 12: CSS_RULE@241..258 + 12: CSS_QUALIFIED_RULE@241..258 0: CSS_SELECTOR_LIST@241..256 0: CSS_COMPOUND_SELECTOR@241..256 0: (empty) @@ -904,7 +904,7 @@ CssRoot { 0: L_CURLY@256..257 "{" [] [] 1: CSS_DECLARATION_LIST@257..257 2: R_CURLY@257..258 "}" [] [] - 13: CSS_RULE@258..277 + 13: CSS_QUALIFIED_RULE@258..277 0: CSS_SELECTOR_LIST@258..275 0: CSS_COMPOUND_SELECTOR@258..275 0: (empty) @@ -928,7 +928,7 @@ CssRoot { 0: L_CURLY@275..276 "{" [] [] 1: CSS_DECLARATION_LIST@276..276 2: R_CURLY@276..277 "}" [] [] - 14: CSS_RULE@277..298 + 14: CSS_QUALIFIED_RULE@277..298 0: CSS_SELECTOR_LIST@277..296 0: CSS_COMPOUND_SELECTOR@277..296 0: (empty) @@ -943,7 +943,7 @@ CssRoot { 0: L_CURLY@296..297 "{" [] [] 1: CSS_DECLARATION_LIST@297..297 2: R_CURLY@297..298 "}" [] [] - 15: CSS_RULE@298..316 + 15: CSS_QUALIFIED_RULE@298..316 0: CSS_SELECTOR_LIST@298..314 0: CSS_COMPOUND_SELECTOR@298..314 0: (empty) @@ -958,7 +958,7 @@ CssRoot { 0: L_CURLY@314..315 "{" [] [] 1: CSS_DECLARATION_LIST@315..315 2: R_CURLY@315..316 "}" [] [] - 16: CSS_RULE@316..354 + 16: CSS_QUALIFIED_RULE@316..354 0: CSS_SELECTOR_LIST@316..352 0: CSS_COMPOUND_SELECTOR@316..352 0: (empty) @@ -977,7 +977,7 @@ CssRoot { 0: L_CURLY@352..353 "{" [] [] 1: CSS_DECLARATION_LIST@353..353 2: R_CURLY@353..354 "}" [] [] - 17: CSS_RULE@354..368 + 17: CSS_QUALIFIED_RULE@354..368 0: CSS_SELECTOR_LIST@354..366 0: CSS_COMPOUND_SELECTOR@354..366 0: (empty) @@ -995,7 +995,7 @@ CssRoot { 0: L_CURLY@366..367 "{" [] [] 1: CSS_DECLARATION_LIST@367..367 2: R_CURLY@367..368 "}" [] [] - 18: CSS_RULE@368..392 + 18: CSS_QUALIFIED_RULE@368..392 0: CSS_SELECTOR_LIST@368..390 0: CSS_COMPOUND_SELECTOR@368..371 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/cue-region.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/cue-region.css.snap index 6153f47e9def..c7a11203f0cc 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/cue-region.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/cue-region.css.snap @@ -19,7 +19,7 @@ video::cue-region( #scroll ) {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -47,7 +47,7 @@ CssRoot { r_curly_token: R_CURLY@19..20 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -89,7 +89,7 @@ CssRoot { r_curly_token: R_CURLY@49..50 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -142,7 +142,7 @@ CssRoot { 0: CSS_ROOT@0..87 0: (empty) 1: CSS_RULE_LIST@0..86 - 0: CSS_RULE@0..20 + 0: CSS_QUALIFIED_RULE@0..20 0: CSS_SELECTOR_LIST@0..18 0: CSS_COMPOUND_SELECTOR@0..18 0: (empty) @@ -160,7 +160,7 @@ CssRoot { 0: L_CURLY@18..19 "{" [] [] 1: CSS_DECLARATION_LIST@19..19 2: R_CURLY@19..20 "}" [] [] - 1: CSS_RULE@20..50 + 1: CSS_QUALIFIED_RULE@20..50 0: CSS_SELECTOR_LIST@20..48 0: CSS_COMPOUND_SELECTOR@20..48 0: (empty) @@ -188,7 +188,7 @@ CssRoot { 0: L_CURLY@48..49 "{" [] [] 1: CSS_DECLARATION_LIST@49..49 2: R_CURLY@49..50 "}" [] [] - 2: CSS_RULE@50..86 + 2: CSS_QUALIFIED_RULE@50..86 0: CSS_SELECTOR_LIST@50..84 0: CSS_COMPOUND_SELECTOR@50..84 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/cue.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/cue.css.snap index a21199841e35..5d9e8c7d57a3 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/cue.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/cue.css.snap @@ -22,7 +22,7 @@ video::cue(#cue1) {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -45,7 +45,7 @@ CssRoot { r_curly_token: R_CURLY@7..8 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -100,7 +100,7 @@ CssRoot { r_curly_token: R_CURLY@33..34 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -155,7 +155,7 @@ CssRoot { r_curly_token: R_CURLY@65..66 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -183,7 +183,7 @@ CssRoot { r_curly_token: R_CURLY@79..80 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -223,7 +223,7 @@ CssRoot { r_curly_token: R_CURLY@96..97 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -276,7 +276,7 @@ CssRoot { 0: CSS_ROOT@0..119 0: (empty) 1: CSS_RULE_LIST@0..118 - 0: CSS_RULE@0..8 + 0: CSS_QUALIFIED_RULE@0..8 0: CSS_SELECTOR_LIST@0..6 0: CSS_COMPOUND_SELECTOR@0..6 0: (empty) @@ -291,7 +291,7 @@ CssRoot { 0: L_CURLY@6..7 "{" [] [] 1: CSS_DECLARATION_LIST@7..7 2: R_CURLY@7..8 "}" [] [] - 1: CSS_RULE@8..34 + 1: CSS_QUALIFIED_RULE@8..34 0: CSS_SELECTOR_LIST@8..32 0: CSS_COMPOUND_SELECTOR@8..32 0: (empty) @@ -328,7 +328,7 @@ CssRoot { 0: L_CURLY@32..33 "{" [] [] 1: CSS_DECLARATION_LIST@33..33 2: R_CURLY@33..34 "}" [] [] - 2: CSS_RULE@34..66 + 2: CSS_QUALIFIED_RULE@34..66 0: CSS_SELECTOR_LIST@34..64 0: CSS_COMPOUND_SELECTOR@34..64 0: (empty) @@ -365,7 +365,7 @@ CssRoot { 0: L_CURLY@64..65 "{" [] [] 1: CSS_DECLARATION_LIST@65..65 2: R_CURLY@65..66 "}" [] [] - 3: CSS_RULE@66..80 + 3: CSS_QUALIFIED_RULE@66..80 0: CSS_SELECTOR_LIST@66..78 0: CSS_COMPOUND_SELECTOR@66..78 0: (empty) @@ -383,7 +383,7 @@ CssRoot { 0: L_CURLY@78..79 "{" [] [] 1: CSS_DECLARATION_LIST@79..79 2: R_CURLY@79..80 "}" [] [] - 4: CSS_RULE@80..97 + 4: CSS_QUALIFIED_RULE@80..97 0: CSS_SELECTOR_LIST@80..95 0: CSS_COMPOUND_SELECTOR@80..95 0: (empty) @@ -410,7 +410,7 @@ CssRoot { 0: L_CURLY@95..96 "{" [] [] 1: CSS_DECLARATION_LIST@96..96 2: R_CURLY@96..97 "}" [] [] - 5: CSS_RULE@97..118 + 5: CSS_QUALIFIED_RULE@97..118 0: CSS_SELECTOR_LIST@97..116 0: CSS_COMPOUND_SELECTOR@97..116 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/escaped.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/escaped.css.snap index 46a32fc672f4..0ba7941b020f 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/escaped.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/escaped.css.snap @@ -17,7 +17,7 @@ div::bef\ore {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -56,7 +56,7 @@ CssRoot { 0: CSS_ROOT@0..16 0: (empty) 1: CSS_RULE_LIST@0..15 - 0: CSS_RULE@0..15 + 0: CSS_QUALIFIED_RULE@0..15 0: CSS_SELECTOR_LIST@0..13 0: CSS_COMPOUND_SELECTOR@0..13 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/highlight.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/highlight.css.snap index 31c51c14cf36..282a6c660a82 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/highlight.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/highlight.css.snap @@ -24,7 +24,7 @@ div::highlight(foo) { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -50,7 +50,7 @@ CssRoot { r_curly_token: R_CURLY@21..23 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -81,7 +81,7 @@ CssRoot { r_curly_token: R_CURLY@46..48 "}" [Newline("\n")] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -123,7 +123,7 @@ CssRoot { 0: CSS_ROOT@0..74 0: (empty) 1: CSS_RULE_LIST@0..73 - 0: CSS_RULE@0..23 + 0: CSS_QUALIFIED_RULE@0..23 0: CSS_SELECTOR_LIST@0..20 0: CSS_COMPOUND_SELECTOR@0..20 0: (empty) @@ -141,7 +141,7 @@ CssRoot { 0: L_CURLY@20..21 "{" [] [] 1: CSS_DECLARATION_LIST@21..21 2: R_CURLY@21..23 "}" [Newline("\n")] [] - 1: CSS_RULE@23..48 + 1: CSS_QUALIFIED_RULE@23..48 0: CSS_SELECTOR_LIST@23..45 0: CSS_COMPOUND_SELECTOR@23..45 0: (empty) @@ -162,7 +162,7 @@ CssRoot { 0: L_CURLY@45..46 "{" [] [] 1: CSS_DECLARATION_LIST@46..46 2: R_CURLY@46..48 "}" [Newline("\n")] [] - 2: CSS_RULE@48..73 + 2: CSS_QUALIFIED_RULE@48..73 0: CSS_SELECTOR_LIST@48..70 0: CSS_COMPOUND_SELECTOR@48..70 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/part.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/part.css.snap index f360bc2e79b8..8aaca7e04a12 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/part.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/part.css.snap @@ -18,7 +18,7 @@ tabbed-custom-element::part( active ) {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -49,7 +49,7 @@ CssRoot { r_curly_token: R_CURLY@37..38 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -91,7 +91,7 @@ CssRoot { 0: CSS_ROOT@0..84 0: (empty) 1: CSS_RULE_LIST@0..83 - 0: CSS_RULE@0..38 + 0: CSS_QUALIFIED_RULE@0..38 0: CSS_SELECTOR_LIST@0..36 0: CSS_COMPOUND_SELECTOR@0..36 0: (empty) @@ -112,7 +112,7 @@ CssRoot { 0: L_CURLY@36..37 "{" [] [] 1: CSS_DECLARATION_LIST@37..37 2: R_CURLY@37..38 "}" [] [] - 1: CSS_RULE@38..83 + 1: CSS_QUALIFIED_RULE@38..83 0: CSS_SELECTOR_LIST@38..81 0: CSS_COMPOUND_SELECTOR@38..81 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/presudo_complex.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/presudo_complex.css.snap index 1a76bfa201f8..105e1979239a 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/presudo_complex.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/presudo_complex.css.snap @@ -22,7 +22,7 @@ video::cue(b) div {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -63,7 +63,7 @@ CssRoot { r_curly_token: R_CURLY@14..15 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -104,7 +104,7 @@ CssRoot { r_curly_token: R_CURLY@32..33 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -157,7 +157,7 @@ CssRoot { r_curly_token: R_CURLY@53..54 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -196,7 +196,7 @@ CssRoot { r_curly_token: R_CURLY@80..81 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssComplexSelector { left: CssCompoundSelector { @@ -235,7 +235,7 @@ CssRoot { r_curly_token: R_CURLY@102..103 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -301,7 +301,7 @@ CssRoot { 0: CSS_ROOT@0..131 0: (empty) 1: CSS_RULE_LIST@0..130 - 0: CSS_RULE@0..15 + 0: CSS_QUALIFIED_RULE@0..15 0: CSS_SELECTOR_LIST@0..13 0: CSS_COMPLEX_SELECTOR@0..13 0: CSS_COMPOUND_SELECTOR@0..8 @@ -328,7 +328,7 @@ CssRoot { 0: L_CURLY@13..14 "{" [] [] 1: CSS_DECLARATION_LIST@14..14 2: R_CURLY@14..15 "}" [] [] - 1: CSS_RULE@15..33 + 1: CSS_QUALIFIED_RULE@15..33 0: CSS_SELECTOR_LIST@15..31 0: CSS_COMPLEX_SELECTOR@15..31 0: CSS_COMPOUND_SELECTOR@15..26 @@ -355,7 +355,7 @@ CssRoot { 0: L_CURLY@31..32 "{" [] [] 1: CSS_DECLARATION_LIST@32..32 2: R_CURLY@32..33 "}" [] [] - 2: CSS_RULE@33..54 + 2: CSS_QUALIFIED_RULE@33..54 0: CSS_SELECTOR_LIST@33..52 0: CSS_COMPLEX_SELECTOR@33..52 0: CSS_COMPOUND_SELECTOR@33..47 @@ -391,7 +391,7 @@ CssRoot { 0: L_CURLY@52..53 "{" [] [] 1: CSS_DECLARATION_LIST@53..53 2: R_CURLY@53..54 "}" [] [] - 3: CSS_RULE@54..81 + 3: CSS_QUALIFIED_RULE@54..81 0: CSS_SELECTOR_LIST@54..79 0: CSS_COMPLEX_SELECTOR@54..79 0: CSS_COMPOUND_SELECTOR@54..74 @@ -418,7 +418,7 @@ CssRoot { 0: L_CURLY@79..80 "{" [] [] 1: CSS_DECLARATION_LIST@80..80 2: R_CURLY@80..81 "}" [] [] - 4: CSS_RULE@81..103 + 4: CSS_QUALIFIED_RULE@81..103 0: CSS_SELECTOR_LIST@81..101 0: CSS_COMPLEX_SELECTOR@81..101 0: CSS_COMPOUND_SELECTOR@81..96 @@ -445,7 +445,7 @@ CssRoot { 0: L_CURLY@101..102 "{" [] [] 1: CSS_DECLARATION_LIST@102..102 2: R_CURLY@102..103 "}" [] [] - 5: CSS_RULE@103..130 + 5: CSS_QUALIFIED_RULE@103..130 0: CSS_SELECTOR_LIST@103..128 0: CSS_COMPOUND_SELECTOR@103..128 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/slotted.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/slotted.css.snap index a5440f8204c1..ac060002c776 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/slotted.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_element/slotted.css.snap @@ -21,7 +21,7 @@ expression: snapshot CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -54,7 +54,7 @@ CssRoot { r_curly_token: R_CURLY@14..15 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -89,7 +89,7 @@ CssRoot { r_curly_token: R_CURLY@33..34 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -124,7 +124,7 @@ CssRoot { r_curly_token: R_CURLY@58..59 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -170,7 +170,7 @@ CssRoot { 0: CSS_ROOT@0..80 0: (empty) 1: CSS_RULE_LIST@0..79 - 0: CSS_RULE@0..15 + 0: CSS_QUALIFIED_RULE@0..15 0: CSS_SELECTOR_LIST@0..13 0: CSS_COMPOUND_SELECTOR@0..13 0: (empty) @@ -193,7 +193,7 @@ CssRoot { 0: L_CURLY@13..14 "{" [] [] 1: CSS_DECLARATION_LIST@14..14 2: R_CURLY@14..15 "}" [] [] - 1: CSS_RULE@15..34 + 1: CSS_QUALIFIED_RULE@15..34 0: CSS_SELECTOR_LIST@15..32 0: CSS_COMPOUND_SELECTOR@15..32 0: (empty) @@ -217,7 +217,7 @@ CssRoot { 0: L_CURLY@32..33 "{" [] [] 1: CSS_DECLARATION_LIST@33..33 2: R_CURLY@33..34 "}" [] [] - 2: CSS_RULE@34..59 + 2: CSS_QUALIFIED_RULE@34..59 0: CSS_SELECTOR_LIST@34..57 0: CSS_COMPOUND_SELECTOR@34..57 0: (empty) @@ -241,7 +241,7 @@ CssRoot { 0: L_CURLY@57..58 "{" [] [] 1: CSS_DECLARATION_LIST@58..58 2: R_CURLY@58..59 "}" [] [] - 3: CSS_RULE@59..79 + 3: CSS_QUALIFIED_RULE@59..79 0: CSS_SELECTOR_LIST@59..76 0: CSS_COMPOUND_SELECTOR@59..76 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/subselector.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/subselector.css.snap index ca5f62bbad07..dc89a8b84eb4 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/subselector.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/subselector.css.snap @@ -21,7 +21,7 @@ div.class.content#id#id, *.class.content#id#id1, .class.content#id#id1 {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -65,7 +65,7 @@ CssRoot { r_curly_token: R_CURLY@25..26 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -107,7 +107,7 @@ CssRoot { r_curly_token: R_CURLY@51..52 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -146,7 +146,7 @@ CssRoot { r_curly_token: R_CURLY@76..77 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -266,7 +266,7 @@ CssRoot { 0: CSS_ROOT@0..153 0: (empty) 1: CSS_RULE_LIST@0..152 - 0: CSS_RULE@0..26 + 0: CSS_QUALIFIED_RULE@0..26 0: CSS_SELECTOR_LIST@0..24 0: CSS_COMPOUND_SELECTOR@0..24 0: (empty) @@ -295,7 +295,7 @@ CssRoot { 0: L_CURLY@24..25 "{" [] [] 1: CSS_DECLARATION_LIST@25..25 2: R_CURLY@25..26 "}" [] [] - 1: CSS_RULE@26..52 + 1: CSS_QUALIFIED_RULE@26..52 0: CSS_SELECTOR_LIST@26..50 0: CSS_COMPOUND_SELECTOR@26..50 0: (empty) @@ -323,7 +323,7 @@ CssRoot { 0: L_CURLY@50..51 "{" [] [] 1: CSS_DECLARATION_LIST@51..51 2: R_CURLY@51..52 "}" [] [] - 2: CSS_RULE@52..77 + 2: CSS_QUALIFIED_RULE@52..77 0: CSS_SELECTOR_LIST@52..75 0: CSS_COMPOUND_SELECTOR@52..75 0: (empty) @@ -349,7 +349,7 @@ CssRoot { 0: L_CURLY@75..76 "{" [] [] 1: CSS_DECLARATION_LIST@76..76 2: R_CURLY@76..77 "}" [] [] - 3: CSS_RULE@77..152 + 3: CSS_QUALIFIED_RULE@77..152 0: CSS_SELECTOR_LIST@77..150 0: CSS_COMPOUND_SELECTOR@77..102 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/type.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/type.css.snap index 4490bf80d2b6..019297259536 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/type.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/type.css.snap @@ -20,7 +20,7 @@ foo|h1 {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -50,7 +50,7 @@ CssRoot { r_curly_token: R_CURLY@8..9 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -76,7 +76,7 @@ CssRoot { r_curly_token: R_CURLY@18..19 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -98,7 +98,7 @@ CssRoot { r_curly_token: R_CURLY@25..26 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -133,7 +133,7 @@ CssRoot { 0: CSS_ROOT@0..35 0: (empty) 1: CSS_RULE_LIST@0..34 - 0: CSS_RULE@0..9 + 0: CSS_QUALIFIED_RULE@0..9 0: CSS_SELECTOR_LIST@0..7 0: CSS_COMPOUND_SELECTOR@0..3 0: (empty) @@ -154,7 +154,7 @@ CssRoot { 0: L_CURLY@7..8 "{" [] [] 1: CSS_DECLARATION_LIST@8..8 2: R_CURLY@8..9 "}" [] [] - 1: CSS_RULE@9..19 + 1: CSS_QUALIFIED_RULE@9..19 0: CSS_SELECTOR_LIST@9..17 0: CSS_COMPOUND_SELECTOR@9..17 0: (empty) @@ -171,7 +171,7 @@ CssRoot { 0: L_CURLY@17..18 "{" [] [] 1: CSS_DECLARATION_LIST@18..18 2: R_CURLY@18..19 "}" [] [] - 2: CSS_RULE@19..26 + 2: CSS_QUALIFIED_RULE@19..26 0: CSS_SELECTOR_LIST@19..24 0: CSS_COMPOUND_SELECTOR@19..24 0: (empty) @@ -186,7 +186,7 @@ CssRoot { 0: L_CURLY@24..25 "{" [] [] 1: CSS_DECLARATION_LIST@25..25 2: R_CURLY@25..26 "}" [] [] - 3: CSS_RULE@26..34 + 3: CSS_QUALIFIED_RULE@26..34 0: CSS_SELECTOR_LIST@26..32 0: CSS_COMPOUND_SELECTOR@26..32 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/universal.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/universal.css.snap index ffc4e252388d..4e5dff17f02f 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/selector/universal.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/universal.css.snap @@ -19,7 +19,7 @@ foo|* {} CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -45,7 +45,7 @@ CssRoot { r_curly_token: R_CURLY@7..8 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -67,7 +67,7 @@ CssRoot { r_curly_token: R_CURLY@14..15 "}" [] [], }, }, - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -102,7 +102,7 @@ CssRoot { 0: CSS_ROOT@0..25 0: (empty) 1: CSS_RULE_LIST@0..24 - 0: CSS_RULE@0..8 + 0: CSS_QUALIFIED_RULE@0..8 0: CSS_SELECTOR_LIST@0..6 0: CSS_COMPOUND_SELECTOR@0..2 0: (empty) @@ -121,7 +121,7 @@ CssRoot { 0: L_CURLY@6..7 "{" [] [] 1: CSS_DECLARATION_LIST@7..7 2: R_CURLY@7..8 "}" [] [] - 1: CSS_RULE@8..15 + 1: CSS_QUALIFIED_RULE@8..15 0: CSS_SELECTOR_LIST@8..13 0: CSS_COMPOUND_SELECTOR@8..13 0: (empty) @@ -136,7 +136,7 @@ CssRoot { 0: L_CURLY@13..14 "{" [] [] 1: CSS_DECLARATION_LIST@14..14 2: R_CURLY@14..15 "}" [] [] - 2: CSS_RULE@15..24 + 2: CSS_QUALIFIED_RULE@15..24 0: CSS_SELECTOR_LIST@15..22 0: CSS_COMPOUND_SELECTOR@15..22 0: (empty) diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/values/url_value.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/values/url_value.css.snap index 09927b4b0305..f7a323e17a5b 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/values/url_value.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/values/url_value.css.snap @@ -56,7 +56,7 @@ div { CssRoot { bom_token: missing (optional), rules: CssRuleList [ - CssRule { + CssQualifiedRule { prelude: CssSelectorList [ CssCompoundSelector { nesting_selector_token: missing (optional), @@ -690,7 +690,7 @@ CssRoot { 0: CSS_ROOT@0..1291 0: (empty) 1: CSS_RULE_LIST@0..1290 - 0: CSS_RULE@0..1290 + 0: CSS_QUALIFIED_RULE@0..1290 0: CSS_SELECTOR_LIST@0..4 0: CSS_COMPOUND_SELECTOR@0..4 0: (empty) diff --git a/crates/biome_css_syntax/src/generated/kind.rs b/crates/biome_css_syntax/src/generated/kind.rs index bfe9bf9d4b27..772580ce5f3c 100644 --- a/crates/biome_css_syntax/src/generated/kind.rs +++ b/crates/biome_css_syntax/src/generated/kind.rs @@ -236,7 +236,7 @@ pub enum CssSyntaxKind { MULTILINE_COMMENT, CSS_ROOT, CSS_RULE_LIST, - CSS_RULE, + CSS_QUALIFIED_RULE, CSS_SELECTOR_LIST, CSS_ANY_FUNCTION, CSS_DECLARATION_LIST_BLOCK, diff --git a/crates/biome_css_syntax/src/generated/macros.rs b/crates/biome_css_syntax/src/generated/macros.rs index 26c056aadf01..60f16d090829 100644 --- a/crates/biome_css_syntax/src/generated/macros.rs +++ b/crates/biome_css_syntax/src/generated/macros.rs @@ -447,6 +447,10 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::CssPseudoElementSelector::new_unchecked(node) }; $body } + $crate::CssSyntaxKind::CSS_QUALIFIED_RULE => { + let $pattern = unsafe { $crate::CssQualifiedRule::new_unchecked(node) }; + $body + } $crate::CssSyntaxKind::CSS_QUERY_FEATURE_BOOLEAN => { let $pattern = unsafe { $crate::CssQueryFeatureBoolean::new_unchecked(node) }; $body @@ -490,10 +494,6 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::CssRoot::new_unchecked(node) }; $body } - $crate::CssSyntaxKind::CSS_RULE => { - let $pattern = unsafe { $crate::CssRule::new_unchecked(node) }; - $body - } $crate::CssSyntaxKind::CSS_RULE_LIST_BLOCK => { let $pattern = unsafe { $crate::CssRuleListBlock::new_unchecked(node) }; $body diff --git a/crates/biome_css_syntax/src/generated/nodes.rs b/crates/biome_css_syntax/src/generated/nodes.rs index 1921acaf391a..7460341c6cf0 100644 --- a/crates/biome_css_syntax/src/generated/nodes.rs +++ b/crates/biome_css_syntax/src/generated/nodes.rs @@ -4417,6 +4417,47 @@ pub struct CssPseudoElementSelectorFields { pub element: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct CssQualifiedRule { + pub(crate) syntax: SyntaxNode, +} +impl CssQualifiedRule { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> CssQualifiedRuleFields { + CssQualifiedRuleFields { + prelude: self.prelude(), + block: self.block(), + } + } + pub fn prelude(&self) -> CssSelectorList { + support::list(&self.syntax, 0usize) + } + pub fn block(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for CssQualifiedRule { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct CssQualifiedRuleFields { + pub prelude: CssSelectorList, + pub block: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct CssQueryFeatureBoolean { pub(crate) syntax: SyntaxNode, } @@ -4855,47 +4896,6 @@ pub struct CssRootFields { pub eof_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct CssRule { - pub(crate) syntax: SyntaxNode, -} -impl CssRule { - #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] - #[doc = r" or a match on [SyntaxNode::kind]"] - #[inline] - pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { - Self { syntax } - } - pub fn as_fields(&self) -> CssRuleFields { - CssRuleFields { - prelude: self.prelude(), - block: self.block(), - } - } - pub fn prelude(&self) -> CssSelectorList { - support::list(&self.syntax, 0usize) - } - pub fn block(&self) -> SyntaxResult { - support::required_node(&self.syntax, 1usize) - } -} -#[cfg(feature = "serde")] -impl Serialize for CssRule { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.as_fields().serialize(serializer) - } -} -#[cfg_attr(feature = "serde", derive(Serialize))] -pub struct CssRuleFields { - pub prelude: CssSelectorList, - pub block: SyntaxResult, -} -#[derive(Clone, PartialEq, Eq, Hash)] pub struct CssRuleListBlock { pub(crate) syntax: SyntaxNode, } @@ -7420,7 +7420,7 @@ impl AnyCssRelativeSelector { pub enum AnyCssRule { CssAtRule(CssAtRule), CssBogusRule(CssBogusRule), - CssRule(CssRule), + CssQualifiedRule(CssQualifiedRule), } impl AnyCssRule { pub fn as_css_at_rule(&self) -> Option<&CssAtRule> { @@ -7435,9 +7435,9 @@ impl AnyCssRule { _ => None, } } - pub fn as_css_rule(&self) -> Option<&CssRule> { + pub fn as_css_qualified_rule(&self) -> Option<&CssQualifiedRule> { match &self { - AnyCssRule::CssRule(item) => Some(item), + AnyCssRule::CssQualifiedRule(item) => Some(item), _ => None, } } @@ -12147,6 +12147,45 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for CssQualifiedRule { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(CSS_QUALIFIED_RULE as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == CSS_QUALIFIED_RULE + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for CssQualifiedRule { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CssQualifiedRule") + .field("prelude", &self.prelude()) + .field("block", &support::DebugSyntaxResult(self.block())) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: CssQualifiedRule) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: CssQualifiedRule) -> SyntaxElement { + n.syntax.into() + } +} impl AstNode for CssQueryFeatureBoolean { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -12567,45 +12606,6 @@ impl From for SyntaxElement { n.syntax.into() } } -impl AstNode for CssRule { - type Language = Language; - const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(CSS_RULE as u16)); - fn can_cast(kind: SyntaxKind) -> bool { - kind == CSS_RULE - } - fn cast(syntax: SyntaxNode) -> Option { - if Self::can_cast(syntax.kind()) { - Some(Self { syntax }) - } else { - None - } - } - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } - fn into_syntax(self) -> SyntaxNode { - self.syntax - } -} -impl std::fmt::Debug for CssRule { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CssRule") - .field("prelude", &self.prelude()) - .field("block", &support::DebugSyntaxResult(self.block())) - .finish() - } -} -impl From for SyntaxNode { - fn from(n: CssRule) -> SyntaxNode { - n.syntax - } -} -impl From for SyntaxElement { - fn from(n: CssRule) -> SyntaxElement { - n.syntax.into() - } -} impl AstNode for CssRuleListBlock { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -17892,24 +17892,24 @@ impl From for AnyCssRule { AnyCssRule::CssBogusRule(node) } } -impl From for AnyCssRule { - fn from(node: CssRule) -> AnyCssRule { - AnyCssRule::CssRule(node) +impl From for AnyCssRule { + fn from(node: CssQualifiedRule) -> AnyCssRule { + AnyCssRule::CssQualifiedRule(node) } } impl AstNode for AnyCssRule { type Language = Language; const KIND_SET: SyntaxKindSet = CssAtRule::KIND_SET .union(CssBogusRule::KIND_SET) - .union(CssRule::KIND_SET); + .union(CssQualifiedRule::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { - matches!(kind, CSS_AT_RULE | CSS_BOGUS_RULE | CSS_RULE) + matches!(kind, CSS_AT_RULE | CSS_BOGUS_RULE | CSS_QUALIFIED_RULE) } fn cast(syntax: SyntaxNode) -> Option { let res = match syntax.kind() { CSS_AT_RULE => AnyCssRule::CssAtRule(CssAtRule { syntax }), CSS_BOGUS_RULE => AnyCssRule::CssBogusRule(CssBogusRule { syntax }), - CSS_RULE => AnyCssRule::CssRule(CssRule { syntax }), + CSS_QUALIFIED_RULE => AnyCssRule::CssQualifiedRule(CssQualifiedRule { syntax }), _ => return None, }; Some(res) @@ -17918,14 +17918,14 @@ impl AstNode for AnyCssRule { match self { AnyCssRule::CssAtRule(it) => &it.syntax, AnyCssRule::CssBogusRule(it) => &it.syntax, - AnyCssRule::CssRule(it) => &it.syntax, + AnyCssRule::CssQualifiedRule(it) => &it.syntax, } } fn into_syntax(self) -> SyntaxNode { match self { AnyCssRule::CssAtRule(it) => it.syntax, AnyCssRule::CssBogusRule(it) => it.syntax, - AnyCssRule::CssRule(it) => it.syntax, + AnyCssRule::CssQualifiedRule(it) => it.syntax, } } } @@ -17934,7 +17934,7 @@ impl std::fmt::Debug for AnyCssRule { match self { AnyCssRule::CssAtRule(it) => std::fmt::Debug::fmt(it, f), AnyCssRule::CssBogusRule(it) => std::fmt::Debug::fmt(it, f), - AnyCssRule::CssRule(it) => std::fmt::Debug::fmt(it, f), + AnyCssRule::CssQualifiedRule(it) => std::fmt::Debug::fmt(it, f), } } } @@ -17943,7 +17943,7 @@ impl From for SyntaxNode { match n { AnyCssRule::CssAtRule(it) => it.into(), AnyCssRule::CssBogusRule(it) => it.into(), - AnyCssRule::CssRule(it) => it.into(), + AnyCssRule::CssQualifiedRule(it) => it.into(), } } } @@ -20035,6 +20035,11 @@ impl std::fmt::Display for CssPseudoElementSelector { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for CssQualifiedRule { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for CssQueryFeatureBoolean { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -20085,11 +20090,6 @@ impl std::fmt::Display for CssRoot { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for CssRule { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(self.syntax(), f) - } -} impl std::fmt::Display for CssRuleListBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/biome_css_syntax/src/generated/nodes_mut.rs b/crates/biome_css_syntax/src/generated/nodes_mut.rs index a1bd3300110e..69f34b9eb69b 100644 --- a/crates/biome_css_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_css_syntax/src/generated/nodes_mut.rs @@ -1742,6 +1742,20 @@ impl CssPseudoElementSelector { ) } } +impl CssQualifiedRule { + pub fn with_prelude(self, element: CssSelectorList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_block(self, element: AnyCssDeclarationListBlock) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } +} impl CssQueryFeatureBoolean { pub fn with_name(self, element: CssIdentifier) -> Self { Self::unwrap_cast( @@ -1918,20 +1932,6 @@ impl CssRoot { ) } } -impl CssRule { - pub fn with_prelude(self, element: CssSelectorList) -> Self { - Self::unwrap_cast( - self.syntax - .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), - ) - } - pub fn with_block(self, element: AnyCssDeclarationListBlock) -> Self { - Self::unwrap_cast( - self.syntax - .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), - ) - } -} impl CssRuleListBlock { pub fn with_l_curly_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( diff --git a/crates/biome_unicode_table/src/tables.rs b/crates/biome_unicode_table/src/tables.rs index 7700038e9f9c..3ed00b032140 100644 --- a/crates/biome_unicode_table/src/tables.rs +++ b/crates/biome_unicode_table/src/tables.rs @@ -318,7 +318,6 @@ pub mod derived_property { ('ῠ', 'Ῥ'), ('ῲ', 'ῴ'), ('ῶ', 'ῼ'), - ('\u{200c}', '\u{200d}'), ('‿', '⁀'), ('⁔', '⁔'), ('ⁱ', 'ⁱ'), @@ -363,7 +362,8 @@ pub mod derived_property { ('〸', '〼'), ('ぁ', 'ゖ'), ('\u{3099}', 'ゟ'), - ('ァ', 'ヿ'), + ('ァ', 'ヺ'), + ('ー', 'ヿ'), ('ㄅ', 'ㄯ'), ('ㄱ', 'ㆎ'), ('ㆠ', 'ㆿ'), @@ -441,7 +441,7 @@ pub mod derived_property { ('A', 'Z'), ('_', '_'), ('a', 'z'), - ('・', 'ᄒ'), + ('ヲ', 'ᄒ'), ('ᅡ', 'ᅦ'), ('ᅧ', 'ᅬ'), ('ᅭ', 'ᅲ'), @@ -782,7 +782,6 @@ pub mod derived_property { ('𫝀', '𫠝'), ('𫠠', '𬺡'), ('𬺰', '𮯠'), - ('\u{2ebf0}', '\u{2ee5d}'), ('丽', '𪘀'), ('𰀀', '𱍊'), ('𱍐', '𲎯'), @@ -1448,7 +1447,6 @@ pub mod derived_property { ('𫝀', '𫠝'), ('𫠠', '𬺡'), ('𬺰', '𮯠'), - ('\u{2ebf0}', '\u{2ee5d}'), ('丽', '𪘀'), ('𰀀', '𱍊'), ('𱍐', '𲎯'), diff --git a/xtask/codegen/css.ungram b/xtask/codegen/css.ungram index 45496400ae0f..e1557dfeb9bc 100644 --- a/xtask/codegen/css.ungram +++ b/xtask/codegen/css.ungram @@ -64,13 +64,13 @@ CssRoot = CssRuleList = AnyCssRule* AnyCssRule = - CssRule + CssQualifiedRule | CssAtRule | CssBogusRule // .header { color: red } // ^^^^^^^^^^^^^^^^^^^^^ -CssRule = +CssQualifiedRule = prelude: CssSelectorList block: AnyCssDeclarationListBlock diff --git a/xtask/codegen/src/css_kinds_src.rs b/xtask/codegen/src/css_kinds_src.rs index 973577f64568..0ee75c1ece88 100644 --- a/xtask/codegen/src/css_kinds_src.rs +++ b/xtask/codegen/src/css_kinds_src.rs @@ -260,7 +260,7 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { nodes: &[ "CSS_ROOT", "CSS_RULE_LIST", - "CSS_RULE", + "CSS_QUALIFIED_RULE", "CSS_SELECTOR_LIST", "CSS_ANY_FUNCTION", "CSS_DECLARATION_LIST_BLOCK", From b20f0a4bc46552e3323e4ef315e1fb1b1aeda4fd Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Fri, 12 Jan 2024 23:29:51 +0100 Subject: [PATCH 33/82] fix(js_formatter): fix #1511 (#1547) Co-authored-by: Emanuele Stoppa --- CHANGELOG.md | 4 +- crates/biome_formatter/src/buffer.rs | 4 -- .../tests/specs/ts/issue1511.ts | 3 ++ .../tests/specs/ts/issue1511.ts.snap | 43 +++++++++++++++++++ .../src/content/docs/internals/changelog.mdx | 4 +- .../docs/zh-cn/internals/changelog.mdx | 4 +- 6 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 crates/biome_js_formatter/tests/specs/ts/issue1511.ts create mode 100644 crates/biome_js_formatter/tests/specs/ts/issue1511.ts.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index dd6f5460d814..060b9d77b307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom - Fix [#1172](https://github.com/biomejs/biome/issues/1172). Fix placement of line comment after function expression parentheses, they are now attached to first statement in body. Contributed by @kalleep +- Fix [#1511](https://github.com/biomejs/biome/issues/1511) that made the JavaScript formatter crash. Contributed @Conaclos + ### JavaScript APIs ### Linter @@ -1916,7 +1918,7 @@ The following rules are now recommended: The code action now removes any whitespace between the parameter name and its initialization. -- Relax [noConfusingArrow](https://biomejs.dev/linter/rules/no-confusing-arrow/) +- Relax `noConfusingArrow` All arrow functions that enclose its parameter with parenthesis are allowed. Thus, the following snippet no longer trigger the rule: diff --git a/crates/biome_formatter/src/buffer.rs b/crates/biome_formatter/src/buffer.rs index 420990336ca7..5012067d2c96 100644 --- a/crates/biome_formatter/src/buffer.rs +++ b/crates/biome_formatter/src/buffer.rs @@ -557,10 +557,6 @@ fn clean_interned( Some((mut cleaned, rest)) => { let mut is_in_expanded_conditional_content = false; for element in rest { - if is_in_expanded_conditional_content { - continue; - } - let element = match element { FormatElement::Tag(Tag::StartConditionalContent(condition)) if condition.mode == PrintMode::Expanded => diff --git a/crates/biome_js_formatter/tests/specs/ts/issue1511.ts b/crates/biome_js_formatter/tests/specs/ts/issue1511.ts new file mode 100644 index 000000000000..5ea570eb2e82 --- /dev/null +++ b/crates/biome_js_formatter/tests/specs/ts/issue1511.ts @@ -0,0 +1,3 @@ +call(a, function (b: () => t1 | t2) {}); + +call(a, (b: () => t1 | t2) => {}); diff --git a/crates/biome_js_formatter/tests/specs/ts/issue1511.ts.snap b/crates/biome_js_formatter/tests/specs/ts/issue1511.ts.snap new file mode 100644 index 000000000000..c39f8cb433b1 --- /dev/null +++ b/crates/biome_js_formatter/tests/specs/ts/issue1511.ts.snap @@ -0,0 +1,43 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: ts/issue1511.ts +--- + +# Input + +```ts +call(a, function (b: () => t1 | t2) {}); + +call(a, (b: () => t1 | t2) => {}); + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Quote style: Double Quotes +JSX quote style: Double Quotes +Quote properties: As needed +Trailing comma: All +Semicolons: Always +Arrow parentheses: Always +Bracket spacing: true +Bracket same line: false +----- + +```ts +call(a, function (b: () => t1 | t2) {}); + +call(a, (b: () => t1 | t2) => {}); +``` + + diff --git a/website/src/content/docs/internals/changelog.mdx b/website/src/content/docs/internals/changelog.mdx index bd9a6ce29c19..0ceda15560e9 100644 --- a/website/src/content/docs/internals/changelog.mdx +++ b/website/src/content/docs/internals/changelog.mdx @@ -43,6 +43,8 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom - Fix [#1172](https://github.com/biomejs/biome/issues/1172). Fix placement of line comment after function expression parentheses, they are now attached to first statement in body. Contributed by @kalleep +- Fix [#1511](https://github.com/biomejs/biome/issues/1511) that made the JavaScript formatter crash. Contributed @Conaclos + ### JavaScript APIs ### Linter @@ -1922,7 +1924,7 @@ The following rules are now recommended: The code action now removes any whitespace between the parameter name and its initialization. -- Relax [noConfusingArrow](https://biomejs.dev/linter/rules/no-confusing-arrow/) +- Relax `noConfusingArrow` All arrow functions that enclose its parameter with parenthesis are allowed. Thus, the following snippet no longer trigger the rule: diff --git a/website/src/content/docs/zh-cn/internals/changelog.mdx b/website/src/content/docs/zh-cn/internals/changelog.mdx index 0eb0fdcdc773..36ce590a7b15 100644 --- a/website/src/content/docs/zh-cn/internals/changelog.mdx +++ b/website/src/content/docs/zh-cn/internals/changelog.mdx @@ -1216,7 +1216,7 @@ The following rules are promoted: #### Removed rules -- Remove [noConfusingArrow](https://biomejs.dev/linter/rules/no-confusing-arrow/). +- Remove `noConfusingArrow` Code formatters, such as prettier and Biome, always adds parentheses around the parameter or the body of an arrow function. This makes the rule useless. @@ -1876,7 +1876,7 @@ The following rules are now recommended: The code action now removes any whitespace between the parameter name and its initialization. -- Relax [noConfusingArrow](https://biomejs.dev/linter/rules/no-confusing-arrow/) +- Relax `noConfusingArrow` All arrow functions that enclose its parameter with parenthesis are allowed. Thus, the following snippet no longer trigger the rule: From 34c919aac00dbe37a44897bfb2d75e4b59607ab8 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Sat, 13 Jan 2024 10:42:15 +0000 Subject: [PATCH 34/82] feat: foudations for migration from prettier (#1545) --- crates/biome_cli/Cargo.toml | 1 + crates/biome_cli/src/commands/migrate.rs | 2 + crates/biome_cli/src/commands/mod.rs | 25 +- crates/biome_cli/src/execute/migrate.rs | 29 +- .../biome_cli/src/execute/migrate/prettier.rs | 461 ++++++++++++++++++ crates/biome_cli/src/execute/mod.rs | 15 +- crates/biome_cli/src/lib.rs | 8 +- crates/biome_deserialize/README.md | 2 +- crates/biome_js_formatter/src/context.rs | 2 +- 9 files changed, 521 insertions(+), 24 deletions(-) create mode 100644 crates/biome_cli/src/execute/migrate/prettier.rs diff --git a/crates/biome_cli/Cargo.toml b/crates/biome_cli/Cargo.toml index 95bbdc367539..06f847fab337 100644 --- a/crates/biome_cli/Cargo.toml +++ b/crates/biome_cli/Cargo.toml @@ -26,6 +26,7 @@ biome_diagnostics = { workspace = true } biome_flags = { workspace = true } biome_formatter = { workspace = true } biome_fs = { workspace = true } +biome_js_formatter = { workspace = true } biome_json_formatter = { workspace = true } biome_json_parser = { workspace = true } biome_json_syntax = { workspace = true } diff --git a/crates/biome_cli/src/commands/migrate.rs b/crates/biome_cli/src/commands/migrate.rs index f1a643ce677b..c84e0dfd86d5 100644 --- a/crates/biome_cli/src/commands/migrate.rs +++ b/crates/biome_cli/src/commands/migrate.rs @@ -11,6 +11,7 @@ pub(crate) fn migrate( session: CliSession, cli_options: CliOptions, write: bool, + prettier: bool, ) -> Result<(), CliDiagnostic> { let base_path = match cli_options.config_path.as_ref() { None => ConfigurationBasePath::default(), @@ -30,6 +31,7 @@ pub(crate) fn migrate( write, configuration_file_path: path, configuration_directory_path: directory_path, + prettier, }), session, &cli_options, diff --git a/crates/biome_cli/src/commands/mod.rs b/crates/biome_cli/src/commands/mod.rs index bb5b66b98cb3..3c78c5097dba 100644 --- a/crates/biome_cli/src/commands/mod.rs +++ b/crates/biome_cli/src/commands/mod.rs @@ -248,12 +248,19 @@ pub enum BiomeCommand { ), /// It updates the configuration when there are breaking changes #[bpaf(command)] - Migrate( - #[bpaf(external(cli_options), hide_usage)] CliOptions, + Migrate { + /// It attempts to find the files `.prettierrc`/`prettier.json` and `.prettierignore`, and map + /// Prettier's configuration into `biome.json` + #[bpaf(long("prettier"), switch, hide, hide_usage)] + prettier: bool, + + #[bpaf(external, hide_usage)] + cli_options: CliOptions, + /// Writes the new configuration file to disk #[bpaf(long("write"), switch)] - bool, - ), + write: bool, + }, /// A command to retrieve the documentation of various aspects of the CLI. /// @@ -294,7 +301,7 @@ impl BiomeCommand { | BiomeCommand::Lint { cli_options, .. } | BiomeCommand::Ci { cli_options, .. } | BiomeCommand::Format { cli_options, .. } - | BiomeCommand::Migrate(cli_options, _) => cli_options.colors.as_ref(), + | BiomeCommand::Migrate { cli_options, .. } => cli_options.colors.as_ref(), BiomeCommand::LspProxy(_) | BiomeCommand::Start(_) | BiomeCommand::Stop @@ -313,7 +320,7 @@ impl BiomeCommand { | BiomeCommand::Lint { cli_options, .. } | BiomeCommand::Ci { cli_options, .. } | BiomeCommand::Format { cli_options, .. } - | BiomeCommand::Migrate(cli_options, _) => cli_options.use_server, + | BiomeCommand::Migrate { cli_options, .. } => cli_options.use_server, BiomeCommand::Init | BiomeCommand::Start(_) | BiomeCommand::Stop @@ -334,7 +341,7 @@ impl BiomeCommand { | BiomeCommand::Lint { cli_options, .. } | BiomeCommand::Format { cli_options, .. } | BiomeCommand::Ci { cli_options, .. } - | BiomeCommand::Migrate(cli_options, _) => cli_options.verbose, + | BiomeCommand::Migrate { cli_options, .. } => cli_options.verbose, BiomeCommand::Version(_) | BiomeCommand::Rage(..) | BiomeCommand::Start(_) @@ -353,7 +360,7 @@ impl BiomeCommand { | BiomeCommand::Lint { cli_options, .. } | BiomeCommand::Format { cli_options, .. } | BiomeCommand::Ci { cli_options, .. } - | BiomeCommand::Migrate(cli_options, _) => cli_options.log_level.clone(), + | BiomeCommand::Migrate { cli_options, .. } => cli_options.log_level.clone(), BiomeCommand::Version(_) | BiomeCommand::LspProxy(_) | BiomeCommand::Rage(..) @@ -371,7 +378,7 @@ impl BiomeCommand { | BiomeCommand::Lint { cli_options, .. } | BiomeCommand::Format { cli_options, .. } | BiomeCommand::Ci { cli_options, .. } - | BiomeCommand::Migrate(cli_options, _) => cli_options.log_kind.clone(), + | BiomeCommand::Migrate { cli_options, .. } => cli_options.log_kind.clone(), BiomeCommand::Version(_) | BiomeCommand::Rage(..) | BiomeCommand::LspProxy(_) diff --git a/crates/biome_cli/src/execute/migrate.rs b/crates/biome_cli/src/execute/migrate.rs index 51598e88a88a..fe3b8645eadf 100644 --- a/crates/biome_cli/src/execute/migrate.rs +++ b/crates/biome_cli/src/execute/migrate.rs @@ -1,3 +1,5 @@ +mod prettier; + use crate::execute::diagnostics::{ContentDiffAdvice, MigrateDiffDiagnostic}; use crate::{CliDiagnostic, CliSession}; use biome_console::{markup, ConsoleExt}; @@ -14,13 +16,26 @@ use std::borrow::Cow; use std::ffi::OsStr; use std::path::PathBuf; -pub(crate) fn run( - session: CliSession, - write: bool, - configuration_file_path: PathBuf, - configuration_directory_path: PathBuf, - verbose: bool, -) -> Result<(), CliDiagnostic> { +pub(crate) struct MigratePayload<'a> { + pub(crate) session: CliSession<'a>, + pub(crate) write: bool, + pub(crate) configuration_file_path: PathBuf, + pub(crate) configuration_directory_path: PathBuf, + pub(crate) verbose: bool, + #[allow(unused)] + pub(crate) prettier: bool, +} + +pub(crate) fn run(migrate_payload: MigratePayload) -> Result<(), CliDiagnostic> { + let MigratePayload { + session, + write, + configuration_file_path, + configuration_directory_path, + verbose, + // we will use it later + prettier: _, + } = migrate_payload; let fs = &*session.app.fs; let has_deprecated_configuration = configuration_file_path.file_name() == Some(OsStr::new("rome.json")); diff --git a/crates/biome_cli/src/execute/migrate/prettier.rs b/crates/biome_cli/src/execute/migrate/prettier.rs new file mode 100644 index 000000000000..4ab3054c0863 --- /dev/null +++ b/crates/biome_cli/src/execute/migrate/prettier.rs @@ -0,0 +1,461 @@ +use biome_deserialize::{ + Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor, Text, + VisitableType, +}; +use biome_formatter::{LineEnding, LineWidth, QuoteStyle}; +use biome_js_formatter::context::{ArrowParentheses, QuoteProperties, Semicolons, TrailingComma}; +use biome_service::configuration::{FormatterConfiguration, PlainIndentStyle}; +use biome_service::JavascriptFormatter; +use biome_text_size::TextRange; + +#[derive(Debug, Eq, PartialEq)] +struct PrettierConfiguration { + /// https://prettier.io/docs/en/options#print-width + print_width: u16, + /// https://prettier.io/docs/en/options#use-tabs + use_tabs: bool, + /// https://prettier.io/docs/en/options#trailing-comma + trailing_comma: PrettierTrailingComma, + /// https://prettier.io/docs/en/options#tab-width + tab_width: u8, + /// https://prettier.io/docs/en/options#semicolons + semi: bool, + /// https://prettier.io/docs/en/options#quotes + single_quote: bool, + /// https://prettier.io/docs/en/options#bracket-spcing + bracket_spacing: bool, + /// https://prettier.io/docs/en/options#bracket-line + bracket_line: bool, + /// https://prettier.io/docs/en/options#quote-props + quote_props: QuoteProps, + /// https://prettier.io/docs/en/options#jsx-quotes + jsx_single_quote: bool, + /// https://prettier.io/docs/en/options#arrow-function-parentheses + arrow_parens: ArrowParens, + /// https://prettier.io/docs/en/options#end-of-line + end_of_line: EndOfLine, +} + +#[derive(Debug, Eq, PartialEq, Default)] +enum EndOfLine { + #[default] + Lf, + Crlf, + Cr, +} + +impl Deserializable for EndOfLine { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + match String::deserialize(value, name, diagnostics)?.as_str() { + "lf" => Some(Self::Lf), + "crlf" => Some(Self::Crlf), + "cr" => Some(Self::Cr), + unknown_variant => { + const ALLOWED_VARIANTS: &[&str] = &["lf", "crlf", "cr"]; + diagnostics.push(DeserializationDiagnostic::new_unknown_value( + unknown_variant, + value.range(), + ALLOWED_VARIANTS, + )); + None + } + } + } +} + +#[derive(Debug, Eq, PartialEq, Default)] +enum ArrowParens { + #[default] + Always, + Avoid, +} + +impl Deserializable for ArrowParens { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + match String::deserialize(value, name, diagnostics)?.as_str() { + "always" => Some(Self::Always), + "avoid" => Some(Self::Avoid), + unknown_variant => { + const ALLOWED_VARIANTS: &[&str] = &["always", "avoid"]; + diagnostics.push(DeserializationDiagnostic::new_unknown_value( + unknown_variant, + value.range(), + ALLOWED_VARIANTS, + )); + None + } + } + } +} + +#[derive(Debug, Eq, PartialEq, Default)] +enum PrettierTrailingComma { + #[default] + All, + None, + Es5, +} + +impl Deserializable for PrettierTrailingComma { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + match String::deserialize(value, name, diagnostics)?.as_str() { + "all" => Some(Self::All), + "none" => Some(Self::None), + "es5" => Some(Self::Es5), + unknown_variant => { + const ALLOWED_VARIANTS: &[&str] = &["all", "none", "es5"]; + diagnostics.push(DeserializationDiagnostic::new_unknown_value( + unknown_variant, + value.range(), + ALLOWED_VARIANTS, + )); + None + } + } + } +} + +#[derive(Debug, Eq, PartialEq, Default)] + +enum QuoteProps { + #[default] + AsNeeded, + Preserve, +} + +impl Deserializable for QuoteProps { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + match String::deserialize(value, name, diagnostics)?.as_str() { + "as-needed" => Some(Self::AsNeeded), + "preserve" => Some(Self::Preserve), + unknown_variant => { + const ALLOWED_VARIANTS: &[&str] = &["as-needed", "preserve"]; + diagnostics.push(DeserializationDiagnostic::new_unknown_value( + unknown_variant, + value.range(), + ALLOWED_VARIANTS, + )); + None + } + } + } +} + +impl TryFrom<&str> for PrettierTrailingComma { + type Error = String; + fn try_from(value: &str) -> Result { + match value { + "all" => Ok(Self::All), + "none" => Ok(Self::None), + "es5" => Ok(Self::Es5), + _ => Err("Option not supported".to_string()), + } + } +} + +impl Default for PrettierConfiguration { + fn default() -> Self { + Self { + print_width: 80, + + use_tabs: false, + trailing_comma: PrettierTrailingComma::default(), + tab_width: 4, + semi: false, + single_quote: true, + bracket_spacing: true, + bracket_line: false, + quote_props: QuoteProps::default(), + jsx_single_quote: false, + arrow_parens: ArrowParens::default(), + end_of_line: EndOfLine::default(), + } + } +} + +impl Deserializable for PrettierConfiguration { + fn deserialize( + value: &impl DeserializableValue, + name: &str, + diagnostics: &mut Vec, + ) -> Option { + value.deserialize(PrettierVisitor, name, diagnostics) + } +} + +struct PrettierVisitor; + +impl DeserializationVisitor for PrettierVisitor { + type Output = PrettierConfiguration; + const EXPECTED_TYPE: VisitableType = VisitableType::MAP; + + fn visit_map( + self, + members: impl Iterator>, + _range: TextRange, + _name: &str, + diagnostics: &mut Vec, + ) -> Option { + let mut result = PrettierConfiguration::default(); + for (key, value) in members.flatten() { + let Some(key_text) = Text::deserialize(&key, "", diagnostics) else { + continue; + }; + match key_text.text() { + "endOfLine" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.end_of_line = val; + } + } + "arrowParens" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.arrow_parens = val; + } + } + "useTabs" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.use_tabs = val; + } + } + + "printWidth" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.print_width = val; + } + } + + "trailingComma" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.trailing_comma = val; + } + } + + "quoteProps" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.quote_props = val; + } + } + + "semi" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.semi = val; + } + } + + "bracketSpacing" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.bracket_spacing = val; + } + } + + "bracketLine" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.bracket_line = val; + } + } + + "jsxSingleQuote" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.jsx_single_quote = val; + } + } + + "singleQuote" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.single_quote = val; + } + } + + "tabWidth" => { + if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) { + result.tab_width = val; + } + } + + _ => {} + } + } + + Some(result) + } +} + +impl From for TrailingComma { + fn from(value: PrettierTrailingComma) -> Self { + match value { + PrettierTrailingComma::All => Self::All, + PrettierTrailingComma::None => Self::None, + PrettierTrailingComma::Es5 => Self::Es5, + } + } +} + +impl From for ArrowParentheses { + fn from(value: ArrowParens) -> Self { + match value { + ArrowParens::Always => Self::Always, + ArrowParens::Avoid => Self::AsNeeded, + } + } +} + +impl From for LineEnding { + fn from(value: EndOfLine) -> Self { + match value { + EndOfLine::Lf => LineEnding::Lf, + EndOfLine::Crlf => LineEnding::Crlf, + EndOfLine::Cr => LineEnding::Cr, + } + } +} + +impl From for QuoteProperties { + fn from(value: QuoteProps) -> Self { + match value { + QuoteProps::AsNeeded => Self::AsNeeded, + QuoteProps::Preserve => Self::Preserve, + } + } +} + +impl TryFrom for FormatterConfiguration { + type Error = String; + fn try_from(value: PrettierConfiguration) -> Result { + // TODO: handle error + let line_width = LineWidth::try_from(value.print_width).unwrap(); + let indent_style = if value.use_tabs { + PlainIndentStyle::Tab + } else { + PlainIndentStyle::Space + }; + Ok(Self { + indent_width: Some(value.tab_width), + line_width: Some(line_width), + indent_style: Some(indent_style), + line_ending: Some(value.end_of_line.into()), + format_with_errors: Some(false), + ignore: None, + include: None, + enabled: Some(true), + // deprecated + indent_size: None, + }) + } +} + +impl TryFrom for JavascriptFormatter { + type Error = String; + fn try_from(value: PrettierConfiguration) -> Result { + let semicolons = if value.semi { + Semicolons::Always + } else { + Semicolons::AsNeeded + }; + let quote_style = if value.single_quote { + QuoteStyle::Single + } else { + QuoteStyle::Double + }; + let jsx_quote_style = if value.jsx_single_quote { + QuoteStyle::Single + } else { + QuoteStyle::Double + }; + Ok(Self { + indent_width: None, + line_width: None, + indent_style: None, + line_ending: None, + enabled: None, + // deprecated + indent_size: None, + + // js ones + bracket_same_line: Some(value.bracket_line), + arrow_parentheses: Some(value.arrow_parens.into()), + semicolons: Some(semicolons), + trailing_comma: Some(value.trailing_comma.into()), + quote_style: Some(quote_style), + quote_properties: Some(value.quote_props.into()), + bracket_spacing: Some(value.bracket_spacing), + jsx_quote_style: Some(jsx_quote_style), + }) + } +} + +#[cfg(test)] +mod test { + use crate::execute::migrate::prettier::{PrettierConfiguration, PrettierTrailingComma}; + use biome_deserialize::json::deserialize_from_json_str; + use biome_json_parser::JsonParserOptions; + + #[test] + fn ok() { + let configuration = deserialize_from_json_str::( + r#"{ "useTabs": true }"#, + JsonParserOptions::default(), + "", + ) + .into_deserialized() + .unwrap(); + + assert_eq!( + configuration, + PrettierConfiguration { + use_tabs: true, + ..PrettierConfiguration::default() + } + ) + } + + #[test] + fn some_properties() { + let configuration = deserialize_from_json_str::( + r#" +{ + "printWidth": 100, + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "useTabs": true, + "jsxSingleQuote": true +} + "#, + JsonParserOptions::default(), + "", + ) + .into_deserialized() + .unwrap(); + + assert_eq!( + configuration, + PrettierConfiguration { + use_tabs: true, + print_width: 100, + semi: true, + single_quote: true, + tab_width: 2, + trailing_comma: PrettierTrailingComma::Es5, + jsx_single_quote: true, + ..PrettierConfiguration::default() + } + ) + } +} diff --git a/crates/biome_cli/src/execute/mod.rs b/crates/biome_cli/src/execute/mod.rs index c3d26fcd5e5a..abf4437f5cf6 100644 --- a/crates/biome_cli/src/execute/mod.rs +++ b/crates/biome_cli/src/execute/mod.rs @@ -5,6 +5,7 @@ mod std_in; mod traverse; use crate::cli_options::CliOptions; +use crate::execute::migrate::MigratePayload; use crate::execute::traverse::traverse; use crate::{CliDiagnostic, CliSession}; use biome_diagnostics::{category, Category}; @@ -109,9 +110,14 @@ pub(crate) enum TraversalMode { }, /// This mode is enabled when running the command `biome migrate` Migrate { + /// Write result to disk write: bool, + /// The path directory where `biome.json` is placed configuration_file_path: PathBuf, + /// The path to `biome.json` configuration_directory_path: PathBuf, + /// Migrate from prettier + prettier: bool, }, } @@ -298,15 +304,18 @@ pub(crate) fn execute_mode( write, configuration_file_path, configuration_directory_path, + prettier, } = mode.traversal_mode { - migrate::run( + let payload = MigratePayload { session, write, configuration_file_path, configuration_directory_path, - cli_options.verbose, - ) + verbose: cli_options.verbose, + prettier, + }; + migrate::run(payload) } else { traverse(mode, session, cli_options, paths) } diff --git a/crates/biome_cli/src/lib.rs b/crates/biome_cli/src/lib.rs index 5359df0c9d15..b59c416f4e47 100644 --- a/crates/biome_cli/src/lib.rs +++ b/crates/biome_cli/src/lib.rs @@ -187,9 +187,11 @@ impl<'app> CliSession<'app> { BiomeCommand::Explain { doc } => commands::explain::explain(self, doc), BiomeCommand::Init => commands::init::init(self), BiomeCommand::LspProxy(config_path) => commands::daemon::lsp_proxy(config_path), - BiomeCommand::Migrate(cli_options, write) => { - commands::migrate::migrate(self, cli_options, write) - } + BiomeCommand::Migrate { + cli_options, + write, + prettier, + } => commands::migrate::migrate(self, cli_options, write, prettier), BiomeCommand::RunServer { stop_on_disconnect, config_path, diff --git a/crates/biome_deserialize/README.md b/crates/biome_deserialize/README.md index 297af4e5074f..1b580a9ec1fd 100644 --- a/crates/biome_deserialize/README.md +++ b/crates/biome_deserialize/README.md @@ -312,7 +312,7 @@ impl Deserializable for Person { diagnostics: &mut Vec, ) -> Option { // Delegate the deserialization to `PersonVisitor`. - // `value` will call the `PersonVisitor::viist_` method that corresponds to its type. + // `value` will call the `PersonVisitor::visit_` method that corresponds to its type. value.deserialize(PersonVisitor, name, diagnostics) } } diff --git a/crates/biome_js_formatter/src/context.rs b/crates/biome_js_formatter/src/context.rs index 2e054c2a2e78..4669c5bf1902 100644 --- a/crates/biome_js_formatter/src/context.rs +++ b/crates/biome_js_formatter/src/context.rs @@ -1,5 +1,5 @@ use crate::comments::{FormatJsLeadingComment, JsCommentStyle, JsComments}; -use crate::context::trailing_comma::TrailingComma; +pub use crate::context::trailing_comma::TrailingComma; use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic, Text}; use biome_deserialize_macros::Merge; use biome_formatter::printer::PrinterOptions; From de9b3e116215c0ed42634551cdca9b4d82fc1dd8 Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Sat, 13 Jan 2024 12:23:55 +0100 Subject: [PATCH 35/82] fix(config): don't ignore `include` when `ignore` is set (#1548) --- CHANGELOG.md | 7 ++ crates/biome_cli/tests/commands/format.rs | 92 +++++++++++++++++++ ...at_ignored_file_in_included_directory.snap | 35 +++++++ ...format_non_included_and_ignored_files.snap | 46 ++++++++++ crates/biome_service/src/settings.rs | 10 +- crates/biome_service/src/workspace/server.rs | 40 +++----- .../content/docs/guides/how-biome-works.mdx | 19 ++-- .../src/content/docs/internals/changelog.mdx | 7 ++ 8 files changed, 217 insertions(+), 39 deletions(-) create mode 100644 crates/biome_cli/tests/snapshots/main_commands_format/does_not_format_ignored_file_in_included_directory.snap create mode 100644 crates/biome_cli/tests/snapshots/main_commands_format/does_not_format_non_included_and_ignored_files.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 060b9d77b307..502b3f8d2c23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,13 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom - Fix [1440](https://github.com/biomejs/biome/issues/1440), a case where `extends` and `overrides` weren't correctly emitting the final configuration. Contributed by @arendjr +- Correctly handle `include` when `ignore` is set (#1468). Contributed by @Conaclos + + Previously, Biome ignored `include` if `ignore` was set. + Now, Biome check both `include` and `ignore`. + A file is processed if it is included and not ignored. + If `include` is not set all files are considered included. + ### Editors ### Formatter diff --git a/crates/biome_cli/tests/commands/format.rs b/crates/biome_cli/tests/commands/format.rs index a7b6d8b77c5c..064b1db208ed 100644 --- a/crates/biome_cli/tests/commands/format.rs +++ b/crates/biome_cli/tests/commands/format.rs @@ -1340,6 +1340,98 @@ fn does_not_format_ignored_directories() { )); } +#[test] +fn does_not_format_non_included_and_ignored_files() { + let config = r#"{ + "files": { + "include": ["file1.js", "file2.js", "file3.js"], + "ignore": ["file2.js"] + }, + "formatter": { + "include": ["file2.js"], + "ignore": ["file3.js"] + } + }"#; + let files = [("file1.js", true), ("file2.js", true), ("file3.js", false)]; + + let mut console = BufferConsole::default(); + let mut fs = MemoryFileSystem::default(); + let file_path = Path::new("biome.json"); + fs.insert(file_path.into(), config); + for (file_path, _) in files { + let file_path = Path::new(file_path); + fs.insert(file_path.into(), UNFORMATTED.as_bytes()); + } + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from([("format"), ("."), ("--write")].as_slice()), + ); + assert!(result.is_ok(), "run_cli returned {result:?}"); + + for (file_path, expect_formatted) in files { + let expected = if expect_formatted { + FORMATTED + } else { + UNFORMATTED + }; + assert_file_contents(&fs, Path::new(file_path), expected); + } + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "does_not_format_non_included_and_ignored_files", + fs, + console, + result, + )); +} + +#[test] +fn does_not_format_ignored_file_in_included_directory() { + let config = r#"{ + "formatter": { + "include": ["src"], + "ignore": ["src/file2.js"] + } + }"#; + let files = [("src/file1.js", true), ("src/file2.js", false)]; + + let mut console = BufferConsole::default(); + let mut fs = MemoryFileSystem::default(); + let file_path = Path::new("biome.json"); + fs.insert(file_path.into(), config); + for (file_path, _) in files { + let file_path = Path::new(file_path); + fs.insert(file_path.into(), UNFORMATTED.as_bytes()); + } + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from([("format"), ("."), ("--write")].as_slice()), + ); + assert!(result.is_ok(), "run_cli returned {result:?}"); + + for (file_path, expect_formatted) in files { + let expected = if expect_formatted { + FORMATTED + } else { + UNFORMATTED + }; + assert_file_contents(&fs, Path::new(file_path), expected); + } + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "does_not_format_ignored_file_in_included_directory", + fs, + console, + result, + )); +} + #[test] fn fs_error_read_only() { let mut fs = MemoryFileSystem::new_read_only(); diff --git a/crates/biome_cli/tests/snapshots/main_commands_format/does_not_format_ignored_file_in_included_directory.snap b/crates/biome_cli/tests/snapshots/main_commands_format/does_not_format_ignored_file_in_included_directory.snap new file mode 100644 index 000000000000..224b027e8bec --- /dev/null +++ b/crates/biome_cli/tests/snapshots/main_commands_format/does_not_format_ignored_file_in_included_directory.snap @@ -0,0 +1,35 @@ +--- +source: crates/biome_cli/tests/snap_test.rs +expression: content +--- +## `biome.json` + +```json +{ + "formatter": { + "include": ["src"], + "ignore": ["src/file2.js"] + } +} +``` + +## `src/file1.js` + +```js +statement(); + +``` + +## `src/file2.js` + +```js + statement( ) +``` + +# Emitted Messages + +```block +Formatted 2 file(s) in
@@ -68,11 +67,10 @@ foo Unsafe fix: Remove the Fragment 1 - <React.Fragment> - 1+ " - 2 2 foo + 2 - foo 3 - </React.Fragment> - 3+ " - 4 4 + 1+ "foo" + 4 2 From c8d0981c5db5a984262e44fdd5c95629e06b73e4 Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Sun, 14 Jan 2024 19:05:27 +0100 Subject: [PATCH 39/82] fix(config): apply global include/ignore before tools' include/ignore (#1560) --- CHANGELOG.md | 23 +++ .../biome_cli/tests/cases/included_files.rs | 138 +------------ crates/biome_cli/tests/commands/format.rs | 192 ++++++++++++++++-- .../include_ignore_cascade.snap | 51 +++++ .../include_vcs_ignore_cascade.snap | 61 ++++++ .../override_don_t_affect_ignored_files.snap | 37 ++++ .../vcs_absolute_path.snap | 36 ++++ .../src/configuration/formatter.rs | 18 +- .../src/configuration/linter/mod.rs | 18 +- .../src/configuration/organize_imports.rs | 12 +- .../src/configuration/overrides.rs | 12 +- crates/biome_service/src/matcher/mod.rs | 28 +-- crates/biome_service/src/settings.rs | 3 - crates/biome_service/src/workspace/server.rs | 76 ++++--- .../src/content/docs/internals/changelog.mdx | 23 +++ 15 files changed, 478 insertions(+), 250 deletions(-) create mode 100644 crates/biome_cli/tests/snapshots/main_commands_format/include_ignore_cascade.snap create mode 100644 crates/biome_cli/tests/snapshots/main_commands_format/include_vcs_ignore_cascade.snap create mode 100644 crates/biome_cli/tests/snapshots/main_commands_format/override_don_t_affect_ignored_files.snap create mode 100644 crates/biome_cli/tests/snapshots/main_commands_format/vcs_absolute_path.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index dae28b20c6d8..20d5115c3798 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,27 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom - Fix [#1512](https://github.com/biomejs/biome/issues/1512) by skipping verbose diagnostics from the count. Contributed by @ematipico +- Correctly handle cascading `include` and `ignore`. + + Previously Biome incorrectly included files that were included at tool level and ignored at global level. + In the following example, `file.js' was formatted when it should have been ignored. + Now, Biome correctly ignores the directory `./src/sub/`. + + ```shell + ❯ tree src + src + └── sub + └── file.js + + ❯ cat biome.json + { + "files": { "ignore": ["./src/sub/"] }, + "formatter": { "include": ["./src"] } + } + ``` + + Contributed by @Conaclos + - Don't emit verbose warnings when a protected file is ignored. Some files, such as `package.json` and `tsconfig.json`, are [protected](https://biomejs.dev/guides/how-biome-works/#protected-files). @@ -28,6 +49,8 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom Contributed by @Conaclos +- `overrides` no longer affect which files are ignored. Contributed by @Conaclos + - The file `biome.json` can't be ignored anymore. Contributed by @ematipico - Fix [#1541](https://github.com/biomejs/biome/issues/1541) where the content of protected files wasn't returned to `stdout`. Contributed by @ematipico diff --git a/crates/biome_cli/tests/cases/included_files.rs b/crates/biome_cli/tests/cases/included_files.rs index f2f0ee8ae4bb..f415893064bc 100644 --- a/crates/biome_cli/tests/cases/included_files.rs +++ b/crates/biome_cli/tests/cases/included_files.rs @@ -1,7 +1,7 @@ use crate::run_cli; use crate::snap_test::{assert_cli_snapshot, assert_file_contents, SnapshotPayload}; use biome_console::BufferConsole; -use biome_fs::{FileSystemExt, MemoryFileSystem}; +use biome_fs::MemoryFileSystem; use biome_service::DynRef; use bpaf::Args; use std::path::Path; @@ -115,42 +115,6 @@ fn does_not_handle_included_files_if_overridden_by_ignore() { )); } -#[test] -fn does_handle_if_included_in_formatter() { - let mut console = BufferConsole::default(); - let mut fs = MemoryFileSystem::default(); - let file_path = Path::new("biome.json"); - fs.insert( - file_path.into(), - r#"{ - "files": { "ignore": ["test.js"] }, "formatter": { "include": ["test.js"] } -} -"# - .as_bytes(), - ); - - let test = Path::new("test.js"); - fs.insert(test.into(), UNFORMATTED.as_bytes()); - - let result = run_cli( - DynRef::Borrowed(&mut fs), - &mut console, - Args::from([("format"), ("--write"), test.as_os_str().to_str().unwrap()].as_slice()), - ); - - assert!(result.is_ok(), "run_cli returned {result:?}"); - - assert_file_contents(&fs, test, FORMATTED); - - assert_cli_snapshot(SnapshotPayload::new( - module_path!(), - "does_handle_if_included_in_formatter", - fs, - console, - result, - )); -} - #[test] fn does_not_handle_included_files_if_overridden_by_ignore_formatter() { let mut console = BufferConsole::default(); @@ -200,55 +164,6 @@ fn does_not_handle_included_files_if_overridden_by_ignore_formatter() { )); } -#[test] -fn does_lint_included_files() { - let mut fs = MemoryFileSystem::default(); - let mut console = BufferConsole::default(); - - let file_path = Path::new("biome.json"); - fs.insert( - file_path.into(), - r#"{ - "files": { "ignore": ["test.js"] }, "linter": { "include": ["test.js"] } -} -"#, - ); - - let file_path = Path::new("test.js"); - fs.insert(file_path.into(), FIX_BEFORE.as_bytes()); - - let result = run_cli( - DynRef::Borrowed(&mut fs), - &mut console, - Args::from( - [ - ("lint"), - ("--apply"), - file_path.as_os_str().to_str().unwrap(), - ] - .as_slice(), - ), - ); - - assert!(result.is_ok(), "run_cli returned {result:?}"); - - let mut buffer = String::new(); - fs.open(file_path) - .unwrap() - .read_to_string(&mut buffer) - .unwrap(); - - assert_eq!(buffer, FIX_AFTER); - - assert_cli_snapshot(SnapshotPayload::new( - module_path!(), - "does_lint_included_files", - fs, - console, - result, - )); -} - #[test] fn does_not_handle_included_files_if_overridden_by_ignore_linter() { let mut console = BufferConsole::default(); @@ -298,57 +213,6 @@ fn does_not_handle_included_files_if_overridden_by_ignore_linter() { )); } -#[test] -fn does_organize_imports_of_included_files() { - let mut fs = MemoryFileSystem::default(); - let mut console = BufferConsole::default(); - - let file_path = Path::new("biome.json"); - fs.insert( - file_path.into(), - r#"{ - "formatter": { "enabled": false }, - "linter": { "enabled": false }, - "files": { "ignore": ["test2.js", "test.js"] }, "organizeImports": { "include": ["test.js"] } -} -"#, - ); - - let test = Path::new("test.js"); - fs.insert(test.into(), UNORGANIZED.as_bytes()); - - let test2 = Path::new("test2.js"); - fs.insert(test2.into(), UNORGANIZED.as_bytes()); - - let result = run_cli( - DynRef::Borrowed(&mut fs), - &mut console, - Args::from( - [ - ("check"), - ("--apply"), - test.as_os_str().to_str().unwrap(), - test2.as_os_str().to_str().unwrap(), - ] - .as_slice(), - ), - ); - - assert!(result.is_ok(), "run_cli returned {result:?}"); - - assert_file_contents(&fs, test2, UNORGANIZED); - - assert_file_contents(&fs, test, ORGANIZED); - - assert_cli_snapshot(SnapshotPayload::new( - module_path!(), - "does_organize_imports_of_included_files", - fs, - console, - result, - )); -} - #[test] fn does_not_handle_included_files_if_overridden_by_organize_imports() { let mut console = BufferConsole::default(); diff --git a/crates/biome_cli/tests/commands/format.rs b/crates/biome_cli/tests/commands/format.rs index 064b1db208ed..60d6d0ab7d56 100644 --- a/crates/biome_cli/tests/commands/format.rs +++ b/crates/biome_cli/tests/commands/format.rs @@ -1341,18 +1341,14 @@ fn does_not_format_ignored_directories() { } #[test] -fn does_not_format_non_included_and_ignored_files() { +fn does_not_format_ignored_file_in_included_directory() { let config = r#"{ - "files": { - "include": ["file1.js", "file2.js", "file3.js"], - "ignore": ["file2.js"] - }, "formatter": { - "include": ["file2.js"], - "ignore": ["file3.js"] + "include": ["src"], + "ignore": ["src/file2.js"] } }"#; - let files = [("file1.js", true), ("file2.js", true), ("file3.js", false)]; + let files = [("src/file1.js", true), ("src/file2.js", false)]; let mut console = BufferConsole::default(); let mut fs = MemoryFileSystem::default(); @@ -1381,7 +1377,7 @@ fn does_not_format_non_included_and_ignored_files() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "does_not_format_non_included_and_ignored_files", + "does_not_format_ignored_file_in_included_directory", fs, console, result, @@ -1389,14 +1385,27 @@ fn does_not_format_non_included_and_ignored_files() { } #[test] -fn does_not_format_ignored_file_in_included_directory() { +fn include_ignore_cascade() { + // Only `file1.js` will be formatted: + // - `file2.js` is ignored at top-level + // - `file3.js` is ignored at formatter-level + // - `file4.js` is not included at top-level let config = r#"{ + "files": { + "include": ["file1.js", "file2.js", "file3.js"], + "ignore": ["file2.js"] + }, "formatter": { - "include": ["src"], - "ignore": ["src/file2.js"] + "include": ["file1.js", "file2.js"], + "ignore": ["file3.js"] } }"#; - let files = [("src/file1.js", true), ("src/file2.js", false)]; + let files = [ + ("file1.js", true), + ("file2.js", false), + ("file3.js", false), + ("file4.js", false), + ]; let mut console = BufferConsole::default(); let mut fs = MemoryFileSystem::default(); @@ -1425,7 +1434,7 @@ fn does_not_format_ignored_file_in_included_directory() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "does_not_format_ignored_file_in_included_directory", + "include_ignore_cascade", fs, console, result, @@ -1879,6 +1888,118 @@ file2.js )); } +#[test] +fn include_vcs_ignore_cascade() { + // Only `file1.js` will be formatted: + // - `file2.js` is ignored at top-level + // - `file3.js` is ignored at formatter-level + // - `file4.js` is ignored in `.gitignore` + let git_ignore = r#"file4.js"#; + let config = r#"{ + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignore": ["file2.js"] + }, + "formatter": { + "include": ["file1.js", "file2.js", "file4.js"], + "ignore": ["file3.js"] + } + }"#; + let files = [ + ("file1.js", true), + ("file2.js", false), + ("file3.js", false), + ("file4.js", false), + ]; + + let mut console = BufferConsole::default(); + let mut fs = MemoryFileSystem::default(); + let gitignore_file = Path::new(".gitignore"); + fs.insert(gitignore_file.into(), git_ignore.as_bytes()); + let file_path = Path::new("biome.json"); + fs.insert(file_path.into(), config); + for (file_path, _) in files { + let file_path = Path::new(file_path); + fs.insert(file_path.into(), UNFORMATTED.as_bytes()); + } + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from([("format"), ("."), ("--write")].as_slice()), + ); + assert!(result.is_ok(), "run_cli returned {result:?}"); + + for (file_path, expect_formatted) in files { + let expected = if expect_formatted { + FORMATTED + } else { + UNFORMATTED + }; + assert_file_contents(&fs, Path::new(file_path), expected); + } + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "include_vcs_ignore_cascade", + fs, + console, + result, + )); +} + +#[test] +fn vcs_absolute_path() { + let git_ignore = r#"file.js"#; + let config = r#"{ + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + } + }"#; + let files = [("/symbolic/link/to/path.js", true)]; + + let mut console = BufferConsole::default(); + let mut fs = MemoryFileSystem::default(); + let gitignore_file = Path::new(".gitignore"); + fs.insert(gitignore_file.into(), git_ignore.as_bytes()); + let file_path = Path::new("biome.json"); + fs.insert(file_path.into(), config); + for (file_path, _) in files { + let file_path = Path::new(file_path); + fs.insert(file_path.into(), UNFORMATTED.as_bytes()); + } + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from([("format"), ("."), ("--write")].as_slice()), + ); + assert!(result.is_ok(), "run_cli returned {result:?}"); + + for (file_path, expect_formatted) in files { + let expected = if expect_formatted { + FORMATTED + } else { + UNFORMATTED + }; + assert_file_contents(&fs, Path::new(file_path), expected); + } + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "vcs_absolute_path", + fs, + console, + result, + )); +} + #[test] fn ignores_unknown_file() { let mut fs = MemoryFileSystem::default(); @@ -2525,3 +2646,46 @@ const a = { result, )); } + +#[test] +fn override_don_t_affect_ignored_files() { + let config = r#"{ + "overrides": [{ + "ignore": ["file2.js"] + }] + }"#; + let files = [("file1.js", true), ("file2.js", true)]; + + let mut console = BufferConsole::default(); + let mut fs = MemoryFileSystem::default(); + let file_path = Path::new("biome.json"); + fs.insert(file_path.into(), config); + for (file_path, _) in files { + let file_path = Path::new(file_path); + fs.insert(file_path.into(), UNFORMATTED.as_bytes()); + } + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from([("format"), ("."), ("--write")].as_slice()), + ); + assert!(result.is_ok(), "run_cli returned {result:?}"); + + for (file_path, expect_formatted) in files { + let expected = if expect_formatted { + FORMATTED + } else { + UNFORMATTED + }; + assert_file_contents(&fs, Path::new(file_path), expected); + } + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "override_don_t_affect_ignored_files", + fs, + console, + result, + )); +} diff --git a/crates/biome_cli/tests/snapshots/main_commands_format/include_ignore_cascade.snap b/crates/biome_cli/tests/snapshots/main_commands_format/include_ignore_cascade.snap new file mode 100644 index 000000000000..7ee5a0e76015 --- /dev/null +++ b/crates/biome_cli/tests/snapshots/main_commands_format/include_ignore_cascade.snap @@ -0,0 +1,51 @@ +--- +source: crates/biome_cli/tests/snap_test.rs +expression: content +--- +## `biome.json` + +```json +{ + "files": { + "include": ["file1.js", "file2.js", "file3.js"], + "ignore": ["file2.js"] + }, + "formatter": { + "include": ["file1.js", "file2.js"], + "ignore": ["file3.js"] + } +} +``` + +## `file1.js` + +```js +statement(); + +``` + +## `file2.js` + +```js + statement( ) +``` + +## `file3.js` + +```js + statement( ) +``` + +## `file4.js` + +```js + statement( ) +``` + +# Emitted Messages + +```block +Formatted 2 file(s) in
correctness/useExhaustiveDependencies.js:5:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━
 
-   This hook does not specify all of its dependencies.
+   This hook does not specify all of its dependencies: a
   
     3 │ function component() {
     4 │     let a = 1;
@@ -84,7 +84,7 @@ function component() {
 
 
correctness/useExhaustiveDependencies.js:5:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━
 
-   This hook specifies more dependencies than necessary.
+   This hook specifies more dependencies than necessary: b
   
     3 │ function component() {
     4 │     let b = 1;
@@ -118,7 +118,7 @@ function component() {
 
 
correctness/useExhaustiveDependencies.js:5:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━
 
-   This hook specifies more dependencies than necessary.
+   This hook specifies more dependencies than necessary: setName
   
     3 │ function component() {
     4 │     const [name, setName] = useState();
@@ -152,7 +152,7 @@ function component() {
 
 
correctness/useExhaustiveDependencies.js:6:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━
 
-   This hook does not specify all of its dependencies.
+   This hook does not specify all of its dependencies: b
   
     4 │     let a = 1;
     5 │     const b = a + 1;

From 1b5f26112a8ad912808034c33e2615b9d0a9e16d Mon Sep 17 00:00:00 2001
From: Jon 
Date: Tue, 16 Jan 2024 07:43:01 -0800
Subject: [PATCH 45/82] feat(css_formatter): Formatting for `border` property
 (#1453)

---
 .../src/css/auxiliary/border.rs               |  23 ++-
 .../src/css/auxiliary/line_style.rs           |   9 +-
 .../src/css/auxiliary/line_width_keyword.rs   |   9 +-
 .../src/css/properties/border_property.rs     |  14 +-
 crates/biome_css_formatter/src/prelude.rs     |   4 +-
 crates/biome_css_formatter/src/utils/mod.rs   |   1 +
 .../src/utils/properties.rs                   | 172 ++++++++++++++++++
 .../biome_css_formatter/tests/quick_test.rs   |   8 +-
 .../tests/specs/css/properties/border.css     |  39 ++++
 .../specs/css/properties/border.css.snap      |  94 ++++++++++
 .../biome_css_syntax/src/generated/nodes.rs   |   8 +-
 crates/biome_formatter/src/arguments.rs       |   9 +-
 crates/biome_js_syntax/src/generated/nodes.rs |   3 +-
 .../biome_json_syntax/src/generated/nodes.rs  |   3 +-
 crates/biome_rowan/src/ast/mod.rs             |  77 ++++++++
 xtask/codegen/src/generate_nodes.rs           |  18 +-
 16 files changed, 468 insertions(+), 23 deletions(-)
 create mode 100644 crates/biome_css_formatter/src/utils/properties.rs
 create mode 100644 crates/biome_css_formatter/tests/specs/css/properties/border.css
 create mode 100644 crates/biome_css_formatter/tests/specs/css/properties/border.css.snap

diff --git a/crates/biome_css_formatter/src/css/auxiliary/border.rs b/crates/biome_css_formatter/src/css/auxiliary/border.rs
index 47bd8b26d03b..f98dd59d8f30 100644
--- a/crates/biome_css_formatter/src/css/auxiliary/border.rs
+++ b/crates/biome_css_formatter/src/css/auxiliary/border.rs
@@ -1,10 +1,25 @@
-use crate::prelude::*;
-use biome_css_syntax::CssBorder;
-use biome_rowan::AstNode;
+use crate::{prelude::*, utils::properties::FormatPropertyValueFields};
+use biome_css_syntax::{CssBorder, CssBorderFields};
+use biome_formatter::{format_args, write};
+
 #[derive(Debug, Clone, Default)]
 pub(crate) struct FormatCssBorder;
 impl FormatNodeRule for FormatCssBorder {
     fn fmt_fields(&self, node: &CssBorder, f: &mut CssFormatter) -> FormatResult<()> {
-        format_verbatim_node(node.syntax()).fmt(f)
+        let CssBorderFields {
+            line_width,
+            line_style,
+            color,
+        } = node.as_fields();
+
+        write!(
+            f,
+            [FormatPropertyValueFields::new(&format_args![
+                line_width.format(),
+                line_style.format(),
+                color.format(),
+            ])
+            .with_slot_map(node.concrete_order_slot_map())]
+        )
     }
 }
diff --git a/crates/biome_css_formatter/src/css/auxiliary/line_style.rs b/crates/biome_css_formatter/src/css/auxiliary/line_style.rs
index 0858d1cf27af..fa68dee4e8e6 100644
--- a/crates/biome_css_formatter/src/css/auxiliary/line_style.rs
+++ b/crates/biome_css_formatter/src/css/auxiliary/line_style.rs
@@ -1,10 +1,13 @@
 use crate::prelude::*;
-use biome_css_syntax::CssLineStyle;
-use biome_rowan::AstNode;
+use biome_css_syntax::{CssLineStyle, CssLineStyleFields};
+use biome_formatter::write;
+
 #[derive(Debug, Clone, Default)]
 pub(crate) struct FormatCssLineStyle;
 impl FormatNodeRule for FormatCssLineStyle {
     fn fmt_fields(&self, node: &CssLineStyle, f: &mut CssFormatter) -> FormatResult<()> {
-        format_verbatim_node(node.syntax()).fmt(f)
+        let CssLineStyleFields { keyword } = node.as_fields();
+
+        write!(f, [keyword.format()])
     }
 }
diff --git a/crates/biome_css_formatter/src/css/auxiliary/line_width_keyword.rs b/crates/biome_css_formatter/src/css/auxiliary/line_width_keyword.rs
index f1eae653ee34..dffbbc50cc16 100644
--- a/crates/biome_css_formatter/src/css/auxiliary/line_width_keyword.rs
+++ b/crates/biome_css_formatter/src/css/auxiliary/line_width_keyword.rs
@@ -1,10 +1,13 @@
 use crate::prelude::*;
-use biome_css_syntax::CssLineWidthKeyword;
-use biome_rowan::AstNode;
+use biome_css_syntax::{CssLineWidthKeyword, CssLineWidthKeywordFields};
+use biome_formatter::write;
+
 #[derive(Debug, Clone, Default)]
 pub(crate) struct FormatCssLineWidthKeyword;
 impl FormatNodeRule for FormatCssLineWidthKeyword {
     fn fmt_fields(&self, node: &CssLineWidthKeyword, f: &mut CssFormatter) -> FormatResult<()> {
-        format_verbatim_node(node.syntax()).fmt(f)
+        let CssLineWidthKeywordFields { keyword } = node.as_fields();
+
+        write!(f, [keyword.format()])
     }
 }
diff --git a/crates/biome_css_formatter/src/css/properties/border_property.rs b/crates/biome_css_formatter/src/css/properties/border_property.rs
index 6e9a0e1b2795..952e94504b5c 100644
--- a/crates/biome_css_formatter/src/css/properties/border_property.rs
+++ b/crates/biome_css_formatter/src/css/properties/border_property.rs
@@ -1,10 +1,18 @@
 use crate::prelude::*;
-use biome_css_syntax::CssBorderProperty;
-use biome_rowan::AstNode;
+use biome_css_syntax::{CssBorderProperty, CssBorderPropertyFields};
+use biome_formatter::write;
 #[derive(Debug, Clone, Default)]
 pub(crate) struct FormatCssBorderProperty;
 impl FormatNodeRule for FormatCssBorderProperty {
     fn fmt_fields(&self, node: &CssBorderProperty, f: &mut CssFormatter) -> FormatResult<()> {
-        format_verbatim_node(node.syntax()).fmt(f)
+        let CssBorderPropertyFields {
+            name,
+            colon_token,
+            value,
+        } = node.as_fields();
+        write!(
+            f,
+            [name.format(), colon_token.format(), space(), value.format()]
+        )
     }
 }
diff --git a/crates/biome_css_formatter/src/prelude.rs b/crates/biome_css_formatter/src/prelude.rs
index 69e4a0665984..9d74d80aa65e 100644
--- a/crates/biome_css_formatter/src/prelude.rs
+++ b/crates/biome_css_formatter/src/prelude.rs
@@ -7,6 +7,8 @@ pub(crate) use crate::{
 };
 pub(crate) use biome_formatter::prelude::*;
 #[allow(unused_imports)]
-pub(crate) use biome_rowan::{AstNode as _, AstNodeList as _, AstSeparatedList as _};
+pub(crate) use biome_rowan::{
+    AstNode as _, AstNodeList as _, AstNodeSlotMap as _, AstSeparatedList as _,
+};
 
 pub(crate) use crate::separated::FormatAstSeparatedListExtension;
diff --git a/crates/biome_css_formatter/src/utils/mod.rs b/crates/biome_css_formatter/src/utils/mod.rs
index bc7584a88994..159a66203376 100644
--- a/crates/biome_css_formatter/src/utils/mod.rs
+++ b/crates/biome_css_formatter/src/utils/mod.rs
@@ -1,2 +1,3 @@
 pub(crate) mod component_value_list;
+pub(crate) mod properties;
 pub(crate) mod string_utils;
diff --git a/crates/biome_css_formatter/src/utils/properties.rs b/crates/biome_css_formatter/src/utils/properties.rs
new file mode 100644
index 000000000000..2d6b9d1b97df
--- /dev/null
+++ b/crates/biome_css_formatter/src/utils/properties.rs
@@ -0,0 +1,172 @@
+use crate::prelude::*;
+use biome_formatter::{write, Arguments};
+
+/// Format all of the fields of a `PropertyValue` node in an arbitrary order,
+/// given by `slot_map`.
+///
+/// Because the CSS grammar allows rules to specify fields that can appear
+/// in any order, there isn't always a linear mapping between the _declared_
+/// order (how they appear in the grammar) and the _concrete_ order (how they
+/// appear in the source text) of the fields. The parser supports this by
+/// building a `slot_map` to map the declared order to the concrete order.
+///
+/// When formatting, by default we want to preserve the ordering of fields as
+/// they were written in the source, but just using the `AstNode` alone will
+/// naturally re-write the value in the _declared_ order. To preserve the
+/// _concrete_ order, we can invert the `slot_map` and sort it to re-determine
+/// the ordering of fields and then iterate that list to format each field
+/// individually.
+///
+/// ## Fields
+///
+/// The caller provides a list of _pre-formatted_ fields, using the
+/// [`biome_formatter::format_args!`] macro. This way, it can either pass
+/// through a field as-is with default formatting, or it can apply any other
+/// formatting it once for that field:
+///
+/// ```rust,ignore
+/// let formatted = format!(CssFormatContext::default(), [
+///     FormatPropertyValueFields::new(&format_args![
+///         text("a"),
+///         text("b"),
+///         group(&block_indent(&format_args![text("c"), hard_line_break(), text("d")]))
+///     ])
+///     .with_slot_map([1, 2, 0])
+/// ])?;
+///
+/// assert_eq!("b
+/// \tc
+/// \td
+/// a", formatted.print()?.as_code());
+/// ```
+///
+/// ## Concrete Ordering
+///
+/// By default, using this struct will format the fields of the node in order.
+/// This is sufficient for nodes that don't have any dynamically-ordered
+/// fields, but for dynamic nodes that want to preserve the order of fields as
+/// they were given in the input, or for any node that wants to change the
+/// ordering of the fields, the caller will need to provide a `slot_map` that
+/// this struct can use to re-order the fields.
+///
+/// To preserve the field order as it was written in the original source, use
+/// [biome_rowan::AstNodeSlotMap::concrete_order_slot_map], which will ensure
+/// the ordering matches what was given. This should be the default for most
+/// if not all dynamic nodes.
+///
+/// ```rust,ignore
+/// .with_slot_map(node.concrete_order_slot_map())
+/// ```
+///
+/// Any other method of building a slot map is also valid, but should generally
+/// be avoided, as ensuring consistency across formats is difficult without a
+/// strong heuristic.
+///
+/// ## Grouping Fields (Future)
+///
+/// In some cases, a property may want to group certain fields together in
+/// order to apply special formatting. As an example, consider a grammar like:
+///
+/// ```ebnf
+///     font =
+///         (style: CssFontStyle ||
+///         variant: CssFontVariant ||
+///         weight: CssFontWeight)?
+///         size: CssNumber ( '/' line_height: CssLineHeight)?
+/// ```
+///
+/// Here, the `style`, `variant`, and `weight` fields can appear conditionally
+/// and in any order, but if `line_height` is present, it (and the slash token)
+/// must appear immediately adjacent to the `size` field. While it would be
+/// valid to just have the fields fill and wrap over lines as needed, the
+/// formatter might want to preserve the adjacency and ensure that `size` and
+/// `line_height` always get written on the same line.
+///
+/// To do this, the value formatter can write both fields in a single group,
+/// and then use an `empty_field_slot` value in the slots where the other
+/// fields have been taken from:
+///
+/// ```rust,ignore
+/// FormatPropertyValueFields::new(&format_args![
+///         style.format(),
+///         variant.format(),
+///         weight.format(),
+///         group(&format_args![
+///             size.format(), slash_token.format(), line_height.format()
+///         ]),
+///         empty_field_slot(),
+///         empty_field_slot()
+///     ])
+///     .with_slot_map(node.concrete_order_slot_map())
+/// ```
+///
+/// The `empty_field_slot()` values will tell this struct to skip formatting
+/// for that field, with the assumption that another field includes its value.
+pub struct FormatPropertyValueFields<'fmt, const N: usize> {
+    slot_map: Option<[u8; N]>,
+    fields: &'fmt Arguments<'fmt, CssFormatContext>,
+}
+
+impl<'fmt, const N: usize> FormatPropertyValueFields<'fmt, N> {
+    pub fn new(fields: &'fmt Arguments<'fmt, CssFormatContext>) -> Self {
+        Self {
+            slot_map: None,
+            fields,
+        }
+    }
+
+    pub fn with_slot_map(mut self, slot_map: [u8; N]) -> Self {
+        debug_assert!(
+            self.fields.items().len() == N,
+            "slot_map must specify the same number of fields as this struct contains"
+        );
+        self.slot_map = Some(slot_map);
+        self
+    }
+}
+
+impl<'fmt, const N: usize> Format for FormatPropertyValueFields<'fmt, N> {
+    fn fmt(&self, f: &mut CssFormatter) -> FormatResult<()> {
+        let values = format_with(|f: &mut Formatter<'_, CssFormatContext>| {
+            let mut filler = f.fill();
+
+            // First, determine the ordering of fields to use. If no slot_map is
+            // provided along with the fields, then they can just be used in the
+            // same order, but if a `slot_map` is present, then the fields are
+            // re-ordered to match the concrete ordering from the source syntax.
+            //
+            // The fields are wrapped with `Option` for two reasons: for nodes
+            // with slot maps, it simplifies how the re-ordered slice is built, and
+            // it also allows empty/missing fields to be removed in the next step.
+            match self.slot_map {
+                None => {
+                    for field in self.fields.items() {
+                        filler.entry(&soft_line_break_or_space(), field);
+                    }
+                }
+                Some(slot_map) => {
+                    for slot in slot_map {
+                        // This condition ensures that missing values are _not_ included in the
+                        // fill. The generated `slot_map` for an AstNode guarantees that all
+                        // present fields have a tangible value here, while all absent fields
+                        // have this sentinel value ([biome_css_syntax::SLOT_MAP_EMPTY_VALUE]).
+                        //
+                        // This check is important to ensure that we don't add empty values to
+                        // the fill, since that would add double separators when we don't want
+                        // them.
+                        if slot == u8::MAX {
+                            continue;
+                        }
+
+                        let field = &self.fields.items()[slot as usize];
+                        filler.entry(&soft_line_break_or_space(), field);
+                    }
+                }
+            };
+
+            filler.finish()
+        });
+
+        write!(f, [group(&indent(&values))])
+    }
+}
diff --git a/crates/biome_css_formatter/tests/quick_test.rs b/crates/biome_css_formatter/tests/quick_test.rs
index 81de0a5b3248..079a9b62b44c 100644
--- a/crates/biome_css_formatter/tests/quick_test.rs
+++ b/crates/biome_css_formatter/tests/quick_test.rs
@@ -14,9 +14,11 @@ mod language {
 fn quick_test() {
     let src = r#"
     div {
-        prod: fn(100px);
-        prod: --fn(100px);
-        prod: --fn--fn(100px);
+        border: #fff    solid
+        
+        2px;
+        border: THICK   #000;
+        border: medium;
     }
 
 "#;
diff --git a/crates/biome_css_formatter/tests/specs/css/properties/border.css b/crates/biome_css_formatter/tests/specs/css/properties/border.css
new file mode 100644
index 000000000000..5d842f5d1c86
--- /dev/null
+++ b/crates/biome_css_formatter/tests/specs/css/properties/border.css
@@ -0,0 +1,39 @@
+div {
+    /* Generic property tests */
+    border: InItial;
+    border
+    :
+    inherit
+    ;
+
+    border  :   zzz-unknown-value  ;
+    border  : a,
+    value list ;
+
+
+    /*  */
+    border : SOLID;
+    border: none
+    ;
+
+    /*  */
+    border : ThIn;
+    border: 
+    medium
+    ;
+    border:   100px;
+
+    /*  */
+    border: 
+    #fff;
+
+    /* combinations */
+    border: 2px
+    dotted;
+    border  :   outset   #f33;
+    border:#000 medium  
+     
+    dashed
+        
+        ;
+}
diff --git a/crates/biome_css_formatter/tests/specs/css/properties/border.css.snap b/crates/biome_css_formatter/tests/specs/css/properties/border.css.snap
new file mode 100644
index 000000000000..c17a650c7980
--- /dev/null
+++ b/crates/biome_css_formatter/tests/specs/css/properties/border.css.snap
@@ -0,0 +1,94 @@
+---
+source: crates/biome_formatter_test/src/snapshot_builder.rs
+info: css/properties/border.css
+---
+
+# Input
+
+```css
+div {
+    /* Generic property tests */
+    border: InItial;
+    border
+    :
+    inherit
+    ;
+
+    border  :   zzz-unknown-value  ;
+    border  : a,
+    value list ;
+
+
+    /*  */
+    border : SOLID;
+    border: none
+    ;
+
+    /*  */
+    border : ThIn;
+    border: 
+    medium
+    ;
+    border:   100px;
+
+    /*  */
+    border: 
+    #fff;
+
+    /* combinations */
+    border: 2px
+    dotted;
+    border  :   outset   #f33;
+    border:#000 medium  
+     
+    dashed
+        
+        ;
+}
+
+```
+
+
+=============================
+
+# Outputs
+
+## Output 1
+
+-----
+Indent style: Tab
+Indent width: 2
+Line ending: LF
+Line width: 80
+Quote style: Double Quotes
+-----
+
+```css
+div {
+	/* Generic property tests */
+	border: initial;
+	border: inherit;
+
+	border: zzz-unknown-value;
+	border: a , value list;
+
+	/*  */
+	border: solid;
+	border: none;
+
+	/*  */
+	border: thin;
+	border: medium;
+	border: 100px;
+
+	/*  */
+	border: #fff;
+
+	/* combinations */
+	border: 2px dotted;
+	border: outset #f33;
+	border: #000 medium dashed;
+}
+```
+
+
diff --git a/crates/biome_css_syntax/src/generated/nodes.rs b/crates/biome_css_syntax/src/generated/nodes.rs
index 7460341c6cf0..d9b357814976 100644
--- a/crates/biome_css_syntax/src/generated/nodes.rs
+++ b/crates/biome_css_syntax/src/generated/nodes.rs
@@ -12,7 +12,8 @@ use crate::{
 use biome_rowan::{support, AstNode, RawSyntaxKind, SyntaxKindSet, SyntaxResult};
 #[allow(unused)]
 use biome_rowan::{
-    AstNodeList, AstNodeListIterator, AstSeparatedList, AstSeparatedListNodesIterator,
+    AstNodeList, AstNodeListIterator, AstNodeSlotMap, AstSeparatedList,
+    AstSeparatedListNodesIterator,
 };
 #[cfg(feature = "serde")]
 use serde::ser::SerializeSeq;
@@ -8245,6 +8246,11 @@ impl AstNode for CssBorder {
         self.syntax
     }
 }
+impl AstNodeSlotMap<3usize> for CssBorder {
+    fn slot_map(&self) -> &[u8; 3usize] {
+        &self.slot_map
+    }
+}
 impl std::fmt::Debug for CssBorder {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("CssBorder")
diff --git a/crates/biome_formatter/src/arguments.rs b/crates/biome_formatter/src/arguments.rs
index ff96b33e0d89..4e40e10fcc74 100644
--- a/crates/biome_formatter/src/arguments.rs
+++ b/crates/biome_formatter/src/arguments.rs
@@ -56,6 +56,13 @@ impl<'fmt, Context> Argument<'fmt, Context> {
     }
 }
 
+impl<'fmt, Context> Format for Argument<'fmt, Context> {
+    #[inline(always)]
+    fn fmt(&self, f: &mut Formatter) -> FormatResult<()> {
+        self.format(f)
+    }
+}
+
 /// Sequence of objects that should be formatted in the specified order.
 ///
 /// The [`format_args!`] macro will safely create an instance of this structure.
@@ -87,7 +94,7 @@ impl<'fmt, Context> Arguments<'fmt, Context> {
 
     /// Returns the arguments
     #[inline]
-    pub(super) fn items(&self) -> &'fmt [Argument<'fmt, Context>] {
+    pub fn items(&self) -> &'fmt [Argument<'fmt, Context>] {
         self.0
     }
 }
diff --git a/crates/biome_js_syntax/src/generated/nodes.rs b/crates/biome_js_syntax/src/generated/nodes.rs
index dfa42cc07991..15cbdccbfd41 100644
--- a/crates/biome_js_syntax/src/generated/nodes.rs
+++ b/crates/biome_js_syntax/src/generated/nodes.rs
@@ -12,7 +12,8 @@ use crate::{
 use biome_rowan::{support, AstNode, RawSyntaxKind, SyntaxKindSet, SyntaxResult};
 #[allow(unused)]
 use biome_rowan::{
-    AstNodeList, AstNodeListIterator, AstSeparatedList, AstSeparatedListNodesIterator,
+    AstNodeList, AstNodeListIterator, AstNodeSlotMap, AstSeparatedList,
+    AstSeparatedListNodesIterator,
 };
 #[cfg(feature = "serde")]
 use serde::ser::SerializeSeq;
diff --git a/crates/biome_json_syntax/src/generated/nodes.rs b/crates/biome_json_syntax/src/generated/nodes.rs
index b755da777f4c..6da549e9f223 100644
--- a/crates/biome_json_syntax/src/generated/nodes.rs
+++ b/crates/biome_json_syntax/src/generated/nodes.rs
@@ -12,7 +12,8 @@ use crate::{
 use biome_rowan::{support, AstNode, RawSyntaxKind, SyntaxKindSet, SyntaxResult};
 #[allow(unused)]
 use biome_rowan::{
-    AstNodeList, AstNodeListIterator, AstSeparatedList, AstSeparatedListNodesIterator,
+    AstNodeList, AstNodeListIterator, AstNodeSlotMap, AstSeparatedList,
+    AstSeparatedListNodesIterator,
 };
 #[cfg(feature = "serde")]
 use serde::ser::SerializeSeq;
diff --git a/crates/biome_rowan/src/ast/mod.rs b/crates/biome_rowan/src/ast/mod.rs
index 4556537a4b22..0462d6dd642c 100644
--- a/crates/biome_rowan/src/ast/mod.rs
+++ b/crates/biome_rowan/src/ast/mod.rs
@@ -319,6 +319,83 @@ pub trait AstNode: Clone {
     }
 }
 
+/// An AstNode that supports dynamic ordering of the fields it contains uses a
+/// `slot_map` to map the _declared_ order of fields to the _concrete_ order
+/// as parsed from the source content. Implementing this trait lets consumers
+///
+pub trait AstNodeSlotMap {
+    /// Return the internal slot_map that was built when constructed from the
+    /// underlying [SyntaxNode].
+    fn slot_map(&self) -> &[u8; N];
+
+    /// Invert and sort the `slot_map` for the [AstNode] to return a mapping of
+    /// _concrete_ field ordering from the source to the _declared_ ordering of
+    /// the [AstNode].
+    ///
+    /// Note that the _entire_ slot map is inverted and returned, including
+    /// both the ordered and the unordered fields. Ordered fields will have
+    /// their slot positions fixed in both the original and the inverted slot
+    /// maps, since they can't be moved. Ordered fields also act as boundary
+    /// points for unordered fields, meaning the concrete order will never
+    /// allow the concrete slot of an unordered field to appear on the opposite
+    /// side of an ordered field, even if the field is empty, and the ordered
+    /// fields will _always_ have the same slot in both maps.
+    ///
+    /// Example: Given a grammar like:
+    ///   MultiplyVectorsNode =
+    ///     (Color
+    ///     || Number
+    ///     || String)
+    ///     'x'
+    ///     (Color
+    ///     || Number
+    ///     || String)
+    /// There are two sets of unordered fields here (the groups combined with
+    /// `||` operators). Each contains three fields, and then there is a single
+    /// ordered field between them, the `x` token. This Node declares a
+    /// `slot_map` with 7 indices. The first three can be mapped in any order,
+    /// and the last three can be mapped in any order, but the `x` token will
+    /// _always_ occupy the fourth slot (zero-based index 3).
+    ///
+    /// Now, given an input like `10 "hello" #fff x "bye" #000 20`, the
+    /// constructed [AstNode]'s slot_map would look like
+    /// `[2, 0, 1, 3, 6, 4, 5]`. The first `Color` field, declared as index 0,
+    /// appears at the 2nd index in the concrete source, so the value at index
+    /// 0 is 2, and so on for the rest of the fields.
+    ///
+    /// The inversion of this slot map, then, is `[1, 2, 0, 3, 5, 6, 4]`. To
+    /// compare these, think: the value 0 in the original `slot_map` appeared
+    /// at index 1, so index 0 in the inverted map has the _value_ 1, then
+    /// apply that for of the slots. As you can see `3` is still in the same
+    /// position, because it is an ordered field.
+    ///
+    /// ## Optional Fields
+    ///
+    /// It's also possible for unordered fields to be _optional_, meaning they
+    /// are not present in the concrete source. In this case, the sentinel
+    /// value of `255` ([`std::u8::MAX`]) is placed in the slot map. When
+    /// inverting the map, if a slot index cannot be found in the map, it is
+    /// preserved as the same sentinel value in the inverted map.
+    ///
+    /// Using the same grammar as before, the input `10 x #000` is also valid,
+    /// but is missing many of the optional fields. The `slot_map` for this
+    /// node would include sentinel values for all of the missing fields, like:
+    /// `[255, 0, 255, 3, 4, 255, 255]`. Inverting this map would then yield:
+    /// `[1, 255, 255, 3, 4, 255, 255]`. Each declared slot is still
+    /// represented in the inverted map, but only the fields that exist in the
+    /// concrete source have usable values.
+    fn concrete_order_slot_map(&self) -> [u8; N] {
+        let mut inverted = [u8::MAX; N];
+        for (declared_slot, concrete_slot) in self.slot_map().iter().enumerate() {
+            if *concrete_slot != u8::MAX {
+                inverted[*concrete_slot as usize] = declared_slot as u8;
+            }
+        }
+
+        inverted
+    }
+}
+
 pub trait SyntaxNodeCast {
     /// Tries to cast the current syntax node to specified AST node.
     ///
diff --git a/xtask/codegen/src/generate_nodes.rs b/xtask/codegen/src/generate_nodes.rs
index e29589e888ef..3302a7e9138e 100644
--- a/xtask/codegen/src/generate_nodes.rs
+++ b/xtask/codegen/src/generate_nodes.rs
@@ -224,6 +224,18 @@ pub fn generate_nodes(ast: &AstSrc, language_kind: LanguageKind) -> Result for #name {
+                        fn slot_map(&self) -> &#slot_map_type {
+                            &self.slot_map
+                        }
+                    }
+                }
+            } else {
+                Default::default()
+            };
+
             (
                 quote! {
                     // TODO: review documentation
@@ -278,6 +290,8 @@ pub fn generate_nodes(ast: &AstSrc, language_kind: LanguageKind) -> Result SyntaxNode { self.syntax }
                     }
 
+                    #ast_node_slot_map_impl
+
                     impl std::fmt::Debug for #name {
                         fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                             f.debug_struct(#string_name)
@@ -884,9 +898,9 @@ pub fn generate_nodes(ast: &AstSrc, language_kind: LanguageKind) -> Result
Date: Wed, 17 Jan 2024 04:41:15 +0900
Subject: [PATCH 46/82] fix(exhaustiveDeps): perform nested capture check in a
 correct way (#1327)

Co-authored-by: Emanuele Stoppa 
---
 .../biome_html_syntax/src/generated/nodes.rs  |   3 +-
 crates/biome_js_analyze/src/lib.rs            |  14 +-
 .../use_exhaustive_dependencies.rs            | 121 +++++++++++-------
 .../useExhaustiveDependencies/valid.js        |  14 +-
 .../useExhaustiveDependencies/valid.js.snap   |  14 +-
 5 files changed, 108 insertions(+), 58 deletions(-)

diff --git a/crates/biome_html_syntax/src/generated/nodes.rs b/crates/biome_html_syntax/src/generated/nodes.rs
index 31da15ef7872..430859542684 100644
--- a/crates/biome_html_syntax/src/generated/nodes.rs
+++ b/crates/biome_html_syntax/src/generated/nodes.rs
@@ -12,7 +12,8 @@ use crate::{
 use biome_rowan::{support, AstNode, RawSyntaxKind, SyntaxKindSet, SyntaxResult};
 #[allow(unused)]
 use biome_rowan::{
-    AstNodeList, AstNodeListIterator, AstSeparatedList, AstSeparatedListNodesIterator,
+    AstNodeList, AstNodeListIterator, AstNodeSlotMap, AstSeparatedList,
+    AstSeparatedListNodesIterator,
 };
 #[cfg(feature = "serde")]
 use serde::ser::SerializeSeq;
diff --git a/crates/biome_js_analyze/src/lib.rs b/crates/biome_js_analyze/src/lib.rs
index 31910cfa92a8..d138a39feb6d 100644
--- a/crates/biome_js_analyze/src/lib.rs
+++ b/crates/biome_js_analyze/src/lib.rs
@@ -243,7 +243,17 @@ mod tests {
             String::from_utf8(buffer).unwrap()
         }
 
-        const SOURCE: &str = r#"require("fs")
+        const SOURCE: &str = r#"
+        import { useEffect } from "react";
+        function MyComponent7() {
+    let someObj = getObj();
+    useEffect(() => {
+        console.log(
+					someObj
+							.name
+				);
+    }, [someObj]);
+}
         "#;
         // const SOURCE: &str = r#"document.querySelector("foo").value = document.querySelector("foo").value
         //
@@ -258,7 +268,7 @@ mod tests {
             closure_index: Some(0),
             dependencies_index: Some(1),
         };
-        let rule_filter = RuleFilter::Rule("nursery", "useNodejsImportProtocol");
+        let rule_filter = RuleFilter::Rule("correctness", "useExhaustiveDependencies");
         options.configuration.rules.push_rule(
             RuleKey::new("nursery", "useHookAtTopLevel"),
             RuleOptions::new(HooksOptions { hooks: vec![hook] }),
diff --git a/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs b/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs
index 1a1a1ed9013f..f763c2f03828 100644
--- a/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs
+++ b/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs
@@ -9,10 +9,12 @@ use biome_deserialize::{
 };
 use biome_js_semantic::{Capture, SemanticModel};
 use biome_js_syntax::{
-    binding_ext::AnyJsBindingDeclaration, JsCallExpression, JsStaticMemberExpression, JsSyntaxKind,
-    JsSyntaxNode, JsVariableDeclaration, TextRange,
+    binding_ext::AnyJsBindingDeclaration, JsCallExpression, JsSyntaxKind, JsSyntaxNode,
+    JsVariableDeclaration, TextRange,
+};
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsMemberExpression, JsIdentifierExpression, TsTypeofType,
 };
-use biome_js_syntax::{AnyJsExpression, JsIdentifierExpression, TsTypeofType};
 use biome_rowan::{AstNode, SyntaxNodeCast};
 use rustc_hash::{FxHashMap, FxHashSet};
 use serde::{Deserialize, Serialize};
@@ -396,13 +398,11 @@ pub enum Fix {
     },
 }
 
-fn get_whole_static_member_expression(
-    reference: &JsSyntaxNode,
-) -> Option {
+fn get_whole_static_member_expression(reference: &JsSyntaxNode) -> Option {
     let root = reference
         .ancestors()
         .skip(2) //IDENT and JS_REFERENCE_IDENTIFIER
-        .take_while(|x| x.kind() == JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION)
+        .take_while(|x| AnyJsMemberExpression::can_cast(x.kind()))
         .last()?;
     root.cast()
 }
@@ -527,6 +527,55 @@ fn is_out_of_function_scope(
     )
 }
 
+fn into_member_vec(node: &JsSyntaxNode) -> Vec {
+    let mut vec = vec![];
+    let mut next = Some(node.clone());
+
+    while let Some(node) = &next {
+        match AnyJsMemberExpression::cast_ref(node) {
+            Some(member_expr) => {
+                let member_name = member_expr
+                    .member_name()
+                    .and_then(|it| it.as_string_constant().map(|it| it.to_owned()));
+                match member_name {
+                    Some(name) => {
+                        vec.insert(0, name);
+                        next = member_expr.object().ok().map(AstNode::into_syntax);
+                    }
+                    None => break,
+                }
+            }
+            None => {
+                vec.insert(0, node.text_trimmed().to_string());
+                break;
+            }
+        }
+    }
+
+    vec
+}
+
+fn compare_member_depth(a: &JsSyntaxNode, b: &JsSyntaxNode) -> (bool, bool) {
+    let mut a_member_iter = into_member_vec(a).into_iter();
+    let mut b_member_iter = into_member_vec(b).into_iter();
+
+    loop {
+        let a_member = a_member_iter.next();
+        let b_member = b_member_iter.next();
+
+        match (a_member, b_member) {
+            (Some(a_member), Some(b_member)) => {
+                if a_member != b_member {
+                    return (false, false);
+                }
+            }
+            (Some(_), None) => return (true, false),
+            (None, Some(_)) => return (false, true),
+            (None, None) => return (true, true),
+        }
+    }
+}
+
 impl Rule for UseExhaustiveDependencies {
     type Query = Semantic;
     type State = Fix;
@@ -566,19 +615,18 @@ impl Rule for UseExhaustiveDependencies {
                 .map(|capture| {
                     let path = get_whole_static_member_expression(capture.node());
 
-                    let (text, range) = if let Some(path) = path {
-                        (
+                    match path {
+                        Some(path) => (
                             path.syntax().text_trimmed().to_string(),
                             path.syntax().text_trimmed_range(),
-                        )
-                    } else {
-                        (
+                            path.syntax().clone(),
+                        ),
+                        None => (
                             capture.node().text_trimmed().to_string(),
                             capture.node().text_trimmed_range(),
-                        )
-                    };
-
-                    (text, range, capture)
+                            capture.node().clone(),
+                        ),
+                    }
                 })
                 .collect();
 
@@ -588,24 +636,14 @@ impl Rule for UseExhaustiveDependencies {
             let mut add_deps: BTreeMap> = BTreeMap::new();
 
             // Evaluate all the captures
-            for (capture_text, capture_range, _) in captures.iter() {
+            for (capture_text, capture_range, capture_path) in captures.iter() {
                 let mut suggested_fix = None;
                 let mut is_captured_covered = false;
                 for dep in deps.iter() {
-                    // capture_text and dependency_text should filter the "?" inside
-                    // in order to ignore optional chaining
-                    let filter_capture_text = capture_text.replace('?', "");
-                    let filter_dependency_text =
-                        dep.syntax().text_trimmed().to_string().replace('?', "");
-                    let capture_deeper_than_dependency =
-                        filter_capture_text.starts_with(&filter_dependency_text);
-                    let dependency_deeper_than_capture =
-                        filter_dependency_text.starts_with(&filter_capture_text);
-
-                    match (
-                        capture_deeper_than_dependency,
-                        dependency_deeper_than_capture,
-                    ) {
+                    let (capture_contains_dep, dep_contains_capture) =
+                        compare_member_depth(capture_path, dep.syntax());
+
+                    match (capture_contains_dep, dep_contains_capture) {
                         // capture == dependency
                         (true, true) => {
                             suggested_fix = None;
@@ -653,22 +691,11 @@ impl Rule for UseExhaustiveDependencies {
             let mut remove_deps: Vec = vec![];
             // Search for dependencies not captured
             for dep in deps {
-                let mut covers_any_capture = false;
-                for (capture_text, _, _) in captures.iter() {
-                    // capture_text and dependency_text should filter the "?" inside
-                    // in order to ignore optional chaining
-                    let filter_capture_text = capture_text.replace('?', "");
-                    let filter_dependency_text =
-                        dep.syntax().text_trimmed().to_string().replace('?', "");
-                    let capture_deeper_dependency =
-                        filter_capture_text.starts_with(&filter_dependency_text);
-                    let dependency_deeper_capture =
-                        filter_dependency_text.starts_with(&filter_capture_text);
-                    if capture_deeper_dependency || dependency_deeper_capture {
-                        covers_any_capture = true;
-                        break;
-                    }
-                }
+                let covers_any_capture = captures.iter().any(|(_, _, capture_path)| {
+                    let (capture_contains_dep, dep_contains_capture) =
+                        compare_member_depth(capture_path, dep.syntax());
+                    capture_contains_dep || dep_contains_capture
+                });
 
                 if !covers_any_capture {
                     remove_deps.push(dep);
diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js
index 633cd459c125..1fd330dd96de 100644
--- a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js
+++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js
@@ -108,17 +108,23 @@ function MyComponent6() {
 function MyComponent7() {
     let someObj = getObj();
     useEffect(() => {
-        console.log(someObj.name);
-        console.log(someObj.age)
+        console.log(
+					someObj
+							.name
+				);
+        console.log(someObj["age"])
     }, [someObj.name, someObj.age]);
 }
 
 // Specified dependency cover captures
 function MyComponent8({ a }) {
     useEffect(() => {
-      console.log(a.b);
+      console.log(
+				a
+			  		.b
+			);
     }, [a]);
-}
+}l
 
 // Capturing const outside of component
 // https://github.com/rome/tools/issues/3727
diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap
index 36b6869a3291..5c723559bbcd 100644
--- a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap
+++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap
@@ -114,17 +114,23 @@ function MyComponent6() {
 function MyComponent7() {
     let someObj = getObj();
     useEffect(() => {
-        console.log(someObj.name);
-        console.log(someObj.age)
+        console.log(
+					someObj
+							.name
+				);
+        console.log(someObj["age"])
     }, [someObj.name, someObj.age]);
 }
 
 // Specified dependency cover captures
 function MyComponent8({ a }) {
     useEffect(() => {
-      console.log(a.b);
+      console.log(
+				a
+			  		.b
+			);
     }, [a]);
-}
+}l
 
 // Capturing const outside of component
 // https://github.com/rome/tools/issues/3727

From 76fbdb8b10bf9e4c9c4ed33bd4146b9542a5c86e Mon Sep 17 00:00:00 2001
From: Zheyu Zhang 
Date: Wed, 17 Jan 2024 16:02:30 +0800
Subject: [PATCH 47/82] fix(js_formatter): fix invalid formatting of nested
 multiline comments (#1581)

---
 CHANGELOG.md                                  |  4 +
 crates/biome_js_formatter/src/comments.rs     | 29 +++----
 .../nested_comments/nested_comments.js        |  8 ++
 .../nested_comments/nested_comments.js.snap   | 84 +++++++++++++++++++
 .../comments/nested_comments/options.json     |  8 ++
 .../src/content/docs/internals/changelog.mdx  |  4 +
 6 files changed, 123 insertions(+), 14 deletions(-)
 create mode 100644 crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/nested_comments.js
 create mode 100644 crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/nested_comments.js.snap
 create mode 100644 crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/options.json

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a2d16bfbc05..36f69c4b874f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,10 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom
 
 ### Formatter
 
+#### Bug fixed
+
+- Fix [#1571](https://github.com/biomejs/biome/issues/1571). Fix invalid formatting of nested multiline comments. Contributed by @ah-yu
+
 ### JavaScript APIs
 
 ### Linter
diff --git a/crates/biome_js_formatter/src/comments.rs b/crates/biome_js_formatter/src/comments.rs
index db7429917799..4b938e6079c1 100644
--- a/crates/biome_js_formatter/src/comments.rs
+++ b/crates/biome_js_formatter/src/comments.rs
@@ -47,21 +47,22 @@ impl FormatRule> for FormatJsLeadingComment {
             // Indent the remaining lines by one space so that all `*` are aligned.
             write!(
                 f,
-                [align(
-                    1,
-                    &format_once(|f| {
-                        for line in lines {
-                            write!(
-                                f,
-                                [hard_line_break(), dynamic_text(line.trim(), source_offset)]
-                            )?;
-
-                            source_offset += line.text_len();
-                        }
+                [&format_once(|f| {
+                    for line in lines {
+                        write!(
+                            f,
+                            [
+                                hard_line_break(),
+                                text(" "),
+                                dynamic_text(line.trim(), source_offset)
+                            ]
+                        )?;
+
+                        source_offset += line.text_len();
+                    }
 
-                        Ok(())
-                    })
-                )]
+                    Ok(())
+                })]
             )
         } else {
             write!(f, [comment.piece().as_piece()])
diff --git a/crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/nested_comments.js b/crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/nested_comments.js
new file mode 100644
index 000000000000..c75f08ea6981
--- /dev/null
+++ b/crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/nested_comments.js
@@ -0,0 +1,8 @@
+condition ? {
+    a: 'a'
+} : {
+    /**
+    * comment
+    */
+    b: 'b'
+}
\ No newline at end of file
diff --git a/crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/nested_comments.js.snap b/crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/nested_comments.js.snap
new file mode 100644
index 000000000000..43e6a48d1ede
--- /dev/null
+++ b/crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/nested_comments.js.snap
@@ -0,0 +1,84 @@
+---
+source: crates/biome_formatter_test/src/snapshot_builder.rs
+info: js/module/comments/nested_comments/nested_comments.js
+---
+
+# Input
+
+```js
+condition ? {
+    a: 'a'
+} : {
+    /**
+    * comment
+    */
+    b: 'b'
+}
+```
+
+
+=============================
+
+# Outputs
+
+## Output 1
+
+-----
+Indent style: Tab
+Indent width: 2
+Line ending: LF
+Line width: 80
+Quote style: Double Quotes
+JSX quote style: Double Quotes
+Quote properties: As needed
+Trailing comma: All
+Semicolons: Always
+Arrow parentheses: Always
+Bracket spacing: true
+Bracket same line: false
+-----
+
+```js
+condition
+	? {
+			a: "a",
+	  }
+	: {
+			/**
+			 * comment
+			 */
+			b: "b",
+	  };
+```
+
+## Output 2
+
+-----
+Indent style: Space
+Indent width: 4
+Line ending: LF
+Line width: 80
+Quote style: Double Quotes
+JSX quote style: Double Quotes
+Quote properties: As needed
+Trailing comma: All
+Semicolons: Always
+Arrow parentheses: Always
+Bracket spacing: true
+Bracket same line: false
+-----
+
+```js
+condition
+    ? {
+          a: "a",
+      }
+    : {
+          /**
+           * comment
+           */
+          b: "b",
+      };
+```
+
+
diff --git a/crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/options.json b/crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/options.json
new file mode 100644
index 000000000000..ead58282a73b
--- /dev/null
+++ b/crates/biome_js_formatter/tests/specs/js/module/comments/nested_comments/options.json
@@ -0,0 +1,8 @@
+{
+	"cases": [
+		{
+			"indent_style": "Space",
+			"indent_width": 4
+		}
+	]
+}
diff --git a/website/src/content/docs/internals/changelog.mdx b/website/src/content/docs/internals/changelog.mdx
index ebbcc9c8b336..d787f8e9baac 100644
--- a/website/src/content/docs/internals/changelog.mdx
+++ b/website/src/content/docs/internals/changelog.mdx
@@ -26,6 +26,10 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom
 
 ### Formatter
 
+#### Bug fixed
+
+- Fix [#1571](https://github.com/biomejs/biome/issues/1571). Fix invalid formatting of nested multiline comments. Contributed by @ah-yu
+
 ### JavaScript APIs
 
 ### Linter

From a15397c33c554a2ca9273827b44b69a6e1e19fff Mon Sep 17 00:00:00 2001
From: Arend van Beelen jr 
Date: Wed, 17 Jan 2024 22:16:03 +0100
Subject: [PATCH 48/82] feat(project): `Deserializable` derive macro (#1564)

---
 Cargo.lock                                    |   7 +-
 Cargo.toml                                    |   3 +-
 crates/biome_analyze/CONTRIBUTING.md          | 116 +----
 crates/biome_cli/Cargo.toml                   |  67 +--
 crates/biome_cli/src/execute/migrate.rs       |   2 +-
 .../biome_cli/src/execute/migrate/prettier.rs | 214 +-------
 crates/biome_deserialize/Cargo.toml           |  23 +-
 crates/biome_deserialize/README.md            | 278 +++--------
 crates/biome_deserialize/src/diagnostics.rs   |  13 +
 crates/biome_deserialize/src/json.rs          |  73 +--
 crates/biome_deserialize/src/lib.rs           |  38 +-
 crates/biome_deserialize/src/string_set.rs    |  15 +-
 crates/biome_deserialize/src/validator.rs     |  57 +++
 crates/biome_deserialize_macros/Cargo.toml    |   3 +-
 .../src/deserializable_derive.rs              | 469 ++++++++++++++++++
 .../enum_variant_attrs.rs                     |  73 +++
 .../src/deserializable_derive/struct_attrs.rs |  64 +++
 .../struct_field_attrs.rs                     | 221 +++++++++
 crates/biome_deserialize_macros/src/lib.rs    | 239 +++++++++
 crates/biome_formatter/src/lib.rs             |  98 ++--
 crates/biome_js_analyze/Cargo.toml            |  45 +-
 .../no_excessive_cognitive_complexity.rs      |  53 +-
 .../nursery/use_consistent_array_type.rs      |  87 +---
 .../nursery/use_filenaming_convention.rs      | 101 +---
 .../a11y/use_valid_aria_role.rs               |  70 +--
 .../use_exhaustive_dependencies.rs            | 126 +----
 .../style/no_restricted_globals.rs            |  62 +--
 .../style/use_naming_convention.rs            |  86 +---
 .../emptyHookNameInOptions.js                 |   0
 .../emptyHookNameInOptions.js.snap            |  26 +
 .../emptyHookNameInOptions.options.json       |  21 +
 .../malformedOptions.js.snap                  |   4 +-
 crates/biome_js_formatter/src/context.rs      |  81 +--
 .../src/context/trailing_comma.rs             |  28 +-
 crates/biome_project/Cargo.toml               |  21 +-
 .../src/node_js_project/package_json.rs       |  16 +-
 crates/biome_service/Cargo.toml               |  17 +-
 crates/biome_service/src/configuration/css.rs |  15 +-
 .../src/configuration/formatter.rs            |  12 +-
 .../src/configuration/javascript/formatter.rs |  17 +-
 .../src/configuration/javascript/mod.rs       |  16 +-
 .../biome_service/src/configuration/json.rs   |  15 +-
 .../src/configuration/linter/mod.rs           |  33 +-
 .../src/configuration/linter/rules.rs         | 182 ++++++-
 crates/biome_service/src/configuration/mod.rs |  58 ++-
 .../src/configuration/organize_imports.rs     |   7 +-
 .../src/configuration/overrides.rs            |  23 +-
 .../configuration/parse/json/configuration.rs |  92 ----
 .../src/configuration/parse/json/css/mod.rs   | 183 -------
 .../src/configuration/parse/json/files.rs     |  60 ---
 .../src/configuration/parse/json/formatter.rs | 115 -----
 .../parse/json/javascript/formatter.rs        | 122 -----
 .../parse/json/javascript/mod.rs              | 138 ------
 .../configuration/parse/json/json_impl/mod.rs | 182 -------
 .../src/configuration/parse/json/linter.rs    | 150 ------
 .../src/configuration/parse/json/mod.rs       |  14 -
 .../parse/json/organize_imports.rs            |  56 ---
 .../src/configuration/parse/json/overrides.rs | 272 ----------
 .../src/configuration/parse/json/vcs.rs       |  98 ----
 .../src/configuration/parse/mod.rs            |   1 -
 crates/biome_service/src/configuration/vcs.rs |  45 +-
 .../formatter_extraneous_field.json.snap      |   1 -
 .../invalid/formatter_quote_style.json.snap   |   1 -
 ...cript_formatter_quote_properties.json.snap |   2 +-
 .../invalid/overrides/incorrect_key.json.snap |   6 +-
 .../top_level_extraneous_field.json.snap      |   6 +-
 .../@biomejs/backend-jsonrpc/src/workspace.ts |   4 +-
 .../@biomejs/biome/configuration_schema.json  |  10 +-
 xtask/codegen/Cargo.toml                      |   2 +-
 xtask/codegen/src/generate_configuration.rs   | 223 ++-------
 xtask/lintdoc/Cargo.toml                      |   2 +-
 71 files changed, 1959 insertions(+), 3121 deletions(-)
 create mode 100644 crates/biome_deserialize/src/validator.rs
 create mode 100644 crates/biome_deserialize_macros/src/deserializable_derive.rs
 create mode 100644 crates/biome_deserialize_macros/src/deserializable_derive/enum_variant_attrs.rs
 create mode 100644 crates/biome_deserialize_macros/src/deserializable_derive/struct_attrs.rs
 create mode 100644 crates/biome_deserialize_macros/src/deserializable_derive/struct_field_attrs.rs
 create mode 100644 crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.js
 create mode 100644 crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.js.snap
 create mode 100644 crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.options.json
 delete mode 100644 crates/biome_service/src/configuration/parse/json/configuration.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/css/mod.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/files.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/formatter.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/javascript/formatter.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/javascript/mod.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/json_impl/mod.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/linter.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/mod.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/organize_imports.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/overrides.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/json/vcs.rs
 delete mode 100644 crates/biome_service/src/configuration/parse/mod.rs

diff --git a/Cargo.lock b/Cargo.lock
index d0d2f43393c8..30443bc26a52 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -169,6 +169,7 @@ dependencies = [
  "biome_console",
  "biome_css_formatter",
  "biome_deserialize",
+ "biome_deserialize_macros",
  "biome_diagnostics",
  "biome_flags",
  "biome_formatter",
@@ -306,6 +307,7 @@ name = "biome_deserialize"
 version = "0.4.0"
 dependencies = [
  "biome_console",
+ "biome_deserialize_macros",
  "biome_diagnostics",
  "biome_json_parser",
  "biome_json_syntax",
@@ -320,8 +322,9 @@ dependencies = [
 
 [[package]]
 name = "biome_deserialize_macros"
-version = "0.3.1"
+version = "0.4.0"
 dependencies = [
+ "convert_case",
  "proc-macro-error",
  "proc-macro2",
  "quote",
@@ -454,6 +457,7 @@ dependencies = [
  "biome_console",
  "biome_control_flow",
  "biome_deserialize",
+ "biome_deserialize_macros",
  "biome_diagnostics",
  "biome_js_factory",
  "biome_js_parser",
@@ -737,6 +741,7 @@ version = "0.0.0"
 dependencies = [
  "biome_console",
  "biome_deserialize",
+ "biome_deserialize_macros",
  "biome_diagnostics",
  "biome_json_parser",
  "biome_json_syntax",
diff --git a/Cargo.toml b/Cargo.toml
index 4ac687785395..24f6f4725a08 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -81,7 +81,7 @@ biome_css_formatter          = { version = "0.4.0", path = "./crates/biome_css_f
 biome_css_parser             = { version = "0.4.0", path = "./crates/biome_css_parser" }
 biome_css_syntax             = { version = "0.4.0", path = "./crates/biome_css_syntax" }
 biome_deserialize            = { version = "0.4.0", path = "./crates/biome_deserialize" }
-biome_deserialize_macros     = { version = "0.3.1", path = "./crates/biome_deserialize_macros" }
+biome_deserialize_macros     = { version = "0.4.0", path = "./crates/biome_deserialize_macros" }
 biome_diagnostics            = { version = "0.4.0", path = "./crates/biome_diagnostics" }
 biome_diagnostics_categories = { version = "0.4.0", path = "./crates/biome_diagnostics_categories" }
 biome_diagnostics_macros     = { version = "0.4.0", path = "./crates/biome_diagnostics_macros" }
@@ -124,6 +124,7 @@ tests_macros         = { path = "./crates/tests_macros" }
 # Crates needed in the workspace
 bitflags          = "2.3.1"
 bpaf              = { version = "0.9.5", features = ["derive"] }
+convert_case      = "0.6.0"
 countme           = "3.0.1"
 dashmap           = "5.4.0"
 ignore            = "0.4.21"
diff --git a/crates/biome_analyze/CONTRIBUTING.md b/crates/biome_analyze/CONTRIBUTING.md
index 0b6e41db5ab9..761591f8f2aa 100644
--- a/crates/biome_analyze/CONTRIBUTING.md
+++ b/crates/biome_analyze/CONTRIBUTING.md
@@ -332,14 +332,16 @@ We would like to set the options in the `biome.json` configuration file:
 The first step is to create the Rust data representation of the rule's options.
 
 ```rust,ignore
-#[derive(Debug, Default, Clone)]
+use biome_deserializable_macros::Deserializable;
+
+#[derive(Clone, Debug, Default, Deserializable)]
 pub struct MyRuleOptions {
     behavior: Behavior,
     threshold: u8,
     behavior_exceptions: Vec
 }
 
-#[derive(Debug, Default, Clone)]
+#[derive(Clone, Debug, Default, Deserializable)]
 pub enum Behavior {
     #[default]
     A,
@@ -350,111 +352,11 @@ pub enum Behavior {
 
 To allow deserializing instances of the types `MyRuleOptions` and `Behavior`,
 they have to implement the `Deserializable` trait from the `biome_deserialize` crate.
+This is what the `Deserializable` keyword in the `#[derive]` statements above did.
+It's a so-called derive macros, which generates the implementation for the `Deserializable` trait
+for you.
 
-In the following code, we implement `Deserializable` for `Behavior`.
-We first deserialize the input into a `TokenText`.
-Then we validate the retrieved text by checking that it is one of the allowed string variants.
-If it is an unknown variant, we emit a diagnostic and return `None` to signal that the deserialization failed.
-Otherwise, we return the corresponding variant.
-
-```rust,ignore
-use biome_deserialize::{Deserializable, DeserializableValue, DeserializationVisitor, Text};
-
-impl Deserializable for Behavior {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match Text::deserialize(&value, name, diagnostics)?.text() {
-            "A" => Some(Behavior::A),
-            "B" => Some(Behavior::B),
-            "C" => Some(Behavior::C),
-            unknown_variant => {
-                const ALLOWED_VARIANTS: &[&str] = &["A", "B", "C"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_variant,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-```
-
-To implement `Deserializable` for `MyRuleOptions`,
-we cannot reuse an existing deserializer because a `struct` has custom fields.
-Instead, we delegate the deserialization to a visitor.
-We implement a visitor by implementing the `DeserializationVisitor` trait from the `biome_deserialize` crate.
-The visitor traverses every field (key-value pair) of our object and deserialize them.
-If an unknown field is found, we emit a diagnostic.
-
-```rust,ignore
-use biome_deserialize::{DeserializationDiagnostic,  Deserializable, DeserializableValue, DeserializationVisitor, Text, VisitableType};
-
-impl Deserializable for MyRuleOptions {
-    fn deserialize(
-        value: &impl DeserializableValue,
-name: &str,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        value.deserialize(MyRuleOptionsVisitor, name, diagnostics)
-    }
-}
-
-struct MyRuleOptionsVisitor;
-impl DeserializationVisitor for MyRuleOptionsVisitor {
-    type Output = MyRuleOptions;
-
-    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-
-    fn visit_map(
-        self,
-        members: impl Iterator>,
-        _name: &str,
-        _range: TextRange,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        let mut result = Self::Output::default();
-        for (key, value) in members.flatten() {
-            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                continue;
-            };
-            match key_text.text() {
-                "behavior" => {
-                    if let Some(behavior) = Deserialize::deserialize(&value, &key_text, diagnostics) {
-                        result.behavior = behavior;
-                    }
-                }
-                "threshold" => {
-                    if let Some(threshold) = Deserialize::deserialize(&value, &key_text, diagnostics) {
-                        result.behavior = threshold;
-                    }
-                }
-                "behaviorExceptions" => {
-                    if let Some(exceptions) = Deserialize::deserialize(&value, &key_text, diagnostics) {
-                        result.behavior_exceptions = exceptions;
-                    }
-                }
-                unknown_key => {
-                    const ALLOWED_KEYS: &[&str] = &["behavior", "threshold", "behaviorExceptions"];
-                    diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                        unknown_key,
-                        key.range(),
-                        ALLOWED_KEYS,
-                    ))
-                }
-            }
-        }
-        Some(result)
-    }
-}
-```
-
-Once done, you can set the associated type `Options` of the rule:
+With these types in place, you can set the associated type `Options` of the rule:
 
 ```rust,ignore
 impl Rule for MyRule {
@@ -467,7 +369,7 @@ impl Rule for MyRule {
 }
 ```
 
-A rule can retrieve its option with:
+A rule can retrieve its options with:
 
 ```rust,ignore
 let options = ctx.options();
diff --git a/crates/biome_cli/Cargo.toml b/crates/biome_cli/Cargo.toml
index 06f847fab337..b42646c6c703 100644
--- a/crates/biome_cli/Cargo.toml
+++ b/crates/biome_cli/Cargo.toml
@@ -18,39 +18,40 @@ name = "biome"
 path = "src/main.rs"
 
 [dependencies]
-anyhow               = "1.0.52"
-biome_analyze        = { workspace = true }
-biome_console        = { workspace = true }
-biome_deserialize    = { workspace = true }
-biome_diagnostics    = { workspace = true }
-biome_flags          = { workspace = true }
-biome_formatter      = { workspace = true }
-biome_fs             = { workspace = true }
-biome_js_formatter   = { workspace = true }
-biome_json_formatter = { workspace = true }
-biome_json_parser    = { workspace = true }
-biome_json_syntax    = { workspace = true }
-biome_lsp            = { workspace = true }
-biome_migrate        = { workspace = true }
-biome_rowan          = { workspace = true }
-biome_service        = { workspace = true }
-biome_text_edit      = { workspace = true }
-biome_text_size      = { workspace = true }
-bpaf                 = { workspace = true, features = ["bright-color"] }
-crossbeam            = "0.8.1"
-dashmap              = { workspace = true }
-hdrhistogram         = { version = "7.5.0", default-features = false }
-indexmap             = { workspace = true }
-lazy_static          = { workspace = true }
-rayon                = "1.5.1"
-rustc-hash           = { workspace = true }
-serde                = { workspace = true, features = ["derive"] }
-serde_json           = { workspace = true }
-tokio                = { workspace = true, features = ["io-std", "io-util", "net", "time", "rt", "sync", "rt-multi-thread", "macros"] }
-tracing              = { workspace = true }
-tracing-appender     = "0.2"
-tracing-subscriber   = { version = "0.3.16", features = ["env-filter", "json"] }
-tracing-tree         = "0.2.2"
+anyhow                   = "1.0.52"
+biome_analyze            = { workspace = true }
+biome_console            = { workspace = true }
+biome_deserialize        = { workspace = true }
+biome_deserialize_macros = { workspace = true }
+biome_diagnostics        = { workspace = true }
+biome_flags              = { workspace = true }
+biome_formatter          = { workspace = true }
+biome_fs                 = { workspace = true }
+biome_js_formatter       = { workspace = true }
+biome_json_formatter     = { workspace = true }
+biome_json_parser        = { workspace = true }
+biome_json_syntax        = { workspace = true }
+biome_lsp                = { workspace = true }
+biome_migrate            = { workspace = true }
+biome_rowan              = { workspace = true }
+biome_service            = { workspace = true }
+biome_text_edit          = { workspace = true }
+biome_text_size          = { workspace = true }
+bpaf                     = { workspace = true, features = ["bright-color"] }
+crossbeam                = "0.8.1"
+dashmap                  = { workspace = true }
+hdrhistogram             = { version = "7.5.0", default-features = false }
+indexmap                 = { workspace = true }
+lazy_static              = { workspace = true }
+rayon                    = "1.5.1"
+rustc-hash               = { workspace = true }
+serde                    = { workspace = true, features = ["derive"] }
+serde_json               = { workspace = true }
+tokio                    = { workspace = true, features = ["io-std", "io-util", "net", "time", "rt", "sync", "rt-multi-thread", "macros"] }
+tracing                  = { workspace = true }
+tracing-appender         = "0.2"
+tracing-subscriber       = { version = "0.3.16", features = ["env-filter", "json"] }
+tracing-tree             = "0.2.2"
 
 [target.'cfg(unix)'.dependencies]
 libc  = "0.2.127"
diff --git a/crates/biome_cli/src/execute/migrate.rs b/crates/biome_cli/src/execute/migrate.rs
index 4db51f35551b..e28db079b035 100644
--- a/crates/biome_cli/src/execute/migrate.rs
+++ b/crates/biome_cli/src/execute/migrate.rs
@@ -138,7 +138,7 @@ pub(crate) fn run(migrate_payload: MigratePayload) -> Result<(), CliDiagnostic>
                     formatter: Some(formatter_configuration.clone()),
                     javascript: Some(JavascriptConfiguration {
                         formatter: Some(javascript_configuration.clone()),
-                        ..NoneState::none()
+                        ..Default::default()
                     }),
                     ..NoneState::none()
                 });
diff --git a/crates/biome_cli/src/execute/migrate/prettier.rs b/crates/biome_cli/src/execute/migrate/prettier.rs
index d992ee127895..3c2eb5c3cb97 100644
--- a/crates/biome_cli/src/execute/migrate/prettier.rs
+++ b/crates/biome_cli/src/execute/migrate/prettier.rs
@@ -2,10 +2,7 @@ use crate::diagnostics::MigrationDiagnostic;
 use crate::CliDiagnostic;
 use biome_console::{markup, Console, ConsoleExt};
 use biome_deserialize::json::deserialize_from_json_str;
-use biome_deserialize::{
-    Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor, Text,
-    VisitableType,
-};
+use biome_deserialize_macros::Deserializable;
 use biome_diagnostics::{DiagnosticExt, PrintDiagnostic};
 use biome_formatter::{LineEnding, LineWidth, QuoteStyle};
 use biome_fs::{FileSystem, OpenOptions};
@@ -13,10 +10,9 @@ use biome_js_formatter::context::{ArrowParentheses, QuoteProperties, Semicolons,
 use biome_json_parser::JsonParserOptions;
 use biome_service::configuration::{FormatterConfiguration, PlainIndentStyle};
 use biome_service::{DynRef, JavascriptFormatter};
-use biome_text_size::TextRange;
 use std::path::{Path, PathBuf};
 
-#[derive(Debug, Eq, PartialEq, Clone)]
+#[derive(Clone, Debug, Deserializable, Eq, PartialEq)]
 pub(crate) struct PrettierConfiguration {
     /// https://prettier.io/docs/en/options#print-width
     print_width: u16,
@@ -64,7 +60,7 @@ impl Default for PrettierConfiguration {
     }
 }
 
-#[derive(Debug, Eq, PartialEq, Default, Clone)]
+#[derive(Clone, Debug, Default, Deserializable, Eq, PartialEq)]
 enum EndOfLine {
     #[default]
     Lf,
@@ -72,59 +68,14 @@ enum EndOfLine {
     Cr,
 }
 
-impl Deserializable for EndOfLine {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match String::deserialize(value, name, diagnostics)?.as_str() {
-            "lf" => Some(Self::Lf),
-            "crlf" => Some(Self::Crlf),
-            "cr" => Some(Self::Cr),
-            unknown_variant => {
-                const ALLOWED_VARIANTS: &[&str] = &["lf", "crlf", "cr"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_variant,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-
-#[derive(Debug, Eq, PartialEq, Default, Clone)]
+#[derive(Clone, Debug, Default, Deserializable, Eq, PartialEq)]
 enum ArrowParens {
     #[default]
     Always,
     Avoid,
 }
 
-impl Deserializable for ArrowParens {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match String::deserialize(value, name, diagnostics)?.as_str() {
-            "always" => Some(Self::Always),
-            "avoid" => Some(Self::Avoid),
-            unknown_variant => {
-                const ALLOWED_VARIANTS: &[&str] = &["always", "avoid"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_variant,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-
-#[derive(Debug, Default, Eq, PartialEq, Clone)]
+#[derive(Clone, Debug, Default, Deserializable, Eq, PartialEq)]
 enum PrettierTrailingComma {
     #[default]
     All,
@@ -132,58 +83,14 @@ enum PrettierTrailingComma {
     Es5,
 }
 
-impl Deserializable for PrettierTrailingComma {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match String::deserialize(value, name, diagnostics)?.as_str() {
-            "all" => Some(Self::All),
-            "none" => Some(Self::None),
-            "es5" => Some(Self::Es5),
-            unknown_variant => {
-                const ALLOWED_VARIANTS: &[&str] = &["all", "none", "es5"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_variant,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-
-#[derive(Debug, Eq, Default, PartialEq, Clone)]
+#[derive(Clone, Debug, Default, Deserializable, Eq, PartialEq)]
 enum QuoteProps {
     #[default]
+    #[deserializable(rename = "as-needed")]
     AsNeeded,
     Preserve,
 }
 
-impl Deserializable for QuoteProps {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match String::deserialize(value, name, diagnostics)?.as_str() {
-            "as-needed" => Some(Self::AsNeeded),
-            "preserve" => Some(Self::Preserve),
-            unknown_variant => {
-                const ALLOWED_VARIANTS: &[&str] = &["as-needed", "preserve"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_variant,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-
 impl TryFrom<&str> for PrettierTrailingComma {
     type Error = String;
     fn try_from(value: &str) -> Result {
@@ -196,113 +103,6 @@ impl TryFrom<&str> for PrettierTrailingComma {
     }
 }
 
-impl Deserializable for PrettierConfiguration {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        value.deserialize(PrettierVisitor, name, diagnostics)
-    }
-}
-
-struct PrettierVisitor;
-
-impl DeserializationVisitor for PrettierVisitor {
-    type Output = PrettierConfiguration;
-    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-
-    fn visit_map(
-        self,
-        members: impl Iterator>,
-        _range: TextRange,
-        _name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        let mut result = PrettierConfiguration::default();
-        for (key, value) in members.flatten() {
-            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                continue;
-            };
-            match key_text.text() {
-                "endOfLine" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.end_of_line = val;
-                    }
-                }
-                "arrowParens" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.arrow_parens = val;
-                    }
-                }
-                "useTabs" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.use_tabs = val;
-                    }
-                }
-
-                "printWidth" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.print_width = val;
-                    }
-                }
-
-                "trailingComma" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.trailing_comma = val;
-                    }
-                }
-
-                "quoteProps" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.quote_props = val;
-                    }
-                }
-
-                "semi" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.semi = val;
-                    }
-                }
-
-                "bracketSpacing" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.bracket_spacing = val;
-                    }
-                }
-
-                "bracketLine" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.bracket_line = val;
-                    }
-                }
-
-                "jsxSingleQuote" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.jsx_single_quote = val;
-                    }
-                }
-
-                "singleQuote" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.single_quote = val;
-                    }
-                }
-
-                "tabWidth" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.tab_width = val;
-                    }
-                }
-
-                _ => {}
-            }
-        }
-
-        Some(result)
-    }
-}
-
 impl From for TrailingComma {
     fn from(value: PrettierTrailingComma) -> Self {
         match value {
diff --git a/crates/biome_deserialize/Cargo.toml b/crates/biome_deserialize/Cargo.toml
index 40f249c22451..bf5a5a571204 100644
--- a/crates/biome_deserialize/Cargo.toml
+++ b/crates/biome_deserialize/Cargo.toml
@@ -11,17 +11,18 @@ repository.workspace = true
 version              = "0.4.0"
 
 [dependencies]
-biome_console     = { workspace = true }
-biome_diagnostics = { workspace = true }
-biome_json_parser = { workspace = true }
-biome_json_syntax = { workspace = true }
-biome_rowan       = { workspace = true }
-bitflags          = { workspace = true }
-indexmap          = { workspace = true, features = ["serde"] }
-schemars          = { workspace = true, optional = true }
-serde             = { workspace = true }
-serde_json        = { workspace = true }
-tracing           = { workspace = true }
+biome_console            = { workspace = true }
+biome_deserialize_macros = { workspace = true }
+biome_diagnostics        = { workspace = true }
+biome_json_parser        = { workspace = true }
+biome_json_syntax        = { workspace = true }
+biome_rowan              = { workspace = true }
+bitflags                 = { workspace = true }
+indexmap                 = { workspace = true, features = ["serde"] }
+schemars                 = { workspace = true, optional = true }
+serde                    = { workspace = true }
+serde_json               = { workspace = true }
+tracing                  = { workspace = true }
 
 [features]
 schema = ["schemars", "schemars/indexmap"]
diff --git a/crates/biome_deserialize/README.md b/crates/biome_deserialize/README.md
index 1b580a9ec1fd..05303631901b 100644
--- a/crates/biome_deserialize/README.md
+++ b/crates/biome_deserialize/README.md
@@ -78,7 +78,35 @@ assert_eq!(deserialized, Some(HashMap::from([("a".to_string(), 0), ("b".to_strin
 assert!(diagnostics.is_empty());
 ```
 
-### Deserializing map and array collections
+### Deserializable derive macro
+
+For more complex types, such as structs and enums, `biome_deserialize_macros` offers a derive macro
+which will generate an implementation of the `Deserializable` trait for you.
+
+For example:
+
+```rs
+#[derive(Clone, Debug, Default, Deserializable)]
+pub struct MyRuleOptions {
+    behavior: Behavior,
+    threshold: u8,
+    behavior_exceptions: Vec
+}
+```
+
+Extensive documentation for the macro is also available if you hover over it in your IDE.
+
+Note that the macro has two main limitations:
+
+- The current implementation only supports enums where none of the variants have custom fields,
+  structs with named fields, and newtypes.
+- Every field must also implement `Deserializable`.
+
+If you want to implement `Deserializable` for a new primitive type, or a complex type that falls
+outside the limitations above, you'll need to implement it manually. See below for the section about
+[Implementing a custom deserializer](#implementing-a-custom-deserializer).
+
+## Deserializing map and array collections
 
 `biome_deserialize` requires that every data format supports arrays and maps.
 A map and an array can be deserialized into several types in Rust.
@@ -98,6 +126,20 @@ If you hesitate between a collection that preserves the insertion order and one
 chooses the collection that preserves the insertion order.
 This often outputs less surprising behavior.
 
+## Implementing a custom deserializer
+
+For most enums and structs, an implementation can be generated by the `Deserializable` macro.
+However, for primitives, and certain complex types, you may need to implement
+`biome_deserialize::Deserializable` yourself.
+
+> [!NOTE]
+> If you are curious about the code generated by the macro, *Rust Analyzer* offers a great feature
+> from the command palette called "Expand macro recursively at caret". Just put your cursor on the
+> word `Deserializable` in the `#[derive(...)]` statement and invoke the command. A panel should
+> open with the expanded macro code.
+
+We provide a few examples for custom implementations below.
+
 ### Custom integer range
 
 Sometimes you want to deserialize an integer and ensure that it is between two given integers.
@@ -183,217 +225,72 @@ assert_eq!(deserialized, None);
 assert_eq!(diagnostics..len(), 1);
 ```
 
-### Deserializing an enumeration of values
-
-Let's assume that we want to deserialize a JSON string that is either `A`, or `B`.
-We represent this string by a Rust's `enum`:
-
-To implement `Deserializable` for `Variant`, we can reuse the `String`,
-because `biome_deserialize` implements `Deserializable` for the `String` type.
-
-Our implementation attempts to deserialize a string and creates the corresponding variant.
-If the variant is not known, we report a diagnostic.
-
-```rust
-use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic};
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
-pub enum Variant { A, B }
-
-impl Deserializable for Variant {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match String::deserialize(value, name, diagnostics)? {
-            "A" => Some(Variant::A),
-            "B" => Some(Variant::B),
-            unknown_variant => {
-                const ALLOWED_VARIANTS: &[&str] = &["A", "B"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_variant,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-
-use biome_deserialize::json::deserialize_from_json_str;
-use biome_deserialize::Deserialized;
-use biome_json_parser::JsonParserOptions;
-
-let json = "\"A\"";
-let Deserialized {
-    deserialized,
-    diagnostics,
-} = deserialize_from_json_str::(&source, JsonParserOptions::default());
-assert_eq!(deserialized, Some(Variant::A));
-assert!(diagnostics.is_empty());
-```
-
-We can improve our implementation by avoiding the heap-allocation of a string.
-To do this, we use `Text` instead of `String`.
-Internally `Text` borrows a slice of the source.
-
-```rust
-use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic, Text};
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
-pub enum Variant { A, B }
-
-impl Deserializable for Variant {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match Text::deserialize(value, name, diagnostics)?.text() {
-            "A" => Some(Variant::A),
-            "B" => Some(Variant::B),
-            unknown_variant => {
-                const ALLOWED_VARIANTS: &[&str] = &["A", "B"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_variant,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-```
-
-### Deserializing a struct
-
-Let's assume we want to deserialize a _JSON_ map (object) into an instance of a Rust `struct`.
-
-Because a `struct` has custom fields and types, we cannot reuse an existing deserializable type.
-We have to delegate the deserialization to a visitor.
-
-To do that, we create a zero-sized struct `PersonViistor` that implements `DeserializationVisitor`.
-A `DeserializationVisitor` provides several `visit_` methods.
-You must implement the `visit_` methods of the type you expect.
-Here we expect a map of key-value pairs.
-Thus, we implement `visit_map` and set the associated constant `EXPECTED_TYPE` to `VisitableType::MAP`.
-We also set the associated type `Output` to the type that we want to produce: `Person`.
-
-The implementation of `Deserialziable` for `Person` simply delegates the deserialization of the visitor.
-Internally, the deserialization of `value` calls the `visit_` method that corresponds to its type.
-If the value is a map of key-value pairs, then `visit_map` is called.
-Otherwise, another `visit_` method is called.
-The default implementation of a `visit_` method reports an incorrect type diagnostic.
+### Deserializing a union
 
-Our implementation of `visit_map` traverses the map of key-value pairs.
-It attempts to deserialize every key as a string and deserialize the value according the key's content.
-If the key is `name`, then we deserialize a `String`.
-If the key is `age`, then we deserialize a `u8`.
-`String` and `u8` implements `Deserializable`.
-Thus, we can reuse `String::deserialize` and `u8::deserialize`.
+Sometimes we want to allow several types for a single value.
+For instance let's assume that we cant to accept a JSON value that is either a string or a boolean.
 
-Note that if you use _Serde_ in tandem with `biome_deserialize`, you have to disambiguate the call to `deserialize`.
-Thus, instead of using `String::deserialize` and `u8::deserialize`, you should use `Deserialize::deserialize`.
+In this case, we'll need to inspect the type of the `DeserializableValue` to know which deserializer
+to use:
 
 ```rust
 use biome_deserialize::{DeserializationDiagnostic, Deserializable, DeserializableValue, DeserializationVisitor, Text, VisitableType};
 use biome_rowan::TextRange;
 
-#[derive(Debug, Default, Eq, PartialEq, Clone)]
-pub struct Person { name: String, age: u8 }
+#[derive(Debug, Eq, PartialEq)]
+enum Union {
+    Bool(bool),
+    Str(String),
+}
 
-impl Deserializable for Person {
+impl Deserializable for Union {
     fn deserialize(
         value: &impl DeserializableValue,
         name: &str,
         diagnostics: &mut Vec,
     ) -> Option {
-        // Delegate the deserialization to `PersonVisitor`.
-        // `value` will call the `PersonVisitor::visit_` method that corresponds to its type.
-        value.deserialize(PersonVisitor, name, diagnostics)
-    }
-}
-
-struct PersonVisitor;
-impl DeserializationVisitor for PersonVisitor {
-    // The visitor deserialize a [Person].
-    type Output = Person;
-
-    // We expect a `map` as data type.
-    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-
-    // Because we expect a `map`, we have to implement the associated method `visit_map`.
-    fn visit_map(
-        self,
-        // Iterator of key-value pairs.
-        members: impl Iterator>,
-        // range of the map in the source text.
-        range: TextRange,
-        _name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        let mut result = Person::default();
-        for (key, value) in members.flatten() {
-            // Try to deserialize the key as a string.
-            // We use `Text` to avoid an heap-allocation.
-            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                // If this failed, then pass to the next key-value pair.
-                continue;
-            };
-            match key_text.text() {
-                "name" => {
-                    if let Some(name) = String::deserialize(&value, &key_text, diagnostics) {
-                        result.name = name;
-                    }
-                },
-                "age" => {
-                    if let Some(age) = u8::deserialize(&value, &key_text, diagnostics) {
-                        result.age = age;
-                    }
-                },
-                unknown_key => {
-                    const ALLOWED_KEYS: &[&str] = &["name"];
-                    diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                        unknown_key,
-                        key.range(),
-                        ALLOWED_KEYS,
-                    ));
-                }
-            }
+        if value.is_type(VisitableType::BOOL) {
+            biome_deserialize::Deserializable::deserialize(value, rule_name, diagnostics)
+                .map(Self::Bool)
+        } else {
+            biome_deserialize::Deserializable::deserialize(value, rule_name, diagnostics)
+                .map(Self::Str)
         }
-        Some(result)
     }
 }
 
 use biome_deserialize::json::deserialize_from_json_str;
 use biome_json_parser::JsonParserOptions;
 
-let source = r#"{ "name": "Isaac Asimov" }"#;
-let deserialized = deserialize_from_json_str::(&source, JsonParserOptions::default());
+let source = r#" "string" "#;
+let deserialized = deserialize_from_json_str::(&source, JsonParserOptions::default());
+assert!(!deserialized.has_errors());
+assert_eq!(deserialized.into_deserialized(), Some(Union::Str("string".to_string())));
+
+let source = "true";
+let deserialized = deserialize_from_json_str::(&source, JsonParserOptions::default());
 assert!(!deserialized.has_errors());
-assert_eq!(deserialized.into_deserialized(), Some(Person { name: "Isaac Asimov".to_string() }));
+assert_eq!(deserialized.into_deserialized(), Some(Union::Bool(true)));
 ```
 
-### Deserializing a union
+Alternatively, we could implement the above using a custom visitor. Note that this approach is much
+more involved and should only be used as a last resort. Here, we will reimplement `Deserializable`
+for our `Union` type with a visitor for demonstration purposes.
 
-Sometimes we want to allow several types for a same value.
-For instance let's assume that we cant to accept a JSON value that is either a string or a boolean.
+To create your own visitor, start with a struct named after your type with a `Visitor` suffix. We'll
+call ours `UnionVisitor`.
 
-Because we accept several types, we have to use a visitor.
+The visitor struct doesn't need any fields, but we do need to implement `DeserializationVisitor` on
+it. A `DeserializationVisitor` provides several `visit_` methods and you must implement the `visit_`
+methods of the type(s) you expect. Here we expect either a boolean or a string, so we'll implement
+`visit_bool()` and `visit_str()`.
 
-```rust
-use biome_deserialize::{DeserializationDiagnostic, Deserializable, DeserializableValue, DeserializationVisitor, Text, VisitableType};
-use biome_rowan::TextRange;
+We also have to set the associated type `Output` to be a union of the types we expect:
+`VisitableType::BOOL.union(VisitableType::STR)`.
 
-#[derive(Debug, Eq, PartialEq)]
-enum Union {
-    Bool(bool),
-    Str(String),
-}
+The full example:
 
+```rust
 impl Deserializable for Union {
     fn deserialize(
         value: &impl DeserializableValue,
@@ -434,17 +331,4 @@ impl DeserializationVisitor for UnionVisitor {
         Some(Union::Str(value.text().to_string()))
     }
 }
-
-use biome_deserialize::json::deserialize_from_json_str;
-use biome_json_parser::JsonParserOptions;
-
-let source = r#" "string" "#;
-let deserialized = deserialize_from_json_str::(&source, JsonParserOptions::default());
-assert!(!deserialized.has_errors());
-assert_eq!(deserialized.into_deserialized(), Some(Union::Str("string".to_string())));
-
-let source = "true";
-let deserialized = deserialize_from_json_str::(&source, JsonParserOptions::default());
-assert!(!deserialized.has_errors());
-assert_eq!(deserialized.into_deserialized(), Some(Union::Bool(true)));
 ```
diff --git a/crates/biome_deserialize/src/diagnostics.rs b/crates/biome_deserialize/src/diagnostics.rs
index 0b42db58326f..98f8febca86a 100644
--- a/crates/biome_deserialize/src/diagnostics.rs
+++ b/crates/biome_deserialize/src/diagnostics.rs
@@ -100,6 +100,19 @@ impl DeserializationDiagnostic {
         .with_range(range)
     }
 
+    /// Emitted when a key is missing, against a set of required ones
+    pub fn new_missing_key(key_name: &str, range: impl AsSpan, required_keys: &[&str]) -> Self {
+        let diagnostic =
+            Self::new(markup!("The key `"{key_name}"` is missing." ))
+                .with_range(range);
+
+        if required_keys.len() > 1 {
+            diagnostic.note_with_list("Required keys", required_keys)
+        } else {
+            diagnostic
+        }
+    }
+
     /// Emitted when a generic node has an incorrect type
     pub fn new_out_of_bound_integer(
         min: impl std::fmt::Display,
diff --git a/crates/biome_deserialize/src/json.rs b/crates/biome_deserialize/src/json.rs
index ec58652a27f0..232953eed14a 100644
--- a/crates/biome_deserialize/src/json.rs
+++ b/crates/biome_deserialize/src/json.rs
@@ -1,7 +1,7 @@
 //! Implementation of [DeserializableValue] for the JSON data format.
 use crate::{
     Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor,
-    Deserialized, Text, TextNumber,
+    Deserialized, Text, TextNumber, VisitableType,
 };
 use biome_diagnostics::{DiagnosticExt, Error};
 use biome_json_parser::{parse_json, JsonParserOptions};
@@ -12,70 +12,23 @@ use biome_rowan::{AstNode, AstSeparatedList};
 /// are consumed and joined with the diagnostics emitted during the deserialization.
 ///
 /// The data structures that need to be deserialized have to implement the [Deserializable] trait.
-/// To implement [Deserializable], it can need to implement [DeserializationVisitor] that allows
-/// visiting a value.
+/// For most data structures, this can be achieved using the
+/// [biome_deserialize_macros::Deserializable] derive.
 ///
 /// `name` corresponds to the name used in a diagnostic to designate the deserialized value.
 ///
 /// ## Examples
 ///
 /// ```
-/// use biome_deserialize::{DeserializationDiagnostic, Deserializable, DeserializableValue, DeserializationVisitor, Text, VisitableType};
 /// use biome_deserialize::json::deserialize_from_json_str;
+/// use biome_deserialize_macros::Deserializable;
 /// use biome_json_parser::JsonParserOptions;
-/// use biome_rowan::{TextRange, TokenText};
 ///
-/// #[derive(Default, Debug, Eq, PartialEq)]
+/// #[derive(Debug, Default, Deserializable, Eq, PartialEq)]
 /// struct NewConfiguration {
 ///     lorem: String
 /// }
 ///
-/// impl Deserializable for NewConfiguration {
-///     fn deserialize(
-///         value: &impl DeserializableValue,
-///         name: &str,
-///         diagnostics: &mut Vec,
-///     ) -> Option {
-///         value.deserialize(Visitor, name, diagnostics)
-///     }
-/// }
-///
-/// struct Visitor;
-/// impl DeserializationVisitor for Visitor {
-///     type Output = NewConfiguration;
-///
-///     const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-///
-///     fn visit_map(
-///         self,
-///         members: impl Iterator>,
-///         _range: TextRange,
-///         _name: &str,
-///         diagnostics: &mut Vec,
-///     ) -> Option {
-///         const ALLOWED_KEYS: &[&str] = &["strictCase", "enumMemberCase"];
-///         let mut result = NewConfiguration::default();
-///         for (key, value) in members.flatten() {
-///             let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-///                 continue;
-///             };
-///             match key_text.text() {
-///                 "lorem" => {
-///                     if let Some(value) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-///                         result.lorem = value;
-///                     }
-///                 },
-///                 _ => diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-///                     &key_text,
-///                     key.range(),
-///                     ALLOWED_KEYS,
-///                 )),
-///             }
-///         }
-///         Some(result)
-///     }
-/// }
-///
 /// let source = r#"{ "lorem": "ipsum" }"#;
 /// let deserialized = deserialize_from_json_str::(&source, JsonParserOptions::default(), "");
 /// assert!(!deserialized.has_errors());
@@ -171,6 +124,18 @@ impl DeserializableValue for AnyJsonValue {
             }
         }
     }
+
+    fn is_type(&self, ty: VisitableType) -> bool {
+        match self {
+            AnyJsonValue::JsonArrayValue(_) => ty == VisitableType::ARRAY,
+            AnyJsonValue::JsonBogusValue(_) => false,
+            AnyJsonValue::JsonBooleanValue(_) => ty == VisitableType::BOOL,
+            AnyJsonValue::JsonNullValue(_) => ty == VisitableType::NULL,
+            AnyJsonValue::JsonNumberValue(_) => ty == VisitableType::NUMBER,
+            AnyJsonValue::JsonObjectValue(_) => ty == VisitableType::MAP,
+            AnyJsonValue::JsonStringValue(_) => ty == VisitableType::STR,
+        }
+    }
 }
 
 impl DeserializableValue for JsonMemberName {
@@ -187,6 +152,10 @@ impl DeserializableValue for JsonMemberName {
         let value = self.inner_string_text().ok()?;
         visitor.visit_str(Text(value), AstNode::range(self), name, diagnostics)
     }
+
+    fn is_type(&self, _ty: VisitableType) -> bool {
+        false
+    }
 }
 
 #[cfg(test)]
diff --git a/crates/biome_deserialize/src/lib.rs b/crates/biome_deserialize/src/lib.rs
index c894f4c259af..8972556c3a2d 100644
--- a/crates/biome_deserialize/src/lib.rs
+++ b/crates/biome_deserialize/src/lib.rs
@@ -35,15 +35,17 @@ pub mod json;
 mod merge;
 mod none_state;
 pub mod string_set;
+mod validator;
 
 use biome_diagnostics::{Error, Severity};
-use biome_rowan::TextRange;
+pub use biome_rowan::TextRange;
 pub use diagnostics::{DeserializationAdvice, DeserializationDiagnostic, VisitableType};
 pub use impls::*;
 pub use merge::Merge;
 pub use none_state::NoneState;
 use std::fmt::Debug;
 pub use string_set::StringSet;
+pub use validator::*;
 
 /// Implemented by data structures that can deserialize any [DeserializableValue].
 ///
@@ -51,46 +53,25 @@ pub use string_set::StringSet;
 /// To implement [Deserializable], you can reuse a type that implements [Deserializable] and
 /// turn the obtained value into what you want.
 ///
-/// When deserializing more complex types, such as a `struct`,
-/// you have to use a type that implements [DeserializationVisitor].
+/// When deserializing more complex types, such as a `struct` or `enum`, you can use the
+/// [Deserializable] derive macro.
 ///
 /// ## Example
 ///
 /// ```
-/// use biome_deserialize::{DeserializationDiagnostic,  Deserializable, Text, DeserializableValue};
+/// use biome_deserialize_macros::Deserializable;
 /// use biome_rowan::TextRange;
 ///
+/// #[derive(Deserializable)]
 /// pub enum Variant {
 ///     A,
 ///     B,
 /// }
 ///
-/// impl Deserializable for Variant {
-///     fn deserialize(
-///         value: &impl DeserializableValue,
-///         name: &str,
-///         diagnostics: &mut Vec,
-///     ) -> Option {
-///         match Text::deserialize(value, name, diagnostics)?.text() {
-///             "A" => Some(Variant::A),
-///             "B" => Some(Variant::B),
-///             unknown_variant => {
-///                 const ALLOWED_VARIANTS: &[&str] = &["A", "B"];
-///                 diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-///                     unknown_variant,
-///                     value.range(),
-///                     ALLOWED_VARIANTS,
-///                 ));
-///                 None
-///             }
-///         }
-///     }
-/// }
-///
 /// use biome_deserialize::json::deserialize_from_json_str;
 /// use biome_json_parser::JsonParserOptions;
 ///
-/// let source = r#""A""#;
+/// let source = r#""a""#;
 /// let deserialized = deserialize_from_json_str::(&source, JsonParserOptions::default(), "");
 /// assert!(!deserialized.has_errors());
 /// assert!(matches!(deserialized.into_deserialized(), Some(Variant::A)));
@@ -123,6 +104,9 @@ pub trait DeserializableValue: Sized {
         name: &str,
         diagnostics: &mut Vec,
     ) -> Option;
+
+    /// Returns whether the value is of the given type.
+    fn is_type(&self, ty: VisitableType) -> bool;
 }
 
 /// This trait represents a visitor that walks through a [DeserializableValue].
diff --git a/crates/biome_deserialize/src/string_set.rs b/crates/biome_deserialize/src/string_set.rs
index 0b715caea02c..ccac8b14a809 100644
--- a/crates/biome_deserialize/src/string_set.rs
+++ b/crates/biome_deserialize/src/string_set.rs
@@ -1,4 +1,5 @@
-use crate::{Deserializable, DeserializableValue, Merge};
+use crate::{self as biome_deserialize, Merge};
+use biome_deserialize_macros::Deserializable;
 use indexmap::set::IntoIter;
 use indexmap::IndexSet;
 use serde::de::{SeqAccess, Visitor};
@@ -9,7 +10,7 @@ use std::str::FromStr;
 
 // To implement serde's traits, we encapsulate `IndexSet` in a new type `StringSet`.
 
-#[derive(Default, Debug, Clone, Eq, PartialEq)]
+#[derive(Clone, Default, Debug, Deserializable, Eq, PartialEq)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 pub struct StringSet(IndexSet);
 
@@ -116,13 +117,3 @@ impl Serialize for StringSet {
         sequence.end()
     }
 }
-
-impl Deserializable for StringSet {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        Deserializable::deserialize(value, name, diagnostics).map(StringSet)
-    }
-}
diff --git a/crates/biome_deserialize/src/validator.rs b/crates/biome_deserialize/src/validator.rs
new file mode 100644
index 000000000000..ec24b1896b74
--- /dev/null
+++ b/crates/biome_deserialize/src/validator.rs
@@ -0,0 +1,57 @@
+use crate::DeserializationDiagnostic;
+use biome_console::markup;
+use biome_rowan::TextRange;
+
+/// Trait that should be implemented on types that use the
+/// `#[deserializable(with_validator)]` annotation with the
+/// [biome_deserialize_macros::Deserializable] derive macro.
+pub trait DeserializableValidator {
+    /// Validates the deserialized instance.
+    ///
+    /// May generate any kind of diagnostics.
+    ///
+    /// Returns `true` if the instance passes validation and `false` when it
+    /// should be rejected.
+    fn validate(
+        &self,
+        name: &str,
+        range: TextRange,
+        diagnostics: &mut Vec,
+    ) -> bool;
+}
+
+/// Validates whether the given value is non-empty.
+pub fn non_empty(
+    value: &T,
+    name: &str,
+    range: TextRange,
+    diagnostics: &mut Vec,
+) -> bool {
+    if value.is_empty() {
+        diagnostics.push(
+            DeserializationDiagnostic::new(markup! {
+                {name}" may not be empty"
+            })
+            .with_range(range),
+        );
+        false
+    } else {
+        true
+    }
+}
+
+pub trait IsEmpty {
+    fn is_empty(&self) -> bool;
+}
+
+impl IsEmpty for String {
+    fn is_empty(&self) -> bool {
+        String::is_empty(self)
+    }
+}
+
+impl IsEmpty for Vec {
+    fn is_empty(&self) -> bool {
+        Vec::is_empty(self)
+    }
+}
diff --git a/crates/biome_deserialize_macros/Cargo.toml b/crates/biome_deserialize_macros/Cargo.toml
index f34bd19e3841..44f7643665a2 100644
--- a/crates/biome_deserialize_macros/Cargo.toml
+++ b/crates/biome_deserialize_macros/Cargo.toml
@@ -8,12 +8,13 @@ keywords.workspace   = true
 license.workspace    = true
 name                 = "biome_deserialize_macros"
 repository.workspace = true
-version              = "0.3.1"
+version              = "0.4.0"
 
 [lib]
 proc-macro = true
 
 [dependencies]
+convert_case     = { workspace = true }
 proc-macro-error = { version = "1.0.4", default-features = false }
 proc-macro2      = "1.0.63"
 quote            = "1.0.14"
diff --git a/crates/biome_deserialize_macros/src/deserializable_derive.rs b/crates/biome_deserialize_macros/src/deserializable_derive.rs
new file mode 100644
index 000000000000..3f7b8674c9a1
--- /dev/null
+++ b/crates/biome_deserialize_macros/src/deserializable_derive.rs
@@ -0,0 +1,469 @@
+mod enum_variant_attrs;
+mod struct_attrs;
+mod struct_field_attrs;
+
+use self::struct_attrs::StructAttrs;
+use self::struct_field_attrs::DeprecatedField;
+use crate::deserializable_derive::enum_variant_attrs::EnumVariantAttrs;
+use crate::deserializable_derive::struct_field_attrs::StructFieldAttrs;
+use convert_case::{Case, Casing};
+use proc_macro2::{Ident, Span, TokenStream};
+use proc_macro_error::*;
+use quote::quote;
+use syn::{Data, GenericParam, Generics, Path, PathSegment, Type};
+
+pub(crate) struct DeriveInput {
+    pub ident: Ident,
+    pub generics: Generics,
+    pub data: DeserializableData,
+}
+
+impl DeriveInput {
+    pub fn parse(input: syn::DeriveInput) -> Self {
+        let data = match input.data {
+            Data::Enum(data) => {
+                let data = data
+                    .variants
+                    .into_iter()
+                    .filter(|variant| {
+                        if variant.fields.is_empty() {
+                            true
+                        } else {
+                            abort!(
+                                variant.fields,
+                                "Deserializable derive cannot handle enum variants with fields -- you may need a custom Deserializable implementation"
+                            )
+                        }
+                    })
+                    .map(|variant| {
+                        let attrs = EnumVariantAttrs::from_attrs(&variant.attrs);
+
+                        let ident = variant.ident;
+                        let key = attrs
+                            .rename
+                            .unwrap_or_else(|| ident.to_string().to_case(Case::Camel));
+
+                        DeserializableVariantData { ident, key }
+                    })
+                    .collect();
+                DeserializableData::Enum(data)
+            }
+            Data::Struct(data) => {
+                if data.fields.iter().all(|field| field.ident.is_some()) {
+                    let attrs = StructAttrs::from_attrs(&input.attrs);
+
+                    let fields = data
+                        .fields
+                        .clone()
+                        .into_iter()
+                        .filter_map(|field| field.ident.map(|ident| (ident, field.attrs, field.ty)))
+                        .map(|(ident, attrs, ty)| {
+                            let attrs = StructFieldAttrs::from_attrs(&attrs);
+                            let key = attrs
+                                .rename
+                                .unwrap_or_else(|| ident.to_string().to_case(Case::Camel));
+
+                            DeserializableFieldData {
+                                bail_on_error: attrs.bail_on_error,
+                                deprecated: attrs.deprecated,
+                                ident,
+                                key,
+                                passthrough_name: attrs.passthrough_name,
+                                required: attrs.required,
+                                ty,
+                                validate: attrs.validate,
+                            }
+                        })
+                        .collect();
+
+                    DeserializableData::Struct(DeserializableStructData {
+                        fields,
+                        from_none: attrs.from_none,
+                        with_validator: attrs.with_validator,
+                    })
+                } else if data.fields.len() == 1 {
+                    let attrs = StructAttrs::from_attrs(&input.attrs);
+
+                    DeserializableData::Newtype(DeserializableNewtypeData {
+                        with_validator: attrs.with_validator,
+                    })
+                } else {
+                    abort!(
+                        data.fields,
+                        "Deserializable derive requires structs to have named fields or a single unnamed one -- you may need a custom Deserializable implementation"
+                    )
+                }
+            }
+            _ => abort!(
+                input,
+                "Deserializable can only be derived for enums and structs"
+            ),
+        };
+
+        Self {
+            ident: input.ident,
+            generics: input.generics,
+            data,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub enum DeserializableData {
+    Enum(Vec),
+    Newtype(DeserializableNewtypeData),
+    Struct(DeserializableStructData),
+}
+
+#[derive(Debug)]
+pub struct DeserializableNewtypeData {
+    with_validator: bool,
+}
+
+#[derive(Debug)]
+pub struct DeserializableStructData {
+    fields: Vec,
+    from_none: bool,
+    with_validator: bool,
+}
+
+#[derive(Clone, Debug)]
+pub struct DeserializableFieldData {
+    bail_on_error: bool,
+    deprecated: Option,
+    ident: Ident,
+    key: String,
+    passthrough_name: bool,
+    required: bool,
+    ty: Type,
+    validate: Option,
+}
+
+#[derive(Debug)]
+pub struct DeserializableVariantData {
+    ident: Ident,
+    key: String,
+}
+
+pub(crate) fn generate_deserializable(input: DeriveInput) -> TokenStream {
+    match input.data {
+        DeserializableData::Enum(variants) => {
+            generate_deserializable_enum(input.ident, input.generics, variants)
+        }
+        DeserializableData::Newtype(data) => {
+            generate_deserializable_newtype(input.ident, input.generics, data)
+        }
+        DeserializableData::Struct(data) => {
+            generate_deserializable_struct(input.ident, input.generics, data)
+        }
+    }
+}
+
+fn generate_deserializable_enum(
+    ident: Ident,
+    generics: Generics,
+    variants: Vec,
+) -> TokenStream {
+    let allowed_variants: Vec<_> = variants
+        .iter()
+        .map(|DeserializableVariantData { key, .. }| quote! { #key })
+        .collect();
+
+    let deserialize_variants: Vec<_> = variants
+        .iter()
+        .map(
+            |DeserializableVariantData {
+                 ident: variant_ident,
+                 key,
+             }| {
+                quote! { #key => Some(Self::#variant_ident) }
+            },
+        )
+        .collect();
+
+    let trait_bounds = generate_trait_bounds(&generics);
+
+    quote! {
+        impl #generics biome_deserialize::Deserializable for #ident #generics #trait_bounds{
+            fn deserialize(
+                value: &impl biome_deserialize::DeserializableValue,
+                name: &str,
+                diagnostics: &mut Vec,
+            ) -> Option {
+                match biome_deserialize::Text::deserialize(value, name, diagnostics)?.text() {
+                    #(#deserialize_variants),*,
+                    unknown_variant => {
+                        const ALLOWED_VARIANTS: &[&str] = &[#(#allowed_variants),*];
+                        diagnostics.push(biome_deserialize::DeserializationDiagnostic::new_unknown_value(
+                            unknown_variant,
+                            value.range(),
+                            ALLOWED_VARIANTS,
+                        ));
+                        None
+                    }
+                }
+            }
+        }
+    }
+}
+
+fn generate_deserializable_newtype(
+    ident: Ident,
+    generics: Generics,
+    data: DeserializableNewtypeData,
+) -> TokenStream {
+    let validator = if data.with_validator {
+        quote! {
+            if !biome_deserialize::DeserializableValidator::validate(&result, name, value.range(), diagnostics) {
+                return None;
+            }
+        }
+    } else {
+        quote! {}
+    };
+
+    let trait_bounds = generate_trait_bounds(&generics);
+
+    quote! {
+        impl #generics biome_deserialize::Deserializable for #ident #generics #trait_bounds {
+            fn deserialize(
+                value: &impl biome_deserialize::DeserializableValue,
+                name: &str,
+                diagnostics: &mut Vec,
+            ) -> Option {
+                let result = biome_deserialize::Deserializable::deserialize(value, name, diagnostics).map(Self)?;
+                #validator
+                Some(result)
+            }
+        }
+    }
+}
+
+fn generate_deserializable_struct(
+    ident: Ident,
+    generics: Generics,
+    data: DeserializableStructData,
+) -> TokenStream {
+    let allowed_keys: Vec<_> = data
+        .fields
+        .iter()
+        // It's not helpful to report deprecated keys as valid alternative.
+        .filter(|data| data.deprecated.is_none())
+        .map(|DeserializableFieldData { key, .. }| quote! { #key })
+        .collect();
+
+    let required_fields: Vec<_> = data
+        .fields
+        .iter()
+        .filter(|data| data.required)
+        .cloned()
+        .collect();
+
+    let deserialize_fields: Vec<_> = data
+        .fields
+        .into_iter()
+        .map(|field_data| {
+            let DeserializableFieldData { ident: field_ident, key, ty, .. } = field_data;
+
+            let is_optional = matches!(
+                &ty,
+                Type::Path(path) if path
+                    .path
+                    .segments
+                    .last()
+                    .is_some_and(|segment| segment.ident == "Option")
+            );
+
+            let deprecation_notice = field_data.deprecated.map(|deprecated| {
+                match deprecated {
+                    DeprecatedField::Message(message) => quote! {
+                        diagnostics.push(DeserializationDiagnostic::new_deprecated(
+                            key_text.text(),
+                            value.range()
+                        ).with_note(#message));
+                    },
+                    DeprecatedField::UseInstead(path) => quote! {
+                        diagnostics.push(DeserializationDiagnostic::new_deprecated_use_instead(
+                            &key_text,
+                            key.range(),
+                            #path,
+                        ));
+                    },
+                }
+            });
+
+            let name = match field_data.passthrough_name {
+                true => quote! { name },
+                false => quote! { &key_text }
+            };
+
+            let validate = field_data.validate.map(|validate| {
+                let path = Path {
+                    leading_colon: None,
+                    segments: validate
+                        .split("::")
+                        .map(|segment| {
+                            PathSegment::from(Ident::new(segment, Span::call_site()))
+                        })
+                        .collect(),
+                };
+
+                quote! {
+                    .filter(|v| #path(v, #key, value.range(), diagnostics))
+                }
+            });
+
+            if is_optional {
+                let error_result = if field_data.bail_on_error || field_data.required {
+                    quote! { return None }
+                } else {
+                    quote! { None }
+                };
+
+                quote! {
+                    #key => {
+                        result.#field_ident = match Deserializable::deserialize(&value, #name, diagnostics)#validate {
+                            Some(value) => {
+                                #deprecation_notice
+                                Some(value)
+                            }
+                            None => #error_result,
+                        };
+                    }
+                }
+            } else {
+                let error_result = if field_data.bail_on_error || field_data.required {
+                    quote! { return None, }
+                } else {
+                    quote! { {} }
+                };
+
+                quote! {
+                    #key => {
+                        match Deserializable::deserialize(&value, #name, diagnostics)#validate {
+                            Some(value) => {
+                                #deprecation_notice
+                                result.#field_ident = value;
+                            }
+                            None => #error_result
+                        }
+                    }
+                }
+            }
+        })
+        .collect();
+
+    let trait_bounds = generate_trait_bounds(&generics);
+
+    let validator = if required_fields.is_empty() {
+        quote! {}
+    } else {
+        let required_keys: Vec<_> = required_fields
+            .iter()
+            .map(|field_data| &field_data.key)
+            .collect();
+        let required_fields = required_fields.iter().map(|field_data| {
+            let DeserializableFieldData {
+                ident: field_ident,
+                key,
+                ty,
+                ..
+            } = field_data;
+            quote! {
+                if result.#field_ident == #ty::default() {
+                    diagnostics.push(DeserializationDiagnostic::new_missing_key(
+                        #key,
+                        range,
+                        REQUIRED_KEYS,
+                    ))
+                }
+            }
+        });
+        quote! {
+            const REQUIRED_KEYS: &[&str] = &[#(#required_keys),*];
+            #(#required_fields)*
+        }
+    };
+    let validator = if data.with_validator {
+        quote! {
+            #validator
+            if !biome_deserialize::DeserializableValidator::validate(&result, name, range, diagnostics) {
+                return None;
+            }
+        }
+    } else {
+        validator
+    };
+
+    let visitor_ident = Ident::new(&format!("{ident}Visitor"), Span::call_site());
+    let result_init = if data.from_none {
+        quote! { biome_deserialize::NoneState::none() }
+    } else {
+        quote! { Self::Output::default() }
+    };
+
+    quote! {
+        impl #generics biome_deserialize::Deserializable for #ident #generics #trait_bounds {
+            fn deserialize(
+                value: &impl biome_deserialize::DeserializableValue,
+                name: &str,
+                diagnostics: &mut Vec,
+            ) -> Option {
+                value.deserialize(#visitor_ident, name, diagnostics)
+            }
+        }
+
+        struct #visitor_ident #generics;
+        impl #generics biome_deserialize::DeserializationVisitor for #visitor_ident #generics {
+            type Output = #ident;
+
+            const EXPECTED_TYPE: biome_deserialize::VisitableType = biome_deserialize::VisitableType::MAP;
+
+            fn visit_map(
+                self,
+                members: impl Iterator>,
+                range: biome_deserialize::TextRange,
+                name: &str,
+                diagnostics: &mut Vec,
+            ) -> Option {
+                use biome_deserialize::{Deserializable, DeserializationDiagnostic, Text};
+                let mut result: Self::Output = #result_init;
+                for (key, value) in members.flatten() {
+                    let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
+                        continue;
+                    };
+                    match key_text.text() {
+                        #(#deserialize_fields)*
+                        unknown_key => {
+                            const ALLOWED_KEYS: &[&str] = &[#(#allowed_keys),*];
+                            diagnostics.push(DeserializationDiagnostic::new_unknown_key(
+                                unknown_key,
+                                key.range(),
+                                ALLOWED_KEYS,
+                            ))
+                        }
+                    }
+                }
+                #validator
+                Some(result)
+            }
+        }
+    }
+}
+
+fn generate_trait_bounds(generics: &Generics) -> TokenStream {
+    if generics.params.is_empty() {
+        quote! {}
+    } else {
+        let params = generics.params.iter().map(|param| match param {
+            GenericParam::Type(ty) => {
+                let ident = &ty.ident;
+                quote! { #ident: biome_deserialize::Deserializable }
+            }
+            _ => abort!(generics, "Unsupported generic parameter"),
+        });
+        quote! {
+            where #(#params),*
+        }
+    }
+}
diff --git a/crates/biome_deserialize_macros/src/deserializable_derive/enum_variant_attrs.rs b/crates/biome_deserialize_macros/src/deserializable_derive/enum_variant_attrs.rs
new file mode 100644
index 000000000000..0662e640d44d
--- /dev/null
+++ b/crates/biome_deserialize_macros/src/deserializable_derive/enum_variant_attrs.rs
@@ -0,0 +1,73 @@
+use proc_macro2::Ident;
+use quote::ToTokens;
+use syn::ext::IdentExt;
+use syn::parse::{Parse, ParseStream, Result};
+use syn::{parenthesized, Attribute, Error, LitStr, Token};
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct EnumVariantAttrs {
+    /// Optional name to use in the serialized format.
+    ///
+    /// See also: 
+    pub rename: Option,
+}
+
+impl EnumVariantAttrs {
+    pub fn from_attrs(attrs: &[Attribute]) -> Self {
+        let mut opts = Self::default();
+        for attr in attrs {
+            if attr.path.is_ident("deserializable") || attr.path.is_ident("serde") {
+                opts.merge_with(
+                    syn::parse2::(attr.tokens.clone())
+                        .expect("Could not parse variant attributes"),
+                );
+            }
+        }
+        opts
+    }
+
+    fn merge_with(&mut self, other: Self) {
+        if other.rename.is_some() {
+            self.rename = other.rename;
+        }
+    }
+}
+
+impl Parse for EnumVariantAttrs {
+    fn parse(input: ParseStream) -> Result {
+        let content;
+        parenthesized!(content in input);
+
+        let parse_value = || -> Result {
+            content.parse::()?;
+            Ok(content
+                .parse::()?
+                .to_token_stream()
+                .to_string()
+                .trim_matches('"')
+                .to_owned())
+        };
+
+        let mut result = Self::default();
+        loop {
+            let key: Ident = content.call(IdentExt::parse_any)?;
+            match key.to_string().as_ref() {
+                "rename" => result.rename = Some(parse_value()?),
+                other => {
+                    return Err(Error::new(
+                        content.span(),
+                        format!("Unexpected variant attribute: {other}"),
+                    ))
+                }
+            }
+
+            if content.is_empty() {
+                break;
+            }
+
+            content.parse::()?;
+        }
+
+        Ok(result)
+    }
+}
diff --git a/crates/biome_deserialize_macros/src/deserializable_derive/struct_attrs.rs b/crates/biome_deserialize_macros/src/deserializable_derive/struct_attrs.rs
new file mode 100644
index 000000000000..3b86ba6b169d
--- /dev/null
+++ b/crates/biome_deserialize_macros/src/deserializable_derive/struct_attrs.rs
@@ -0,0 +1,64 @@
+use proc_macro2::Ident;
+use syn::ext::IdentExt;
+use syn::parse::{Parse, ParseStream, Result};
+use syn::{parenthesized, Attribute, Error, Token};
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct StructAttrs {
+    pub from_none: bool,
+    pub with_validator: bool,
+}
+
+impl StructAttrs {
+    pub fn from_attrs(attrs: &[Attribute]) -> Self {
+        let mut opts = Self::default();
+        for attr in attrs {
+            if attr.path.is_ident("deserializable") {
+                opts.merge_with(
+                    syn::parse2::(attr.tokens.clone())
+                        .expect("Could not parse field attributes"),
+                );
+            }
+        }
+        opts
+    }
+
+    fn merge_with(&mut self, other: Self) {
+        if other.from_none {
+            self.from_none = other.from_none;
+        }
+        if other.with_validator {
+            self.with_validator = other.with_validator;
+        }
+    }
+}
+
+impl Parse for StructAttrs {
+    fn parse(input: ParseStream) -> Result {
+        let content;
+        parenthesized!(content in input);
+
+        let mut result = Self::default();
+        loop {
+            let key: Ident = content.call(IdentExt::parse_any)?;
+            match key.to_string().as_ref() {
+                "from_none" => result.from_none = true,
+                "with_validator" => result.with_validator = true,
+                other => {
+                    return Err(Error::new(
+                        content.span(),
+                        format!("Unexpected field attribute: {other}"),
+                    ))
+                }
+            }
+
+            if content.is_empty() {
+                break;
+            }
+
+            content.parse::()?;
+        }
+
+        Ok(result)
+    }
+}
diff --git a/crates/biome_deserialize_macros/src/deserializable_derive/struct_field_attrs.rs b/crates/biome_deserialize_macros/src/deserializable_derive/struct_field_attrs.rs
new file mode 100644
index 000000000000..74968e89652c
--- /dev/null
+++ b/crates/biome_deserialize_macros/src/deserializable_derive/struct_field_attrs.rs
@@ -0,0 +1,221 @@
+use proc_macro2::Ident;
+use quote::ToTokens;
+use syn::ext::IdentExt;
+use syn::parse::{Parse, ParseStream, Result};
+use syn::{parenthesized, Attribute, Error, LitStr, Token};
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct StructFieldAttrs {
+    /// If `true`, bails on deserializing the entire struct if validation for
+    /// this field fails.
+    ///
+    /// Note the struct may still be deserialized if the field is not present in
+    /// the serialized representation at all. In that case `Default::default()`
+    /// will be filled in.
+    pub bail_on_error: bool,
+
+    /// If set, this provides information about the deprecated of the field.
+    pub deprecated: Option,
+
+    /// If set, the name passed to the deserializer (which was passed by the
+    /// deserializer of the parent object) is also passed through to the
+    /// deserializer of the field value.
+    pub passthrough_name: bool,
+
+    /// Optional name to use in the serialized format.
+    ///
+    /// See also: 
+    pub rename: Option,
+
+    /// If `true`, presence of this field is required for successful
+    /// deserialization of the struct.
+    ///
+    /// Implies `bail_on_error`.
+    pub required: bool,
+
+    /// Optional validation function to be called on the field value.
+    pub validate: Option,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum DeprecatedField {
+    /// A generic message that explains what to do or why the field is deprecated.
+    Message(String),
+
+    /// Provides the path for a new field to use instead.
+    UseInstead(String),
+}
+
+impl StructFieldAttrs {
+    pub fn from_attrs(attrs: &[Attribute]) -> Self {
+        let mut opts = Self::default();
+        for attr in attrs {
+            if attr.path.is_ident("deserializable") {
+                opts.merge_with(
+                    syn::parse2::(attr.tokens.clone())
+                        .expect("Could not parse field attributes"),
+                );
+            } else if attr.path.is_ident("serde") {
+                opts.merge_with_serde(
+                    syn::parse2::(attr.tokens.clone())
+                        .expect("Could not parse Serde field attributes"),
+                );
+            }
+        }
+        opts
+    }
+
+    fn merge_with(&mut self, other: Self) {
+        if other.bail_on_error {
+            self.bail_on_error = other.bail_on_error;
+        }
+        if other.deprecated.is_some() {
+            self.deprecated = other.deprecated;
+        }
+        if other.passthrough_name {
+            self.passthrough_name = other.passthrough_name;
+        }
+        if other.rename.is_some() {
+            self.rename = other.rename;
+        }
+        if other.required {
+            self.required = other.required;
+        }
+        if other.validate.is_some() {
+            self.validate = other.validate;
+        }
+    }
+
+    fn merge_with_serde(&mut self, other: SerdeStructFieldAttrs) {
+        if other.rename.is_some() {
+            self.rename = other.rename;
+        }
+    }
+}
+
+impl Parse for StructFieldAttrs {
+    fn parse(input: ParseStream) -> Result {
+        let content;
+        parenthesized!(content in input);
+
+        let parse_value = || -> Result {
+            content.parse::()?;
+            Ok(content
+                .parse::()?
+                .to_token_stream()
+                .to_string()
+                .trim_matches('"')
+                .to_owned())
+        };
+
+        let mut result = Self::default();
+        loop {
+            let key: Ident = content.call(IdentExt::parse_any)?;
+            match key.to_string().as_ref() {
+                "bail_on_error" => result.bail_on_error = true,
+                "deprecated" => {
+                    result.deprecated = Some(content.parse::()?);
+                }
+                "passthrough_name" => result.passthrough_name = true,
+                "rename" => result.rename = Some(parse_value()?),
+                "required" => result.required = true,
+                "validate" => result.validate = Some(parse_value()?),
+                other => {
+                    return Err(Error::new(
+                        content.span(),
+                        format!("Unexpected field attribute: {other}"),
+                    ))
+                }
+            }
+
+            if content.is_empty() {
+                break;
+            }
+
+            content.parse::()?;
+        }
+
+        Ok(result)
+    }
+}
+
+impl Parse for DeprecatedField {
+    fn parse(input: ParseStream) -> Result {
+        let content;
+        parenthesized!(content in input);
+
+        let parse_value = || -> Result {
+            content.parse::()?;
+            Ok(content
+                .parse::()?
+                .to_token_stream()
+                .to_string()
+                .trim_matches('"')
+                .to_owned())
+        };
+
+        let key: Ident = content.call(IdentExt::parse_any)?;
+        let result = match key.to_string().as_ref() {
+            "message" => Self::Message(parse_value()?),
+            "use_instead" => Self::UseInstead(parse_value()?),
+            other => {
+                return Err(Error::new(
+                    content.span(),
+                    format!("Unexpected field attribute inside deprecated(): {other}"),
+                ))
+            }
+        };
+
+        if !content.is_empty() {
+            return Err(Error::new(
+                content.span(),
+                "Only one attribute expected inside deprecated()",
+            ));
+        }
+
+        Ok(result)
+    }
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+struct SerdeStructFieldAttrs {
+    rename: Option,
+}
+
+impl Parse for SerdeStructFieldAttrs {
+    fn parse(input: ParseStream) -> Result {
+        let content;
+        parenthesized!(content in input);
+
+        let parse_value = || -> Result {
+            content.parse::()?;
+            Ok(content
+                .parse::()?
+                .to_token_stream()
+                .to_string()
+                .trim_matches('"')
+                .to_owned())
+        };
+
+        let mut result = Self::default();
+        loop {
+            let key: Ident = content.call(IdentExt::parse_any)?;
+            match key.to_string().as_ref() {
+                "rename" => result.rename = Some(parse_value()?),
+                _ => {
+                    // Don't fail on unrecognized Serde attrs,
+                    // but consume values to advance the parser.
+                    let _result = parse_value();
+                }
+            }
+
+            if content.is_empty() {
+                break;
+            }
+
+            content.parse::()?;
+        }
+
+        Ok(result)
+    }
+}
diff --git a/crates/biome_deserialize_macros/src/lib.rs b/crates/biome_deserialize_macros/src/lib.rs
index 0a12c0123fa6..433aa2e78109 100644
--- a/crates/biome_deserialize_macros/src/lib.rs
+++ b/crates/biome_deserialize_macros/src/lib.rs
@@ -1,3 +1,4 @@
+mod deserializable_derive;
 mod merge_derive;
 mod none_state_derive;
 
@@ -5,6 +6,244 @@ use proc_macro::TokenStream;
 use proc_macro_error::*;
 use syn::{parse_macro_input, DeriveInput};
 
+/// Derives the [biome_deserialize::Deserializable] trait for a custom enum or
+/// struct.
+///
+/// It supports implementing the trait for the following data structures:
+/// - Structs with named fields where every field is of type `T` or `Option`
+///   and where `T` also implements [biome_deserialize::Deserializable]. The
+///   struct must also implement `Default`.
+/// - Structs with a single unnamed field (a so-called
+///   [newtype](https://doc.rust-lang.org/rust-by-example/generics/new_types.html)).
+/// - Enums, so long as the variants don't also have fields.
+///
+/// ## Examples
+///
+/// ```no_test
+/// #[derive(Default, Deserializable)]
+/// struct StructWithNamedFields {
+///     foo: String,
+///     bar: Option,
+/// }
+///
+/// #[derive(Deserializable)]
+/// struct NewType(u16);
+///
+/// #[derive(Deserializable)]
+/// enum EnumWithPlainVariants {
+///     Variant1,
+///     Variant2
+/// }
+/// ```
+///
+/// ## Struct attributes
+///
+/// When [Deserializable] is derived on a struct, its behavior may be adjusted
+/// through attributes.
+///
+/// ### `with_validator`
+///
+/// When the `with_validator` attribute is present, the deserializable type is
+/// expected to implement the `DeserializableValidator` trait. The generated
+/// deserializer will call its `validate()` method and reject the instance if
+/// it returns `false`.
+///
+/// ```no_test
+/// #[derive(Default, Deserializable)]
+/// #[deserializable(with_validator)]
+/// struct ValidatedStruct {
+///     // In real code, please use the `validate` attribute instead (see below)!
+///     non_empty: String,
+/// }
+///
+/// impl DeserializableValidator for ValidatedStruct {
+///     fn fn validate(
+///         &self,
+///         name: &str,
+///         range: biome_rowan::TextRange,
+///         diagnostics: &mut Vec,
+///     ) -> bool {
+///         if self.non_empty.is_empty() {
+///             diagnostics.push(
+///                 DeserializationDiagnostic::new(markup! {
+///                     "foo"" may not be empty"
+///                 })
+///                 .with_range(range),
+///             );
+///             false
+///         } else {
+///             true
+///         }
+///     }
+/// }
+/// ```
+///
+/// ## Struct field attributes
+///
+/// A struct's fields may also be adjusted through attributes.
+///
+/// ### `bail_on_error`
+///
+/// If present, bails on deserializing the entire struct if validation for this
+/// this field fails.
+///
+/// Note the struct may still be deserialized if the field is not present in the
+/// serialized representation at all. In that case `Default::default()` will be
+/// filled in. If this is not what you want, you probably want to use the
+/// `required` attribute instead.
+///
+/// ```no_test
+/// #[derive(Default, Deserializable)]
+/// pub struct ReactHook {
+///     #[deserializable(bail_on_error)]
+///     pub name: String,
+/// }
+/// ```
+///
+/// ### `deprecated`
+///
+/// Used to generate diagnostics when a deprecated field is deserialized. Note
+/// this does not alter the behavior of the deserializer, so you still need to
+/// account for the field's value after deserialization.
+///
+/// ```no_test
+/// #[derive(Default, Deserializable)]
+/// pub struct ReactHook {
+///     #[deserializable(deprecated(use_instead = "name"))]
+///     pub old_name: String,
+///
+///     #[deserializable(deprecated(message = "nick names are not used anymore"))]
+///     pub nick_name: String,
+///
+///     pub name: String,
+/// }
+/// ```
+///
+/// ### `passthrough_name`
+///
+/// A particularly noteworthy attribute is `passthrough_name`. Every
+/// deserializer receives a `name` argument which is used to add context to
+/// diagnostics reported when deserializing the value. Typically, when the value
+/// of an object property is deserialized, the `name` refers to the name of the
+/// property. In some cases however, you may want to pass through the name given
+/// to the object's deserializer instead, in order to prevent losing more
+/// "distant" context.
+///
+/// An example is the `RuleWithOptions` struct, where the name of the rule is
+/// passed through to the deserializer for `PossibleOptions`. Without this
+/// attribute, the name given to the field's deserializer would always be
+/// "options".
+///
+/// ```no_test
+/// #[derive(Default, Deserializable)]
+/// pub struct RuleWithOptions {
+///     pub level: RulePlainConfiguration,
+///
+///     #[deserializable(passthrough_name)]
+///     pub options: Option,
+/// }
+/// ```
+///
+/// ### `rename`
+///
+/// ```no_test
+/// #[derive(Default, Deserializable)]
+/// struct StructWithFields {
+///     // Use "$schema" as serialized property, instead of "schema". By
+///     // default, field names use "camelCase".
+///     #[deserializable(rename = "$schema")]
+///     schema: Option,
+/// }
+/// ```
+///
+/// For structs that also implement Serde's `Serialize` or `Deserialize`, it
+/// automatically picks up on Serde's `rename` attribute:
+///
+/// ```no_test
+/// #[derive(Default, Deserialize, Deserializable, Serialize)]
+/// struct StructWithFields {
+///     // This also works:
+///     #[serde(rename = "$schema")]
+///     schema: Option,
+/// }
+/// ```
+///
+/// ### `required`
+///
+/// If present, presence of this field is required for successful
+/// deserialization of the struct.
+///
+/// Note this does not check whether the value is meaningful. For instance, an
+/// empty string would still be accepted. For such use case, you may also want
+/// to use the `validate` attribute.
+///
+/// Implies `bail_on_error`.
+///
+/// ```no_test
+/// #[derive(Default, Deserializable)]
+/// pub struct StructWithRequiredName {
+///     #[deserializable(required)]
+///     pub name: String,
+/// }
+/// ```
+///
+/// ### `validate`
+///
+/// Like the `with_validator` annotation, this allows the invocation of a
+/// validation function that can emit diagnostics and reject instances. But
+/// unlike `with_validator`, this attribute takes an argument that allows you
+/// to specify the validation function.
+///
+/// Note the validator is not invoked if the field is not present in the
+/// serialized representation at all. To cover this, you may also want to use
+/// the `required` attribute.
+///
+/// ```no_test
+/// #[derive(Default, Deserializable)]
+/// struct ValidatedStruct {
+///     #[deserializable(required, validate = "biome_deserialize::non_empty")]
+///     non_empty: String,
+/// }
+/// ```
+///
+/// ## Enum variant attributes
+///
+/// When [Deserializable] is derived on a enum, the deserialization of its
+/// variants may also be adjusted through attributes.
+///
+/// ### `rename`
+///
+/// ```no_test
+/// #[derive(Deserializable)]
+/// enum Cases {
+///     // Serialized representation defaults to "camelCase", which is what we
+///     // want here.
+///     CamelCase,
+///     // The following attribute provides a custom serialization:
+///     #[deserializable(rename = "kebab-case")]
+///     KebabCase,
+/// }
+/// ```
+///
+/// Using Serde's attributes is supported on enums too.
+#[proc_macro_derive(Deserializable, attributes(deserializable))]
+#[proc_macro_error]
+pub fn derive_deserializable(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+
+    let input = deserializable_derive::DeriveInput::parse(input);
+
+    let tokens = deserializable_derive::generate_deserializable(input);
+
+    if false {
+        panic!("{tokens}");
+    }
+
+    TokenStream::from(tokens)
+}
+
+/// Derives the [biome_deserialize::Merge] trait for a custom enum or
+/// struct.
 #[proc_macro_derive(Merge)]
 #[proc_macro_error]
 pub fn derive_mergeable(input: TokenStream) -> TokenStream {
diff --git a/crates/biome_formatter/src/lib.rs b/crates/biome_formatter/src/lib.rs
index 382069558d58..f7b3b8031b7c 100644
--- a/crates/biome_formatter/src/lib.rs
+++ b/crates/biome_formatter/src/lib.rs
@@ -56,8 +56,9 @@ use crate::printer::{Printer, PrinterOptions};
 use crate::trivia::{format_skipped_token_trivia, format_trimmed_token};
 pub use arguments::{Argument, Arguments};
 use biome_deserialize::{
-    Deserializable, DeserializableValue, DeserializationDiagnostic, Text, TextNumber,
+    Deserializable, DeserializableValue, DeserializationDiagnostic, TextNumber,
 };
+use biome_deserialize_macros::Deserializable;
 use biome_deserialize_macros::Merge;
 use biome_rowan::{
     Language, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxResult, SyntaxToken, SyntaxTriviaPiece,
@@ -126,7 +127,7 @@ impl std::fmt::Display for IndentStyle {
     }
 }
 
-#[derive(Clone, Copy, Debug, Eq, Hash, Merge, PartialEq)]
+#[derive(Clone, Copy, Debug, Deserializable, Eq, Hash, Merge, PartialEq)]
 #[cfg_attr(
     feature = "serde",
     derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema),
@@ -195,27 +196,6 @@ impl std::fmt::Display for LineEnding {
     }
 }
 
-impl Deserializable for LineEnding {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        let value_text = Text::deserialize(value, name, diagnostics)?;
-        if let Ok(value) = value_text.parse::() {
-            Some(value)
-        } else {
-            const ALLOWED_VARIANTS: &[&str] = &["lf", "crlf", "cr"];
-            diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                &value_text,
-                value.range(),
-                ALLOWED_VARIANTS,
-            ));
-            None
-        }
-    }
-}
-
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 #[cfg_attr(
     feature = "serde",
@@ -357,7 +337,7 @@ impl From for u16 {
     }
 }
 
-#[derive(Clone, Copy, Debug, Default, Eq, Hash, Merge, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, Deserializable, Eq, Hash, Merge, PartialEq)]
 #[cfg_attr(
     feature = "serde",
     derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema),
@@ -444,28 +424,6 @@ impl From for Quote {
     }
 }
 
-impl Deserializable for QuoteStyle {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        const ALLOWED_VARIANTS: &[&str] = &["double", "single"];
-        match Text::deserialize(value, name, diagnostics)?.text() {
-            "double" => Some(QuoteStyle::Double),
-            "single" => Some(QuoteStyle::Single),
-            unknown_variant => {
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_variant,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-
 /// Context object storing data relevant when formatting an object.
 pub trait FormatContext {
     type Options: FormatOptions;
@@ -1773,3 +1731,51 @@ pub struct FormatStateSnapshot {
     #[cfg(debug_assertions)]
     printed_tokens: printed_tokens::PrintedTokensSnapshot,
 }
+
+#[cfg(test)]
+mod tests {
+    use super::LineWidth;
+    use biome_deserialize::json::deserialize_from_json_str;
+    use biome_deserialize_macros::Deserializable;
+    use biome_diagnostics::Error;
+
+    #[test]
+    fn test_out_of_range_line_width() {
+        #[derive(Debug, Default, Deserializable, Eq, PartialEq)]
+        struct TestConfig {
+            line_width: LineWidth,
+        }
+
+        struct DiagnosticPrinter<'a> {
+            diagnostics: &'a [Error],
+        }
+
+        impl<'a> DiagnosticPrinter<'a> {
+            fn new(diagnostics: &'a [Error]) -> Self {
+                Self { diagnostics }
+            }
+        }
+
+        impl<'a> std::fmt::Display for DiagnosticPrinter<'a> {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                for diagnostic in self.diagnostics {
+                    diagnostic.description(f)?;
+                }
+                Ok(())
+            }
+        }
+
+        let source = r#"{ "lineWidth": 500 }"#;
+        let deserialized = deserialize_from_json_str::(source, Default::default(), "");
+        assert_eq!(
+            format!("{}", DiagnosticPrinter::new(deserialized.diagnostics())),
+            "The number should be an integer between 1 and 320."
+        );
+        assert_eq!(
+            deserialized.into_deserialized().unwrap(),
+            TestConfig {
+                line_width: LineWidth(80)
+            }
+        );
+    }
+}
diff --git a/crates/biome_js_analyze/Cargo.toml b/crates/biome_js_analyze/Cargo.toml
index 6b6812736955..e60e02d8cfc3 100644
--- a/crates/biome_js_analyze/Cargo.toml
+++ b/crates/biome_js_analyze/Cargo.toml
@@ -11,28 +11,29 @@ repository.workspace = true
 version              = "0.4.0"
 
 [dependencies]
-biome_analyze       = { workspace = true }
-biome_aria          = { workspace = true }
-biome_console       = { workspace = true }
-biome_control_flow  = { workspace = true }
-biome_deserialize   = { workspace = true }
-biome_diagnostics   = { workspace = true }
-biome_js_factory    = { workspace = true }
-biome_js_semantic   = { workspace = true }
-biome_js_syntax     = { workspace = true }
-biome_json_factory  = { workspace = true }
-biome_json_syntax   = { workspace = true }
-biome_rowan         = { workspace = true }
-biome_unicode_table = { workspace = true }
-lazy_static         = { workspace = true }
-log                 = "0.4.20"
-natord              = "1.0.9"
-roaring             = "0.10.1"
-rustc-hash          = { workspace = true }
-schemars            = { workspace = true, optional = true }
-serde               = { workspace = true, features = ["derive"] }
-serde_json          = { workspace = true }
-smallvec            = { workspace = true }
+biome_analyze            = { workspace = true }
+biome_aria               = { workspace = true }
+biome_console            = { workspace = true }
+biome_control_flow       = { workspace = true }
+biome_deserialize        = { workspace = true }
+biome_deserialize_macros = { workspace = true }
+biome_diagnostics        = { workspace = true }
+biome_js_factory         = { workspace = true }
+biome_js_semantic        = { workspace = true }
+biome_js_syntax          = { workspace = true }
+biome_json_factory       = { workspace = true }
+biome_json_syntax        = { workspace = true }
+biome_rowan              = { workspace = true }
+biome_unicode_table      = { workspace = true }
+lazy_static              = { workspace = true }
+log                      = "0.4.20"
+natord                   = "1.0.9"
+roaring                  = "0.10.1"
+rustc-hash               = { workspace = true }
+schemars                 = { workspace = true, optional = true }
+serde                    = { workspace = true, features = ["derive"] }
+serde_json               = { workspace = true }
+smallvec                 = { workspace = true }
 
 [dev-dependencies]
 biome_js_parser  = { path = "../biome_js_parser", features = ["tests"] }
diff --git a/crates/biome_js_analyze/src/analyzers/complexity/no_excessive_cognitive_complexity.rs b/crates/biome_js_analyze/src/analyzers/complexity/no_excessive_cognitive_complexity.rs
index b556c693d775..4a8c15233989 100644
--- a/crates/biome_js_analyze/src/analyzers/complexity/no_excessive_cognitive_complexity.rs
+++ b/crates/biome_js_analyze/src/analyzers/complexity/no_excessive_cognitive_complexity.rs
@@ -3,10 +3,7 @@ use biome_analyze::{
     RuleDiagnostic, RuleSource, ServiceBag, Visitor, VisitorContext,
 };
 use biome_console::markup;
-use biome_deserialize::{
-    Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor, Text,
-    VisitableType,
-};
+use biome_deserialize_macros::Deserializable;
 use biome_js_syntax::{
     AnyFunctionLike, JsBreakStatement, JsContinueStatement, JsElseClause, JsLanguage,
     JsLogicalExpression, JsLogicalOperator,
@@ -368,7 +365,7 @@ pub struct ComplexityScore {
 }
 
 /// Options for the rule `noExcessiveCognitiveComplexity`.
-#[derive(Deserialize, Serialize, Debug, Eq, PartialEq, Clone)]
+#[derive(Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schema", derive(JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct ComplexityOptions {
@@ -383,49 +380,3 @@ impl Default for ComplexityOptions {
         }
     }
 }
-
-impl Deserializable for ComplexityOptions {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        value.deserialize(ComplexityOptionsVisitor, name, diagnostics)
-    }
-}
-
-struct ComplexityOptionsVisitor;
-impl DeserializationVisitor for ComplexityOptionsVisitor {
-    type Output = ComplexityOptions;
-
-    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-
-    fn visit_map(
-        self,
-        members: impl Iterator>,
-        _range: TextRange,
-        _name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        const ALLOWED_KEYS: &[&str] = &["maxAllowedComplexity"];
-        let mut result = Self::Output::default();
-        for (key, value) in members.flatten() {
-            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                continue;
-            };
-            match key_text.text() {
-                "maxAllowedComplexity" => {
-                    if let Some(val) = Deserializable::deserialize(&value, &key_text, diagnostics) {
-                        result.max_allowed_complexity = val;
-                    }
-                }
-                text => diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                    text,
-                    key.range(),
-                    ALLOWED_KEYS,
-                )),
-            }
-        }
-        Some(result)
-    }
-}
diff --git a/crates/biome_js_analyze/src/analyzers/nursery/use_consistent_array_type.rs b/crates/biome_js_analyze/src/analyzers/nursery/use_consistent_array_type.rs
index 71f7c1dd7c21..7c231478503f 100644
--- a/crates/biome_js_analyze/src/analyzers/nursery/use_consistent_array_type.rs
+++ b/crates/biome_js_analyze/src/analyzers/nursery/use_consistent_array_type.rs
@@ -1,28 +1,21 @@
-use std::str::FromStr;
-
+use crate::JsRuleAction;
 use biome_analyze::{
     context::RuleContext, declare_rule, ActionCategory, Ast, FixKind, Rule, RuleDiagnostic,
     RuleSource,
 };
 use biome_console::{markup, Markup, MarkupBuf};
-use biome_deserialize::{
-    Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor, Text,
-    VisitableType,
-};
+use biome_deserialize_macros::Deserializable;
 use biome_diagnostics::Applicability;
 use biome_js_factory::make;
 use biome_js_syntax::{
     AnyTsName, AnyTsType, JsSyntaxKind, JsSyntaxToken, TriviaPieceKind, TsReferenceType,
     TsTypeArguments, T,
 };
-use biome_rowan::{
-    AstNode, AstSeparatedList, BatchMutationExt, SyntaxNodeOptionExt, TextRange, TriviaPiece,
-};
+use biome_rowan::{AstNode, AstSeparatedList, BatchMutationExt, SyntaxNodeOptionExt, TriviaPiece};
 #[cfg(feature = "schemars")]
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-
-use crate::JsRuleAction;
+use std::str::FromStr;
 
 declare_rule! {
     /// Require consistently using either `T[]` or `Array`
@@ -476,14 +469,14 @@ where
     )
 }
 
-#[derive(Deserialize, Serialize, Default, Debug, Clone, Eq, PartialEq)]
+#[derive(Clone, Debug, Default, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schemars", derive(JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct ConsistentArrayTypeOptions {
     syntax: ConsistentArrayType,
 }
 
-#[derive(Deserialize, Serialize, Debug, Default, Clone, Copy, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schemars", derive(JsonSchema))]
 #[serde(rename_all = "camelCase")]
 pub enum ConsistentArrayType {
@@ -494,53 +487,6 @@ pub enum ConsistentArrayType {
     Generic,
 }
 
-impl Deserializable for ConsistentArrayTypeOptions {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        struct Visitor;
-        impl DeserializationVisitor for Visitor {
-            type Output = ConsistentArrayTypeOptions;
-            const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-            fn visit_map(
-                self,
-                members: impl Iterator<
-                    Item = Option<(impl DeserializableValue, impl DeserializableValue)>,
-                >,
-                _range: TextRange,
-                _name: &str,
-                diagnostics: &mut Vec,
-            ) -> Option {
-                const ALLOWED_KEYS: &[&str] = &["syntax"];
-                let mut result = Self::Output::default();
-                for (key, value) in members.flatten() {
-                    let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                        continue;
-                    };
-                    match key_text.text() {
-                        "syntax" => {
-                            if let Some(val) =
-                                Deserializable::deserialize(&value, &key_text, diagnostics)
-                            {
-                                result.syntax = val;
-                            }
-                        }
-                        text => diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                            text,
-                            key.range(),
-                            ALLOWED_KEYS,
-                        )),
-                    }
-                }
-                Some(result)
-            }
-        }
-        value.deserialize(Visitor, name, diagnostics)
-    }
-}
-
 impl FromStr for ConsistentArrayType {
     type Err = &'static str;
 
@@ -552,24 +498,3 @@ impl FromStr for ConsistentArrayType {
         }
     }
 }
-
-impl Deserializable for ConsistentArrayType {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        const ALLOWED_VARIANTS: &[&str] = &["shorthand", "generic"];
-        let value_text = Text::deserialize(value, name, diagnostics)?;
-        if let Ok(value) = value_text.parse::() {
-            Some(value)
-        } else {
-            diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                value_text.text(),
-                value.range(),
-                ALLOWED_VARIANTS,
-            ));
-            None
-        }
-    }
-}
diff --git a/crates/biome_js_analyze/src/analyzers/nursery/use_filenaming_convention.rs b/crates/biome_js_analyze/src/analyzers/nursery/use_filenaming_convention.rs
index 3f32fed32ea8..3369e918c563 100644
--- a/crates/biome_js_analyze/src/analyzers/nursery/use_filenaming_convention.rs
+++ b/crates/biome_js_analyze/src/analyzers/nursery/use_filenaming_convention.rs
@@ -1,17 +1,15 @@
-use std::{hash::Hash, str::FromStr};
-
 use crate::{semantic_services::SemanticServices, utils::case::Case};
 use biome_analyze::{
     context::RuleContext, declare_rule, Rule, RuleDiagnostic, RuleSource, RuleSourceKind,
 };
 use biome_console::markup;
-use biome_deserialize::{
-    Deserializable, DeserializationDiagnostic, DeserializationVisitor, Text, VisitableType,
-};
+use biome_deserialize_macros::Deserializable;
 use biome_rowan::TextRange;
 use rustc_hash::FxHashSet;
 use serde::{Deserialize, Serialize};
+use std::{hash::Hash, str::FromStr};
 
+use biome_deserialize::{DeserializableValue, DeserializationDiagnostic};
 #[cfg(feature = "schemars")]
 use schemars::JsonSchema;
 use smallvec::SmallVec;
@@ -213,7 +211,7 @@ pub(crate) enum FileNamingConventionState {
 }
 
 /// Rule's options.
-#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schemars", derive(JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct FilenamingConventionOptions {
@@ -251,67 +249,7 @@ impl Default for FilenamingConventionOptions {
     }
 }
 
-impl Deserializable for FilenamingConventionOptions {
-    fn deserialize(
-        value: &impl biome_deserialize::DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        value.deserialize(FilenamingConventionOptionsVisitor, name, diagnostics)
-    }
-}
-
-struct FilenamingConventionOptionsVisitor;
-impl DeserializationVisitor for FilenamingConventionOptionsVisitor {
-    type Output = FilenamingConventionOptions;
-
-    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-
-    fn visit_map(
-        self,
-        members: impl Iterator<
-            Item = Option<(
-                impl biome_deserialize::DeserializableValue,
-                impl biome_deserialize::DeserializableValue,
-            )>,
-        >,
-        _range: biome_rowan::TextRange,
-        _name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        const ALLOWED_KEYS: &[&str] = &["strictCase", "filenameCases"];
-        let mut result = Self::Output::default();
-        for (key, value) in members.flatten() {
-            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                continue;
-            };
-            match key_text.text() {
-                "strictCase" => {
-                    if let Some(strict_case) =
-                        Deserializable::deserialize(&value, &key_text, diagnostics)
-                    {
-                        result.strict_case = strict_case;
-                    }
-                }
-                "filenameCases" => {
-                    if let Some(filename_cases) =
-                        Deserializable::deserialize(&value, &key_text, diagnostics)
-                    {
-                        result.filename_cases = filename_cases;
-                    }
-                }
-                unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                    unknown_key,
-                    key.range(),
-                    ALLOWED_KEYS,
-                )),
-            }
-        }
-        Some(result)
-    }
-}
-
-#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schemars", derive(JsonSchema))]
 pub struct FilenameCases(FxHashSet);
 
@@ -335,13 +273,14 @@ impl Default for FilenameCases {
     }
 }
 
-impl Deserializable for FilenameCases {
+impl biome_deserialize::Deserializable for FilenameCases {
     fn deserialize(
-        value: &impl biome_deserialize::DeserializableValue,
+        value: &impl DeserializableValue,
         name: &str,
         diagnostics: &mut Vec,
     ) -> Option {
-        let cases: FxHashSet<_> = Deserializable::deserialize(value, name, diagnostics)?;
+        let cases: FxHashSet<_> =
+            biome_deserialize::Deserializable::deserialize(value, name, diagnostics)?;
         if cases.is_empty() {
             diagnostics.push(
                 DeserializationDiagnostic::new(markup! {
@@ -356,7 +295,7 @@ impl Deserializable for FilenameCases {
 }
 
 /// Supported cases for TypeScript `enum` member names.
-#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug, Deserialize, Deserializable, Eq, Hash, PartialEq, Serialize)]
 #[cfg_attr(feature = "schemars", derive(JsonSchema))]
 pub enum FilenameCase {
     /// camelCase
@@ -405,26 +344,6 @@ impl FromStr for FilenameCase {
     }
 }
 
-impl Deserializable for FilenameCase {
-    fn deserialize(
-        value: &impl biome_deserialize::DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        let value_text = Text::deserialize(value, name, diagnostics)?;
-        if let Ok(value) = value_text.parse::() {
-            Some(value)
-        } else {
-            diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                value_text.text(),
-                value.range(),
-                Self::ALLOWED_VARIANTS,
-            ));
-            None
-        }
-    }
-}
-
 impl TryFrom for Case {
     type Error = &'static str;
 
diff --git a/crates/biome_js_analyze/src/aria_analyzers/a11y/use_valid_aria_role.rs b/crates/biome_js_analyze/src/aria_analyzers/a11y/use_valid_aria_role.rs
index be36c2a0dcae..98a2094aad5a 100644
--- a/crates/biome_js_analyze/src/aria_analyzers/a11y/use_valid_aria_role.rs
+++ b/crates/biome_js_analyze/src/aria_analyzers/a11y/use_valid_aria_role.rs
@@ -3,9 +3,7 @@ use biome_analyze::{
     context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, RuleSource,
 };
 use biome_console::markup;
-use biome_deserialize::{
-    Deserializable, DeserializationDiagnostic, DeserializationVisitor, Text, VisitableType,
-};
+use biome_deserialize_macros::Deserializable;
 use biome_diagnostics::Applicability;
 use biome_js_syntax::jsx_ext::AnyJsxElement;
 use biome_rowan::{AstNode, BatchMutationExt};
@@ -75,74 +73,14 @@ declare_rule! {
     }
 }
 
-#[derive(Default, Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
+#[derive(Clone, Debug, Default, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct ValidAriaRoleOptions {
-    allowed_invalid_roles: Vec,
+    allow_invalid_roles: Vec,
     ignore_non_dom: bool,
 }
 
-impl Deserializable for ValidAriaRoleOptions {
-    fn deserialize(
-        value: &impl biome_deserialize::DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        value.deserialize(ValidAriaRoleOptionsVisitor, name, diagnostics)
-    }
-}
-
-struct ValidAriaRoleOptionsVisitor;
-impl DeserializationVisitor for ValidAriaRoleOptionsVisitor {
-    type Output = ValidAriaRoleOptions;
-
-    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-
-    fn visit_map(
-        self,
-        members: impl Iterator<
-            Item = Option<(
-                impl biome_deserialize::DeserializableValue,
-                impl biome_deserialize::DeserializableValue,
-            )>,
-        >,
-        _range: biome_rowan::TextRange,
-        _name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        let mut result = Self::Output::default();
-        for (key, value) in members.flatten() {
-            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                continue;
-            };
-            match key_text.text() {
-                "allowInvalidRoles" => {
-                    if let Some(roles) = Deserializable::deserialize(&value, &key_text, diagnostics)
-                    {
-                        result.allowed_invalid_roles = roles;
-                    }
-                }
-                "ignoreNonDom" => {
-                    if let Some(value) = Deserializable::deserialize(&value, &key_text, diagnostics)
-                    {
-                        result.ignore_non_dom = value;
-                    }
-                }
-                unknown_key => {
-                    const ALLOWED_KEYS: &[&str] = &["allowInvalidRoles", "ignoreNonDom"];
-                    diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                        unknown_key,
-                        key.range(),
-                        ALLOWED_KEYS,
-                    ));
-                }
-            }
-        }
-        Some(result)
-    }
-}
-
 impl Rule for UseValidAriaRole {
     type Query = Aria;
     type State = ();
@@ -155,7 +93,7 @@ impl Rule for UseValidAriaRole {
         let aria_roles = ctx.aria_roles();
 
         let ignore_non_dom = options.ignore_non_dom;
-        let allowed_invalid_roles = &options.allowed_invalid_roles;
+        let allowed_invalid_roles = &options.allow_invalid_roles;
 
         if ignore_non_dom && node.is_custom_component() {
             return None;
diff --git a/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs b/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs
index f763c2f03828..11928f5c79db 100644
--- a/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs
+++ b/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs
@@ -3,10 +3,8 @@ use crate::semantic_services::Semantic;
 use biome_analyze::RuleSource;
 use biome_analyze::{context::RuleContext, declare_rule, Rule, RuleDiagnostic};
 use biome_console::markup;
-use biome_deserialize::{
-    Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor, Text,
-    VisitableType,
-};
+use biome_deserialize::non_empty;
+use biome_deserialize_macros::Deserializable;
 use biome_js_semantic::{Capture, SemanticModel};
 use biome_js_syntax::{
     binding_ext::AnyJsBindingDeclaration, JsCallExpression, JsSyntaxKind, JsSyntaxNode,
@@ -222,71 +220,21 @@ impl Default for ReactExtensiveDependenciesOptions {
 }
 
 /// Options for the rule `useExhaustiveDependencies`
-#[derive(Default, Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
+#[derive(Clone, Debug, Default, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schemars", derive(JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct HooksOptions {
     /// List of safe hooks
+    #[deserializable(validate = "non_empty")]
     pub hooks: Vec,
 }
 
-impl Deserializable for HooksOptions {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        value.deserialize(HooksOptionsVisitor, name, diagnostics)
-    }
-}
-
-struct HooksOptionsVisitor;
-impl DeserializationVisitor for HooksOptionsVisitor {
-    type Output = HooksOptions;
-
-    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-
-    fn visit_map(
-        self,
-        members: impl Iterator>,
-        _range: TextRange,
-        _name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        const ALLOWED_KEYS: &[&str] = &["hooks"];
-        let mut result = Self::Output::default();
-        for (key, value) in members.flatten() {
-            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                continue;
-            };
-            match key_text.text() {
-                "hooks" => {
-                    let val_range = value.range();
-                    result.hooks = Deserializable::deserialize(&value, &key_text, diagnostics)
-                        .unwrap_or_default();
-                    if result.hooks.is_empty() {
-                        diagnostics.push(
-                            DeserializationDiagnostic::new("At least one element is needed")
-                                .with_range(val_range),
-                        );
-                    }
-                }
-                text => diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                    text,
-                    key.range(),
-                    ALLOWED_KEYS,
-                )),
-            }
-        }
-        Some(result)
-    }
-}
-
-#[derive(Default, Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
+#[derive(Clone, Debug, Default, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schemars", derive(JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct Hooks {
     /// The name of the hook
+    #[deserializable(validate = "non_empty")]
     pub name: String,
     /// The "position" of the closure function, starting from zero.
     ///
@@ -296,68 +244,6 @@ pub struct Hooks {
     pub dependencies_index: Option,
 }
 
-impl Deserializable for Hooks {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        value.deserialize(HooksVisitor, name, diagnostics)
-    }
-}
-
-struct HooksVisitor;
-impl DeserializationVisitor for HooksVisitor {
-    type Output = Hooks;
-
-    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-
-    fn visit_map(
-        self,
-        members: impl Iterator>,
-        _range: TextRange,
-        _name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        const ALLOWED_KEYS: &[&str] = &["name", "closureIndex", "dependenciesIndex"];
-        let mut result = Self::Output::default();
-        for (key, value) in members.flatten() {
-            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                continue;
-            };
-            match key_text.text() {
-                "name" => {
-                    let val_range = value.range();
-                    result.name = Deserializable::deserialize(&value, &key_text, diagnostics)
-                        .unwrap_or_default();
-                    if result.name.is_empty() {
-                        diagnostics.push(
-                            DeserializationDiagnostic::new(markup!(
-                                "The field ""name"" is mandatory"
-                            ))
-                            .with_range(val_range),
-                        )
-                    }
-                }
-                "closureIndex" => {
-                    result.closure_index =
-                        Deserializable::deserialize(&value, &key_text, diagnostics);
-                }
-                "dependenciesIndex" => {
-                    result.dependencies_index =
-                        Deserializable::deserialize(&value, &key_text, diagnostics);
-                }
-                unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                    unknown_key,
-                    key.range(),
-                    ALLOWED_KEYS,
-                )),
-            }
-        }
-        Some(result)
-    }
-}
-
 impl ReactExtensiveDependenciesOptions {
     pub fn new(hooks: HooksOptions) -> Self {
         let mut default = ReactExtensiveDependenciesOptions::default();
diff --git a/crates/biome_js_analyze/src/semantic_analyzers/style/no_restricted_globals.rs b/crates/biome_js_analyze/src/semantic_analyzers/style/no_restricted_globals.rs
index cb86ea0d7c89..38a6d57a00f0 100644
--- a/crates/biome_js_analyze/src/semantic_analyzers/style/no_restricted_globals.rs
+++ b/crates/biome_js_analyze/src/semantic_analyzers/style/no_restricted_globals.rs
@@ -2,10 +2,7 @@ use crate::semantic_services::SemanticServices;
 use biome_analyze::context::RuleContext;
 use biome_analyze::{declare_rule, Rule, RuleDiagnostic, RuleSource};
 use biome_console::markup;
-use biome_deserialize::{
-    Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor, Text,
-    VisitableType,
-};
+use biome_deserialize_macros::Deserializable;
 use biome_js_semantic::{Binding, BindingExtensions};
 use biome_js_syntax::{AnyJsIdentifierUsage, TextRange};
 use biome_rowan::AstNode;
@@ -59,57 +56,13 @@ declare_rule! {
 const RESTRICTED_GLOBALS: [&str; 2] = ["event", "error"];
 
 /// Options for the rule `noRestrictedGlobals`.
-#[derive(Default, Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
+#[derive(Clone, Debug, Default, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct RestrictedGlobalsOptions {
     /// A list of names that should trigger the rule
-    #[serde(skip_serializing_if = "Option::is_none")]
-    denied_globals: Option>,
-}
-
-impl Deserializable for RestrictedGlobalsOptions {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        value.deserialize(RestrictedGlobalsOptionsVisitor, name, diagnostics)
-    }
-}
-
-struct RestrictedGlobalsOptionsVisitor;
-impl DeserializationVisitor for RestrictedGlobalsOptionsVisitor {
-    type Output = RestrictedGlobalsOptions;
-
-    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-
-    fn visit_map(
-        self,
-        members: impl Iterator>,
-        _range: TextRange,
-        _name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        const ALLOWED_KEYS: &[&str] = &["deniedGlobals"];
-        let mut denied_globals = None;
-        for (key, value) in members.flatten() {
-            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                continue;
-            };
-            match key_text.text() {
-                "deniedGlobals" => {
-                    denied_globals = Deserializable::deserialize(&value, &key_text, diagnostics);
-                }
-                unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                    unknown_key,
-                    key.range(),
-                    ALLOWED_KEYS,
-                )),
-            }
-        }
-        Some(Self::Output { denied_globals })
-    }
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    denied_globals: Vec,
 }
 
 impl Rule for NoRestrictedGlobals {
@@ -146,11 +99,8 @@ impl Rule for NoRestrictedGlobals {
                 };
                 let token = token.ok()?;
                 let text = token.text_trimmed();
-                let denied_globals = if let Some(denied_globals) = options.denied_globals.as_ref() {
-                    denied_globals.iter().map(AsRef::as_ref).collect::>()
-                } else {
-                    vec![]
-                };
+                let denied_globals: Vec<_> =
+                    options.denied_globals.iter().map(AsRef::as_ref).collect();
                 is_restricted(text, &binding, denied_globals.as_slice())
                     .map(|text| (token.text_trimmed_range(), text))
             })
diff --git a/crates/biome_js_analyze/src/semantic_analyzers/style/use_naming_convention.rs b/crates/biome_js_analyze/src/semantic_analyzers/style/use_naming_convention.rs
index a23cea9da9b7..f594c4e0e60c 100644
--- a/crates/biome_js_analyze/src/semantic_analyzers/style/use_naming_convention.rs
+++ b/crates/biome_js_analyze/src/semantic_analyzers/style/use_naming_convention.rs
@@ -12,10 +12,7 @@ use biome_analyze::{
     RuleSourceKind,
 };
 use biome_console::markup;
-use biome_deserialize::{
-    Deserializable, DeserializableValue, DeserializationDiagnostic, DeserializationVisitor, Text,
-    VisitableType,
-};
+use biome_deserialize_macros::Deserializable;
 use biome_diagnostics::Applicability;
 use biome_js_semantic::CanBeImportedExported;
 use biome_js_syntax::{
@@ -25,7 +22,7 @@ use biome_js_syntax::{
     JsVariableDeclarator, JsVariableKind, TsEnumMember, TsIdentifierBinding, TsTypeParameterName,
 };
 use biome_rowan::{
-    declare_node_union, AstNode, AstNodeList, BatchMutationExt, SyntaxResult, TextRange, TokenText,
+    declare_node_union, AstNode, AstNodeList, BatchMutationExt, SyntaxResult, TokenText,
 };
 use biome_unicode_table::is_js_ident;
 use serde::{Deserialize, Serialize};
@@ -459,7 +456,7 @@ pub(crate) struct State {
 }
 
 /// Rule's options.
-#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Clone, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schemars", derive(JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct NamingConventionOptions {
@@ -497,62 +494,8 @@ impl Default for NamingConventionOptions {
     }
 }
 
-impl Deserializable for NamingConventionOptions {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        value.deserialize(NamingConventionOptionsVisitor, name, diagnostics)
-    }
-}
-
-struct NamingConventionOptionsVisitor;
-impl DeserializationVisitor for NamingConventionOptionsVisitor {
-    type Output = NamingConventionOptions;
-
-    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;
-
-    fn visit_map(
-        self,
-        members: impl Iterator>,
-        _range: TextRange,
-        _name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        const ALLOWED_KEYS: &[&str] = &["strictCase", "enumMemberCase"];
-        let mut result = Self::Output::default();
-        for (key, value) in members.flatten() {
-            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
-                continue;
-            };
-            match key_text.text() {
-                "strictCase" => {
-                    if let Some(strict_case) =
-                        Deserializable::deserialize(&value, &key_text, diagnostics)
-                    {
-                        result.strict_case = strict_case;
-                    }
-                }
-                "enumMemberCase" => {
-                    if let Some(case) = Deserializable::deserialize(&value, &key_text, diagnostics)
-                    {
-                        result.enum_member_case = case;
-                    }
-                }
-                unknown_key => diagnostics.push(DeserializationDiagnostic::new_unknown_key(
-                    unknown_key,
-                    key.range(),
-                    ALLOWED_KEYS,
-                )),
-            }
-        }
-        Some(result)
-    }
-}
-
 /// Supported cases for TypeScript `enum` member names.
-#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug, Default, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schemars", derive(JsonSchema))]
 pub enum EnumMemberCase {
     /// PascalCase
@@ -582,27 +525,6 @@ impl FromStr for EnumMemberCase {
     }
 }
 
-impl Deserializable for EnumMemberCase {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        const ALLOWED_VARIANTS: &[&str] = &["camelCase", "CONSTANT_CASE", "PascalCase"];
-        let value_text = Text::deserialize(value, name, diagnostics)?;
-        if let Ok(value) = value_text.parse::() {
-            Some(value)
-        } else {
-            diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                value_text.text(),
-                value.range(),
-                ALLOWED_VARIANTS,
-            ));
-            None
-        }
-    }
-}
-
 impl From for Case {
     fn from(case: EnumMemberCase) -> Case {
         match case {
diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.js b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.js
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.js.snap
new file mode 100644
index 000000000000..a838f1481bd2
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.js.snap
@@ -0,0 +1,26 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: emptyHookNameInOptions.js
+---
+# Input
+```jsx
+
+```
+
+# Diagnostics
+```
+emptyHookNameInOptions.options:11:17 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  × name may not be empty
+  
+     9 │ 						"hooks": [
+    10 │ 							{
+  > 11 │ 								"name": "",
+       │ 								        ^^
+    12 │ 								"closureIndex": 0,
+    13 │ 								"dependenciesIndex": 1
+  
+
+```
+
+
diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.options.json b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.options.json
new file mode 100644
index 000000000000..d524698e9eb7
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/emptyHookNameInOptions.options.json
@@ -0,0 +1,21 @@
+{
+	"$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json",
+	"linter": {
+		"rules": {
+			"correctness": {
+				"useExhaustiveDependencies": {
+					"level": "error",
+					"options": {
+						"hooks": [
+							{
+								"name": "",
+								"closureIndex": 0,
+								"dependenciesIndex": 1
+							}
+						]
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.js.snap b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.js.snap
index 554b63c95810..d927f5069d9f 100644
--- a/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.js.snap
+++ b/crates/biome_js_analyze/tests/specs/style/useNamingConvention/malformedOptions.js.snap
@@ -22,9 +22,9 @@ malformedOptions.options:9:25 deserialize ━━━━━━━━━━━━
   
   i Accepted values:
   
-  - camelCase
-  - CONSTANT_CASE
   - PascalCase
+  - CONSTANT_CASE
+  - camelCase
   
 
 ```
diff --git a/crates/biome_js_formatter/src/context.rs b/crates/biome_js_formatter/src/context.rs
index 4669c5bf1902..49156c319c13 100644
--- a/crates/biome_js_formatter/src/context.rs
+++ b/crates/biome_js_formatter/src/context.rs
@@ -1,7 +1,7 @@
+pub mod trailing_comma;
+
 use crate::comments::{FormatJsLeadingComment, JsCommentStyle, JsComments};
-pub use crate::context::trailing_comma::TrailingComma;
-use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic, Text};
-use biome_deserialize_macros::Merge;
+use biome_deserialize_macros::{Deserializable, Merge};
 use biome_formatter::printer::PrinterOptions;
 use biome_formatter::{
     CstFormatContext, FormatContext, FormatElement, FormatOptions, IndentStyle, IndentWidth,
@@ -12,8 +12,7 @@ use std::fmt;
 use std::fmt::Debug;
 use std::rc::Rc;
 use std::str::FromStr;
-
-pub mod trailing_comma;
+pub use trailing_comma::TrailingComma;
 
 #[derive(Debug, Clone)]
 pub struct JsFormatContext {
@@ -379,7 +378,7 @@ impl fmt::Display for JsFormatOptions {
     }
 }
 
-#[derive(Clone, Copy, Debug, Default, Eq, Hash, Merge, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, Deserializable, Eq, Hash, Merge, PartialEq)]
 #[cfg_attr(
     feature = "serde",
     derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema),
@@ -413,29 +412,7 @@ impl fmt::Display for QuoteProperties {
     }
 }
 
-impl Deserializable for QuoteProperties {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match Text::deserialize(value, name, diagnostics)?.text() {
-            "asNeeded" => Some(QuoteProperties::AsNeeded),
-            "preserve" => Some(QuoteProperties::Preserve),
-            unknown_variant => {
-                const ALLOWED_VARIANTS: &[&str] = &["preserve", "asNeeded"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_variant,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, Hash, Merge, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, Deserializable, Eq, Hash, Merge, PartialEq)]
 #[cfg_attr(
     feature = "serde",
     derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema),
@@ -478,29 +455,7 @@ impl fmt::Display for Semicolons {
     }
 }
 
-impl Deserializable for Semicolons {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match Text::deserialize(value, name, diagnostics)?.text() {
-            "always" => Some(Semicolons::Always),
-            "asNeeded" => Some(Semicolons::AsNeeded),
-            unknown_value => {
-                const ALLOWED_VARIANTS: &[&str] = &["always", "asNeeded"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_value,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, Hash, Merge, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, Deserializable, Eq, Hash, Merge, PartialEq)]
 #[cfg_attr(
     feature = "serde",
     derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema),
@@ -544,28 +499,6 @@ impl fmt::Display for ArrowParentheses {
     }
 }
 
-impl Deserializable for ArrowParentheses {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match Text::deserialize(value, name, diagnostics)?.text() {
-            "always" => Some(ArrowParentheses::Always),
-            "asNeeded" => Some(ArrowParentheses::AsNeeded),
-            unknown_value => {
-                const ALLOWED_VARIANTS: &[&str] = &["asNeeded", "always"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_value,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
-
 #[derive(Clone, Copy, Debug, Eq, Hash, Merge, PartialEq)]
 #[cfg_attr(
     feature = "serde",
diff --git a/crates/biome_js_formatter/src/context/trailing_comma.rs b/crates/biome_js_formatter/src/context/trailing_comma.rs
index 8bf2b9c90fa9..84bcaf79c671 100644
--- a/crates/biome_js_formatter/src/context/trailing_comma.rs
+++ b/crates/biome_js_formatter/src/context/trailing_comma.rs
@@ -1,7 +1,6 @@
 use crate::prelude::*;
 use crate::{JsFormatContext, JsFormatOptions};
-use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic, Text};
-use biome_deserialize_macros::Merge;
+use biome_deserialize_macros::{Deserializable, Merge};
 use biome_formatter::prelude::{if_group_breaks, text};
 use biome_formatter::write;
 use biome_formatter::{Format, FormatResult};
@@ -52,7 +51,7 @@ impl Format for FormatTrailingComma {
 }
 
 /// Print trailing commas wherever possible in multi-line comma-separated syntactic structures.
-#[derive(Clone, Copy, Debug, Default, Eq, Hash, Merge, PartialEq)]
+#[derive(Clone, Copy, Default, Debug, Deserializable, Eq, Hash, Merge, PartialEq)]
 #[cfg_attr(
     feature = "serde",
     derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema),
@@ -103,26 +102,3 @@ impl fmt::Display for TrailingComma {
         }
     }
 }
-
-impl Deserializable for TrailingComma {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        match Text::deserialize(value, name, diagnostics)?.text() {
-            "all" => Some(TrailingComma::All),
-            "es5" => Some(TrailingComma::Es5),
-            "none" => Some(TrailingComma::None),
-            unknown_variant => {
-                const ALLOWED_VARIANTS: &[&str] = &["all", "es5", "none"];
-                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
-                    unknown_variant,
-                    value.range(),
-                    ALLOWED_VARIANTS,
-                ));
-                None
-            }
-        }
-    }
-}
diff --git a/crates/biome_project/Cargo.toml b/crates/biome_project/Cargo.toml
index ab44e2a88944..94b5dac32b06 100644
--- a/crates/biome_project/Cargo.toml
+++ b/crates/biome_project/Cargo.toml
@@ -14,16 +14,17 @@ version              = "0.0.0"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-biome_console     = { workspace = true }
-biome_deserialize = { workspace = true }
-biome_diagnostics = { workspace = true }
-biome_json_syntax = { workspace = true }
-biome_parser      = { workspace = true }
-biome_rowan       = { workspace = true }
-biome_text_size   = { workspace = true }
-node-semver       = "2.1.0"
-rustc-hash        = { workspace = true }
-serde             = { workspace = true }
+biome_console            = { workspace = true }
+biome_deserialize        = { workspace = true }
+biome_deserialize_macros = { workspace = true }
+biome_diagnostics        = { workspace = true }
+biome_json_syntax        = { workspace = true }
+biome_parser             = { workspace = true }
+biome_rowan              = { workspace = true }
+biome_text_size          = { workspace = true }
+node-semver              = "2.1.0"
+rustc-hash               = { workspace = true }
+serde                    = { workspace = true }
 
 [dev-dependencies]
 biome_json_parser = { path = "../biome_json_parser" }
diff --git a/crates/biome_project/src/node_js_project/package_json.rs b/crates/biome_project/src/node_js_project/package_json.rs
index 603db54cb96a..5bddaab507ac 100644
--- a/crates/biome_project/src/node_js_project/package_json.rs
+++ b/crates/biome_project/src/node_js_project/package_json.rs
@@ -28,7 +28,7 @@ impl Manifest for PackageJson {
     }
 }
 
-#[derive(Debug, Default)]
+#[derive(Debug, Default, biome_deserialize_macros::Deserializable)]
 pub struct Dependencies(FxHashMap);
 
 #[derive(Debug)]
@@ -107,20 +107,6 @@ impl DeserializationVisitor for PackageJsonVisitor {
     }
 }
 
-impl Deserializable for Dependencies {
-    fn deserialize(
-        value: &impl DeserializableValue,
-        name: &str,
-        diagnostics: &mut Vec,
-    ) -> Option {
-        Some(Dependencies(Deserializable::deserialize(
-            value,
-            name,
-            diagnostics,
-        )?))
-    }
-}
-
 impl Deserializable for Version {
     fn deserialize(
         value: &impl DeserializableValue,
diff --git a/crates/biome_service/Cargo.toml b/crates/biome_service/Cargo.toml
index 1c47b9db6a5a..e24c7ed50c64 100644
--- a/crates/biome_service/Cargo.toml
+++ b/crates/biome_service/Cargo.toml
@@ -41,15 +41,14 @@ biome_rowan              = { workspace = true, features = ["serde"] }
 biome_text_edit          = { workspace = true }
 bpaf                     = { workspace = true }
 dashmap                  = { workspace = true }
-
-ignore      = { workspace = true }
-indexmap    = { workspace = true, features = ["serde"] }
-lazy_static = { workspace = true }
-rustc-hash  = { workspace = true }
-schemars    = { workspace = true, features = ["indexmap1"], optional = true }
-serde       = { workspace = true, features = ["derive"] }
-serde_json  = { workspace = true, features = ["raw_value"] }
-tracing     = { workspace = true, features = ["attributes", "log"] }
+ignore                   = { workspace = true }
+indexmap                 = { workspace = true, features = ["serde"] }
+lazy_static              = { workspace = true }
+rustc-hash               = { workspace = true }
+schemars                 = { workspace = true, features = ["indexmap1"], optional = true }
+serde                    = { workspace = true, features = ["derive"] }
+serde_json               = { workspace = true, features = ["raw_value"] }
+tracing                  = { workspace = true, features = ["attributes", "log"] }
 
 [features]
 schema = [
diff --git a/crates/biome_service/src/configuration/css.rs b/crates/biome_service/src/configuration/css.rs
index 31f4d17f9600..5bb11132d11d 100644
--- a/crates/biome_service/src/configuration/css.rs
+++ b/crates/biome_service/src/configuration/css.rs
@@ -1,11 +1,13 @@
 use crate::configuration::{deserialize_line_width, serialize_line_width, PlainIndentStyle};
-use biome_deserialize_macros::{Merge, NoneState};
+use biome_deserialize_macros::{Deserializable, Merge};
 use biome_formatter::{LineEnding, LineWidth, QuoteStyle};
 use bpaf::Bpaf;
 use serde::{Deserialize, Serialize};
 
 /// Options applied to CSS files
-#[derive(Bpaf, Clone, Default, Debug, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(
+    Bpaf, Clone, Default, Debug, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(default, deny_unknown_fields)]
 pub struct CssConfiguration {
@@ -21,7 +23,9 @@ pub struct CssConfiguration {
 }
 
 /// Options that changes how the CSS parser behaves
-#[derive(Bpaf, Clone, Default, Debug, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(
+    Bpaf, Clone, Default, Debug, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase", default, deny_unknown_fields)]
 pub struct CssParser {
@@ -31,7 +35,9 @@ pub struct CssParser {
     pub allow_wrong_line_comments: Option,
 }
 
-#[derive(Bpaf, Clone, Default, Debug, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(
+    Bpaf, Clone, Default, Debug, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase", default, deny_unknown_fields)]
 pub struct CssFormatter {
@@ -53,6 +59,7 @@ pub struct CssFormatter {
     /// The size of the indentation applied to CSS (and its super languages) files. Default to 2.
     #[serde(skip_serializing_if = "Option::is_none")]
     #[bpaf(long("css-formatter-indent-size"), argument("NUMBER"), optional)]
+    #[deserializable(deprecated(use_instead = "css.formatter.indentWidth"))]
     pub indent_size: Option,
 
     /// The type of line ending applied to CSS (and its super languages) files.
diff --git a/crates/biome_service/src/configuration/formatter.rs b/crates/biome_service/src/configuration/formatter.rs
index 0bb57d053b30..b7480295791d 100644
--- a/crates/biome_service/src/configuration/formatter.rs
+++ b/crates/biome_service/src/configuration/formatter.rs
@@ -2,7 +2,7 @@ use crate::configuration::overrides::OverrideFormatterConfiguration;
 use crate::settings::{to_matcher, FormatSettings};
 use crate::{Matcher, WorkspaceError};
 use biome_deserialize::StringSet;
-use biome_deserialize_macros::{Merge, NoneState};
+use biome_deserialize_macros::{Deserializable, Merge, NoneState};
 use biome_formatter::{IndentStyle, LineEnding, LineWidth};
 use bpaf::Bpaf;
 use serde::{Deserialize, Serialize};
@@ -10,8 +10,11 @@ use std::path::PathBuf;
 use std::str::FromStr;
 
 /// Generic options applied to all files
-#[derive(Bpaf, Clone, Debug, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(
+    Bpaf, Clone, Debug, Deserialize, Deserializable, Eq, Merge, NoneState, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
+#[deserializable(from_none)]
 #[serde(rename_all = "camelCase", default, deny_unknown_fields)]
 pub struct FormatterConfiguration {
     // if `false`, it disables the feature. `true` by default
@@ -30,6 +33,7 @@ pub struct FormatterConfiguration {
 
     /// The size of the indentation, 2 by default (deprecated, use `indent-width`)
     #[serde(skip_serializing_if = "Option::is_none")]
+    #[deserializable(deprecated(use_instead = "formatter.indentWidth"))]
     #[bpaf(long("indent-size"), argument("NUMBER"), optional)]
     pub indent_size: Option,
 
@@ -177,7 +181,9 @@ where
     s.serialize_u16(line_width.unwrap_or_default().get())
 }
 
-#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Merge, PartialEq, Serialize)]
+#[derive(
+    Clone, Copy, Debug, Default, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase")]
 pub enum PlainIndentStyle {
diff --git a/crates/biome_service/src/configuration/javascript/formatter.rs b/crates/biome_service/src/configuration/javascript/formatter.rs
index 04a8ce5549b4..e8887c6ff2fd 100644
--- a/crates/biome_service/src/configuration/javascript/formatter.rs
+++ b/crates/biome_service/src/configuration/javascript/formatter.rs
@@ -1,6 +1,6 @@
 use crate::configuration::PlainIndentStyle;
 use crate::configuration::{deserialize_line_width, serialize_line_width};
-use biome_deserialize_macros::{Merge, NoneState};
+use biome_deserialize_macros::{Deserializable, Merge, NoneState};
 use biome_formatter::{LineEnding, LineWidth, QuoteStyle};
 use biome_js_formatter::context::trailing_comma::TrailingComma;
 use biome_js_formatter::context::{ArrowParentheses, QuoteProperties, Semicolons};
@@ -8,7 +8,19 @@ use bpaf::Bpaf;
 use serde::{Deserialize, Serialize};
 
 /// Formatting options specific to the JavaScript files
-#[derive(Default, Debug, Deserialize, Merge, NoneState, Serialize, Eq, PartialEq, Clone, Bpaf)]
+#[derive(
+    Default,
+    Debug,
+    Deserialize,
+    Deserializable,
+    Merge,
+    NoneState,
+    Serialize,
+    Eq,
+    PartialEq,
+    Clone,
+    Bpaf,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase", default, deny_unknown_fields)]
 pub struct JavascriptFormatter {
@@ -57,6 +69,7 @@ pub struct JavascriptFormatter {
 
     /// The size of the indentation applied to JavaScript (and its super languages) files. Default to 2.
     #[serde(skip_serializing_if = "Option::is_none")]
+    #[deserializable(deprecated(use_instead = "javascript.formatter.indentWidth"))]
     #[bpaf(long("javascript-formatter-indent-size"), argument("NUMBER"), optional)]
     pub indent_size: Option,
 
diff --git a/crates/biome_service/src/configuration/javascript/mod.rs b/crates/biome_service/src/configuration/javascript/mod.rs
index 9e7902f53594..fb0634a6c9fb 100644
--- a/crates/biome_service/src/configuration/javascript/mod.rs
+++ b/crates/biome_service/src/configuration/javascript/mod.rs
@@ -1,13 +1,15 @@
 mod formatter;
 
-pub use crate::configuration::javascript::formatter::{javascript_formatter, JavascriptFormatter};
 use biome_deserialize::StringSet;
-use biome_deserialize_macros::{Merge, NoneState};
+use biome_deserialize_macros::{Deserializable, Merge};
 use bpaf::Bpaf;
+pub use formatter::{javascript_formatter, JavascriptFormatter};
 use serde::{Deserialize, Serialize};
 
 /// A set of options applied to the JavaScript files
-#[derive(Bpaf, Clone, Default, Debug, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(
+    Bpaf, Clone, Default, Debug, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(default, deny_unknown_fields)]
 pub struct JavascriptConfiguration {
@@ -42,13 +44,17 @@ impl JavascriptConfiguration {
     }
 }
 
-#[derive(Bpaf, Clone, Debug, Default, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(
+    Bpaf, Clone, Debug, Default, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(default, deny_unknown_fields)]
 pub struct JavascriptOrganizeImports {}
 
 /// Options that changes how the JavaScript parser behaves
-#[derive(Bpaf, Clone, Debug, Default, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(
+    Bpaf, Clone, Debug, Default, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase", default, deny_unknown_fields)]
 pub struct JavascriptParser {
diff --git a/crates/biome_service/src/configuration/json.rs b/crates/biome_service/src/configuration/json.rs
index 3374df3b1857..7f6d0a7207bb 100644
--- a/crates/biome_service/src/configuration/json.rs
+++ b/crates/biome_service/src/configuration/json.rs
@@ -1,11 +1,13 @@
 use crate::configuration::{deserialize_line_width, serialize_line_width, PlainIndentStyle};
-use biome_deserialize_macros::{Merge, NoneState};
+use biome_deserialize_macros::{Deserializable, Merge};
 use biome_formatter::{LineEnding, LineWidth};
 use bpaf::Bpaf;
 use serde::{Deserialize, Serialize};
 
 /// Options applied to JSON files
-#[derive(Bpaf, Clone, Debug, Default, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(
+    Bpaf, Clone, Debug, Default, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(default, deny_unknown_fields)]
 pub struct JsonConfiguration {
@@ -21,7 +23,9 @@ pub struct JsonConfiguration {
 }
 
 /// Options that changes how the JSON parser behaves
-#[derive(Bpaf, Clone, Debug, Default, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(
+    Bpaf, Clone, Debug, Default, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase", default, deny_unknown_fields)]
 pub struct JsonParser {
@@ -35,7 +39,9 @@ pub struct JsonParser {
     pub allow_trailing_commas: Option,
 }
 
-#[derive(Bpaf, Clone, Debug, Default, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(
+    Bpaf, Clone, Debug, Default, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase", default, deny_unknown_fields)]
 pub struct JsonFormatter {
@@ -57,6 +63,7 @@ pub struct JsonFormatter {
     /// The size of the indentation applied to JSON (and its super languages) files. Default to 2.
     #[serde(skip_serializing_if = "Option::is_none")]
     #[bpaf(long("json-formatter-indent-size"), argument("NUMBER"), optional)]
+    #[deserializable(deprecated(use_instead = "json.formatter.indentWidth"))]
     pub indent_size: Option,
 
     /// The type of line ending applied to JSON (and its super languages) files.
diff --git a/crates/biome_service/src/configuration/linter/mod.rs b/crates/biome_service/src/configuration/linter/mod.rs
index da40ec4b1ad5..6aad86a37733 100644
--- a/crates/biome_service/src/configuration/linter/mod.rs
+++ b/crates/biome_service/src/configuration/linter/mod.rs
@@ -5,8 +5,10 @@ pub use crate::configuration::linter::rules::Rules;
 use crate::configuration::overrides::OverrideLinterConfiguration;
 use crate::settings::{to_matcher, LinterSettings};
 use crate::{Matcher, WorkspaceError};
-use biome_deserialize::{Merge, StringSet};
-use biome_deserialize_macros::{Merge, NoneState};
+use biome_deserialize::{
+    DeserializableValue, DeserializationDiagnostic, Merge, StringSet, VisitableType,
+};
+use biome_deserialize_macros::{Deserializable, Merge, NoneState};
 use biome_diagnostics::Severity;
 use biome_js_analyze::options::PossibleOptions;
 use bpaf::Bpaf;
@@ -17,8 +19,11 @@ use serde::{Deserialize, Serialize};
 use std::path::PathBuf;
 use std::str::FromStr;
 
-#[derive(Debug, Deserialize, Merge, NoneState, Serialize, Clone, Bpaf, Eq, PartialEq)]
+#[derive(
+    Bpaf, Clone, Debug, Deserialize, Deserializable, Eq, Merge, NoneState, PartialEq, Serialize,
+)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
+#[deserializable(from_none)]
 #[serde(rename_all = "camelCase", default, deny_unknown_fields)]
 pub struct LinterConfiguration {
     /// if `false`, it disables the feature and the linter won't be executed. `true` by default
@@ -96,6 +101,22 @@ pub enum RuleConfiguration {
     WithOptions(Box),
 }
 
+impl biome_deserialize::Deserializable for RuleConfiguration {
+    fn deserialize(
+        value: &impl DeserializableValue,
+        rule_name: &str,
+        diagnostics: &mut Vec,
+    ) -> Option {
+        if value.is_type(VisitableType::STR) {
+            biome_deserialize::Deserializable::deserialize(value, rule_name, diagnostics)
+                .map(Self::Plain)
+        } else {
+            biome_deserialize::Deserializable::deserialize(value, rule_name, diagnostics)
+                .map(|rule| Self::WithOptions(Box::new(rule)))
+        }
+    }
+}
+
 impl FromStr for RuleConfiguration {
     type Err = String;
 
@@ -188,7 +209,7 @@ impl From<&RulePlainConfiguration> for Severity {
     }
 }
 
-#[derive(Default, Deserialize, Serialize, Debug, Eq, PartialEq, Clone)]
+#[derive(Clone, Debug, Default, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schema", derive(JsonSchema))]
 #[serde(rename_all = "camelCase")]
 pub enum RulePlainConfiguration {
@@ -211,11 +232,13 @@ impl FromStr for RulePlainConfiguration {
     }
 }
 
-#[derive(Default, Deserialize, Serialize, Debug, Eq, PartialEq, Clone)]
+#[derive(Clone, Debug, Default, Deserialize, Deserializable, Eq, PartialEq, Serialize)]
 #[cfg_attr(feature = "schema", derive(JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct RuleWithOptions {
     pub level: RulePlainConfiguration,
+
+    #[deserializable(passthrough_name)]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub options: Option,
 }
diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs
index e88f65a368ee..969400bf2294 100644
--- a/crates/biome_service/src/configuration/linter/rules.rs
+++ b/crates/biome_service/src/configuration/linter/rules.rs
@@ -2,13 +2,17 @@
 
 use crate::RuleConfiguration;
 use biome_analyze::RuleFilter;
-use biome_deserialize_macros::{Merge, NoneState};
+use biome_console::markup;
+use biome_deserialize::{DeserializableValidator, DeserializationDiagnostic};
+use biome_deserialize_macros::{Deserializable, Merge, NoneState};
 use biome_diagnostics::{Category, Severity};
+use biome_rowan::TextRange;
 use indexmap::IndexSet;
 #[cfg(feature = "schema")]
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-#[derive(Clone, Debug, Deserialize, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[derive(Clone, Debug, Deserialize, Deserializable, Eq, Merge, NoneState, PartialEq, Serialize)]
+#[deserializable(from_none, with_validator)]
 #[cfg_attr(feature = "schema", derive(JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct Rules {
@@ -18,20 +22,28 @@ pub struct Rules {
     #[doc = r" It enables ALL rules. The rules that belong to `nursery` won't be enabled."]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub all: Option,
+    #[deserializable(rename = "a11y")]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub a11y: Option,
+    #[deserializable(rename = "complexity")]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub complexity: Option,
+    #[deserializable(rename = "correctness")]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub correctness: Option,
+    #[deserializable(rename = "nursery")]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub nursery: Option,
+    #[deserializable(rename = "performance")]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub performance: Option,
+    #[deserializable(rename = "security")]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub security: Option,
+    #[deserializable(rename = "style")]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub style: Option